fix(external): fetch and build each clone once per build pass #39
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!39
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/issue-34"
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?
Closes #34.
653dd0balready put a lock on each external clone, which stopped parallel pulls from breaking.git/FETCH_HEAD. But every test configuration still pulled the shared clone itself, one after another: 26 Crafter.Graphics tests cost 26 network round trips per unpinned dependency, and as many configure checks and builds for a CMake one. This PR does the issue's two remaining items: each clone is fetched once per build pass, and there is a regression test on the path the issue hit.What changes
depResults. The map already records each dependency configuration built in one pass, keyed by itsPcmDir. External dependencies now go in the same map, keyed by the directory their build leaves (ExternalBuildDir: the clone, or its CMake build directory). The first configuration that needs one fetches and builds it, and the rest wait for that outcome. A fresh map is a fresh pass, so the next build still pulls. Every caller ofBuild()gets this unchanged:RunTests, a plain build, and the pragma dependenciesLoadProjectbuilds.BuildExternalis split.BuildExternal(dep, target)is now the shared work (lock, fetch, configure, build) and returns the error. The newExternalOutputs(dep, target)gives each declaration its own flags, because two declarations of one clone can differ inincludeDirs,libDirsandlibs.ExternalBuildResultloses itserrorfield.Build()already does. Other configurations may be waiting on it, and a run cut short would leave them a clone nobody fetched. The cost: a build that fails for another reason while an external is fetching now also waits for that external's CMake build.OncePerPassreplaces the inline promise and future code of the dependency threads, and the external threads use it too. An exception thrown inBuildExternalused to escape its thread intostd::terminate; it is now a build error.Choices worth a look
depResultsputs two kinds of entries in one map, and an external's entry only usesBuildResult::result. The alternative was a new pass type inBuild()'s signature, which 15 tests and every in-process caller spell out. I kept the signature; the interface comment onBuild()now says what the map holds.ExternalBuildDirrather than the clone. Keyed on the clone, a CMake declaration could take the outcome of a header-only declaration of the same clone, which only fetched it, and then link against a build that never ran. That case is unlikely and has no test.BuildExternalandExternalBuildResultare exported, and both change. Nothing outsideBuild()and one test used them, in this repo or the sibling repos.Tests
SharedExternalFetch(new). Eight tests that include a header from one tracked branch of a local repository run throughRunTestsat once, twice, with the branch moving in between.GIT_TRACEcounts the fetches: each clone or pull runs onegit upload-pack. On the commit before the fix, each run fetches 8 times. With it, each run fetches once, and the second run still pulls the new commit.ConcurrentExternalFetch(extended). Its threads now also raceBuildExternal, whose lock still keeps two crafter-builds apart. With that lock removed the test failed in 2 of 3 runs: it is a race test, not a deterministic one.ExternalArtifactTimeis adapted to the split API.The README now says how often an unpinned external is pulled, and lists
targetin the clone's cache key. AGENTS.md's note ondepResultsmentions fetches.Verification
./bin/crafter-build test: 34 passed, 2 skipped (ConcurrentCacheRacewithoutCRAFTER_BUILD_HOME,WasmHelloRunswithout wasmtime). That is master's result plus the new test.ConcurrentCacheRacepasses withCRAFTER_BUILD_HOMEset.format --checkis clean, and the self-host build succeeds.lintexits 1 with 48[naming]findings. Master has exactly the same set; this branch adds none.🤖 Generated with Claude Code