fayalite/crates/fayalite/src/build.rs

2803 lines
85 KiB
Rust

// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
build::graph::JobGraph,
bundle::{Bundle, BundleType},
intern::{Intern, InternSlice, Interned},
module::Module,
platform::{DynPlatform, Platform},
util::{job_server::AcquiredJob, os_str_strip_prefix},
vendor,
};
use clap::ArgAction;
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{DeserializeOwned, Error as _},
ser::Error as _,
};
use std::{
any::{Any, TypeId},
borrow::Cow,
cmp::Ordering,
ffi::{OsStr, OsString},
fmt,
hash::{Hash, Hasher},
io::Write,
marker::PhantomData,
path::{Path, PathBuf},
sync::{Arc, OnceLock},
};
use tempfile::TempDir;
pub mod external;
pub mod firrtl;
pub mod formal;
pub mod graph;
pub mod registry;
pub mod verilog;
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
[DynJobKind::new(BaseJobKind)]
.into_iter()
.chain(firrtl::built_in_job_kinds())
.chain(formal::built_in_job_kinds())
.chain(vendor::built_in_job_kinds())
.chain(verilog::built_in_job_kinds())
}
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum JobItem {
Path {
path: Interned<Path>,
},
DynamicPaths {
paths: Vec<Interned<Path>>,
source_job_name: Interned<str>,
},
}
impl JobItem {
pub fn name(&self) -> JobItemName {
match self {
&JobItem::Path { path } => JobItemName::Path { path },
&JobItem::DynamicPaths {
paths: _,
source_job_name,
} => JobItemName::DynamicPaths { source_job_name },
}
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub enum JobItemName {
Path { path: Interned<Path> },
DynamicPaths { source_job_name: Interned<str> },
}
impl JobItemName {
fn as_ref(&self) -> JobItemNameRef<'_> {
match self {
JobItemName::Path { path } => JobItemNameRef::Path { path },
JobItemName::DynamicPaths { source_job_name } => {
JobItemNameRef::DynamicPaths { source_job_name }
}
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum JobItemNameRef<'a> {
Path { path: &'a Path },
DynamicPaths { source_job_name: &'a str },
}
/// ordered by string contents, not by `Interned`
impl PartialOrd for JobItemName {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
/// ordered by string contents, not by `Interned`
impl Ord for JobItemName {
fn cmp(&self, other: &Self) -> Ordering {
if self == other {
Ordering::Equal
} else {
self.as_ref().cmp(&other.as_ref())
}
}
}
pub trait WriteArgs:
for<'a> Extend<&'a str>
+ for<'a> Extend<&'a OsStr>
+ for<'a> Extend<&'a Path>
+ for<'a> Extend<Cow<'a, str>>
+ for<'a> Extend<Cow<'a, OsStr>>
+ for<'a> Extend<Cow<'a, Path>>
+ Extend<String>
+ Extend<OsString>
+ Extend<PathBuf>
+ Extend<Interned<str>>
+ Extend<Interned<OsStr>>
+ Extend<Interned<Path>>
{
fn write_display_args(&mut self, args: impl IntoIterator<Item: fmt::Display>) {
self.extend(args.into_iter().map(|v| v.to_string()));
}
fn write_owned_args(&mut self, args: impl IntoIterator<Item: Into<OsString>>) {
self.extend(args.into_iter().map(Into::<OsString>::into))
}
fn write_args<'a>(&mut self, args: impl IntoIterator<Item: AsRef<OsStr>>);
fn write_interned_args(&mut self, args: impl IntoIterator<Item: Into<Interned<OsStr>>>) {
self.extend(args.into_iter().map(Into::<Interned<OsStr>>::into))
}
fn write_display_arg(&mut self, arg: impl fmt::Display) {
self.write_display_args([arg]);
}
fn write_owned_arg(&mut self, arg: impl Into<OsString>) {
self.extend([arg.into()]);
}
fn write_arg(&mut self, arg: impl AsRef<OsStr>) {
self.extend([arg.as_ref()]);
}
/// writes `--{name}={value}`
fn write_long_option_eq(&mut self, name: impl AsRef<OsStr>, value: impl AsRef<OsStr>) {
let name = name.as_ref();
let value = value.as_ref();
let mut option =
OsString::with_capacity(name.len().saturating_add(value.len()).saturating_add(3));
option.push("--");
option.push(name);
option.push("=");
option.push(value);
self.write_owned_arg(option);
}
fn write_interned_arg(&mut self, arg: impl Into<Interned<OsStr>>) {
self.extend([arg.into()]);
}
/// finds the first option that is `--{option_name}={value}` and returns `value`
fn get_long_option_eq(&self, option_name: impl AsRef<str>) -> Option<&OsStr>;
}
pub trait ArgsWriterArg:
AsRef<OsStr>
+ From<Interned<OsStr>>
+ for<'a> From<Cow<'a, OsStr>>
+ for<'a> From<&'a OsStr>
+ From<OsString>
{
}
impl ArgsWriterArg for Interned<OsStr> {}
impl ArgsWriterArg for OsString {}
pub struct ArgsWriter<A: ArgsWriterArg>(pub Vec<A>);
impl<A: ArgsWriterArg> Default for ArgsWriter<A> {
fn default() -> Self {
Self(Default::default())
}
}
impl<A: ArgsWriterArg> ArgsWriter<A> {
fn get_long_option_eq_helper(&self, option_name: &str) -> Option<&OsStr> {
self.0.iter().find_map(|arg| {
os_str_strip_prefix(arg.as_ref(), "--")
.and_then(|arg| os_str_strip_prefix(arg, option_name))
.and_then(|arg| os_str_strip_prefix(arg, "="))
})
}
}
impl<'a, A: ArgsWriterArg> Extend<&'a str> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(AsRef::<OsStr>::as_ref))
}
}
impl<'a, A: ArgsWriterArg> Extend<&'a OsStr> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = &'a OsStr>>(&mut self, iter: T) {
self.0.extend(iter.into_iter().map(Into::into))
}
}
impl<'a, A: ArgsWriterArg> Extend<&'a Path> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = &'a Path>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(AsRef::<OsStr>::as_ref))
}
}
impl<A: ArgsWriterArg> Extend<String> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = String>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(OsString::from))
}
}
impl<A: ArgsWriterArg> Extend<OsString> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = OsString>>(&mut self, iter: T) {
self.0.extend(iter.into_iter().map(Into::into))
}
}
impl<A: ArgsWriterArg> Extend<PathBuf> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = PathBuf>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(OsString::from))
}
}
impl<A: ArgsWriterArg> Extend<Interned<str>> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = Interned<str>>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(Interned::<OsStr>::from))
}
}
impl<A: ArgsWriterArg> Extend<Interned<OsStr>> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = Interned<OsStr>>>(&mut self, iter: T) {
self.0.extend(iter.into_iter().map(Into::into))
}
}
impl<A: ArgsWriterArg> Extend<Interned<Path>> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = Interned<Path>>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(Interned::<OsStr>::from))
}
}
impl<'a, A: ArgsWriterArg> Extend<Cow<'a, str>> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = Cow<'a, str>>>(&mut self, iter: T) {
self.0.extend(iter.into_iter().map(|v| {
match v {
Cow::Borrowed(v) => Cow::<OsStr>::Borrowed(v.as_ref()),
Cow::Owned(v) => Cow::Owned(v.into()),
}
.into()
}))
}
}
impl<'a, A: ArgsWriterArg> Extend<Cow<'a, OsStr>> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = Cow<'a, OsStr>>>(&mut self, iter: T) {
self.0.extend(iter.into_iter().map(Into::into))
}
}
impl<'a, A: ArgsWriterArg> Extend<Cow<'a, Path>> for ArgsWriter<A> {
fn extend<T: IntoIterator<Item = Cow<'a, Path>>>(&mut self, iter: T) {
self.0.extend(iter.into_iter().map(|v| {
match v {
Cow::Borrowed(v) => Cow::<OsStr>::Borrowed(v.as_ref()),
Cow::Owned(v) => Cow::Owned(v.into()),
}
.into()
}))
}
}
impl<A: ArgsWriterArg> WriteArgs for ArgsWriter<A> {
fn write_args<'a>(&mut self, args: impl IntoIterator<Item: AsRef<OsStr>>) {
self.0.extend(args.into_iter().map(|v| v.as_ref().into()))
}
fn get_long_option_eq(&self, option_name: impl AsRef<str>) -> Option<&OsStr> {
self.get_long_option_eq_helper(option_name.as_ref())
}
}
pub trait ToArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized));
fn to_interned_args(&self) -> Interned<[Interned<OsStr>]> {
Intern::intern_owned(self.to_interned_args_vec())
}
fn to_interned_args_vec(&self) -> Vec<Interned<OsStr>> {
let mut retval = ArgsWriter::default();
self.to_args(&mut retval);
retval.0
}
fn to_os_string_args(&self) -> Vec<OsString> {
let mut retval = ArgsWriter::default();
self.to_args(&mut retval);
retval.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct JobKindAndArgs<K: JobKind> {
pub kind: K,
pub args: K::Args,
}
impl<K: JobKind> JobKindAndArgs<K> {
pub fn args_to_jobs(
self,
dependencies: <K::Dependencies as JobDependencies>::KindsAndArgs,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<K>> {
K::args_to_jobs(
JobArgsAndDependencies {
args: self,
dependencies,
},
params,
global_params,
)
}
}
impl<K: JobKind<Args: Copy>> Copy for JobKindAndArgs<K> {}
impl<K: JobKind> From<JobKindAndArgs<K>> for DynJobArgs {
fn from(value: JobKindAndArgs<K>) -> Self {
let JobKindAndArgs { kind, args } = value;
DynJobArgs::new(kind, args)
}
}
impl<K: JobKind> TryFrom<DynJobArgs> for JobKindAndArgs<K> {
type Error = DynJobArgs;
fn try_from(value: DynJobArgs) -> Result<Self, Self::Error> {
value.downcast()
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct JobAndKind<K: JobKind> {
pub kind: K,
pub job: K::Job,
}
impl<K: JobKind<Job: Clone>> Clone for JobAndKind<K> {
fn clone(&self) -> Self {
Self {
kind: self.kind.clone(),
job: self.job.clone(),
}
}
}
impl<K: JobKind<Job: Copy>> Copy for JobAndKind<K> {}
impl<K: JobKind> From<JobAndKind<K>> for DynJob {
fn from(value: JobAndKind<K>) -> Self {
let JobAndKind { kind, job } = value;
DynJob::new(kind, job)
}
}
impl<K: JobKind<Job: Clone>> TryFrom<DynJob> for JobAndKind<K> {
type Error = DynJob;
fn try_from(value: DynJob) -> Result<Self, Self::Error> {
value.downcast()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct JobKindAndDependencies<K: JobKind> {
pub kind: K,
pub dependencies: K::Dependencies,
}
impl<K: JobKind + Default> Default for JobKindAndDependencies<K> {
fn default() -> Self {
Self::new(K::default())
}
}
impl<K: JobKind> JobKindAndDependencies<K> {
pub fn new(kind: K) -> Self {
Self {
kind,
dependencies: kind.dependencies(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct JobAndDependencies<K: JobKind> {
pub job: JobAndKind<K>,
pub dependencies: <K::Dependencies as JobDependencies>::JobsAndKinds,
}
impl<K: JobKind> JobAndDependencies<K> {
pub fn get_job<J, Position>(&self) -> &J
where
Self: GetJob<J, Position>,
{
GetJob::get_job(self)
}
pub fn base_job(&self) -> &BaseJob {
self.job.kind.base_job(&self.job.job, &self.dependencies)
}
}
impl<K: JobKind> Clone for JobAndDependencies<K>
where
K::Job: Clone,
<K::Dependencies as JobDependencies>::JobsAndKinds: Clone,
{
fn clone(&self) -> Self {
Self {
job: self.job.clone(),
dependencies: self.dependencies.clone(),
}
}
}
impl<K: JobKind> Copy for JobAndDependencies<K>
where
K::Job: Copy,
<K::Dependencies as JobDependencies>::JobsAndKinds: Copy,
{
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct JobArgsAndDependencies<K: JobKind> {
pub args: JobKindAndArgs<K>,
pub dependencies: <K::Dependencies as JobDependencies>::KindsAndArgs,
}
impl<K: JobKind> Copy for JobArgsAndDependencies<K>
where
K::Args: Copy,
<K::Dependencies as JobDependencies>::KindsAndArgs: Copy,
{
}
impl<K: JobKind> JobArgsAndDependencies<K> {
pub fn args_to_jobs(
self,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<K>> {
K::args_to_jobs(self, params, global_params)
}
pub fn base_job_args(&self) -> &BaseJobArgs {
self.args
.kind
.base_job_args(&self.args.args, &self.dependencies)
}
}
impl<K: JobKind<Dependencies = JobKindAndDependencies<D>>, D: JobKind> JobArgsAndDependencies<K> {
pub fn args_to_jobs_simple<F>(
self,
params: &JobParams,
global_params: &GlobalParams,
f: F,
) -> eyre::Result<JobAndDependencies<K>>
where
F: FnOnce(K, K::Args, &mut JobAndDependencies<D>) -> eyre::Result<K::Job>,
{
let Self {
args: JobKindAndArgs { kind, args },
dependencies,
} = self;
let mut dependencies = dependencies.args_to_jobs(params, global_params)?;
let job = f(kind, args, &mut dependencies)?;
Ok(JobAndDependencies {
job: JobAndKind { kind, job },
dependencies,
})
}
}
impl<C: external::ExternalCommand<Dependencies = JobKindAndDependencies<D>>, D: JobKind>
JobArgsAndDependencies<external::ExternalCommandJobKind<C>>
{
pub fn args_to_jobs_external_simple<F>(
self,
params: &JobParams,
global_params: &GlobalParams,
f: F,
) -> eyre::Result<(
C::AdditionalJobData,
<C::Dependencies as JobDependencies>::JobsAndKinds,
)>
where
F: FnOnce(
external::ExternalCommandArgs<C>,
&mut JobAndDependencies<D>,
) -> eyre::Result<C::AdditionalJobData>,
{
let Self {
args: JobKindAndArgs { kind: _, args },
dependencies,
} = self;
let mut dependencies = dependencies.args_to_jobs(params, global_params)?;
let additional_job_data = f(args, &mut dependencies)?;
Ok((additional_job_data, dependencies))
}
}
pub trait JobDependencies: 'static + Send + Sync + Hash + Eq + fmt::Debug + Copy {
type KindsAndArgs: 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone;
type JobsAndKinds: 'static + Send + Sync + Hash + Eq + fmt::Debug;
fn kinds_dyn_extend<E: ?Sized + Extend<DynJobKind>>(self, dyn_kinds: &mut E);
fn kinds_dyn(self) -> Vec<DynJobKind> {
let mut retval = Vec::new();
self.kinds_dyn_extend(&mut retval);
retval
}
fn into_dyn_jobs_extend<E: ?Sized + Extend<DynJob>>(jobs: Self::JobsAndKinds, dyn_jobs: &mut E);
fn into_dyn_jobs(jobs: Self::JobsAndKinds) -> Vec<DynJob> {
let mut retval = Vec::new();
Self::into_dyn_jobs_extend(jobs, &mut retval);
retval
}
#[track_caller]
fn from_dyn_args_prefix<I: ?Sized + Iterator<Item = DynJobArgs>>(
args: &mut I,
) -> Self::KindsAndArgs;
#[track_caller]
fn from_dyn_args<I: IntoIterator<Item = DynJobArgs>>(args: I) -> Self::KindsAndArgs {
let mut iter = args.into_iter();
let retval = Self::from_dyn_args_prefix(&mut iter);
if iter.next().is_some() {
panic!("wrong number of dependencies");
}
retval
}
}
pub trait JobDependenciesHasBase: JobDependencies {
fn base_job_args(args: &Self::KindsAndArgs) -> &BaseJobArgs;
fn base_job(jobs: &Self::JobsAndKinds) -> &BaseJob;
#[track_caller]
fn base_job_args_dyn(dependencies_args: &[DynJobArgs]) -> &BaseJobArgs;
#[track_caller]
fn base_job_dyn(dependencies: &[DynJob]) -> &BaseJob;
}
impl<K: JobKind> JobDependencies for JobKindAndDependencies<K> {
type KindsAndArgs = JobArgsAndDependencies<K>;
type JobsAndKinds = JobAndDependencies<K>;
fn kinds_dyn_extend<E: ?Sized + Extend<DynJobKind>>(self, dyn_kinds: &mut E) {
let Self { kind, dependencies } = self;
dependencies.kinds_dyn_extend(dyn_kinds);
dyn_kinds.extend([DynJobKind::new(kind)]);
}
fn into_dyn_jobs_extend<E: ?Sized + Extend<DynJob>>(
jobs: Self::JobsAndKinds,
dyn_jobs: &mut E,
) {
let JobAndDependencies { job, dependencies } = jobs;
K::Dependencies::into_dyn_jobs_extend(dependencies, dyn_jobs);
dyn_jobs.extend([job.into()]);
}
#[track_caller]
fn from_dyn_args_prefix<I: ?Sized + Iterator<Item = DynJobArgs>>(
args: &mut I,
) -> Self::KindsAndArgs {
let dependencies = K::Dependencies::from_dyn_args_prefix(args);
let Some(args) = args.next() else {
panic!("wrong number of dependencies");
};
match args.downcast() {
Ok(args) => JobArgsAndDependencies { args, dependencies },
Err(args) => {
panic!(
"wrong type of dependency, expected {} got:\n{args:?}",
std::any::type_name::<K>()
)
}
}
}
}
impl<K: JobKind> JobDependenciesHasBase for JobKindAndDependencies<K> {
fn base_job_args(args: &Self::KindsAndArgs) -> &BaseJobArgs {
args.base_job_args()
}
fn base_job(jobs: &Self::JobsAndKinds) -> &BaseJob {
jobs.base_job()
}
#[track_caller]
fn base_job_args_dyn(dependencies_args: &[DynJobArgs]) -> &BaseJobArgs {
let [dependencies_args @ .., args] = dependencies_args else {
panic!("wrong number of dependencies");
};
let Some((kind, args)) = args.downcast_ref::<K>() else {
panic!(
"wrong type of dependency, expected {} got:\n{args:?}",
std::any::type_name::<K>()
)
};
kind.base_job_args_dyn(args, dependencies_args)
}
#[track_caller]
fn base_job_dyn(dependencies: &[DynJob]) -> &BaseJob {
let [dependencies @ .., job] = dependencies else {
panic!("wrong number of dependencies");
};
let Some((kind, job)) = job.downcast_ref::<K>() else {
panic!(
"wrong type of dependency, expected {} got:\n{job:?}",
std::any::type_name::<K>()
)
};
kind.base_job_dyn(job, dependencies)
}
}
macro_rules! impl_job_dependencies {
(@impl $(($v:ident: $T:ident),)*) => {
impl<$($T: JobDependencies),*> JobDependencies for ($($T,)*) {
type KindsAndArgs = ($($T::KindsAndArgs,)*);
type JobsAndKinds = ($($T::JobsAndKinds,)*);
fn kinds_dyn_extend<E: ?Sized + Extend<DynJobKind>>(self, dyn_kinds: &mut E) {
#![allow(unused_variables)]
let ($($v,)*) = self;
$($T::kinds_dyn_extend($v, dyn_kinds);)*
}
fn into_dyn_jobs_extend<E: ?Sized + Extend<DynJob>>(
jobs: Self::JobsAndKinds,
dyn_jobs: &mut E,
) {
#![allow(unused_variables)]
let ($($v,)*) = jobs;
$($T::into_dyn_jobs_extend($v, dyn_jobs);)*
}
#[track_caller]
fn from_dyn_args_prefix<I: ?Sized + Iterator<Item = DynJobArgs>>(
args: &mut I,
) -> Self::KindsAndArgs {
#![allow(unused_variables)]
$(let $v = $T::from_dyn_args_prefix(args);)*
($($v,)*)
}
}
};
($($first:tt, $($rest:tt,)*)?) => {
impl_job_dependencies!(@impl $($first, $($rest,)*)?);
$(impl_job_dependencies!($($rest,)*);)?
};
}
impl_job_dependencies! {
(v0: T0),
(v1: T1),
(v2: T2),
(v3: T3),
(v4: T4),
(v5: T5),
(v6: T6),
(v7: T7),
(v8: T8),
(v9: T9),
(v10: T10),
(v11: T11),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct JobParams {
main_module: Module<Bundle>,
}
impl AsRef<Self> for JobParams {
fn as_ref(&self) -> &Self {
self
}
}
impl JobParams {
pub fn new_canonical(main_module: Module<Bundle>) -> Self {
Self { main_module }
}
pub fn new<B: BundleType>(main_module: impl AsRef<Module<B>>) -> Self {
Self::new_canonical(main_module.as_ref().canonical())
}
pub fn main_module(&self) -> &Module<Bundle> {
&self.main_module
}
}
#[derive(Clone, Debug)]
pub struct GlobalParams {
top_level_cmd: Option<clap::Command>,
application_name: Interned<str>,
}
impl AsRef<Self> for GlobalParams {
fn as_ref(&self) -> &Self {
self
}
}
impl GlobalParams {
pub fn new(top_level_cmd: Option<clap::Command>, application_name: impl AsRef<str>) -> Self {
Self {
top_level_cmd,
application_name: application_name.as_ref().intern(),
}
}
pub fn top_level_cmd(&self) -> Option<&clap::Command> {
self.top_level_cmd.as_ref()
}
pub fn into_top_level_cmd(self) -> Option<clap::Command> {
self.top_level_cmd
}
pub fn extract_clap_error(&self, e: eyre::Report) -> eyre::Result<clap::Error> {
let e = e.downcast::<clap::Error>()?;
Ok(match &self.top_level_cmd {
Some(cmd) => e.with_cmd(cmd),
None => e,
})
}
pub fn exit_if_clap_error(&self, e: eyre::Report) -> eyre::Report {
match self.extract_clap_error(e) {
Ok(e) => e.exit(),
Err(e) => e,
}
}
pub fn clap_error(
&self,
kind: clap::error::ErrorKind,
message: impl fmt::Display,
) -> clap::Error {
match self.top_level_cmd.clone() {
Some(top_level_cmd) => top_level_cmd.clone().error(kind, message),
None => clap::Error::raw(kind, message),
}
}
pub fn application_name(&self) -> Interned<str> {
self.application_name
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct CommandParams {
pub command_line: Interned<[Interned<OsStr>]>,
pub current_dir: Option<Interned<Path>>,
}
impl CommandParams {
fn to_unix_shell_line<E>(
self,
output: &mut String,
mut escape_arg: impl FnMut(&OsStr, &mut String) -> Result<(), E>,
) -> Result<(), E> {
let Self {
command_line,
current_dir,
} = self;
let mut end = None;
let mut separator = if let Some(current_dir) = current_dir {
output.push_str("(cd ");
end = Some(")");
if !current_dir
.as_os_str()
.as_encoded_bytes()
.first()
.is_some_and(|ch| ch.is_ascii_alphanumeric() || matches!(ch, b'/' | b'\\' | b'.'))
{
output.push_str("-- ");
}
escape_arg(current_dir.as_ref(), output)?;
"; exec -- "
} else {
""
};
for arg in command_line {
output.push_str(separator);
separator = " ";
escape_arg(&arg, output)?;
}
if let Some(end) = end {
output.push_str(end);
}
Ok(())
}
}
pub trait JobKindHelper: 'static + Send + Sync + Hash + Eq + fmt::Debug + Copy {
fn base_job_args<'a>(
self,
args: &'a <Self as JobKind>::Args,
dependencies: &'a <<Self as JobKind>::Dependencies as JobDependencies>::KindsAndArgs,
) -> &'a BaseJobArgs
where
Self: JobKind;
fn base_job<'a>(
self,
job: &'a <Self as JobKind>::Job,
dependencies: &'a <<Self as JobKind>::Dependencies as JobDependencies>::JobsAndKinds,
) -> &'a BaseJob
where
Self: JobKind;
#[track_caller]
fn base_job_args_dyn<'a>(
self,
args: &'a <Self as JobKind>::Args,
dependencies_args: &'a [DynJobArgs],
) -> &'a BaseJobArgs
where
Self: JobKind;
#[track_caller]
fn base_job_dyn<'a>(
self,
job: &'a <Self as JobKind>::Job,
dependencies: &'a [DynJob],
) -> &'a BaseJob
where
Self: JobKind;
}
impl<K: JobKind<Dependencies: JobDependenciesHasBase>> JobKindHelper for K {
fn base_job_args<'a>(
self,
_args: &'a <Self as JobKind>::Args,
dependencies: &'a <<Self as JobKind>::Dependencies as JobDependencies>::KindsAndArgs,
) -> &'a BaseJobArgs {
K::Dependencies::base_job_args(dependencies)
}
fn base_job<'a>(
self,
_job: &'a <Self as JobKind>::Job,
dependencies: &'a <<Self as JobKind>::Dependencies as JobDependencies>::JobsAndKinds,
) -> &'a BaseJob {
K::Dependencies::base_job(dependencies)
}
#[track_caller]
fn base_job_args_dyn<'a>(
self,
_args: &'a <Self as JobKind>::Args,
dependencies_args: &'a [DynJobArgs],
) -> &'a BaseJobArgs {
K::Dependencies::base_job_args_dyn(dependencies_args)
}
#[track_caller]
fn base_job_dyn<'a>(
self,
_job: &'a <Self as JobKind>::Job,
dependencies: &'a [DynJob],
) -> &'a BaseJob {
K::Dependencies::base_job_dyn(dependencies)
}
}
pub trait JobKind: JobKindHelper {
type Args: ToArgs;
type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug + Serialize + DeserializeOwned;
type Dependencies: JobDependencies;
fn dependencies(self) -> Self::Dependencies;
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>>;
fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]>;
fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]>;
fn name(self) -> Interned<str>;
fn external_command_params(self, job: &Self::Job) -> Option<CommandParams>;
fn run(
self,
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
global_params: &GlobalParams,
acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>>;
fn subcommand_hidden(self) -> bool {
false
}
fn external_program(self) -> Option<Interned<external::ExternalProgram>> {
None
}
}
trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug {
fn as_any(&self) -> &dyn Any;
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
fn eq_dyn(&self, other: &dyn DynJobKindTrait) -> bool;
fn hash_dyn(&self, state: &mut dyn Hasher);
fn dependencies_kinds_dyn(&self) -> Vec<DynJobKind>;
fn args_group_id_dyn(&self) -> Option<clap::Id>;
fn augment_args_dyn(&self, cmd: clap::Command) -> clap::Command;
fn augment_args_for_update_dyn(&self, cmd: clap::Command) -> clap::Command;
fn from_arg_matches_dyn(
&self,
matches: &mut clap::ArgMatches,
) -> clap::error::Result<DynJobArgs>;
fn name_dyn(&self) -> Interned<str>;
fn subcommand_hidden_dyn(&self) -> bool;
fn deserialize_job_from_json_str(self: Arc<Self>, json: &str) -> serde_json::Result<DynJob>;
fn deserialize_job_from_json_value(
self: Arc<Self>,
json: &serde_json::Value,
) -> serde_json::Result<DynJob>;
}
impl<K: JobKind> DynJobKindTrait for K {
fn as_any(&self) -> &dyn Any {
self
}
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self
}
fn eq_dyn(&self, other: &dyn DynJobKindTrait) -> bool {
other
.as_any()
.downcast_ref::<Self>()
.is_some_and(|other| self == other)
}
fn hash_dyn(&self, mut state: &mut dyn Hasher) {
self.hash(&mut state);
}
fn dependencies_kinds_dyn(&self) -> Vec<DynJobKind> {
self.dependencies().kinds_dyn()
}
fn args_group_id_dyn(&self) -> Option<clap::Id> {
<K::Args as clap::Args>::group_id()
}
fn augment_args_dyn(&self, cmd: clap::Command) -> clap::Command {
<K::Args as clap::Args>::augment_args(cmd)
}
fn augment_args_for_update_dyn(&self, cmd: clap::Command) -> clap::Command {
<K::Args as clap::Args>::augment_args_for_update(cmd)
}
fn from_arg_matches_dyn(
&self,
matches: &mut clap::ArgMatches,
) -> clap::error::Result<DynJobArgs> {
Ok(DynJobArgs::new(
*self,
<K::Args as clap::FromArgMatches>::from_arg_matches_mut(matches)?,
))
}
fn name_dyn(&self) -> Interned<str> {
self.name()
}
fn subcommand_hidden_dyn(&self) -> bool {
self.subcommand_hidden()
}
fn deserialize_job_from_json_str(self: Arc<Self>, json: &str) -> serde_json::Result<DynJob> {
Ok(DynJob::from_arc(self, serde_json::from_str(json)?))
}
fn deserialize_job_from_json_value(
self: Arc<Self>,
json: &serde_json::Value,
) -> serde_json::Result<DynJob> {
Ok(DynJob::from_arc(self, Deserialize::deserialize(json)?))
}
}
#[derive(Clone)]
pub struct DynJobKind(Arc<dyn DynJobKindTrait>);
impl DynJobKind {
pub fn from_arc<K: JobKind>(job_kind: Arc<K>) -> Self {
Self(job_kind)
}
pub fn new<K: JobKind>(job_kind: K) -> Self {
Self(Arc::new(job_kind))
}
pub fn type_id(&self) -> TypeId {
DynJobKindTrait::as_any(&*self.0).type_id()
}
pub fn downcast<K: JobKind>(&self) -> Option<K> {
DynJobKindTrait::as_any(&*self.0).downcast_ref().copied()
}
pub fn downcast_arc<K: JobKind>(self) -> Result<Arc<K>, Self> {
if self.downcast::<K>().is_some() {
Ok(Arc::downcast::<K>(self.0.as_arc_any())
.ok()
.expect("already checked type"))
} else {
Err(self)
}
}
pub fn dependencies_kinds(&self) -> Vec<DynJobKind> {
DynJobKindTrait::dependencies_kinds_dyn(&*self.0)
}
pub fn args_group_id(&self) -> Option<clap::Id> {
DynJobKindTrait::args_group_id_dyn(&*self.0)
}
pub fn augment_args(&self, cmd: clap::Command) -> clap::Command {
DynJobKindTrait::augment_args_dyn(&*self.0, cmd)
}
pub fn augment_args_for_update(&self, cmd: clap::Command) -> clap::Command {
DynJobKindTrait::augment_args_for_update_dyn(&*self.0, cmd)
}
pub fn from_arg_matches(
&self,
matches: &mut clap::ArgMatches,
) -> clap::error::Result<DynJobArgs> {
DynJobKindTrait::from_arg_matches_dyn(&*self.0, matches)
}
pub fn name(&self) -> Interned<str> {
DynJobKindTrait::name_dyn(&*self.0)
}
pub fn subcommand_hidden(&self) -> bool {
DynJobKindTrait::subcommand_hidden_dyn(&*self.0)
}
pub fn deserialize_job_from_json_str(self, json: &str) -> serde_json::Result<DynJob> {
DynJobKindTrait::deserialize_job_from_json_str(self.0, json)
}
pub fn deserialize_job_from_json_value(
self,
json: &serde_json::Value,
) -> serde_json::Result<DynJob> {
DynJobKindTrait::deserialize_job_from_json_value(self.0, json)
}
fn make_subcommand_without_args(&self) -> clap::Command {
clap::Command::new(Interned::into_inner(self.name())).hide(self.subcommand_hidden())
}
pub fn make_subcommand(&self) -> clap::Command {
let mut subcommand = self.make_subcommand_without_args();
for dependency in self.dependencies_kinds() {
subcommand = dependency.augment_args(subcommand);
}
self.augment_args(subcommand)
}
pub fn make_subcommand_for_update(&self) -> clap::Command {
let mut subcommand = self.make_subcommand_without_args();
for dependency in self.dependencies_kinds() {
subcommand = dependency.augment_args_for_update(subcommand);
}
self.augment_args_for_update(subcommand)
}
}
impl Hash for DynJobKind {
fn hash<H: Hasher>(&self, state: &mut H) {
self.type_id().hash(state);
DynJobKindTrait::hash_dyn(&*self.0, state);
}
}
impl PartialEq for DynJobKind {
fn eq(&self, other: &Self) -> bool {
DynJobKindTrait::eq_dyn(&*self.0, &*other.0)
}
}
impl Eq for DynJobKind {}
impl fmt::Debug for DynJobKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Serialize for DynJobKind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.name().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for DynJobKind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let name = Cow::<str>::deserialize(deserializer)?;
match Self::registry().get_by_name(&name) {
Some(retval) => Ok(retval.clone()),
None => Err(D::Error::custom(format_args!(
"unknown job kind: name not found in registry: {name:?}"
))),
}
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct DynJobKindValueParser;
#[derive(Clone, PartialEq, Eq, Hash)]
struct DynJobKindValueEnum {
name: Interned<str>,
job_kind: DynJobKind,
}
impl clap::ValueEnum for DynJobKindValueEnum {
fn value_variants<'a>() -> &'a [Self] {
Interned::into_inner(
registry::JobKindRegistrySnapshot::get()
.iter_with_names()
.map(|(name, job_kind)| Self {
name,
job_kind: job_kind.clone(),
})
.collect(),
)
}
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
Some(clap::builder::PossibleValue::new(Interned::into_inner(
self.name,
)))
}
}
impl clap::builder::TypedValueParser for DynJobKindValueParser {
type Value = DynJobKind;
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> clap::error::Result<Self::Value> {
clap::builder::EnumValueParser::<DynJobKindValueEnum>::new()
.parse_ref(cmd, arg, value)
.map(|v| v.job_kind)
}
fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
static ENUM_VALUE_PARSER: OnceLock<clap::builder::EnumValueParser<DynJobKindValueEnum>> =
OnceLock::new();
ENUM_VALUE_PARSER
.get_or_init(clap::builder::EnumValueParser::<DynJobKindValueEnum>::new)
.possible_values()
}
}
impl clap::builder::ValueParserFactory for DynJobKind {
type Parser = DynJobKindValueParser;
fn value_parser() -> Self::Parser {
DynJobKindValueParser::default()
}
}
trait DynExtendInternedStr {
fn extend_from_slice(&mut self, items: &[Interned<str>]);
}
impl Extend<Interned<str>> for dyn DynExtendInternedStr + '_ {
fn extend<T: IntoIterator<Item = Interned<str>>>(&mut self, iter: T) {
let mut buf = [Interned::default(); 64];
let mut buf_len = 0;
iter.into_iter().for_each(|item| {
buf[buf_len] = item;
buf_len += 1;
if buf_len == buf.len() {
<dyn DynExtendInternedStr as DynExtendInternedStr>::extend_from_slice(self, &buf);
buf_len = 0;
}
});
if buf_len > 0 {
<dyn DynExtendInternedStr as DynExtendInternedStr>::extend_from_slice(
self,
&buf[..buf_len],
);
}
}
}
impl<T: Extend<Interned<str>>> DynExtendInternedStr for T {
fn extend_from_slice(&mut self, items: &[Interned<str>]) {
self.extend(items.iter().copied());
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct DynJobArgsInner<K: JobKind>(JobKindAndArgs<K>);
impl<K: JobKind + fmt::Debug> fmt::Debug for DynJobArgsInner<K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self(JobKindAndArgs { kind, args }) = self;
f.debug_struct("DynJobArgs")
.field("kind", kind)
.field("args", args)
.finish()
}
}
trait DynJobArgsTrait: 'static + Send + Sync + fmt::Debug {
fn as_any(&self) -> &dyn Any;
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
fn kind_type_id(&self) -> TypeId;
fn eq_dyn(&self, other: &dyn DynJobArgsTrait) -> bool;
fn hash_dyn(&self, state: &mut dyn Hasher);
fn kind(&self) -> DynJobKind;
fn to_args_extend_vec(&self, args: Vec<Interned<OsStr>>) -> Vec<Interned<OsStr>>;
fn clone_into_arc(&self) -> Arc<dyn DynJobArgsTrait>;
fn update_from_arg_matches(
&mut self,
matches: &mut clap::ArgMatches,
) -> clap::error::Result<()>;
#[track_caller]
fn args_to_jobs(
self: Arc<Self>,
dependencies_args: Vec<DynJobArgs>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(DynJob, Vec<DynJob>)>;
#[track_caller]
fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs;
}
impl<K: JobKind> DynJobArgsTrait for DynJobArgsInner<K> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self
}
fn kind_type_id(&self) -> TypeId {
TypeId::of::<K>()
}
fn eq_dyn(&self, other: &dyn DynJobArgsTrait) -> bool {
other
.as_any()
.downcast_ref::<Self>()
.is_some_and(|other| self == other)
}
fn hash_dyn(&self, mut state: &mut dyn Hasher) {
self.hash(&mut state);
}
fn kind(&self) -> DynJobKind {
DynJobKind::new(self.0.kind)
}
fn to_args_extend_vec(&self, args: Vec<Interned<OsStr>>) -> Vec<Interned<OsStr>> {
let mut writer = ArgsWriter(args);
self.0.args.to_args(&mut writer);
writer.0
}
fn clone_into_arc(&self) -> Arc<dyn DynJobArgsTrait> {
Arc::new(self.clone())
}
fn update_from_arg_matches(
&mut self,
matches: &mut clap::ArgMatches,
) -> clap::error::Result<()> {
clap::FromArgMatches::update_from_arg_matches_mut(&mut self.0.args, matches)
}
#[track_caller]
fn args_to_jobs(
self: Arc<Self>,
dependencies_args: Vec<DynJobArgs>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(DynJob, Vec<DynJob>)> {
let JobAndDependencies { job, dependencies } = JobArgsAndDependencies {
args: Arc::unwrap_or_clone(self).0,
dependencies: K::Dependencies::from_dyn_args(dependencies_args),
}
.args_to_jobs(params, global_params)?;
Ok((job.into(), K::Dependencies::into_dyn_jobs(dependencies)))
}
#[track_caller]
fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs {
self.0
.kind
.base_job_args_dyn(&self.0.args, dependencies_args)
}
}
#[derive(Clone)]
pub struct DynJobArgs(Arc<dyn DynJobArgsTrait>);
impl DynJobArgs {
pub fn new<K: JobKind>(kind: K, args: K::Args) -> Self {
Self(Arc::new(DynJobArgsInner(JobKindAndArgs { kind, args })))
}
pub fn kind_type_id(&self) -> TypeId {
DynJobArgsTrait::kind_type_id(&*self.0)
}
pub fn downcast_ref<K: JobKind>(&self) -> Option<(&K, &K::Args)> {
let DynJobArgsInner::<K>(JobKindAndArgs { kind, args }) =
DynJobArgsTrait::as_any(&*self.0).downcast_ref()?;
Some((kind, args))
}
pub fn downcast<K: JobKind>(self) -> Result<JobKindAndArgs<K>, Self> {
if self.downcast_ref::<K>().is_some() {
let this = Arc::downcast::<DynJobArgsInner<K>>(self.0.as_arc_any())
.ok()
.expect("already checked type");
Ok(Arc::unwrap_or_clone(this).0)
} else {
Err(self)
}
}
pub fn kind(&self) -> DynJobKind {
DynJobArgsTrait::kind(&*self.0)
}
pub fn to_args_vec(&self) -> Vec<Interned<OsStr>> {
self.to_args_extend_vec(Vec::new())
}
pub fn to_args_extend_vec(&self, args: Vec<Interned<OsStr>>) -> Vec<Interned<OsStr>> {
DynJobArgsTrait::to_args_extend_vec(&*self.0, args)
}
fn make_mut(&mut self) -> &mut dyn DynJobArgsTrait {
// can't just return the reference if the first get_mut returns Some since
// as of rustc 1.90.0 this causes a false-positive lifetime error.
if Arc::get_mut(&mut self.0).is_none() {
self.0 = DynJobArgsTrait::clone_into_arc(&*self.0);
}
Arc::get_mut(&mut self.0).expect("clone_into_arc returns a new arc with a ref-count of 1")
}
pub fn update_from_arg_matches(
&mut self,
matches: &mut clap::ArgMatches,
) -> clap::error::Result<()> {
DynJobArgsTrait::update_from_arg_matches(self.make_mut(), matches)
}
pub fn args_to_jobs(
self,
dependencies_args: Vec<DynJobArgs>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(DynJob, Vec<DynJob>)> {
DynJobArgsTrait::args_to_jobs(self.0, dependencies_args, params, global_params)
}
#[track_caller]
pub fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs {
DynJobArgsTrait::base_job_args_dyn(&*self.0, dependencies_args)
}
}
impl Hash for DynJobArgs {
fn hash<H: Hasher>(&self, state: &mut H) {
self.kind_type_id().hash(state);
DynJobArgsTrait::hash_dyn(&*self.0, state);
}
}
impl PartialEq for DynJobArgs {
fn eq(&self, other: &Self) -> bool {
DynJobArgsTrait::eq_dyn(&*self.0, &*other.0)
}
}
impl Eq for DynJobArgs {}
impl fmt::Debug for DynJobArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(PartialEq, Eq, Hash)]
struct DynJobInner<K: JobKind> {
kind: Arc<K>,
job: K::Job,
inputs: Interned<[JobItemName]>,
outputs: Interned<[JobItemName]>,
external_command_params: Option<CommandParams>,
}
impl<K: JobKind<Job: Clone>> Clone for DynJobInner<K> {
fn clone(&self) -> Self {
Self {
kind: self.kind.clone(),
job: self.job.clone(),
inputs: self.inputs,
outputs: self.outputs,
external_command_params: self.external_command_params,
}
}
}
impl<K: JobKind> fmt::Debug for DynJobInner<K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
kind,
job,
inputs,
outputs,
external_command_params,
} = self;
f.debug_struct("DynJob")
.field("kind", kind)
.field("job", job)
.field("inputs", inputs)
.field("outputs", outputs)
.field("external_command_params", external_command_params)
.finish()
}
}
trait DynJobTrait: 'static + Send + Sync + fmt::Debug {
fn as_any(&self) -> &dyn Any;
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
fn eq_dyn(&self, other: &dyn DynJobTrait) -> bool;
fn hash_dyn(&self, state: &mut dyn Hasher);
fn kind_type_id(&self) -> TypeId;
fn kind(&self) -> DynJobKind;
fn inputs(&self) -> Interned<[JobItemName]>;
fn outputs(&self) -> Interned<[JobItemName]>;
fn external_command_params(&self) -> Option<CommandParams>;
fn serialize_to_json_ascii(&self) -> serde_json::Result<String>;
fn serialize_to_json_value(&self) -> serde_json::Result<serde_json::Value>;
fn run(
&self,
inputs: &[JobItem],
params: &JobParams,
global_params: &GlobalParams,
acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>>;
#[track_caller]
fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob;
}
impl<K: JobKind> DynJobTrait for DynJobInner<K> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self
}
fn eq_dyn(&self, other: &dyn DynJobTrait) -> bool {
other
.as_any()
.downcast_ref::<Self>()
.is_some_and(|other| self == other)
}
fn hash_dyn(&self, mut state: &mut dyn Hasher) {
self.hash(&mut state);
}
fn kind_type_id(&self) -> TypeId {
TypeId::of::<K>()
}
fn kind(&self) -> DynJobKind {
DynJobKind(self.kind.clone())
}
fn inputs(&self) -> Interned<[JobItemName]> {
self.inputs
}
fn outputs(&self) -> Interned<[JobItemName]> {
self.outputs
}
fn external_command_params(&self) -> Option<CommandParams> {
self.external_command_params
}
fn serialize_to_json_ascii(&self) -> serde_json::Result<String> {
crate::util::serialize_to_json_ascii(&self.job)
}
fn serialize_to_json_value(&self) -> serde_json::Result<serde_json::Value> {
serde_json::to_value(&self.job)
}
fn run(
&self,
inputs: &[JobItem],
params: &JobParams,
global_params: &GlobalParams,
acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
self.kind
.run(&self.job, inputs, params, global_params, acquired_job)
}
#[track_caller]
fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob {
self.kind.base_job_dyn(&self.job, dependencies)
}
}
#[derive(Clone, Debug)]
pub struct DynJob(Arc<dyn DynJobTrait>);
impl DynJob {
pub fn from_arc<K: JobKind>(job_kind: Arc<K>, job: K::Job) -> Self {
let inputs = job_kind.inputs(&job);
let outputs = job_kind.outputs(&job);
let external_command_params = job_kind.external_command_params(&job);
Self(Arc::new(DynJobInner {
kind: job_kind,
job,
inputs,
outputs,
external_command_params,
}))
}
pub fn new<K: JobKind>(job_kind: K, job: K::Job) -> Self {
Self::from_arc(Arc::new(job_kind), job)
}
pub fn kind_type_id(&self) -> TypeId {
self.0.kind_type_id()
}
pub fn downcast_ref<K: JobKind>(&self) -> Option<(&K, &K::Job)> {
let DynJobInner { kind, job, .. } = self.0.as_any().downcast_ref()?;
Some((kind, job))
}
pub fn downcast<K: JobKind<Job: Clone>>(self) -> Result<JobAndKind<K>, Self> {
if self.kind_type_id() == TypeId::of::<K>() {
let DynJobInner { kind, job, .. } = Arc::unwrap_or_clone(
self.0
.as_arc_any()
.downcast::<DynJobInner<K>>()
.expect("already checked type"),
);
Ok(JobAndKind { kind: *kind, job })
} else {
Err(self)
}
}
pub fn kind(&self) -> DynJobKind {
DynJobTrait::kind(&*self.0)
}
pub fn inputs(&self) -> Interned<[JobItemName]> {
DynJobTrait::inputs(&*self.0)
}
pub fn outputs(&self) -> Interned<[JobItemName]> {
DynJobTrait::outputs(&*self.0)
}
pub fn serialize_to_json_ascii(&self) -> serde_json::Result<String> {
DynJobTrait::serialize_to_json_ascii(&*self.0)
}
pub fn serialize_to_json_value(&self) -> serde_json::Result<serde_json::Value> {
DynJobTrait::serialize_to_json_value(&*self.0)
}
pub fn external_command_params(&self) -> Option<CommandParams> {
DynJobTrait::external_command_params(&*self.0)
}
#[track_caller]
pub fn internal_command_params_with_program_prefix(
&self,
internal_program_prefix: &[Interned<OsStr>],
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> CommandParams {
let mut command_line = internal_program_prefix.to_vec();
let command_line = match RunSingleJob::try_add_subcommand(platform, self, &mut command_line)
{
Ok(()) => {
command_line.extend_from_slice(extra_args);
Intern::intern_owned(command_line)
}
Err(e) => panic!("Serializing job {:?} failed: {e}", self.kind().name()),
};
CommandParams {
command_line,
current_dir: None,
}
}
#[track_caller]
pub fn internal_command_params(
&self,
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> CommandParams {
self.internal_command_params_with_program_prefix(
&[program_name_for_internal_jobs()],
platform,
extra_args,
)
}
#[track_caller]
pub fn command_params_with_internal_program_prefix(
&self,
internal_program_prefix: &[Interned<OsStr>],
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> CommandParams {
match self.external_command_params() {
Some(v) => v,
None => self.internal_command_params_with_program_prefix(
internal_program_prefix,
platform,
extra_args,
),
}
}
#[track_caller]
pub fn command_params(
&self,
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> CommandParams {
self.command_params_with_internal_program_prefix(
&[program_name_for_internal_jobs()],
platform,
extra_args,
)
}
pub fn run(
&self,
inputs: &[JobItem],
params: &JobParams,
global_params: &GlobalParams,
acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
DynJobTrait::run(&*self.0, inputs, params, global_params, acquired_job)
}
#[track_caller]
pub fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob {
DynJobTrait::base_job_dyn(&*self.0, dependencies)
}
}
impl Eq for DynJob {}
impl PartialEq for DynJob {
fn eq(&self, other: &Self) -> bool {
DynJobTrait::eq_dyn(&*self.0, &*other.0)
}
}
impl Hash for DynJob {
fn hash<H: Hasher>(&self, state: &mut H) {
DynJobTrait::hash_dyn(&*self.0, state);
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename = "DynJob")]
struct DynJobSerde {
kind: DynJobKind,
job: serde_json::Value,
}
impl Serialize for DynJob {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
DynJobSerde {
kind: self.kind(),
job: self.serialize_to_json_value().map_err(S::Error::custom)?,
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for DynJob {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let DynJobSerde { kind, job } = Deserialize::deserialize(deserializer)?;
kind.deserialize_job_from_json_value(&job)
.map_err(D::Error::custom)
}
}
pub trait RunBuild<Extra: ToArgs = NoArgs>: Sized {
fn main_without_platform<F>(application_name: impl AsRef<str>, make_params: F)
where
Self: clap::Parser + Clone,
F: FnOnce(Self, Extra) -> eyre::Result<JobParams>,
{
let application_name = application_name.as_ref();
match Self::try_main_without_platform(application_name, make_params) {
Ok(()) => {}
Err(e) => {
let e = GlobalParams::new(Some(Self::command()), application_name)
.exit_if_clap_error(e);
eprintln!("{e:#}");
std::process::exit(1);
}
}
}
fn try_main_without_platform<F>(
application_name: impl AsRef<str>,
make_params: F,
) -> eyre::Result<()>
where
Self: clap::Parser + Clone,
F: FnOnce(Self, Extra) -> eyre::Result<JobParams>,
{
let args = Self::parse();
let global_params = GlobalParams::new(Some(Self::command()), application_name);
args.clone()
.run_without_platform(|extra| make_params(args, extra), &global_params)
.map_err(|e| global_params.exit_if_clap_error(e))
}
fn run_without_platform<F>(
self,
make_params: F,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(Extra) -> eyre::Result<JobParams>;
fn get_platform(&self) -> Option<&DynPlatform>;
fn main<F>(application_name: impl AsRef<str>, make_params: F)
where
Self: clap::Parser + Clone,
F: FnOnce(Self, DynPlatform, Extra) -> eyre::Result<JobParams>,
{
let application_name = application_name.as_ref();
match Self::try_main(application_name, make_params) {
Ok(()) => {}
Err(e) => {
let e = GlobalParams::new(Some(Self::command()), application_name)
.exit_if_clap_error(e);
eprintln!("{e:#}");
std::process::exit(1);
}
}
}
fn try_main<F>(application_name: impl AsRef<str>, make_params: F) -> eyre::Result<()>
where
Self: clap::Parser + Clone,
F: FnOnce(Self, DynPlatform, Extra) -> eyre::Result<JobParams>,
{
let args = Self::parse();
let global_params = GlobalParams::new(Some(Self::command()), application_name);
let Some(platform) = args.get_platform().cloned() else {
return args.handle_missing_platform(&global_params);
};
args.clone()
.run(
|platform, extra| make_params(args, platform, extra),
platform,
&global_params,
)
.map_err(|e| global_params.exit_if_clap_error(e))
}
fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> {
global_params
.clap_error(
clap::error::ErrorKind::MissingRequiredArgument,
"--platform is required",
)
.exit();
}
fn run<F>(
self,
make_params: F,
platform: DynPlatform,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(DynPlatform, Extra) -> eyre::Result<JobParams>,
{
self.run_without_platform(|extra| make_params(platform, extra), global_params)
}
}
impl<K: JobKind> RunBuild for JobArgsAndDependencies<K> {
fn run_without_platform<F>(
self,
make_params: F,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(NoArgs) -> eyre::Result<JobParams>,
{
let params = make_params(NoArgs)?;
self.args_to_jobs(&params, global_params)?
.run_without_platform(|_| Ok(params), global_params)
}
fn get_platform(&self) -> Option<&DynPlatform> {
self.base_job_args().platform.as_ref()
}
fn run<F>(
self,
make_params: F,
platform: DynPlatform,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(DynPlatform, NoArgs) -> eyre::Result<JobParams>,
{
let params = make_params(platform.clone(), NoArgs)?;
self.args_to_jobs(&params, global_params)?
.run(|_, _| Ok(params), platform, global_params)
}
}
impl<K: JobKind> RunBuild for JobAndDependencies<K> {
fn run_without_platform<F>(
self,
make_params: F,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(NoArgs) -> eyre::Result<JobParams>,
{
let params = make_params(NoArgs)?;
let Self { job, dependencies } = self;
let mut jobs = vec![DynJob::from(job)];
K::Dependencies::into_dyn_jobs_extend(dependencies, &mut jobs);
let mut job_graph = JobGraph::new();
job_graph.add_jobs(jobs); // add all at once to avoid recomputing graph properties multiple times
job_graph.run(&params, global_params)
}
fn get_platform(&self) -> Option<&DynPlatform> {
self.base_job().platform()
}
fn run<F>(
self,
make_params: F,
platform: DynPlatform,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(DynPlatform, NoArgs) -> eyre::Result<JobParams>,
{
let params = make_params(platform, NoArgs)?;
let Self { job, dependencies } = self;
let mut jobs = vec![DynJob::from(job)];
K::Dependencies::into_dyn_jobs_extend(dependencies, &mut jobs);
let mut job_graph = JobGraph::new();
job_graph.add_jobs(jobs); // add all at once to avoid recomputing graph properties multiple times
job_graph.run(&params, global_params)
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct RunSingleJob<Extra = NoArgs> {
pub platform: Option<DynPlatform>,
pub job: DynJob,
pub extra: Extra,
}
impl RunSingleJob {
pub const SUBCOMMAND_NAME: &'static str = "run-single-job";
fn try_add_subcommand(
platform: Option<&DynPlatform>,
job: &DynJob,
subcommand_line: &mut Vec<Interned<OsStr>>,
) -> serde_json::Result<()> {
let mut json = job.serialize_to_json_ascii()?;
json.insert_str(0, "--json=");
subcommand_line.push(Self::SUBCOMMAND_NAME.intern().into());
if let Some(platform) = platform {
subcommand_line.push(
format!("--platform={}", platform.name())
.intern_deref()
.into(),
);
}
subcommand_line.push(
format!("--name={}", job.kind().name())
.intern_deref()
.into(),
);
subcommand_line.push(json.intern_deref().into());
Ok(())
}
}
impl<Extra: ToArgs> TryFrom<RunSingleJobClap<Extra>> for RunSingleJob<Extra> {
type Error = clap::Error;
fn try_from(value: RunSingleJobClap<Extra>) -> Result<Self, Self::Error> {
let RunSingleJobClap::RunSingleJob {
platform,
name: job_kind,
json,
extra,
} = value;
let name = job_kind.name();
job_kind
.deserialize_job_from_json_str(&json)
.map_err(|e| {
clap::Error::raw(
clap::error::ErrorKind::ValueValidation,
format_args!("failed to parse job {name} from JSON: {e}"),
)
})
.map(|job| Self {
platform,
job,
extra,
})
}
}
#[derive(clap::Subcommand)]
enum RunSingleJobClap<Extra: ToArgs = NoArgs> {
#[command(name = RunSingleJob::SUBCOMMAND_NAME, hide = true)]
RunSingleJob {
#[arg(long)]
platform: Option<DynPlatform>,
#[arg(long)]
name: DynJobKind,
#[arg(long)]
json: String,
#[command(flatten)]
extra: Extra,
},
}
impl<Extra: ToArgs> clap::Subcommand for RunSingleJob<Extra> {
fn augment_subcommands(cmd: clap::Command) -> clap::Command {
RunSingleJobClap::<Extra>::augment_subcommands(cmd)
}
fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command {
RunSingleJobClap::<Extra>::augment_subcommands(cmd)
}
fn has_subcommand(name: &str) -> bool {
RunSingleJobClap::<Extra>::has_subcommand(name)
}
}
impl<Extra: ToArgs> clap::FromArgMatches for RunSingleJob<Extra> {
fn from_arg_matches(matches: &clap::ArgMatches) -> clap::error::Result<Self> {
RunSingleJobClap::from_arg_matches(matches)?.try_into()
}
fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> clap::error::Result<Self> {
RunSingleJobClap::from_arg_matches_mut(matches)?.try_into()
}
fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> clap::error::Result<()> {
*self = Self::from_arg_matches(matches)?;
Ok(())
}
fn update_from_arg_matches_mut(
&mut self,
matches: &mut clap::ArgMatches,
) -> clap::error::Result<()> {
*self = Self::from_arg_matches_mut(matches)?;
Ok(())
}
}
impl<Extra: ToArgs> RunBuild<Extra> for RunSingleJob<Extra> {
fn run_without_platform<F>(
self,
make_params: F,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(Extra) -> eyre::Result<JobParams>,
{
let params = make_params(self.extra)?;
let mut job_graph = JobGraph::new();
job_graph.add_jobs([self.job]);
job_graph.run(&params, global_params)
}
fn get_platform(&self) -> Option<&DynPlatform> {
self.platform.as_ref()
}
}
#[derive(Clone, PartialEq, Eq, Hash, clap::Subcommand)]
pub enum Completions {
#[non_exhaustive]
Completions {
#[arg(default_value = Self::shell_str_from_env(), required = Self::shell_from_env().is_none())]
shell: clap_complete::aot::Shell,
},
}
impl Completions {
pub fn new(shell: clap_complete::aot::Shell) -> Self {
Self::Completions { shell }
}
pub fn from_env() -> Option<Self> {
Some(Self::Completions {
shell: Self::shell_from_env()?,
})
}
fn shell_from_env() -> Option<clap_complete::aot::Shell> {
static SHELL: OnceLock<Option<clap_complete::aot::Shell>> = OnceLock::new();
*SHELL.get_or_init(clap_complete::aot::Shell::from_env)
}
fn shell_str_from_env() -> clap::builder::Resettable<clap::builder::OsStr> {
static SHELL_STR: OnceLock<Option<String>> = OnceLock::new();
SHELL_STR
.get_or_init(|| Self::shell_from_env().map(|v| v.to_string()))
.as_deref()
.map(Into::into)
.into()
}
}
impl RunBuild for Completions {
fn run_without_platform<F>(
self,
_make_params: F,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(NoArgs) -> eyre::Result<JobParams>,
{
let Self::Completions { shell } = self;
let Some(cmd) = global_params.top_level_cmd() else {
eyre::bail!("completions command requires GlobalParams::top_level_cmd() to be Some");
};
let bin_name = cmd.get_bin_name().map(str::intern).unwrap_or_else(|| {
program_name_for_internal_jobs()
.to_interned_str()
.expect("program name is invalid UTF-8")
});
clap_complete::aot::generate(
shell,
&mut cmd.clone(),
&*bin_name,
&mut std::io::BufWriter::new(std::io::stdout().lock()),
);
Ok(())
}
fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> {
self.run_without_platform(|_| unreachable!(), global_params)
}
fn get_platform(&self) -> Option<&DynPlatform> {
None
}
}
#[derive(
clap::Args,
Copy,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Debug,
Default,
Serialize,
Deserialize,
)]
pub struct NoArgs;
impl ToArgs for NoArgs {
fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) {
let Self {} = self;
}
}
#[derive(Clone, PartialEq, Eq, Hash, clap::Parser)]
pub enum BuildCli<Extra: ToArgs = NoArgs> {
#[clap(flatten)]
Job(AnyJobSubcommand<Extra>),
#[clap(flatten)]
RunSingleJob(RunSingleJob<Extra>),
#[clap(flatten)]
Completions(Completions),
#[cfg(unix)]
#[clap(flatten)]
CreateUnixShellScript(CreateUnixShellScript<Extra>),
}
impl<Extra: ToArgs> RunBuild<Extra> for BuildCli<Extra> {
fn run_without_platform<F>(
self,
make_params: F,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(Extra) -> eyre::Result<JobParams>,
{
match self {
BuildCli::Job(v) => v.run_without_platform(make_params, global_params),
BuildCli::RunSingleJob(v) => v.run_without_platform(make_params, global_params),
BuildCli::Completions(v) => {
v.run_without_platform(|NoArgs {}| unreachable!(), global_params)
}
#[cfg(unix)]
BuildCli::CreateUnixShellScript(v) => {
v.run_without_platform(make_params, global_params)
}
}
}
fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> {
match self {
BuildCli::Job(v) => v.handle_missing_platform(global_params),
BuildCli::RunSingleJob(v) => v.handle_missing_platform(global_params),
BuildCli::Completions(v) => v.handle_missing_platform(global_params),
#[cfg(unix)]
BuildCli::CreateUnixShellScript(v) => v.handle_missing_platform(global_params),
}
}
fn get_platform(&self) -> Option<&DynPlatform> {
match self {
BuildCli::Job(v) => v.get_platform(),
BuildCli::RunSingleJob(v) => v.get_platform(),
BuildCli::Completions(v) => v.get_platform(),
#[cfg(unix)]
BuildCli::CreateUnixShellScript(v) => v.get_platform(),
}
}
fn run<F>(
self,
make_params: F,
platform: DynPlatform,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(DynPlatform, Extra) -> eyre::Result<JobParams>,
{
match self {
BuildCli::Job(v) => v.run(make_params, platform, global_params),
BuildCli::RunSingleJob(v) => v.run(make_params, platform, global_params),
BuildCli::Completions(v) => {
v.run(|_, NoArgs {}| unreachable!(), platform, global_params)
}
#[cfg(unix)]
BuildCli::CreateUnixShellScript(v) => v.run(make_params, platform, global_params),
}
}
}
#[cfg(unix)]
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Subcommand)]
enum CreateUnixShellScriptInner<Extra: ToArgs> {
CreateUnixShellScript {
#[arg(name = "i-know-this-is-incomplete", long, required = true, action = ArgAction::SetTrue)]
_incomplete: (),
#[command(subcommand)]
inner: AnyJobSubcommand<Extra>,
},
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct CreateUnixShellScript<Extra: ToArgs = NoArgs>(CreateUnixShellScriptInner<Extra>);
impl<Extra: ToArgs> RunBuild<Extra> for CreateUnixShellScript<Extra> {
fn run_without_platform<F>(
self,
make_params: F,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(Extra) -> eyre::Result<JobParams>,
{
let platform = self.get_platform().cloned();
let CreateUnixShellScriptInner::CreateUnixShellScript {
_incomplete: (),
inner:
AnyJobSubcommand {
args,
dependencies_args,
extra,
},
} = self.0;
let extra_args = extra.to_interned_args_vec();
let params = make_params(extra)?;
let bin_name = global_params
.top_level_cmd()
.and_then(clap::Command::get_bin_name)
.map(|v| OsStr::new(v).intern());
let (job, dependencies) = args.args_to_jobs(dependencies_args, &params, global_params)?;
let mut job_graph = JobGraph::new();
job_graph.add_jobs([job].into_iter().chain(dependencies));
std::io::stdout().write_all(
job_graph
.to_unix_shell_script_with_internal_program_prefix(
&[bin_name.unwrap_or_else(|| program_name_for_internal_jobs())],
platform.as_ref(),
&extra_args,
)
.as_bytes(),
)?;
Ok(())
}
fn get_platform(&self) -> Option<&DynPlatform> {
let CreateUnixShellScriptInner::CreateUnixShellScript { inner, .. } = &self.0;
inner.get_platform()
}
}
impl<Extra: ToArgs> clap::FromArgMatches for CreateUnixShellScript<Extra> {
fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
clap::FromArgMatches::from_arg_matches(matches).map(Self)
}
fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> Result<Self, clap::Error> {
clap::FromArgMatches::from_arg_matches_mut(matches).map(Self)
}
fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> {
self.0.update_from_arg_matches(matches)
}
fn update_from_arg_matches_mut(
&mut self,
matches: &mut clap::ArgMatches,
) -> Result<(), clap::Error> {
self.0.update_from_arg_matches_mut(matches)
}
}
#[cfg(unix)]
impl<Extra: ToArgs> clap::Subcommand for CreateUnixShellScript<Extra> {
fn augment_subcommands(cmd: clap::Command) -> clap::Command {
CreateUnixShellScriptInner::<Extra>::augment_subcommands(cmd)
}
fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command {
CreateUnixShellScriptInner::<Extra>::augment_subcommands_for_update(cmd)
}
fn has_subcommand(name: &str) -> bool {
CreateUnixShellScriptInner::<Extra>::has_subcommand(name)
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct AnyJobSubcommand<Extra: ToArgs = NoArgs> {
pub args: DynJobArgs,
pub dependencies_args: Vec<DynJobArgs>,
pub extra: Extra,
}
impl<Extra: ToArgs> AnyJobSubcommand<Extra> {
pub fn from_subcommand_arg_matches(
job_kind: &DynJobKind,
matches: &mut clap::ArgMatches,
) -> clap::error::Result<Self> {
let dependencies = job_kind.dependencies_kinds();
let dependencies_args = Result::from_iter(
dependencies
.into_iter()
.map(|dependency| dependency.from_arg_matches(matches)),
)?;
Ok(Self {
args: job_kind.clone().from_arg_matches(matches)?,
dependencies_args,
extra: Extra::from_arg_matches_mut(matches)?,
})
}
pub fn update_from_subcommand_arg_matches(
&mut self,
job_kind: &DynJobKind,
matches: &mut clap::ArgMatches,
) -> clap::error::Result<()> {
let Self {
args,
dependencies_args,
extra,
} = self;
if *job_kind == args.kind() {
for dependency in dependencies_args {
dependency.update_from_arg_matches(matches)?;
}
args.update_from_arg_matches(matches)?;
} else {
let dependencies = job_kind.dependencies_kinds();
let new_dependencies_args = Result::from_iter(
dependencies
.into_iter()
.map(|dependency| dependency.from_arg_matches(matches)),
)?;
*args = job_kind.clone().from_arg_matches(matches)?;
*dependencies_args = new_dependencies_args;
}
extra.update_from_arg_matches_mut(matches)
}
}
impl<Extra: ToArgs> clap::Subcommand for AnyJobSubcommand<Extra> {
fn augment_subcommands(mut cmd: clap::Command) -> clap::Command {
let snapshot = registry::JobKindRegistrySnapshot::get();
for job_kind in &snapshot {
cmd = cmd.subcommand(Extra::augment_args(job_kind.make_subcommand()));
}
cmd
}
fn augment_subcommands_for_update(mut cmd: clap::Command) -> clap::Command {
let snapshot = registry::JobKindRegistrySnapshot::get();
for job_kind in &snapshot {
cmd = cmd.subcommand(Extra::augment_args_for_update(
job_kind.make_subcommand_for_update(),
));
}
cmd
}
fn has_subcommand(name: &str) -> bool {
registry::JobKindRegistrySnapshot::get()
.get_by_name(name)
.is_some()
}
}
impl<Extra: ToArgs> clap::FromArgMatches for AnyJobSubcommand<Extra> {
fn from_arg_matches(matches: &clap::ArgMatches) -> clap::error::Result<Self> {
Self::from_arg_matches_mut(&mut matches.clone())
}
fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> clap::error::Result<Self> {
if let Some((name, mut matches)) = matches.remove_subcommand() {
let job_kind_registry_snapshot = registry::JobKindRegistrySnapshot::get();
if let Some(job_kind) = job_kind_registry_snapshot.get_by_name(&name) {
Self::from_subcommand_arg_matches(job_kind, &mut matches)
} else {
Err(clap::Error::raw(
clap::error::ErrorKind::InvalidSubcommand,
format!("the subcommand '{name}' wasn't recognized"),
))
}
} else {
Err(clap::Error::raw(
clap::error::ErrorKind::MissingSubcommand,
"a subcommand is required but one was not provided",
))
}
}
fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> clap::error::Result<()> {
Self::update_from_arg_matches_mut(self, &mut matches.clone())
}
fn update_from_arg_matches_mut(
&mut self,
matches: &mut clap::ArgMatches,
) -> clap::error::Result<()> {
if let Some((name, mut matches)) = matches.remove_subcommand() {
let job_kind_registry_snapshot = registry::JobKindRegistrySnapshot::get();
if let Some(job_kind) = job_kind_registry_snapshot.get_by_name(&name) {
self.update_from_subcommand_arg_matches(job_kind, &mut matches)
} else {
Err(clap::Error::raw(
clap::error::ErrorKind::InvalidSubcommand,
format!("the subcommand '{name}' wasn't recognized"),
))
}
} else {
Err(clap::Error::raw(
clap::error::ErrorKind::MissingSubcommand,
"a subcommand is required but one was not provided",
))
}
}
}
impl<Extra: ToArgs> RunBuild<Extra> for AnyJobSubcommand<Extra> {
fn run_without_platform<F>(
self,
make_params: F,
global_params: &GlobalParams,
) -> eyre::Result<()>
where
F: FnOnce(Extra) -> eyre::Result<JobParams>,
{
let Self {
args,
dependencies_args,
extra,
} = self;
let params = make_params(extra)?;
let (job, dependencies) = args.args_to_jobs(dependencies_args, &params, global_params)?;
let mut job_graph = JobGraph::new();
job_graph.add_jobs([job].into_iter().chain(dependencies)); // add all at once to avoid recomputing graph properties multiple times
job_graph.run(&params, global_params)
}
fn get_platform(&self) -> Option<&DynPlatform> {
self.args
.base_job_args_dyn(&self.dependencies_args)
.platform
.as_ref()
}
}
pub fn program_name_for_internal_jobs() -> Interned<OsStr> {
static PROGRAM_NAME: OnceLock<Interned<OsStr>> = OnceLock::new();
*PROGRAM_NAME.get_or_init(|| {
std::env::args_os()
.next()
.expect("can't get program name")
.intern_deref()
})
}
#[derive(clap::Args, Debug, Clone, Hash, PartialEq, Eq)]
#[group(id = "BaseJob")]
#[non_exhaustive]
pub struct BaseJobArgs {
/// the directory to put the generated main output file and associated files in
#[arg(short, long, value_hint = clap::ValueHint::DirPath)]
pub output: Option<PathBuf>,
#[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")]
pub keep_temp_dir: bool,
/// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo
#[arg(long)]
pub file_stem: Option<OsString>,
/// run commands even if their results are already cached
#[arg(long, env = Self::RUN_EVEN_IF_CACHED_ENV_NAME)]
pub run_even_if_cached: bool,
/// platform
#[arg(long)]
pub platform: Option<DynPlatform>,
}
impl BaseJobArgs {
pub const RUN_EVEN_IF_CACHED_ENV_NAME: &'static str = "FAYALITE_RUN_EVEN_IF_CACHED";
pub fn from_output_dir_and_env(output: PathBuf, platform: Option<DynPlatform>) -> Self {
Self {
output: Some(output),
keep_temp_dir: false,
file_stem: None,
run_even_if_cached: std::env::var_os(Self::RUN_EVEN_IF_CACHED_ENV_NAME).is_some(),
platform,
}
}
}
impl ToArgs for BaseJobArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let Self {
output,
keep_temp_dir,
file_stem,
run_even_if_cached,
platform,
} = self;
if let Some(output) = output {
args.write_long_option_eq("output", output);
}
if *keep_temp_dir {
args.write_arg("--keep-temp-dir");
}
if let Some(file_stem) = file_stem {
args.write_long_option_eq("file-stem", file_stem);
}
if *run_even_if_cached {
args.write_arg("--run-even-if-cached");
}
if let Some(platform) = platform {
args.write_long_option_eq("platform", platform.name());
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BaseJob {
output_dir: Interned<Path>,
#[serde(skip)]
temp_dir: Option<Arc<TempDir>>,
file_stem: Interned<OsStr>,
run_even_if_cached: bool,
platform: Option<DynPlatform>,
}
impl Hash for BaseJob {
fn hash<H: Hasher>(&self, state: &mut H) {
let Self {
output_dir,
temp_dir: _,
file_stem,
run_even_if_cached,
platform,
} = self;
output_dir.hash(state);
file_stem.hash(state);
run_even_if_cached.hash(state);
platform.hash(state);
}
}
impl Eq for BaseJob {}
impl PartialEq for BaseJob {
fn eq(&self, other: &Self) -> bool {
let Self {
output_dir,
temp_dir: _,
file_stem,
run_even_if_cached,
ref platform,
} = *self;
output_dir == other.output_dir
&& file_stem == other.file_stem
&& run_even_if_cached == other.run_even_if_cached
&& *platform == other.platform
}
}
impl BaseJob {
pub fn output_dir(&self) -> Interned<Path> {
self.output_dir
}
pub fn temp_dir(&self) -> Option<&Arc<TempDir>> {
self.temp_dir.as_ref()
}
pub fn file_stem(&self) -> Interned<OsStr> {
self.file_stem
}
pub fn file_with_ext(&self, ext: impl AsRef<OsStr>) -> Interned<Path> {
let mut retval = self.output_dir().join(self.file_stem());
retval.set_extension(ext);
retval.intern_deref()
}
pub fn run_even_if_cached(&self) -> bool {
self.run_even_if_cached
}
pub fn platform(&self) -> Option<&DynPlatform> {
self.platform.as_ref()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct BaseJobKind;
impl JobKindHelper for BaseJobKind {
fn base_job<'a>(
self,
job: &'a <Self as JobKind>::Job,
_dependencies: &'a <<Self as JobKind>::Dependencies as JobDependencies>::JobsAndKinds,
) -> &'a BaseJob {
job
}
fn base_job_args<'a>(
self,
args: &'a <Self as JobKind>::Args,
_dependencies: &'a <<Self as JobKind>::Dependencies as JobDependencies>::KindsAndArgs,
) -> &'a BaseJobArgs {
args
}
#[track_caller]
fn base_job_args_dyn<'a>(
self,
args: &'a <Self as JobKind>::Args,
dependencies_args: &'a [DynJobArgs],
) -> &'a BaseJobArgs {
let [] = dependencies_args else {
panic!("wrong number of dependencies");
};
args
}
#[track_caller]
fn base_job_dyn<'a>(
self,
job: &'a <Self as JobKind>::Job,
dependencies: &'a [DynJob],
) -> &'a BaseJob {
let [] = dependencies else {
panic!("wrong number of dependencies");
};
job
}
}
impl JobKind for BaseJobKind {
type Args = BaseJobArgs;
type Job = BaseJob;
type Dependencies = ();
fn dependencies(self) -> Self::Dependencies {
()
}
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
_global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
let BaseJobArgs {
output,
keep_temp_dir,
file_stem,
run_even_if_cached,
platform,
} = args.args.args;
let (output_dir, temp_dir) = if let Some(output) = output {
(Intern::intern_owned(output), None)
} else {
// we create the temp dir here rather than in run so other
// jobs can have their paths based on the chosen temp dir
let temp_dir = TempDir::new()?;
let output_dir = temp_dir.path().intern();
let temp_dir = if keep_temp_dir {
// use TempDir::into_path() to no longer automatically delete the temp dir
let temp_dir_path = temp_dir.into_path();
println!("created temporary directory: {}", temp_dir_path.display());
None
} else {
Some(Arc::new(temp_dir))
};
(output_dir, temp_dir)
};
let file_stem = file_stem
.map(Intern::intern_deref)
.unwrap_or(params.main_module().name().into());
Ok(JobAndDependencies {
job: JobAndKind {
kind: BaseJobKind,
job: BaseJob {
output_dir,
temp_dir,
file_stem,
run_even_if_cached,
platform,
},
},
dependencies: (),
})
}
fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> {
Interned::default()
}
fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
[JobItemName::Path {
path: job.output_dir,
}]
.intern_slice()
}
fn name(self) -> Interned<str> {
"base-job".intern()
}
fn external_command_params(self, job: &Self::Job) -> Option<CommandParams> {
Some(CommandParams {
command_line: [
"mkdir".intern().into(),
"-p".intern().into(),
"--".intern().into(),
job.output_dir.into(),
]
.intern_slice(),
current_dir: None,
})
}
fn run(
self,
job: &Self::Job,
inputs: &[JobItem],
_params: &JobParams,
_global_params: &GlobalParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
let [] = inputs else {
panic!("invalid inputs for BaseJob");
};
std::fs::create_dir_all(&*job.output_dir)?;
Ok(vec![JobItem::Path {
path: job.output_dir,
}])
}
fn subcommand_hidden(self) -> bool {
true
}
}
pub trait GetJob<J, Position> {
fn get_job(this: &Self) -> &J;
}
impl<J, Position, T: ?Sized + GetJob<J, Position>> GetJob<J, Position> for &'_ T {
fn get_job(this: &Self) -> &J {
T::get_job(this)
}
}
impl<J, Position, T: ?Sized + GetJob<J, Position>> GetJob<J, Position> for &'_ mut T {
fn get_job(this: &Self) -> &J {
T::get_job(this)
}
}
impl<J, Position, T: ?Sized + GetJob<J, Position>> GetJob<J, Position> for Box<T> {
fn get_job(this: &Self) -> &J {
T::get_job(this)
}
}
pub struct GetJobPositionDependencies<T>(PhantomData<T>);
impl<T> Default for GetJobPositionDependencies<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> fmt::Debug for GetJobPositionDependencies<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"GetJobPositionDependencies<{}>",
std::any::type_name::<T>()
)
}
}
impl<T> Hash for GetJobPositionDependencies<T> {
fn hash<H: Hasher>(&self, _state: &mut H) {}
}
impl<T> Ord for GetJobPositionDependencies<T> {
fn cmp(&self, _other: &Self) -> Ordering {
Ordering::Equal
}
}
impl<T> PartialOrd for GetJobPositionDependencies<T> {
fn partial_cmp(&self, _other: &Self) -> Option<Ordering> {
Some(Ordering::Equal)
}
}
impl<T> Eq for GetJobPositionDependencies<T> {}
impl<T> PartialEq for GetJobPositionDependencies<T> {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<T> Clone for GetJobPositionDependencies<T> {
fn clone(&self) -> Self {
Self(PhantomData)
}
}
impl<T> Copy for GetJobPositionDependencies<T> {}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct GetJobPositionJob;
impl<J, Position, K: JobKind<Dependencies: JobDependencies<JobsAndKinds: GetJob<J, Position>>>>
GetJob<J, GetJobPositionDependencies<Position>> for JobAndDependencies<K>
{
fn get_job(this: &Self) -> &J {
GetJob::get_job(&this.dependencies)
}
}
impl<K: JobKind> GetJob<K::Job, GetJobPositionJob> for JobAndDependencies<K> {
fn get_job(this: &Self) -> &K::Job {
&this.job.job
}
}
impl<J, Position, K: JobKind<Dependencies: JobDependencies<KindsAndArgs: GetJob<J, Position>>>>
GetJob<J, GetJobPositionDependencies<Position>> for JobArgsAndDependencies<K>
{
fn get_job(this: &Self) -> &J {
GetJob::get_job(&this.dependencies)
}
}
impl<K: JobKind> GetJob<K::Args, GetJobPositionJob> for JobArgsAndDependencies<K> {
fn get_job(this: &Self) -> &K::Args {
&this.args.args
}
}
impl<J, Position, K: JobKind<Dependencies: GetJob<J, Position>>>
GetJob<J, GetJobPositionDependencies<Position>> for JobKindAndDependencies<K>
{
fn get_job(this: &Self) -> &J {
GetJob::get_job(&this.dependencies)
}
}
impl<K: JobKind> GetJob<K, GetJobPositionJob> for JobKindAndDependencies<K> {
fn get_job(this: &Self) -> &K {
&this.kind
}
}