diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index 9005f785..65ca5340 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -7,13 +7,16 @@ use crate::{ module::Module, util::{HashMap, HashSet, job_server::AcquiredJob}, }; -use hashbrown::hash_map::Entry; use petgraph::{ algo::{DfsSpace, kosaraju_scc, toposort}, graph::DiGraph, visit::{GraphBase, Visitable}, }; -use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::SerializeSeq}; +use serde::{ + Deserialize, Deserializer, Serialize, Serializer, + de::{DeserializeOwned, Error}, + ser::SerializeSeq, +}; use std::{ any::{Any, TypeId}, borrow::Cow, @@ -25,13 +28,14 @@ use std::{ marker::PhantomData, panic, rc::Rc, - sync::{Arc, OnceLock, RwLock, RwLockWriteGuard, mpsc}, + sync::{Arc, OnceLock, mpsc}, thread::{self, ScopedJoinHandle}, }; use tempfile::TempDir; pub mod external; pub mod firrtl; +pub mod registry; macro_rules! write_str { ($s:expr, $($rest:tt)*) => { @@ -93,43 +97,87 @@ impl Ord for JobItemName { } } -pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug { - type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug; - fn inputs_and_direct_dependencies<'a>( - &'a self, - job: &'a Self::Job, - ) -> Cow<'a, BTreeMap>>; - fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]>; - /// gets the part of the command line that is common for all members of this job kind -- usually the executable name/path and any global options and/or subcommands - fn command_line_prefix(&self) -> Interned<[Interned]>; - fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]>; - /// return the subcommand if this is an internal JobKind - fn subcommand(&self) -> Option; - /// Parse from [`ArgMatches`], this should only be called with the results of parsing [`subcommand()`]. - /// If [`subcommand()`] returned [`None`], you should not call this function since it will panic. - /// - /// [`ArgMatches`]: clap::ArgMatches - /// [`subcommand()`]: JobKind::subcommand - fn from_arg_matches(&self, matches: &mut clap::ArgMatches) -> clap::error::Result; - fn debug_name(&self, job: &Self::Job) -> String { - let name = self - .command_line_prefix() - .last() - .copied() - .or_else(|| self.to_command_line(job).first().copied()) - .unwrap_or_default(); - let name = match name.rsplit_once(['/', '\\']) { - Some((_, name)) if name.trim() != "" => name, - _ => &*name, - }; - format!("job:{name}") - } - fn parse_command_line( - &self, - command_line: Interned<[Interned]>, - ) -> clap::error::Result; +pub trait JobArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone { + fn to_args + ?Sized>(&self, args: &mut Args); +} + +pub trait JobDependencies: 'static + Send + Sync + Hash + Eq + fmt::Debug + Copy { + type KindsAndArgs: 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone; + type KindsAndJobs: 'static + Send + Sync + Hash + Eq + fmt::Debug; + fn kinds_dyn(self) -> Vec; + fn into_dyn_jobs(jobs: Self::KindsAndJobs) -> Vec; + #[track_caller] + fn from_dyn_args(args: Vec) -> Self::KindsAndArgs; +} + +macro_rules! impl_job_dependencies { + (@impl $(($a:ident, $b:ident: $T:ident),)*) => { + impl<$($T: JobKind),*> JobDependencies for ($($T,)*) { + type KindsAndArgs = ($(($T, $T::Args),)*); + type KindsAndJobs = ($(($T, $T::Job),)*); + + fn kinds_dyn(self) -> Vec { + let ($($a,)*) = self; + vec![$(DynJobKind::new($a),)*] + } + + fn into_dyn_jobs(jobs: Self::KindsAndJobs) -> Vec { + let ($(($a, $b),)*) = jobs; + vec![$(DynJob::new($a, $b),)*] + } + + #[track_caller] + fn from_dyn_args(args: Vec) -> Self::KindsAndArgs { + let Ok([$($a,)*]): Result<[DynJobArgs; _], _> = args.try_into() else { + panic!("wrong number of dependencies"); + }; + $(let $a = match $a.downcast::<$T>() { + Ok(kind_and_args) => kind_and_args, + Err($a) => panic!("wrong type of dependency, expected {} got:\n{:?}", std::any::type_name::<$T>(), $a), + };)* + ($($a,)*) + } + } + }; + ($($first:tt, $($rest:tt,)*)?) => { + impl_job_dependencies!(@impl $($first, $($rest,)*)?); + $(impl_job_dependencies!($($rest,)*);)? + }; +} + +impl_job_dependencies! { + (a0, b0: T0), + (a1, b1: T1), + (a2, b2: T2), + (a3, b3: T3), + (a4, b4: T4), + (a5, b5: T5), + (a6, b6: T6), + (a7, b7: T7), + (a8, b8: T8), + (a9, b9: T9), + (a10, b10: T10), + (a11, b11: T11), +} + +pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy { + type Args: JobArgs; + type Job: AsRef + 'static + Send + Sync + Hash + Eq + fmt::Debug; + type Dependencies: JobDependencies; + fn dependencies(self) -> Self::Dependencies; + fn args_to_jobs( + self, + args: Self::Args, + dependencies_args: ::KindsAndArgs, + ) -> eyre::Result<( + Self::Job, + ::KindsAndJobs, + )>; + fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]>; + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]>; + fn name(self) -> Interned; fn run( - &self, + self, job: &Self::Job, inputs: &[JobItem], acquired_job: &mut AcquiredJob, @@ -141,16 +189,15 @@ trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug { fn as_arc_any(self: Arc) -> Arc; fn eq_dyn(&self, other: &dyn DynJobKindTrait) -> bool; fn hash_dyn(&self, state: &mut dyn Hasher); - fn command_line_prefix_dyn(&self) -> Interned<[Interned]>; - fn subcommand_dyn(&self) -> Option; + fn dependencies_kinds_dyn(&self) -> Vec; + fn args_group_id_dyn(&self) -> Option; + 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: Arc, matches: &mut clap::ArgMatches, - ) -> clap::error::Result; - fn parse_command_line_dyn( - self: Arc, - command_line: Interned<[Interned]>, - ) -> clap::error::Result; + ) -> clap::error::Result; + fn name_dyn(&self) -> Interned; } impl DynJobKindTrait for T { @@ -165,36 +212,42 @@ impl DynJobKindTrait for T { fn eq_dyn(&self, other: &dyn DynJobKindTrait) -> bool { other .as_any() - .downcast_ref::() + .downcast_ref::() .is_some_and(|other| self == other) } fn hash_dyn(&self, mut state: &mut dyn Hasher) { - self.hash(&mut state) + self.hash(&mut state); } - fn command_line_prefix_dyn(&self) -> Interned<[Interned]> { - self.command_line_prefix() + fn dependencies_kinds_dyn(&self) -> Vec { + self.dependencies().kinds_dyn() } - fn subcommand_dyn(&self) -> Option { - self.subcommand() + fn args_group_id_dyn(&self) -> Option { + ::group_id() + } + + fn augment_args_dyn(&self, cmd: clap::Command) -> clap::Command { + ::augment_args(cmd) + } + + fn augment_args_for_update_dyn(&self, cmd: clap::Command) -> clap::Command { + ::augment_args_for_update(cmd) } fn from_arg_matches_dyn( - self: Arc, + self: Arc, matches: &mut clap::ArgMatches, - ) -> clap::error::Result { - let job = self.from_arg_matches(matches)?; - Ok(DynJob::from_arc(self, job)) + ) -> clap::error::Result { + Ok(DynJobArgs::from_arc( + self, + ::from_arg_matches_mut(matches)?, + )) } - fn parse_command_line_dyn( - self: Arc, - command_line: Interned<[Interned]>, - ) -> clap::error::Result { - let job = self.parse_command_line(command_line)?; - Ok(DynJob::from_arc(self, job)) + fn name_dyn(&self) -> Interned { + self.name() } } @@ -203,31 +256,19 @@ pub struct DynJobKind(Arc); impl DynJobKind { pub fn from_arc(job_kind: Arc) -> Self { - if TypeId::of::() == TypeId::of::() { - Self::clone( - &Arc::downcast::(job_kind.as_arc_any()) - .ok() - .expect("already checked type"), - ) - } else { - Self(job_kind) - } + Self(job_kind) } pub fn new(job_kind: T) -> Self { - if let Some(job_kind) = DynJobKindTrait::as_any(&job_kind).downcast_ref::() { - job_kind.clone() - } else { - Self(Arc::new(job_kind)) - } + Self(Arc::new(job_kind)) } pub fn type_id(&self) -> TypeId { DynJobKindTrait::as_any(&*self.0).type_id() } - pub fn downcast_ref(&self) -> Option<&T> { - DynJobKindTrait::as_any(&*self.0).downcast_ref() + pub fn downcast(&self) -> Option { + DynJobKindTrait::as_any(&*self.0).downcast_ref().copied() } pub fn downcast_arc(self) -> Result, Self> { - if self.downcast_ref::().is_some() { + if self.downcast::().is_some() { Ok(Arc::downcast::(self.0.as_arc_any()) .ok() .expect("already checked type")) @@ -235,18 +276,43 @@ impl DynJobKind { Err(self) } } - pub fn registry() -> JobKindRegistrySnapshot { - JobKindRegistrySnapshot(JobKindRegistry::get()) + pub fn dependencies_kinds(&self) -> Vec { + DynJobKindTrait::dependencies_kinds_dyn(&*self.0) } - #[track_caller] - pub fn register(self) { - JobKindRegistry::register(JobKindRegistry::lock(), self); + pub fn args_group_id(&self) -> Option { + 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 { + DynJobKindTrait::from_arg_matches_dyn(self.0, matches) + } + pub fn name(&self) -> Interned { + DynJobKindTrait::name_dyn(&*self.0) + } + pub fn make_subcommand(&self) -> clap::Command { + let mut subcommand = clap::Command::new(Interned::into_inner(self.name())); + for dependency in self.dependencies_kinds() { + subcommand = dependency.augment_args(subcommand); + } + self.augment_args(subcommand) + } + pub fn make_subcommand_without_dependencies(&self) -> clap::Command { + let subcommand = clap::Command::new(Interned::into_inner(self.name())); + self.augment_args(subcommand) } } impl Hash for DynJobKind { fn hash(&self, state: &mut H) { - DynJobKindTrait::as_any(&*self.0).type_id().hash(state); + self.type_id().hash(state); DynJobKindTrait::hash_dyn(&*self.0, state); } } @@ -270,7 +336,7 @@ impl Serialize for DynJobKind { where S: Serializer, { - self.command_line_prefix().serialize(serializer) + self.name().serialize(serializer) } } @@ -279,240 +345,294 @@ impl<'de> Deserialize<'de> for DynJobKind { where D: Deserializer<'de>, { - let command_line_prefix: Cow<'_, [Interned]> = Cow::deserialize(deserializer)?; - match Self::registry().get_by_command_line_prefix(&command_line_prefix) { + let name = Cow::::deserialize(deserializer)?; + match Self::registry().get_by_name(&name) { Some(retval) => Ok(retval.clone()), None => Err(D::Error::custom(format_args!( - "unknown job kind: command line prefix not found in registry: {command_line_prefix:?}" + "unknown job kind: name not found in registry: {name:?}" ))), } } } -#[derive(Clone, Debug)] -struct JobKindRegistry { - command_line_prefix_to_job_kind_map: HashMap<&'static [Interned], DynJobKind>, - job_kinds: Vec, - subcommand_names: BTreeMap<&'static str, DynJobKind>, +trait DynExtendString { + fn extend_one(&mut self, item: String); } -enum JobKindRegisterError { - SameCommandLinePrefix { - command_line_prefix: &'static [Interned], - old_job_kind: DynJobKind, - new_job_kind: DynJobKind, - }, - CommandLinePrefixDoesNotMatchSubcommandName { - command_line_prefix: &'static [Interned], - expected: [Interned; 2], - job_kind: DynJobKind, - }, -} - -impl fmt::Display for JobKindRegisterError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::SameCommandLinePrefix { - command_line_prefix, - old_job_kind, - new_job_kind, - } => write!( - f, - "two different `DynJobKind` can't share the same `command_line_prefix` of:\n\ - {command_line_prefix:?}\n\ - old job kind:\n\ - {old_job_kind:?}\n\ - new job kind:\n\ - {new_job_kind:?}", - ), - Self::CommandLinePrefixDoesNotMatchSubcommandName { - command_line_prefix, - expected, - job_kind, - } => write!( - f, - "`JobKind::subcommand()` returned `Some` but the `command_line_prefix` is not as expected\n\ - (it should be `[program_name_for_internal_jobs(), subcommand_name]`):\n\ - command_line_prefix:\n\ - {command_line_prefix:?}\n\ - expected:\n\ - {expected:?}\n\ - job kind:\n\ - {job_kind:?}", - ), - } +impl Extend for dyn DynExtendString + '_ { + fn extend>(&mut self, iter: T) { + iter.into_iter() + .for_each(|item| ::extend_one(self, item)); } } -trait JobKindRegistryRegisterLock { - type Locked; - fn lock(self) -> Self::Locked; - fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry; -} - -impl JobKindRegistryRegisterLock for &'static RwLock> { - type Locked = RwLockWriteGuard<'static, Arc>; - fn lock(self) -> Self::Locked { - self.write().expect("shouldn't be poisoned") - } - fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry { - Arc::make_mut(locked) +impl> DynExtendString for T { + fn extend_one(&mut self, item: String) { + self.extend([item]); } } -impl JobKindRegistryRegisterLock for &'_ mut JobKindRegistry { - type Locked = Self; +trait DynJobArgsTrait: 'static + Send + Sync + fmt::Debug { + fn as_any(&self) -> &dyn Any; + fn as_arc_any(self: Arc) -> Arc; + 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(&self, args: &mut dyn DynExtendString); + fn to_args_vec(&self) -> Vec; + fn clone_into_arc(&self) -> Arc; + fn update_from_arg_matches( + &mut self, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result<()>; + fn args_to_jobs( + self: Arc, + dependencies_args: Vec, + ) -> eyre::Result<(DynJob, Vec)>; +} - fn lock(self) -> Self::Locked { +impl DynJobArgsTrait for inner::DynJobArgs { + fn as_any(&self) -> &dyn Any { self } - fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry { - locked + fn as_arc_any(self: Arc) -> Arc { + self } -} -impl JobKindRegistry { - fn lock() -> &'static RwLock> { - static REGISTRY: OnceLock>> = OnceLock::new(); - REGISTRY.get_or_init(Default::default) + fn kind_type_id(&self) -> TypeId { + TypeId::of::() } - fn try_register( - lock: L, - job_kind: DynJobKind, - ) -> Result<(), JobKindRegisterError> { - let command_line_prefix = Interned::into_inner(job_kind.command_line_prefix()); - let subcommand_name = job_kind - .subcommand() - .map(|subcommand| subcommand.get_name().intern()); - if let Some(subcommand_name) = subcommand_name { - let expected = [program_name_for_internal_jobs(), subcommand_name]; - if command_line_prefix != &expected { - return Err( - JobKindRegisterError::CommandLinePrefixDoesNotMatchSubcommandName { - command_line_prefix, - expected, - job_kind, - }, - ); - } - } - // run user code only outside of lock - let mut locked = lock.lock(); - let this = L::make_mut(&mut locked); - let result = match this - .command_line_prefix_to_job_kind_map - .entry(command_line_prefix) - { - Entry::Occupied(entry) => Err(JobKindRegisterError::SameCommandLinePrefix { - command_line_prefix, - old_job_kind: entry.get().clone(), - new_job_kind: job_kind, - }), - Entry::Vacant(entry) => { - this.job_kinds.push(job_kind.clone()); - if let Some(subcommand_name) = subcommand_name { - this.subcommand_names - .insert(Interned::into_inner(subcommand_name), job_kind.clone()); - } - entry.insert(job_kind); - Ok(()) - } - }; - drop(locked); - // outside of lock now, so we can test if it's the same DynJobKind - match result { - Err(JobKindRegisterError::SameCommandLinePrefix { - command_line_prefix: _, - old_job_kind, - new_job_kind, - }) if old_job_kind == new_job_kind => Ok(()), - result => result, - } - } - #[track_caller] - fn register(lock: L, job_kind: DynJobKind) { - match Self::try_register(lock, job_kind) { - Err(e) => panic!("{e}"), - Ok(()) => {} - } - } - fn get() -> Arc { - Self::lock().read().expect("shouldn't be poisoned").clone() - } -} -impl Default for JobKindRegistry { - fn default() -> Self { - let mut retval = Self { - command_line_prefix_to_job_kind_map: HashMap::default(), - job_kinds: Vec::new(), - subcommand_names: BTreeMap::new(), - }; - for job_kind in [] { - Self::register(&mut retval, job_kind); - } + fn eq_dyn(&self, other: &dyn DynJobArgsTrait) -> bool { + other + .as_any() + .downcast_ref::() + .is_some_and(|other| self == other) + } + + fn hash_dyn(&self, mut state: &mut dyn Hasher) { + self.hash(&mut state); + } + + fn kind(&self) -> DynJobKind { + DynJobKind::from_arc(self.kind.clone()) + } + + fn to_args(&self, args: &mut dyn DynExtendString) { + self.args.to_args(args) + } + + fn to_args_vec(&self) -> Vec { + let mut retval = Vec::new(); + self.args.to_args(&mut retval); retval } -} -#[derive(Clone, Debug)] -pub struct JobKindRegistrySnapshot(Arc); + fn clone_into_arc(&self) -> Arc { + Arc::new(self.clone()) + } -impl JobKindRegistrySnapshot { - pub fn get() -> Self { - JobKindRegistrySnapshot(JobKindRegistry::get()) + fn update_from_arg_matches( + &mut self, + matches: &mut clap::ArgMatches, + ) -> clap::error::Result<()> { + clap::FromArgMatches::update_from_arg_matches_mut(&mut self.args, matches) } - pub fn get_by_command_line_prefix<'a>( - &'a self, - command_line_prefix: &[Interned], - ) -> Option<&'a DynJobKind> { - self.0 - .command_line_prefix_to_job_kind_map - .get(command_line_prefix) - } - pub fn job_kinds(&self) -> &[DynJobKind] { - &self.0.job_kinds + + fn args_to_jobs( + self: Arc, + dependencies_args: Vec, + ) -> eyre::Result<(DynJob, Vec)> { + let inner::DynJobArgs { kind, args } = Arc::unwrap_or_clone(self); + let (job, dependencies) = + kind.args_to_jobs(args, K::Dependencies::from_dyn_args(dependencies_args))?; + Ok(( + DynJob::from_arc(kind, job), + K::Dependencies::into_dyn_jobs(dependencies), + )) } } -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct AnyInternalJob(pub DynJob); +#[derive(Clone)] +pub struct DynJobArgs(Arc); -impl clap::Subcommand for AnyInternalJob { +impl DynJobArgs { + pub fn from_arc(kind: Arc, args: K::Args) -> Self { + Self(Arc::new(inner::DynJobArgs { kind, args })) + } + pub fn new(kind: K, args: K::Args) -> Self { + Self::from_arc(Arc::new(kind), args) + } + pub fn kind_type_id(&self) -> TypeId { + DynJobArgsTrait::kind_type_id(&*self.0) + } + pub fn downcast_ref(&self) -> Option<(&K, &K::Args)> { + let inner::DynJobArgs:: { kind, args } = + DynJobArgsTrait::as_any(&*self.0).downcast_ref()?; + Some((kind, args)) + } + pub fn downcast(self) -> Result<(K, K::Args), Self> { + if self.downcast_ref::().is_some() { + let inner::DynJobArgs:: { kind, args } = Arc::unwrap_or_clone( + Arc::downcast(self.0.as_arc_any()) + .ok() + .expect("already checked type"), + ); + Ok((*kind, args)) + } else { + Err(self) + } + } + pub fn kind(&self) -> DynJobKind { + DynJobArgsTrait::kind(&*self.0) + } + pub fn to_args>(&self, args: &mut Args) { + DynJobArgsTrait::to_args(&*self.0, args); + } + pub fn to_args_vec(&self) -> Vec { + DynJobArgsTrait::to_args_vec(&*self.0) + } + 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, + ) -> eyre::Result<(DynJob, Vec)> { + DynJobArgsTrait::args_to_jobs(self.0, dependencies_args) + } +} + +impl Hash for DynJobArgs { + fn hash(&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(Serialize, Deserialize)] +#[serde(rename = "DynJobArgs")] +struct DynJobArgsSerde { + kind: DynJobKind, + args: Vec, +} + +impl Serialize for DynJobArgs { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + DynJobArgsSerde { + kind: self.kind(), + args: self.to_args_vec(), + } + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for DynJobArgs { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let DynJobArgsSerde { kind, args } = DynJobArgsSerde::deserialize(deserializer)?; + let mut matches = kind + .make_subcommand_without_dependencies() + .no_binary_name(true) + .disable_help_flag(true) + .disable_version_flag(true) + .styles(clap::builder::Styles::plain()) + .try_get_matches_from(args) + .map_err(D::Error::custom)?; + kind.from_arg_matches(&mut matches) + .map_err(D::Error::custom) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct AnyJobSubcommand { + args: DynJobArgs, + dependencies_args: Vec, +} + +impl clap::Subcommand for AnyJobSubcommand { fn augment_subcommands(mut cmd: clap::Command) -> clap::Command { - for job_kind in JobKindRegistrySnapshot::get().0.subcommand_names.values() { - let Some(subcommand) = job_kind.subcommand() else { - // shouldn't happen, ignore it - continue; - }; - cmd = cmd.subcommand(subcommand); + let snapshot = registry::JobKindRegistrySnapshot::get(); + for (name, job_kind) in snapshot.iter_with_names() { + let mut subcommand = clap::Command::new(Interned::into_inner(name)); + for dependency in job_kind.dependencies_kinds() { + subcommand = dependency.augment_args(subcommand); + } + cmd = cmd.subcommand(job_kind.augment_args(subcommand)); } cmd } - fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command { - Self::augment_subcommands(cmd) + fn augment_subcommands_for_update(mut cmd: clap::Command) -> clap::Command { + let snapshot = registry::JobKindRegistrySnapshot::get(); + for (name, job_kind) in snapshot.iter_with_names() { + let mut subcommand = clap::Command::new(Interned::into_inner(name)); + for dependency in job_kind.dependencies_kinds() { + subcommand = dependency.augment_args_for_update(subcommand); + } + cmd = cmd.subcommand(job_kind.augment_args_for_update(subcommand)); + } + cmd } fn has_subcommand(name: &str) -> bool { - JobKindRegistrySnapshot::get() - .0 - .subcommand_names - .contains_key(name) + registry::JobKindRegistrySnapshot::get() + .get_by_name(name) + .is_some() } } -impl clap::FromArgMatches for AnyInternalJob { - fn from_arg_matches(matches: &clap::ArgMatches) -> Result { +impl clap::FromArgMatches for AnyJobSubcommand { + fn from_arg_matches(matches: &clap::ArgMatches) -> clap::error::Result { Self::from_arg_matches_mut(&mut matches.clone()) } - fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> Result { + fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> clap::error::Result { if let Some((name, mut matches)) = matches.remove_subcommand() { - let job_kind_registry_snapshot = JobKindRegistrySnapshot::get(); - if let Some(job_kind) = job_kind_registry_snapshot.0.subcommand_names.get(&*name) { - Ok(Self(job_kind.from_arg_matches(&mut matches)?)) + let job_kind_registry_snapshot = registry::JobKindRegistrySnapshot::get(); + if let Some(job_kind) = job_kind_registry_snapshot.get_by_name(&name) { + let dependencies = job_kind.dependencies_kinds(); + let dependencies_args = Result::from_iter( + dependencies + .into_iter() + .map(|dependency| dependency.from_arg_matches(&mut matches)), + )?; + Ok(Self { + args: job_kind.clone().from_arg_matches(&mut matches)?, + dependencies_args, + }) } else { Err(clap::Error::raw( clap::error::ErrorKind::InvalidSubcommand, @@ -527,17 +647,47 @@ impl clap::FromArgMatches for AnyInternalJob { } } - fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { - *self = Self::from_arg_matches(matches)?; - Ok(()) + 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, - ) -> Result<(), clap::Error> { - *self = Self::from_arg_matches_mut(matches)?; - Ok(()) + ) -> 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) { + if *job_kind == self.args.kind() { + for dependency in &mut self.dependencies_args { + dependency.update_from_arg_matches(&mut matches)?; + } + self.args.update_from_arg_matches(&mut matches)?; + } else { + let dependencies = job_kind.dependencies_kinds(); + let dependencies_args = Result::from_iter( + dependencies + .into_iter() + .map(|dependency| dependency.from_arg_matches(&mut matches)), + )?; + *self = Self { + args: job_kind.clone().from_arg_matches(&mut matches)?, + dependencies_args, + }; + } + Ok(()) + } 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", + )) + } } } @@ -547,10 +697,9 @@ trait DynJobTrait: 'static + Send + Sync + fmt::Debug { fn hash_dyn(&self, state: &mut dyn Hasher); fn kind_type_id(&self) -> TypeId; fn kind(&self) -> DynJobKind; - fn inputs_and_direct_dependencies<'a>(&'a self) -> &'a BTreeMap>; + fn args(&self) -> DynJobArgs; + fn inputs(&self) -> Interned<[JobItemName]>; fn outputs(&self) -> Interned<[JobItemName]>; - fn to_command_line(&self) -> Interned<[Interned]>; - fn debug_name(&self) -> String; fn run(&self, inputs: &[JobItem], acquired_job: &mut AcquiredJob) -> eyre::Result>; } @@ -558,11 +707,17 @@ trait DynJobTrait: 'static + Send + Sync + fmt::Debug { mod inner { use super::*; + #[derive(Debug, PartialEq, Eq, Hash, Clone)] + pub(crate) struct DynJobArgs { + pub(crate) kind: Arc, + pub(crate) args: T::Args, + } + #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct DynJob { pub(crate) kind: Arc, pub(crate) job: T::Job, - pub(crate) inputs_and_direct_dependencies: BTreeMap>, + pub(crate) inputs: Interned<[JobItemName]>, pub(crate) outputs: Interned<[JobItemName]>, } } @@ -575,7 +730,7 @@ impl DynJobTrait for inner::DynJob { fn eq_dyn(&self, other: &dyn DynJobTrait) -> bool { other .as_any() - .downcast_ref::>() + .downcast_ref::() .is_some_and(|other| self == other) } @@ -591,22 +746,21 @@ impl DynJobTrait for inner::DynJob { DynJobKind(self.kind.clone()) } - fn inputs_and_direct_dependencies<'a>(&'a self) -> &'a BTreeMap> { - &self.inputs_and_direct_dependencies + fn args(&self) -> DynJobArgs { + DynJobArgs::from_arc( + self.kind.clone(), + AsRef::::as_ref(&self.job).clone(), + ) + } + + fn inputs(&self) -> Interned<[JobItemName]> { + self.inputs } fn outputs(&self) -> Interned<[JobItemName]> { self.outputs } - fn to_command_line(&self) -> Interned<[Interned]> { - self.kind.to_command_line(&self.job) - } - - fn debug_name(&self) -> String { - self.kind.debug_name(&self.job) - } - fn run( &self, inputs: &[JobItem], @@ -620,34 +774,18 @@ impl DynJobTrait for inner::DynJob { pub struct DynJob(Arc); impl DynJob { - fn new_unchecked(job_kind: Arc, job: T::Job) -> Self { - let inputs_and_direct_dependencies = - job_kind.inputs_and_direct_dependencies(&job).into_owned(); + pub fn from_arc(job_kind: Arc, job: T::Job) -> Self { + let inputs = job_kind.inputs(&job); let outputs = job_kind.outputs(&job); Self(Arc::new(inner::DynJob { kind: job_kind, job, - inputs_and_direct_dependencies, + inputs, outputs, })) } - pub fn from_arc(job_kind: Arc, job: T::Job) -> Self { - if TypeId::of::() == TypeId::of::() { - ::downcast_ref::(&job) - .expect("already checked type") - .clone() - } else { - Self::new_unchecked(job_kind, job) - } - } pub fn new(job_kind: T, job: T::Job) -> Self { - if TypeId::of::() == TypeId::of::() { - ::downcast_ref::(&job) - .expect("already checked type") - .clone() - } else { - Self::new_unchecked(Arc::new(job_kind), job) - } + Self::from_arc(Arc::new(job_kind), job) } pub fn kind_type_id(&self) -> TypeId { self.0.kind_type_id() @@ -659,20 +797,15 @@ impl DynJob { pub fn kind(&self) -> DynJobKind { DynJobTrait::kind(&*self.0) } - pub fn inputs_and_direct_dependencies<'a>( - &'a self, - ) -> &'a BTreeMap> { - DynJobTrait::inputs_and_direct_dependencies(&*self.0) + pub fn args(&self) -> DynJobArgs { + DynJobTrait::args(&*self.0) + } + pub fn inputs(&self) -> Interned<[JobItemName]> { + DynJobTrait::inputs(&*self.0) } pub fn outputs(&self) -> Interned<[JobItemName]> { DynJobTrait::outputs(&*self.0) } - pub fn to_command_line(&self) -> Interned<[Interned]> { - DynJobTrait::to_command_line(&*self.0) - } - pub fn debug_name(&self) -> String { - DynJobTrait::debug_name(&*self.0) - } pub fn run( &self, inputs: &[JobItem], @@ -727,53 +860,6 @@ impl<'de> Deserialize<'de> for DynJob { } } -impl JobKind for DynJobKind { - type Job = DynJob; - - fn inputs_and_direct_dependencies<'a>( - &'a self, - job: &'a Self::Job, - ) -> Cow<'a, BTreeMap>> { - Cow::Borrowed(job.inputs_and_direct_dependencies()) - } - - fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> { - job.outputs() - } - - fn command_line_prefix(&self) -> Interned<[Interned]> { - self.0.command_line_prefix_dyn() - } - - fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned]> { - job.to_command_line() - } - - fn subcommand(&self) -> Option { - self.0.subcommand_dyn() - } - - fn from_arg_matches(&self, matches: &mut clap::ArgMatches) -> clap::error::Result { - self.0.clone().from_arg_matches_dyn(matches) - } - - fn parse_command_line( - &self, - command_line: Interned<[Interned]>, - ) -> clap::error::Result { - self.0.clone().parse_command_line_dyn(command_line) - } - - fn run( - &self, - job: &Self::Job, - inputs: &[JobItem], - acquired_job: &mut AcquiredJob, - ) -> eyre::Result> { - job.run(inputs, acquired_job) - } -} - #[derive(Clone, Debug)] enum JobGraphNode { Job(DynJob), diff --git a/crates/fayalite/src/build/registry.rs b/crates/fayalite/src/build/registry.rs new file mode 100644 index 00000000..9ea5400f --- /dev/null +++ b/crates/fayalite/src/build/registry.rs @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + build::{DynJobKind, JobKind}, + intern::Interned, +}; +use std::{ + borrow::Borrow, + cmp::Ordering, + collections::BTreeMap, + fmt, + sync::{Arc, OnceLock, RwLock, RwLockWriteGuard}, +}; + +impl DynJobKind { + pub fn registry() -> JobKindRegistrySnapshot { + JobKindRegistrySnapshot(JobKindRegistry::get()) + } + #[track_caller] + pub fn register(self) { + JobKindRegistry::register(JobKindRegistry::lock(), self); + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +struct InternedStrCompareAsStr(Interned); + +impl fmt::Debug for InternedStrCompareAsStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Ord for InternedStrCompareAsStr { + fn cmp(&self, other: &Self) -> Ordering { + str::cmp(&self.0, &other.0) + } +} + +impl PartialOrd for InternedStrCompareAsStr { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Borrow for InternedStrCompareAsStr { + fn borrow(&self) -> &str { + &self.0 + } +} + +#[derive(Clone, Debug)] +struct JobKindRegistry { + job_kinds: BTreeMap, +} + +enum JobKindRegisterError { + SameName { + name: InternedStrCompareAsStr, + old_job_kind: DynJobKind, + new_job_kind: DynJobKind, + }, +} + +impl fmt::Display for JobKindRegisterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::SameName { + name, + old_job_kind, + new_job_kind, + } => write!( + f, + "two different `JobKind` can't share the same name:\n\ + {name:?}\n\ + old job kind:\n\ + {old_job_kind:?}\n\ + new job kind:\n\ + {new_job_kind:?}", + ), + } + } +} + +trait JobKindRegistryRegisterLock { + type Locked; + fn lock(self) -> Self::Locked; + fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry; +} + +impl JobKindRegistryRegisterLock for &'static RwLock> { + type Locked = RwLockWriteGuard<'static, Arc>; + fn lock(self) -> Self::Locked { + self.write().expect("shouldn't be poisoned") + } + fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry { + Arc::make_mut(locked) + } +} + +impl JobKindRegistryRegisterLock for &'_ mut JobKindRegistry { + type Locked = Self; + + fn lock(self) -> Self::Locked { + self + } + + fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry { + locked + } +} + +impl JobKindRegistry { + fn lock() -> &'static RwLock> { + static REGISTRY: OnceLock>> = OnceLock::new(); + REGISTRY.get_or_init(Default::default) + } + fn try_register( + lock: L, + job_kind: DynJobKind, + ) -> Result<(), JobKindRegisterError> { + use std::collections::btree_map::Entry; + let name = InternedStrCompareAsStr(job_kind.name()); + // run user code only outside of lock + let mut locked = lock.lock(); + let this = L::make_mut(&mut locked); + let result = match this.job_kinds.entry(name) { + Entry::Occupied(entry) => Err(JobKindRegisterError::SameName { + name, + old_job_kind: entry.get().clone(), + new_job_kind: job_kind, + }), + Entry::Vacant(entry) => { + entry.insert(job_kind); + Ok(()) + } + }; + drop(locked); + // outside of lock now, so we can test if it's the same DynJobKind + match result { + Err(JobKindRegisterError::SameName { + name: _, + old_job_kind, + new_job_kind, + }) if old_job_kind == new_job_kind => Ok(()), + result => result, + } + } + #[track_caller] + fn register(lock: L, job_kind: DynJobKind) { + match Self::try_register(lock, job_kind) { + Err(e) => panic!("{e}"), + Ok(()) => {} + } + } + fn get() -> Arc { + Self::lock().read().expect("shouldn't be poisoned").clone() + } +} + +impl Default for JobKindRegistry { + fn default() -> Self { + let mut retval = Self { + job_kinds: BTreeMap::new(), + }; + for job_kind in [] { + Self::register(&mut retval, job_kind); + } + retval + } +} + +#[derive(Clone, Debug)] +pub struct JobKindRegistrySnapshot(Arc); + +impl JobKindRegistrySnapshot { + pub fn get() -> Self { + JobKindRegistrySnapshot(JobKindRegistry::get()) + } + pub fn get_by_name<'a>(&'a self, name: &str) -> Option<&'a DynJobKind> { + self.0.job_kinds.get(name) + } + pub fn iter_with_names(&self) -> JobKindRegistryIterWithNames<'_> { + JobKindRegistryIterWithNames(self.0.job_kinds.iter()) + } + pub fn iter(&self) -> JobKindRegistryIter<'_> { + JobKindRegistryIter(self.0.job_kinds.values()) + } +} + +impl<'a> IntoIterator for &'a JobKindRegistrySnapshot { + type Item = &'a DynJobKind; + type IntoIter = JobKindRegistryIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> IntoIterator for &'a mut JobKindRegistrySnapshot { + type Item = &'a DynJobKind; + type IntoIter = JobKindRegistryIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[derive(Clone, Debug)] +pub struct JobKindRegistryIter<'a>( + std::collections::btree_map::Values<'a, InternedStrCompareAsStr, DynJobKind>, +); + +impl<'a> Iterator for JobKindRegistryIter<'a> { + type Item = &'a DynJobKind; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.0.count() + } + + fn last(self) -> Option { + self.0.last() + } + + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n) + } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0.fold(init, f) + } +} + +impl<'a> std::iter::FusedIterator for JobKindRegistryIter<'a> {} + +impl<'a> ExactSizeIterator for JobKindRegistryIter<'a> {} + +impl<'a> DoubleEndedIterator for JobKindRegistryIter<'a> { + fn next_back(&mut self) -> Option { + self.0.next_back() + } + + fn nth_back(&mut self, n: usize) -> Option { + self.0.nth_back(n) + } + + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0.rfold(init, f) + } +} + +#[derive(Clone, Debug)] +pub struct JobKindRegistryIterWithNames<'a>( + std::collections::btree_map::Iter<'a, InternedStrCompareAsStr, DynJobKind>, +); + +impl<'a> Iterator for JobKindRegistryIterWithNames<'a> { + type Item = (Interned, &'a DynJobKind); + + fn next(&mut self) -> Option { + self.0.next().map(|(name, job_kind)| (name.0, job_kind)) + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.0.count() + } + + fn last(self) -> Option { + self.0.last().map(|(name, job_kind)| (name.0, job_kind)) + } + + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n).map(|(name, job_kind)| (name.0, job_kind)) + } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0 + .map(|(name, job_kind)| (name.0, job_kind)) + .fold(init, f) + } +} + +impl<'a> std::iter::FusedIterator for JobKindRegistryIterWithNames<'a> {} + +impl<'a> ExactSizeIterator for JobKindRegistryIterWithNames<'a> {} + +impl<'a> DoubleEndedIterator for JobKindRegistryIterWithNames<'a> { + fn next_back(&mut self) -> Option { + self.0 + .next_back() + .map(|(name, job_kind)| (name.0, job_kind)) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.0 + .nth_back(n) + .map(|(name, job_kind)| (name.0, job_kind)) + } + + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0 + .map(|(name, job_kind)| (name.0, job_kind)) + .rfold(init, f) + } +} + +#[track_caller] +pub fn register_job_kind(kind: K) { + DynJobKind::new(kind).register(); +}