Skip to content

Commit

Permalink
rule expansion can refer to build vars that refer to top-level vars
Browse files Browse the repository at this point in the history
Fixes #83.
  • Loading branch information
evmar committed Aug 30, 2023
1 parent 00dcdbe commit 3a39030
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 19 deletions.
27 changes: 18 additions & 9 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl<T: AsRef<str>> EvalString<T> {
pub fn new(parts: Vec<EvalPart<T>>) -> Self {
EvalString(parts)
}

pub fn evaluate(&self, envs: &[&dyn Env]) -> String {
let mut val = String::new();
for part in &self.0 {
Expand All @@ -46,6 +47,7 @@ impl<T: AsRef<str>> EvalString<T> {
val
}
}

impl EvalString<&str> {
pub fn into_owned(self) -> EvalString<String> {
EvalString(
Expand Down Expand Up @@ -78,19 +80,26 @@ impl<'a> Env for Vars<'a> {
}
}

/// A single scope's worth of variable definitions, before $-expansion.
/// For variables attached to a rule we keep them unexpanded in memory because
/// they may be expanded in multiple different ways depending on which rule uses
/// them.
impl Env for SmallMap<&str, EvalString<String>> {
// Impl for Loader.rules
impl Env for SmallMap<String, EvalString<String>> {
fn get_var(&self, var: &str) -> Option<Cow<str>> {
// TODO(#83): this is wrong, it should consider envs.
// TODO(#83): this is wrong in that it doesn't include envs.
// This can occur when you have e.g.
// rule foo
// bar = $baz
// build ...: foo
// x = $bar
// When evaluating the value of `x`, we find `bar` in the rule but
// then need to pick the right env to evaluate $baz. But we also don't
// wanna generically always use all available envs because we don't
// wanna get into evaluation cycles.
self.get(var).map(|val| Cow::Owned(val.evaluate(&[])))
}
}
impl Env for SmallMap<String, EvalString<String>> {

// Impl for the variables attached to a build.
impl Env for SmallMap<&str, String> {
fn get_var(&self, var: &str) -> Option<Cow<str>> {
// TODO(#83): this is wrong, it should consider envs.
self.get(var).map(|val| Cow::Owned(val.evaluate(&[])))
self.get(var).map(|val| Cow::Borrowed(val.as_str()))
}
}
24 changes: 16 additions & 8 deletions src/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,22 @@ impl Loader {
graph: &self.graph,
build: &build,
};
let build_vars = &b.vars;
let envs: [&dyn eval::Env; 4] = [&implicit_vars, build_vars, rule, env];

let lookup = |key: &str| {
build_vars
.get(key)
.or_else(|| rule.get(key))
.map(|var| var.evaluate(&envs))

// Expand all build-scoped variable values, as they may be referred to in rules.
let mut build_vars = SmallMap::default();
for &(name, ref val) in b.vars.iter() {
let val = val.evaluate(&[&implicit_vars, &build_vars, env]);
build_vars.insert(name, val);
}

let envs: [&dyn eval::Env; 4] = [&implicit_vars, &build_vars, rule, env];
let lookup = |key: &str| -> Option<String> {
// Look up `key = ...` binding in build and rule block.
let val = match build_vars.get(key) {
Some(val) => val.clone(),
None => rule.get(key)?.evaluate(&envs),
};
Some(val)
};

let cmdline = lookup("command");
Expand Down
4 changes: 4 additions & 0 deletions src/smallmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ impl<K: PartialEq, V> SmallMap<K, V> {
None
}

pub fn iter(&self) -> std::slice::Iter<(K, V)> {
self.0.iter()
}

pub fn iter_mut(&mut self) -> std::slice::IterMut<(K, V)> {
self.0.iter_mut()
}
Expand Down
3 changes: 1 addition & 2 deletions tests/e2e/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,6 @@ build out: custom
)?;

let out = space.run_expect(&mut n2_command(vec!["out"]))?;
// WRONG(#83): this should be `echo 123 hello 123`.
assert_output_contains(&out, "echo hello 123");
assert_output_contains(&out, "echo 123 hello 123");
Ok(())
}

0 comments on commit 3a39030

Please sign in to comment.