feat(external): forward a project's args to its sibling projects by default; lint with the lite rules #40
No reviewers
Labels
No labels
bug
claude:blocked
claude:done
claude:failed
claude:in-progress
claude:ready
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Catcrafts/Crafter.Build!40
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/forward-project-args"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two changes: sibling projects get the parent's args without the
depArgscopy, and this tree is linted with the lite house rules. The last seven commits fix what review of the first five found.Forward a project's args to its sibling projects by default
Every
project.cppwith a sibling dependency opened withand passed
.args = depArgsto eachGitProject/LocalProject. There were about 45 copies across the Catcrafts repos.CrafterBuildProjectgets a span of string_views and the specs wanted owned strings, so the copy was needed. A spec without it built the sibling with none of the project's flags: for the host while the project cross-compiled, and in release under--debug.The fix: both specs now default
args = ProjectArgs(), which returns the args the runningCrafterBuildProjectwas loaded with. That is the same shape asConfiguration::target = HostTarget(). The value comes from the per-thread stackLoadProjectalready keeps forIsTopLevelProject(), whose frames now record the args..args = depArgsmeans exactly what it did. The one call in the Catcrafts repos without.argsis Crafter.LintRules'GitProjectof this repo, since7d93289. It now forwards LintRules' args, as it did before42a2701, except that--localis no longer removed first.ProjectArgs()is filled in just beforeCrafterBuildProjectis called, and is empty everywhere else. That covers the#pragma crafter dependencypre-pass, so build tools get none of the consumer's args without any opt-out. It also covers the project file's static initialisers, which run only on a file's first load.ProjectArgs()is declared: in:External, not:Platform. Importing:Platformthere would make the exportedstruct Configuration;an exported redeclaration of:Platform's unexported one, and clang 23 rejects that.project.cppwithout.argsstill compiles on crafter-build ≤ 2.2.0, but there it forwards nothing. So the sibling repos keep their copies until a release with this change is on stable (list below).Lint with
LintRules::AddLiteRulesCrafter.LintRules now has a lite set: the 8 rules that ask for different code, without the layout, whitespace and naming rules.
project.cppswitches to it.With the full set,
lintfailed with 49namingfindings. The rule now wants camelCase statics, and this tree spells them PascalCase (Failures,LoadingFrames, …). With the lite set,lintandformat --checkare clean. This also stops checking eight layout and whitespace rules the tree already passes: tabs, trailing whitespace, final newline, brace style, one declaration per statement, single-lineif, wrapping and paren spacing.AGENTS.md "Code style" now says which rules
lintenforces, thatformatonly rewrites string concatenation and builtin integer types, and that layout and naming are kept by hand.Related: Crafter.LintRules
masterdid not compile after42a2701: it still used.args = depArgsafter the declaration was removed. Every load of this repo'sproject.cpp, and so CI, would have failed. The fix (7d93289) is already pushed to its master.Fixes from review
CrafterBuildProjectcall. The frame used to get the args when it was pushed. So the pragma pre-pass needed a hand-written.args = {}on both of its specs. And a spec made at namespace scope kept the args of the file's first load for every later load, becausedlopenruns static initialisers once. Now the args are set just before the call, and the dependencies the pre-pass resolved just beforedlopen.ProjectDependency()from a static initialiser used to find no dependencies and terminate the process.#pragma crafter dependencythat both load one sibling with the same args shared a singleConfiguration.ForceHostToolingrewrote it to host settings in place, so the project built against-march=nativeBMIs. A per-thread count of pragma loads now goes into the cache key. Shared clones stay shared.--ends crafter-build's options. This also predates the branch. The help has always advertised[-- project-args...], but--itself landed in the project args. It changed the variant hash of the project and, with this branch, of every sibling. The words after it were still read as crafter-build's own options. After--, every word is now an operand: a project arg, a glob aftertest,lintorformat, the script and its args afterrun.HostToolingSiblingtest,--cases inScript, and a namespace-scope spec case inProjectArgs. That test now checks through one helper that prints what each sibling got.ProjectDependencygains a static-initialiser case in a child process. Its args check is folded into the first load, because the separate load's check could never fail and its early return skipped every later check.cfg.projectArgsis no substitute forProjectArgs(). The spec comment no longer names--localas a flag that carries down a chain, and theGitProjectcomment no longer says the clone is keyed by the args. AGENTS.md notes that a stale harness gives no symptom for a changed default member initialiser.#41, two builds of one sibling ending up in one program, gets its own branch and PR.
Testing
formatandlintare clean with the 8 lite rules.WasmHelloRunsskips because wasmtime isn't on PATH, andConcurrentCacheRaceskips because no installed share dir is found. It passes when run withCRAFTER_BUILD_HOME=$PWD/share/crafter-build.tests/ProjectArgscovers a LocalProject chainapp → lib → leaf:.argsinherit the args, transitively..args = {}gets nothing, and an explicit list replaces the args.GitProjectSpecdefault is checked on the spec itself.HostToolingSiblingfails 3 checks.--falling through to the project args, bothScriptchecks fail.dlopen, the namespace-scope check fails withprojectArgs ["--flavor=x"].dlopen, the static-initialiser load exits 134 ("declared: none").["--flavor=x"]../bin/crafter-buildand./bin/crafter-build --debugbuild with the depArgs-freeproject.cpp.Needs
./build.shafter pulling: interfaces changed, andproject.cppreaches the new exportedProjectArgssymbol.Follow-up after the next release
.args:graphicsArgscopies (VulkanTriangle, RTStress, Sponza, SponzaBench, RayQueryPick, HDRBloom, RTMultiShadow, RTVolume).--no-quic.--local.--target=.🤖 Generated with Claude Code
Every project.cpp with a sibling dependency opened with the same line, std::vector<std::string> depArgs(args.begin(), args.end()); and handed depArgs to each GitProject and LocalProject. CrafterBuildProject receives a span of string_views and the specs wanted owned strings, and without the copy the sibling got none of the project's args: it was built for the host while the project cross-compiled, and in release while it built --debug. GitProject then turned the copy straight back into string_views for LoadProject. Both specs now default their args to ProjectArgs(), the args LoadProject was given for the project file whose CrafterBuildProject is running. It reads the per-thread stack LoadProject already keeps for IsTopLevelProject and ProjectDependency, whose frames now record the args. A default member initializer rather than a new type or an optional: the member stays a std::vector<std::string>, so every existing `.args = depArgs` keeps its exact meaning (each of the ~50 calls across the Catcrafts projects passes one), and `.args = {}` still forwards nothing. It is the shape of Configuration::target = HostTarget(). A project.cpp that drops its .args needs a crafter-build with this change: an older one compiles it and forwards nothing. ProjectArgs() is declared in :External, next to the defaults that call it. :Platform, where it is defined, cannot be imported there: its unexported `struct Configuration;` would make :External's exported one an ill-formed redeclaration, which clang rejects. The #pragma crafter dependency path now passes `.args = {}` explicitly. Host tooling is built for the host whatever the consumer targets and was loaded with no args; the default would otherwise hand it the consumer's --target= and project flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>ProjectArgs loads a chain app -> lib -> leaf of LocalProjects with --debug and a project flag. lib, loaded without .args, has to see both, and so does its own sibling, which takes the default one level further down. Copies loaded with `.args = {}` and with an explicit list get nothing and exactly that list. The GitProjectSpec default is checked on the spec itself, since a GitProject would need a repository to clone. Without the defaults, five of its checks fail. ProjectDependency now also loads its consumer with a project flag: the pragma dependency must still come back with no args. Without the explicit `.args = {}` in the pragma path it gets the consumer's flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>The sibling-project example dropped its `.args = depArgs`, which no longer has to be written, and says what a sibling receives instead: the args the project file was loaded with, or whatever `.args` is set to, with `{}` for nothing. It also says that releases up to 2.2.0 forward nothing without `.args`, which matters for a project.cpp that has to load on both. The pragma section says that a project file's own dependency is built for the host and gets none of the project's args, the one place the default does not apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>f60268b17a5cf295e1d4ProjectArgs() read args that LoadProject recorded when it pushed the frame, so they were there for everything that ran on that frame, not only for CrafterBuildProject. Two things went wrong with that. The pragma pre-pass runs on the frame too, so both of its specs needed a hand-written `.args = {}` to keep the consumer's args from reaching host tooling, and anything else built there would have needed one as well. And dlopen runs a project file's static initialisers only on the first load of that file: a later load gets the same mapping back. A spec made at namespace scope therefore kept the first load's args for every later load of the file, a silently wrong variant. The frame now gets the args just before CrafterBuildProject is called. The pre-pass and the static initialisers see none, on every load, so the two opt-outs go. The dependencies the pre-pass resolved move the other way, to just before dlopen: they are the same on every load, and ProjectDependency() called from a static initialiser used to find none and terminate the process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>--instead of forwarding it 368fd2c527--reaches neither the project args nor the script 70b68b98a5Merged into master as
7514227(Merge branch 'claude/forward-project-args'), on top of claude/dependency-notes. The conflicts were all places both branches added to; the merge commit's message lists how each was resolved. Closed by hand because the repository does not allow marking a PR as manually merged.Pull request closed