Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: model (parts of) Context via associated module #425

Merged
merged 9 commits into from
Nov 25, 2024
8 changes: 4 additions & 4 deletions src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down
88 changes: 75 additions & 13 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ where
Deserialize::deserialize(deserializer).map(Some)
}

fn default_none<T>() -> Option<T> {
None
}

fn deserialize_version_checked<'de, D>(deserializer: D) -> Result<Option<Version>, D::Error>
where
// T: Deserialize<'de>,
Expand Down Expand Up @@ -95,6 +99,24 @@ struct YamlFile {
_meta: Option<Value>,
}

fn check_module_name<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let v = Option::<String>::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 {
Expand Down Expand Up @@ -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<String>,
context: Option<StringOrVecString>,
help: Option<String>,
Expand Down Expand Up @@ -353,12 +376,15 @@ pub fn load(filename: &Utf8Path, build_dir: &Utf8Path) -> Result<(ContextBag, Fi
is_builder: bool,
filename: &Utf8PathBuf,
import_root: &Option<ImportRoot>,
) -> Result<(), Error> {
) -> Result<Module, Error> {
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 {
Expand All @@ -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,
Expand Down Expand Up @@ -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::<Vec<_>>(),
);
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(
Expand Down Expand Up @@ -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);
}
}
}
Expand All @@ -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);
Expand Down
33 changes: 24 additions & 9 deletions src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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())
Expand All @@ -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() {
Expand Down
68 changes: 49 additions & 19 deletions src/model/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -21,9 +21,11 @@ pub struct Context {
pub modules: IndexMap<String, Module>,
pub rules: Option<IndexMap<String, Rule>>,
pub env: Option<Env>,
pub select: Option<Vec<Dependency<String>>>,
// TODO(context-early-disables)
pub disable: Option<Vec<String>>,
pub provides: Option<im::HashMap<String, IndexSet<String>>>,

// map of providables that are provided in this context or its parents
pub provided: Option<im::HashMap<String, IndexSet<String>>>,

pub var_options: Option<im::HashMap<String, MergeOption>>,

Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -176,26 +186,32 @@ impl Context {
result
}

pub fn collect_selected_modules(&self, contexts: &ContextBag) -> Vec<Dependency<String>> {
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 {
Expand All @@ -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
}
}
Loading
Loading