forked from libre-chip/fayalite
2803 lines
85 KiB
Rust
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(¶ms, 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(¶ms, 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(¶ms, 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(¶ms, 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(¶ms, 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, ¶ms, 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, ¶ms, 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(¶ms, 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
|
|
}
|
|
}
|