Summary
A rule whose output path is also a checked-in source file is accepted, and in an in-tree build putup overwrites the source file on disk with generated content. Upstream tup rejects the project outright.
Reproduced
a/Tupfile: : gen.src |> cp %f %o |> x.dat
a/x.dat checked in, contains FROMSRC
a/gen.src contains FROMRULE
putup, in-tree:
before: FROMSRC
[.] Build completed: 1 commands in 1ms
after: FROMRULE <- the tracked file is gone
tup, same project:
tup error: Attempting to insert 'x.dat' as a generated node when it already exists as a
different type (normal file). You can do one of two things to fix this:
1) If this file is really supposed to be created from the command, delete the file from
the filesystem and try again.
2) Change your rule in the Tupfile so you aren't trying to overwrite the file.
tup error: Error parsing Tupfile line 1
Out-of-tree the file is not overwritten (the output lands under the build root), but the source is silently unreachable: a glob matching *.dat resolves to the generated node, so the checked-in file can never be consumed.
Why this matters
Losing a tracked file to a build is the worst outcome in the class — it is not a wrong artifact, it is deleted work. tup treats it as ill-formed and says so, with both remedies spelled out.
Direction
Reject a rule output whose path already exists as a source file, with tup's diagnostic shape (name the file, give the two remedies). The check belongs where output edges are created (src/graph/builder.cpp, beside the existing duplicate-output rejection), which already has the node types needed to tell a File from a Generated.
Related
Provenance
Found by an adversarial review of PR #193; the tup behaviour was verified by running the real tup binary, and the overwrite by running putup.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA
Summary
A rule whose output path is also a checked-in source file is accepted, and in an in-tree build putup overwrites the source file on disk with generated content. Upstream tup rejects the project outright.
Reproduced
putup, in-tree:
tup, same project:
Out-of-tree the file is not overwritten (the output lands under the build root), but the source is silently unreachable: a glob matching
*.datresolves to the generated node, so the checked-in file can never be consumed.Why this matters
Losing a tracked file to a build is the worst outcome in the class — it is not a wrong artifact, it is deleted work. tup treats it as ill-formed and says so, with both remedies spelled out.
Direction
Reject a rule output whose path already exists as a source file, with tup's diagnostic shape (name the file, give the two remedies). The check belongs where output edges are created (
src/graph/builder.cpp, beside the existing duplicate-output rejection), which already has the node types needed to tell aFilefrom aGenerated.Related
test/unit/test_e2e.cpp("A generated file shadowing a source file is one glob match", tagged[deviation]). That test asserts only the dedup Glob matches are compared across two path spaces, so out-of-tree %f order, dedup and exclusions are all wrong #191 is about — one glob match rather than two — and points here; it is not a claim that shadowing should be legal. If this issue is fixed by rejecting the project, that scenario should be rewritten to expect the error.%f) is only reachable in a project this issue would reject, so that half of Glob matches are compared across two path spaces, so out-of-tree %f order, dedup and exclusions are all wrong #191's fix is defensive rather than user-facing.Provenance
Found by an adversarial review of PR #193; the tup behaviour was verified by running the real
tupbinary, and the overwrite by running putup.🤖 Generated with Claude Code
https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA