diff --git a/src/build.rs b/src/build.rs index 05df70f0..187af797 100644 --- a/src/build.rs +++ b/src/build.rs @@ -189,8 +189,8 @@ impl<'a> Resolver<'a> { // TODO: (consistency): this should be handled *after* modules // which match the exact name - if let Some(provides) = &self.build.build_context.provides { - if let Some(providing_modules) = provides.get(dep_name) { + if let Some(provided) = &self.build.build_context.provided { + if let Some(providing_modules) = provided.get(dep_name) { if self.resolve_module_list(providing_modules, dep_name) > 0 { optional = true; if self.disabled_modules.contains(dep_name) { @@ -266,7 +266,7 @@ impl<'a: 'b, 'b> Build<'b> { let mut build_context = Context::new_build_context(builder.name.clone(), builder); if let Some(parent) = build_context.get_parent(contexts) { - build_context.provides.clone_from(&parent.provides); + build_context.provided.clone_from(&parent.provided); } // TODO: opt: see if Cow improves performance @@ -282,7 +282,7 @@ impl<'a: 'b, 'b> Build<'b> { .flat_map(|x| x.iter()) .cloned() .chain(binary.selects.drain(..)) - .chain(build_context.collect_selected_modules(contexts).drain(..)) + .chain(std::iter::once(Dependency::Hard(builder.module_name()))) .collect(); let mut build = Build { diff --git a/src/data.rs b/src/data.rs index f98d3067..1ac463c2 100644 --- a/src/data.rs +++ b/src/data.rs @@ -42,6 +42,10 @@ where Deserialize::deserialize(deserializer).map(Some) } +fn default_none() -> Option { + None +} + fn deserialize_version_checked<'de, D>(deserializer: D) -> Result, D::Error> where // T: Deserialize<'de>, @@ -95,6 +99,24 @@ struct YamlFile { _meta: Option, } +fn check_module_name<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let v = Option::::deserialize(deserializer)?; + + if let Some(v) = v.as_ref() { + if v.starts_with("context::") { + return Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Str(v), + &"a string not starting with \"context::\"", + )); + } + } + + Ok(v) +} + #[derive(Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] struct YamlContext { @@ -123,6 +145,7 @@ enum StringOrVecString { #[derive(Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] struct YamlModule { + #[serde(default = "default_none", deserialize_with = "check_module_name")] name: Option, context: Option, help: Option, @@ -353,12 +376,15 @@ pub fn load(filename: &Utf8Path, build_dir: &Utf8Path) -> Result<(ContextBag, Fi is_builder: bool, filename: &Utf8PathBuf, import_root: &Option, - ) -> Result<(), Error> { + ) -> Result { let context_name = &context.name; let context_parent = match &context.parent { Some(x) => x.clone(), None => "default".to_string(), }; + + let is_default = context_name.as_str() == "default"; + // println!( // "{} {} parent {}", // match is_builder { @@ -372,9 +398,10 @@ pub fn load(filename: &Utf8Path, build_dir: &Utf8Path) -> Result<(ContextBag, Fi .add_context_or_builder( Context::new( context_name.clone(), - match context_name.as_str() { - "default" => None, - _ => Some(context_parent), + if is_default { + None + } else { + Some(context_parent.clone()) }, ), is_builder, @@ -440,19 +467,47 @@ pub fn load(filename: &Utf8Path, build_dir: &Utf8Path) -> Result<(ContextBag, Fi context_.defined_in = Some(filename.clone()); + // TODO(context-early-disables) context_.disable.clone_from(&context.disables); + // Each Context has an associated module. + // This holds: + // - selects + // - disables + // TODO: + // - env (in global env) + // - rules + // - tasks + let module_name = Some(context_.module_name()); + let mut module = init_module( + &module_name, + Some(context_name), + false, + filename, + import_root, + None, + ); + // collect context level "select:" - if let Some(select) = &context.selects { - context_.select = Some( - select - .iter() - .map(dependency_from_string) - .collect::>(), - ); + if let Some(selects) = &context.selects { + for dep_name in selects { + // println!("- {}", dep_name); + module.selects.push(dependency_from_string(dep_name)); + } + } + + if let Some(disables) = context.disables.as_ref() { + module.conflicts = Some(disables.clone()); } - Ok(()) + // make context module depend on its parent's context module + if !is_default { + module + .selects + .push(Dependency::Hard(Context::module_name_for(&context_parent))); + } + + Ok(module) } fn init_module( @@ -729,17 +784,19 @@ pub fn load(filename: &Utf8Path, build_dir: &Utf8Path) -> Result<(ContextBag, Fi // collect and convert contexts // this needs to be done before collecting modules, as that requires // contexts to be finalized. + let mut context_modules = Vec::new(); for data in &yaml_datas { for (list, is_builder) in [(&data.contexts, false), (&data.builders, true)].iter() { if let Some(context_list) = list { for context in context_list { - convert_context( + let module = convert_context( context, &mut contexts, *is_builder | context.is_builder, data.filename.as_ref().unwrap(), &data.import_root, )?; + context_modules.push(module); } } } @@ -749,6 +806,11 @@ pub fn load(filename: &Utf8Path, build_dir: &Utf8Path) -> Result<(ContextBag, Fi // modules can now be processed. contexts.finalize()?; + // add the associated modules to their respective contexts + for module in context_modules.drain(..) { + contexts.add_module(module)?; + } + // for context in &contexts.contexts { // if let Some(env) = &context.env { // println!("context {} env:", context.name); diff --git a/src/generate.rs b/src/generate.rs index f2cca34a..bd398d91 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -439,14 +439,25 @@ fn configure_build( EnvKey::List( resolved .modules - .keys() - .cloned() - .cloned() + .iter() + .filter(|(_, m)| !m.is_context_module()) + .map(|(n, _)| (*n).clone()) .sorted() .collect::<_>(), ), ); + // insert list of actually used contexts + global_env.insert( + "contexts".into(), + EnvKey::List( + builder + .context_iter(contexts) + .map(|c| c.name.clone()) + .collect::<_>(), + ), + ); + // if provided, merge CLI env overrides if let Some(cli_env) = cli_env { global_env.merge(cli_env); @@ -561,6 +572,16 @@ fn configure_build( }; module_info.insert(module.name.clone(), info); } + + // "srcdir" is either the folder of laze.yml that defined this module, + // *or* if it was downloaded, the download folder. + // *or*, it was overridden using "srcdir:" + // *or*, None if this is a "Context module" + let srcdir = match module.srcdir.as_ref() { + Some(srcdir) => srcdir, + None => continue, // this is a Context module, so we're done here + }; + // finalize this module's environment let flattened_env = module_env .flatten_with_opts_option(merge_opts.as_ref()) @@ -573,12 +594,6 @@ fn configure_build( ninja_entries.extend(download_rules.drain(..)); } - // "srcdir" is either the folder of laze.yml that defined this module, - // *or* if it was downloaded, the download folder. - // *or*, it was overridden using "srcdir:" - // This is populated in data.rs, so unwrap() always succeeds. - let srcdir = module.srcdir.as_ref().unwrap(); - let mut src_tagfile = None; if let Some(download) = module.download.as_ref() { diff --git a/src/model/context.rs b/src/model/context.rs index bb153e02..43d1f1c3 100644 --- a/src/model/context.rs +++ b/src/model/context.rs @@ -7,7 +7,7 @@ use camino::Utf8PathBuf; use crate::Env; use crate::MergeOption; -use crate::{ContextBag, Dependency, Module, Rule, Task}; +use crate::{ContextBag, Module, Rule, Task}; #[derive(Eq)] pub struct Context { @@ -21,9 +21,11 @@ pub struct Context { pub modules: IndexMap, pub rules: Option>, pub env: Option, - pub select: Option>>, + // TODO(context-early-disables) pub disable: Option>, - pub provides: Option>>, + + // map of providables that are provided in this context or its parents + pub provided: Option>>, pub var_options: Option>, @@ -42,9 +44,8 @@ impl Context { index: None, parent_index: None, modules: IndexMap::new(), - select: None, disable: None, - provides: None, + provided: None, env: None, env_early: Env::new(), rules: None, @@ -75,6 +76,15 @@ impl Context { result.push(self); } + /// Creates an iterator over a context and its parents, starting + /// with the context. + pub(crate) fn context_iter<'a>(&'a self, bag: &'a ContextBag) -> ParentIterator { + ParentIterator { + bag, + next_context: Some(self), + } + } + pub fn resolve_module<'a: 'b, 'b>( &'a self, module_name: &String, @@ -176,26 +186,32 @@ impl Context { result } - pub fn collect_selected_modules(&self, contexts: &ContextBag) -> Vec> { - let mut result = Vec::new(); - let mut parents = Vec::new(); - self.get_parents(contexts, &mut parents); - for parent in parents { - if let Some(select) = &parent.select { - for entry in select { - result.push(entry.clone()); - } - } - } - result - } - pub fn apply_early_env(&mut self) -> Result<(), Error> { if let Some(env) = &mut self.env { env.expand(&self.env_early)?; } Ok(()) } + + pub fn module_name(&self) -> String { + Self::module_name_for(&self.name) + } + + pub(crate) fn module_name_for(context_name: &str) -> String { + format!("context::{}", context_name) + } + + pub(crate) fn new_default() -> Context { + let mut default = Context::new("default".to_string(), None); + let default_module = + Module::new("context::default".to_string(), Some("default".to_string())); + + default + .modules + .insert("context::default".to_string(), default_module); + + default + } } impl Hash for Context { @@ -209,3 +225,17 @@ impl PartialEq for Context { self.name == other.name } } + +pub struct ParentIterator<'a> { + bag: &'a ContextBag, + next_context: Option<&'a Context>, +} + +impl<'a> Iterator for ParentIterator<'a> { + type Item = &'a Context; + fn next(&mut self) -> Option<&'a Context> { + let res = self.next_context; + self.next_context = self.next_context.and_then(|c| c.get_parent(self.bag)); + res + } +} diff --git a/src/model/context_bag.rs b/src/model/context_bag.rs index a2668c41..05f04724 100644 --- a/src/model/context_bag.rs +++ b/src/model/context_bag.rs @@ -30,8 +30,7 @@ impl ContextBag { pub fn finalize(&mut self) -> Result<(), Error> { // ensure there's a "default" context if self.get_by_name(&"default".to_string()).is_none() { - self.add_context(Context::new("default".to_string(), None)) - .unwrap(); + self.add_context(Context::new_default()).unwrap(); } // set the "parent" index of each context that sets a "parent_name" @@ -179,7 +178,7 @@ impl ContextBag { } indexmap::map::Entry::Vacant(entry) => { if let Some(provides) = &module.provides { - let context_provides = context.provides.get_or_insert_with(im::HashMap::new); + let context_provides = context.provided.get_or_insert_with(im::HashMap::new); for provided in provides { context_provides .entry(provided.clone()) @@ -304,8 +303,8 @@ impl ContextBag { continue; } let context = &self.contexts[n]; - let provides = &context.provides; - let parent_provides = &self.contexts[context.parent_index.unwrap()].provides; + let provides = &context.provided; + let parent_provides = &self.contexts[context.parent_index.unwrap()].provided; let combined_provides = { if let Some(provides) = provides { if let Some(parent_provides) = parent_provides { @@ -352,7 +351,7 @@ impl ContextBag { }); let context = &mut self.contexts[n]; - context.provides = combined_provides; + context.provided = combined_provides; } } diff --git a/src/model/module.rs b/src/model/module.rs index 2b214404..fa518db3 100644 --- a/src/model/module.rs +++ b/src/model/module.rs @@ -206,6 +206,7 @@ impl Module { if self.notify_all { let all_modules = modules .iter() + .filter(|(_, dep)| !dep.is_context_module()) .map(|(_, dep)| dep.create_module_define()) .collect::>(); module_env.insert("notify".into(), nested_env::EnvKey::List(all_modules)); @@ -225,6 +226,7 @@ impl Module { '/' => '_', '.' => '_', '-' => '_', + ':' => '_', _ => x, }) .collect() @@ -247,6 +249,10 @@ impl Module { } } + pub fn is_context_module(&self) -> bool { + self.name.starts_with("context::") + } + // returns all fixed and optional sources with srcdir prepended // pub fn get_all_sources(&self, srcdir: Utf8PathBuf) -> Vec { // let mut res = self diff --git a/src/tests/41_insights/build_expected/insights.json b/src/tests/41_insights/build_expected/insights.json index 733f9fdb..bb3e50ba 100644 --- a/src/tests/41_insights/build_expected/insights.json +++ b/src/tests/41_insights/build_expected/insights.json @@ -8,11 +8,13 @@ "deps": [ "first_module", "second_module", - "third_module" + "third_module", + "context::default" ] }, "first_module": {}, - "second_module": {} + "second_module": {}, + "context::default": {} } } }