Symptom
A file whose basename is literally <g> and a group <g> declared in the same directory become one node. Which node survives depends on the order of the rules in the Tupfile, and neither order gives the right graph.
Group rule first — the file is never tracked. Tupfile:
: a.c |> cp %f %o |> a.o <g>
: *g* |> cat '%f' > %o |> out.txt
$ printf 'A\n' > '<g>'; putup -B b # Build completed: 2 commands
$ printf 'B\n' > '<g>'; putup -B b
[b] Nothing to do (up to date).
$ cat b/out.txt
A
show graph has one <g> node, and it is the group: b/a.o -> <g> -> cat '<g>' > b/out.txt. The rule's input binds to the group, whose only member is a.o, so the command reruns when a.o changes and never when the file it reads changes. The file on disk has no node.
Consumer rule first — the group adopts the file. Swap the two rules and the edit is picked up (out.txt becomes B). But the graph is still wrong. The glob creates <g> as a file node, and the group rule's pre-check (find_by_dir_name on the directory and <g>) returns that node and uses it as the group, which gives b/a.o -> <g>, a group-membership edge into a source file. cat now reruns whenever a.o changes even though it does not read a.o.
Both fixtures were run against build/putup at dddfb5620.
Cause
NodeType::Group is path-addressable (is_path_addressable in include/pup/core/types.hpp), and a group node is named with its brackets and parented to the Tupfile's directory. So it occupies exactly the (directory, name) key that a file named <g> spells, and the second producer finds the first producer's node by name and reuses it.
This is the residual left open by #486. That fix took Variable, Condition and Phi out of the path namespace and left Group in because a group is looked up by path. PR #490 does not close this either: neither order makes a second add_file_node call, because each is a lookup that returns the node already there.
Upstream
tup keeps groups and files in the same (dir, name) key. On a type mismatch, tup_db_create_node_part_display does not refuse. It force-deletes the existing node (tup_del_id_force) and recreates it with the new type. I have read that from the source but not run tup on this fixture, so I don't know what tup's scanner and parser do to each other across builds.
Decision to make
Symptom
A file whose basename is literally
<g>and a group<g>declared in the same directory become one node. Which node survives depends on the order of the rules in the Tupfile, and neither order gives the right graph.Group rule first — the file is never tracked. Tupfile:
show graphhas one<g>node, and it is the group:b/a.o -> <g> -> cat '<g>' > b/out.txt. The rule's input binds to the group, whose only member isa.o, so the command reruns whena.ochanges and never when the file it reads changes. The file on disk has no node.Consumer rule first — the group adopts the file. Swap the two rules and the edit is picked up (
out.txtbecomesB). But the graph is still wrong. The glob creates<g>as a file node, and the group rule's pre-check (find_by_dir_nameon the directory and<g>) returns that node and uses it as the group, which givesb/a.o -> <g>, a group-membership edge into a source file.catnow reruns whenevera.ochanges even though it does not reada.o.Both fixtures were run against
build/putupatdddfb5620.Cause
NodeType::Groupis path-addressable (is_path_addressableininclude/pup/core/types.hpp), and a group node is named with its brackets and parented to the Tupfile's directory. So it occupies exactly the(directory, name)key that a file named<g>spells, and the second producer finds the first producer's node by name and reuses it.This is the residual left open by #486. That fix took
Variable,ConditionandPhiout of the path namespace and leftGroupin because a group is looked up by path. PR #490 does not close this either: neither order makes a secondadd_file_nodecall, because each is a lookup that returns the node already there.Upstream
tup keeps groups and files in the same
(dir, name)key. On a type mismatch,tup_db_create_node_part_displaydoes not refuse. It force-deletes the existing node (tup_del_id_force) and recreates it with the new type. I have read that from the source but not run tup on this fixture, so I don't know what tup's scanner and parser do to each other across builds.Decision to make
DuplicateNodeparse error. Or<g>operands.