Symptom
With an in-source tup.config, a CONFIG_<KEY> whose key matches a source file's name in the config directory makes that file invisible: edits to it are never noticed, and the build reports Nothing to do (up to date) for ever.
Reproduction
On c02eeeb (PR #485 branch; also on 2987923bf and on a fix-reverted binary - this predates both). A/B against an identical project whose only difference is the config key's name.
mkdir p && cd p && touch Tupfile.ini
printf 'CONFIG_LICENSE=y\n' > tup.config
printf 'MIT\n' > LICENSE
printf ': LICENSE |> cp %%f %%o |> out.txt\n' > Tupfile
putup -B . # Build completed: 1 commands out.txt: MIT
printf 'BSD\n' > LICENSE
putup -B . # Nothing to do (up to date). out.txt: MIT # stale
Control, CONFIG_OTHER=y in place of CONFIG_LICENSE=y and nothing else changed: the second build re-runs and out.txt is BSD.
Mechanism
add_tupfile creates a NodeType::Variable node per non-CONFIG_ config key, named with the bare key and parented to the config directory (src/graph/builder.cpp:2490-2512). add_file_node interns <parent path>/<name> into a PathId and registers it in path_to_node (src/graph/dag.cpp:96-103), so the config variable occupies the path <config_dir>/LICENSE.
The rule's input then resolves through ensure_file_node (src/graph/dag.cpp:127-129), which returns the existing path_to_node entry and only special-cases NodeType::Generated. It hands back the Variable node.
show graph for the failing project confirms there is no File node for LICENSE at all - the rule's input edge points at the Variable node:
f3 [label="LICENSE"]; # the config variable, doubling as the input
f3 -> c1;
Nothing ever stats the file, so no content change can be detected.
config_dir_id falls back to NodeId { 0 } (the source root) when options.config_path is empty (src/graph/builder.cpp:2445), which is why the in-source tup.config shape is the one that triggers it - an out-of-tree -B build puts the config directory at build/, where source files are not.
Relationship to #484
Same class, one map over. #484 was a NodeType::Variable keyed by a user-spellable name sharing a namespace with nodes putup owns; this is a NodeType::Variable sharing a path namespace with source files. PR #485 fixed the first by moving putup's keys outside the Tupfile-spellable name space; it does not touch config_var_nodes, and this reproduces unchanged on that branch.
See also the sibling filed alongside this one: add_file_node discards SortedPairVec::insert's duplicate-key return, so a path collision of this kind is silent by construction.
Decisions to make
- Whether a config variable belongs in the path namespace at all. It is not a file; giving it a
PathId under a real directory is what puts it in reach of ensure_file_node.
- Failing that, whether
ensure_file_node should refuse to return a node whose type is not a file kind, rather than only special-casing Generated.
- Whether the
config_dir_id = NodeId { 0 } fallback is right - parenting config variables to the source root maximises the collision surface.
Symptom
With an in-source
tup.config, aCONFIG_<KEY>whose key matches a source file's name in the config directory makes that file invisible: edits to it are never noticed, and the build reportsNothing to do (up to date)for ever.Reproduction
On
c02eeeb(PR #485 branch; also on2987923bfand on a fix-reverted binary - this predates both). A/B against an identical project whose only difference is the config key's name.Control,
CONFIG_OTHER=yin place ofCONFIG_LICENSE=yand nothing else changed: the second build re-runs andout.txtisBSD.Mechanism
add_tupfilecreates aNodeType::Variablenode per non-CONFIG_config key, named with the bare key and parented to the config directory (src/graph/builder.cpp:2490-2512).add_file_nodeinterns<parent path>/<name>into aPathIdand registers it inpath_to_node(src/graph/dag.cpp:96-103), so the config variable occupies the path<config_dir>/LICENSE.The rule's input then resolves through
ensure_file_node(src/graph/dag.cpp:127-129), which returns the existingpath_to_nodeentry and only special-casesNodeType::Generated. It hands back the Variable node.show graphfor the failing project confirms there is noFilenode forLICENSEat all - the rule's input edge points at the Variable node:Nothing ever stats the file, so no content change can be detected.
config_dir_idfalls back toNodeId { 0 }(the source root) whenoptions.config_pathis empty (src/graph/builder.cpp:2445), which is why the in-sourcetup.configshape is the one that triggers it - an out-of-tree-B buildputs the config directory atbuild/, where source files are not.Relationship to #484
Same class, one map over. #484 was a
NodeType::Variablekeyed by a user-spellable name sharing a namespace with nodes putup owns; this is aNodeType::Variablesharing a path namespace with source files. PR #485 fixed the first by moving putup's keys outside the Tupfile-spellable name space; it does not touchconfig_var_nodes, and this reproduces unchanged on that branch.See also the sibling filed alongside this one:
add_file_nodediscardsSortedPairVec::insert's duplicate-key return, so a path collision of this kind is silent by construction.Decisions to make
PathIdunder a real directory is what puts it in reach ofensure_file_node.ensure_file_nodeshould refuse to return a node whose type is not a file kind, rather than only special-casingGenerated.config_dir_id = NodeId { 0 }fallback is right - parenting config variables to the source root maximises the collision surface.