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.
Symptom
A
NodeType::Variablenode'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).FOOis unset in the environment throughout.The command was expanded from
FOO=one; the node that carriesFOOinto command identity records it as empty.Two
imports show the same precedence withoutexportinvolved: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: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 withFOO=alphathenFOO=beta: re-runs correctly both times, becauseimportreads 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
exportafterimportwith a default is a reasonable thing for a Tupfile to contain, and today one of the two values vanishes without a word.