Merge filesystem and generated glob matches into one path-ordered list (fixes #177, fixes #178) - #180
Conversation
fixes #177, fixes #178) expand_glob_pattern expanded a pattern two ways and treated the second as a fallback: if the filesystem glob matched anything it returned early, so one file on disk discarded every generated match for that pattern. The generated scan also pushed matches in nodes_of_type order -- arena order, i.e. node creation order -- with no sort, while #174 had just made the filesystem branch path-ordered. Both defects are visible in one build. With putup's default in-tree layout the generated files land beside the sources, so the branch flips between the first and second build of an unchanged project: : foreach zeta.in mike.in alpha.in |> cp %f %o |> %B.gen : *.gen |> echo %f > %o |> matches.txt build 1 (clean): zeta.gen mike.gen alpha.gen graph branch, creation order build 2 (no change): alpha.gen mike.gen zeta.gen filesystem branch, path order and the command re-runs to produce it %f is hashed by compute_command_identity, so that is command identity moving under an unchanged project -- the #166/#167 failure class, reached through the input set rather than the hash. Now both sources feed one list that is sorted by path and deduped (an in-tree build sees a generated file from both sides; it is one input). This matches tup, which serves file and generated nodes from a single query over node_dir_index (dir, name) -- measured: for the fixture above, tup emits alpha.o mike.o zeta.o. request_demand_driven_parse becomes unconditional. It ran only on the fallback path, so with a merge the generated half of the match set would otherwise depend on how far the parse fixpoint had progressed. Cost: the generated-node scan now runs for every glob rather than only for patterns that match nothing on disk. It is a linear scan of the file arena per pattern; tup answers the same question from an index. Both scenarios were observed failing first: matches.txt read "zeta.gen mike.gen alpha.gen" for the ordering fixture and "kept.gen" alone for the mixed source/generated one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA
PR metricsPerformance (gcc example, Linux)
Deterministic signals: instructions (cachegrind-simulated instruction reads — exact across runs, no PMU needed), page faults, peak RSS, and the cachegrind D1/LL miss rates. CPU time is user+sys from time(1). Internal statistics (gcc example, up-to-date dry run)
Counters from Binary size (Linux)
Test coverage (lines)
103 files · 14988/17315 lines covered Deltas vs main@1738aa715. Updated for d85186d |
The merge in the previous commit deduped on raw strings from two different path spaces. pup::path::join does not normalize, so for a pattern spelled ../b/*.q the filesystem half pushed "z/../b/gen.q" while the generated half pushed the graph's "b/gen.q". std::unique compares StringId handles, so both survived and the rule received the same file twice: build 1: cat ../b/gen.q > out.txt (generated half only) build 2: cat ../b/gen.q ../b/gen.q > out.txt (both halves, two spellings) With gcc %f or ld %f that is a multiple-definition failure. Normalizing the filesystem half conforms to expand_inputs, which already normalizes the same way one call up (builder.cpp:955). Found by adversarial review; no fixture used a ../ or ./ glob, so CI stayed green. INDEX_VERSION 16 -> 17: the merge changes both the membership and the order of %f for any pattern a generated file matches, so v16 identities no longer join for those commands. PR #174 set this rule three commits back for the same class of change; leaving it at 16 was a silent divergence from it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA
|
Correction to this PR's description, verified on a branch with all four PRs merged. The description says making So for cross-directory generated matches, Filing that as a design issue rather than leaving the claim standing. |
expand_glob_patternexpanded a pattern two ways and treated the second as a fallback: if the filesystem glob matched anything it returned early, so one file on disk discarded every generated match (#178). The generated scan also pushed matches innodes_of_typeorder — arena order, i.e. node creation order — with no sort, while #174 had just made the filesystem branch path-ordered (#177).Both defects in one build
With putup's default in-tree layout the generated files land beside the sources, so the branch flips between the first and second build of an unchanged project:
%fis hashed bycompute_command_identity, so the before-column is command identity moving under an unchanged project — the #166/#167 failure class, reached through the input set rather than through the hash.Fix
Both sources feed one list, sorted by path and deduped (an in-tree build sees a generated file from both sides; it is one input). This matches tup, which serves file and generated nodes from a single query over
node_dir_index (dir, name)— measured against real tup, which emitsalpha.o mike.o zeta.ofor the same fixture.request_demand_driven_parsebecomes unconditional: it ran only on the fallback path, so with a merge the generated half of the match set would otherwise depend on how far the parse fixpoint had progressed.Cost
The generated-node scan now runs for every glob, not only for patterns matching nothing on disk — a linear scan of the file arena per pattern. tup answers the same question from an index; if this shows up in profiles, a per-directory index of generated nodes is the fix.
Tests
Two fixtures, both observed failing first:
matches.txtreadzeta.gen mike.gen alpha.genfor the ordering scenario, andkept.genalone for the mixed source/generated one.make testgreen (142321 assertions, 618 cases, 32 e2e shards);make tidyandmake iwyuclean; bootstrap scripts unchanged.🤖 Generated with Claude Code
https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA