build: rebuild an output another configuration left in the build directory - #7236
Conversation
The walk that collects the preprocess targets resolves rules, and it reaches past the target it runs for: the products of one build lead to the objects of the `mrbc` build it generated, and those have rules of their own. Resolved while the loop is still on the first target, they see compilers that have not been handed their generated header directory yet, though the compile that follows them has it. Add the directory for every target in a pass of its own, before the walk begins, so that a rule resolved for any target sees the include paths the compile will use.
`define_rules` writes the same rule twice for every source extension, once looking for the source beside the sources and once among the generated files. The directory is all that differs.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: Your plan includes up to 8 reviews per rolling hour; 4 remain after this review. 📝 WalkthroughWalkthroughThe build system records compiler flags for each output, detects outputs from other configurations, reports mismatches once per build, and removes stale outputs. Presymbol include paths are initialized before presymbol dependency resolution. ChangesCompiler output configuration tracking
Estimated code review effort: 3 (Moderate) | ~25 minutes Merge Risk: ⚪ Minimal · up to The build now records compiler flags and rebuilds outputs when configurations differ, preventing stale objects from being reused across configurations; no actionable merge-blocking risk remains after normal checks and review. Sequence Diagram(s)sequenceDiagram
participant Compiler
participant DependencyResolver
participant FlagsRecord
participant MRubyBuild
Compiler->>Compiler: construct compile_flags
Compiler->>DependencyResolver: resolve dependencies for source
Compiler->>FlagsRecord: read output configuration
Compiler->>MRubyBuild: report_flags_change(recorded, current)
Compiler->>Compiler: remove mismatched output
Compiler->>FlagsRecord: write successful output configuration
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ctory A build directory records what was built, not what it was built with, so two configs that share `MRUBY_BUILD_DIR` and a build name hand each other their objects. `MRuby::Command::Compiler#all_flags` assembles the defines when the compile runs and writes them nowhere; the `.d` file beside an object carries header dependencies only; and the config file is a dependency by path, so its mtime does not move when another config takes the directory over. The object is up to date against every dependency it has, and the binary silently carries the defines of whichever config came first. `build_config/no-float.rb` over a directory a default `rake` has populated is the case that shows, because `CrossBuild#initialize` generates its minimal build under the name `host`, which is the name `build_config/default.rb` uses too: ```console $ rake test MRUBY_CONFIG=build_config/no-float.rb ... test/t/argumenterror.rb:34: irep load error (ScriptError) ``` Not one compile line for `host`. The float-enabled `mrbc` of the earlier build writes `IREP_TT_FLOAT` pool entries and `src/load.c` refuses them on the `MRB_NO_FLOAT` target, the failure a build directory of its own no longer has. Record the command line each compile ran with, beside its output the way the `.d` file records the headers it read, and compare it when the rule for that output is resolved. An output the record does not answer for, or that has no record at all, is removed, so that rake builds it again. Only a compile that succeeded writes a record, so a build that stops halfway leaves nothing claiming a configuration its output was not built with. The directory says once what changed: ```console $ rake test MRUBY_CONFIG=build_config/no-float.rb /tmp/build/host: output here was built by another configuration, rebuilding it flags added: -DMRB_NO_FLOAT -DMRB_NO_GEMS flags removed: -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DMRB_USE_RATIONAL ... ``` The record is compared only through the rule that finds its source, the same test rake applies before it uses a rule. Every compiler of a build defines a rule for the object names, differing in the extension of the source it looks for, and the ones that do not build the output would otherwise judge it against flags no compile of it ever used.
46e6573 to
a0e25e8
Compare
A build directory records what was built, not what it was built with, so two
configs that share a build directory and a build name share the objects, and the
second one silently gets the first one's compiler flags.
MRuby::Command::Compiler#all_flags(lib/mruby/build/command.rb:95-101onmaster) assembles the defines at the moment of the call and writes them nowhere
rakewill read again. The.dfile beside an object records headerdependencies only, and
MRUBY_CONFIGis a dependency by path, whose mtime doesnot move when another config takes the directory over. Every dependency the
object has says it is up to date, so nothing is compiled, and the binary carries
the defines of whichever config came first.
The name is what brings two configs together:
CrossBuild#initializegeneratesits minimal build under the name
host, which is the namebuild_config/default.rbuses too. Two runs of the same build name fromdifferent build configs collide the same way.
Before
The same tree and the same command twice,
rake test MRUBY_CONFIG=build_config/no-float.rb, differing only in what was inthe build directory beforehand: nothing, or a default
rakebuild.hostmrbcit usesMRB_NO_FLOATirep load errorWith a directory of its own, the generated
hostbuild carriesMRB_NO_FLOAT,and its
mrbcrefuses the float literal where it is written:Over a directory a plain
rakehas populated, not one compile line forhost.The float-enabled
mrbcof the earlier build is reused, writesIREP_TT_FLOATpool entries, and
src/load.crefuses them on theMRB_NO_FLOATtarget: thepre-#7230 failure, on a tree that carries #7230.
MRB_NO_FLOATis the loud case, because the answer changes shape.MRB_INT32/MRB_INT64,MRB_GC_FIXED_ARENAand the boxing modes are the quiet ones, and ameasurement taken in a shared directory can report that a merged fix did not
land.
After
Every compile records the command line it ran with, beside its output, the way
the
.dfile records the headers it read:The record is compared when the rule for that output is resolved. An output the
record does not answer for is removed, so
rakebuilds it again. The shareddirectory of the second run above:
hostirep load errorMRB_NO_FLOATobjectsOnly a compile that succeeded writes a record, so a build that stops halfway
leaves nothing claiming a configuration its output was not built with: what it
did not reach is still the earlier output, still without a record that answers
for it, and the next run removes that too.
Two things this rests on:
the test
rakeitself applies before it uses a rule. Every compiler of abuild defines a rule for the object names, differing in the extension of the
source it looks for, and a compiler that does not build the output would
otherwise judge it against flags no compile of it ever used.
tasks/presym.rakenow hands every target its generated header directorybefore the walk that resolves rules begins. That walk reaches past the target
it runs for, since a build's products lead to the objects of the
mrbcbuildit generated, and a rule resolved for those objects has to see the include
paths the compile will use.
What it costs
One directory, the default gembox:
rake -mwith nothing to doThe first
rakein a directory built before this change rebuilds it once, therebeing no record to compare it against, and says so:
output here has no record of what built it, rebuilding it.An object built by a
filetask written by hand rather than by a rule is notcompared, the check hanging off the rule.
mrbgems/mruby-compileris the onlysuch place in the tree: its Prism objects choose their flags inside the task
body, and they carry no dependency on
MRUBY_CONFIGeither.Verification
Under
build_config/, each run twice:default.rbci/gcc-clang.rbhost-cxx.rbhost-debug.rbno-float.rbminimal.rbThe last row is a config for the rules a build can define for one object name:
enable_cxx_exception, so that thecxxcompiler defines rules for the objectnames too, and two gems from
examples/mrbgems, which are outsidemrbgems/and so use all four compilers. Overwriting the record of one core object and one
gem object in that build rebuilds those two and nothing else.
One directory, in this order:
rake -m testrake testwithno-float.rbhost, stops at the literalrake -m testhost, 2123 assertions, 0 KOrake -m testEnvironment
No C source changes, so
.textis unaffected in every build.🤖 Generated with Claude Code
https://claude.ai/code/session_01ArcdfvwWWVJiJr1sWW4EQf
Summary by CodeRabbit