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

BindingEnv elision in manifest parser breaks evaluation order #1516

Open
rprichard opened this issue Jan 12, 2019 · 1 comment
Open

BindingEnv elision in manifest parser breaks evaluation order #1516

rprichard opened this issue Jan 12, 2019 · 1 comment
Labels

Comments

@rprichard
Copy link

I was studying the BindingEnv::LookupWithFallback function today, and I noticed some curious behavior:

rule cc
  command = echo cc
build test1: cc
build test2: cc
  not_used = blank
command = echo parent

Output:

$ ninja test1
[1/1] echo parent
parent
$ ninja test2
[1/1] echo cc
cc

Edge lookups should happen in this order:

  /// This is tricky.  Edges want lookup scope to go in this order:
  /// 1) value set on edge itself (edge_->env_)
  /// 2) value set on rule, with expansion in the edge's scope
  /// 3) value set on enclosing scope of edge (edge_->env_->parent_)
  /// This function takes as parameters the necessary info to do (2).

However, there's an optimization in the manifest parser that's changing the behavior:

  // Bindings on edges are rare, so allocate per-edge envs only when needed.
  bool has_indent_token = lexer_.PeekToken(Lexer::INDENT);
  BindingEnv* env = has_indent_token ? new BindingEnv(env_) : env_;
  while (has_indent_token) {
    ...
    env->AddBinding(key, val.Evaluate(env_));
    ...
  }

  Edge* edge = state_->AddEdge(rule);
  edge->env_ = env;

If the edge has no bindings, then edge->env_ is actually its enclosing scope, which changes the lookup order to something like:

  • value set directly on the edge's enclosing scope
  • value set on the rule, with expansion in the edge's scope
  • value set on the edge's grandparent scope
@jhasse jhasse added the bug label Jan 14, 2019
michaelforney added a commit to michaelforney/samurai that referenced this issue Mar 4, 2019
The documented behavior at https://ninja-build.org/manual.html is:

1. Special built-in variables ($in, $out).
2. Build-level variables from the build block.
3. Rule-level variables from the rule block (i.e. $command). (Note
   from the above discussion on expansion that these are expanded
   "late", and may make use of in-scope bindings like $in.)
4. File-level variables from the file that the build line was in.
5. Variables from the file that included that file using the subninja
   keyword.

Due to ninja-build/ninja#1516, ninja's
actual behavior depends on whether or not the edge has any bindings.
If it does, it behaves as 1, 2, 3, 4, 5 as documented. If it doesn't,
it behaves as 1, (2 empty), 4, 3, 5.

Previously, we looked up a variable first in the edge's bindings,
then the file's bindings, and then the rule's bindings. This is 1,
2, 4, 5, 3, which roughly corresponded to ninja's behavior for edges
with no bindings.

Rather than try to match ninja's quirk, just implement the documented
order.
@michaelforney
Copy link

I'm curious about how this will be resolved so I can match the behavior in my ninja clone.

In samurai, I was previously looking up variables in the edge, then parent, then grandparent, then rule, which matches ninja's behavior when there are no edge bindings and no relevant grandparent bindings.

As of the above commit, I'm now using the documented evaluation rules, but I'm worried there might be projects out there that rely on the quirky behavior to build correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants