Skip to content

A Variable node's recorded value is frozen at its first producer, so the index can contradict the value the build used #488

Description

@typeless

Symptom

A NodeType::Variable node's recorded value is frozen at whichever producer created it. A later producer with a different value for the same variable is ignored, so the value the index records can contradict the value the build actually used.

Reproduction

On c02eeeb (PR #485 branch). FOO is unset in the environment throughout.

export FOO
import FOO=one
: |> echo "[$(FOO)]" > %o |> out.txt
$ env -u FOO putup -B .
$ cat out.txt
[one]
$ env -u FOO putup show graph -B . | grep FOO
  f4 [label="$/FOO="];

The command was expanded from FOO=one; the node that carries FOO into command identity records it as empty.

Two imports show the same precedence without export involved:

import FOO=one
import FOO=two

leaves $/FOO=one. The second value is dropped.

Mechanism, and how it differs from what the code says

ensure_env_var_node (src/graph/builder.cpp:1293-1330) is documented as "Updates the existing node's name and content_hash if the value changed", and the code reads that way:

if (auto const* existing_node_id = state.imported_env_var_nodes.find(var_name_id)) {
    if (auto* existing = get_file_node(ctx.state->graph, *existing_node_id)) {
        if (existing->name != name_id) {
            existing->name = name_id;
            existing->content_hash = content_hash;
        }
    }
    return *existing_node_id;
}

Measured behaviour is the opposite: first writer wins. In both orderings above the second producer's value is discarded, so the update branch is not taking effect. I did not chase why - that is the first thing to find out.

This matters beyond the inconsistency: this overwrite path was the mechanism #484 was filed on, and the fix in PR #485 (moving putup's keys outside the Tupfile-spellable name space) is correct regardless of which writer wins. But the issue text for #484 asserts last-writer-wins, and that assertion is wrong.

What I could not demonstrate

I tried and failed to turn this into a wrong build:

  • import FOO=<default> + export FOO + $(FOO) in the command text: any change to the effective value also changes the command text, which re-runs the command on its own.
  • import FOO=<default> + export FOO + sh -c 'echo "[$FOO]"' (value read at runtime, text fixed), built with FOO=alpha then FOO=beta: re-runs correctly both times, because import reads the set environment and records it first.

So this is filed as a latent inconsistency with a demonstrated observable symptom (recorded value contradicts value used), not as a demonstrated stale build. If someone finds the ordering that makes it one, that raises the severity.

Decisions to make

  • Why the update branch does not fire, and whether it is dead code or broken.
  • Whether "one variable name, one value per build" should be an invariant that a second producer with a different value reports rather than silently loses - export after import with a default is a reasonable thing for a Tupfile to contain, and today one of the two values vanishes without a word.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions