Precompile the test environment from a base-target child under a portable JULIA_CPU_TARGET - #108
Merged
Merged
Conversation
…able JULIA_CPU_TARGET CI gives the test processes a portable, multi-versioned `JULIA_CPU_TARGET` (`generic;sandybridge,-xsaveopt,clone_all;haswell,-rdrnd,base(1)` on x86_64) so that a cached depot survives moving between runner CPUs. That variable never changes the process it is set in: Julia reads it only when it spawns a precompilation worker, which gets it as `-C`, and when it names a cache file. The test process itself keeps running on `native` and judges cache freshness against that. So it accepted whatever native images the restored depot already held, compiled only the stale package under test in a worker, and that worker -- on base target `generic` -- refused the very same native images. Julia 1.12 and older recompiled the dependency nested inside the worker, which is why activation took minutes; Julia 1.13 launches its workers with `--compiled-modules=strict` (base/precompilation.jl:1186), so activation "succeeded" and every `using` in the run failed with "Precompiled image ... not available with flags ...". Every 1.13 leg of JuliaWorkspaces.jl #322 and LanguageServer.jl #1474 went down that way, and the 32-bit legs added a cascade of `UndefVarError: X not defined in JuliaWorkspaces` on top, because `require` had fallen back to loading the package from source and left the half-evaluated module registered. Under such a target, environment activation now runs `Pkg.precompile()` in a child Julia started with `-C <base variant>` (`-C generic` for the target above) instead of in-process. The child rejects native images the way the workers do and rebuilds them under the portable target once; the native test process and any worker it later spawns then agree on every image in the depot. The child is `Base.julia_cmd(; cpu_target)`, which replicates the cache-affecting flags (`--check-bounds`, `-O`, `-g`, `--inline`, `--pkgimages`, `--compiled-modules`, coverage), plus the expanded `LOAD_PATH` as `JULIA_LOAD_PATH` -- what `create_expr_cache` gives Pkg's own workers -- with stdout and stderr inherited, so its output is process-level activation output like before. In the sandbox branch `TestEnv.activate` runs with `JULIA_PKG_PRECOMPILE_AUTO=0` so its in-process `Pkg._auto_precompile` is skipped; the workspace-member branch calls the child instead of `Pkg.precompile()`. The request's cancellation token is polled and kills the child. Only on Julia 1.10+ (package images are checked against the CPU from there), with compiled modules and package images on -- everything else is byte-for-byte what it was, and without the variable nothing changes at all. The new end-to-end item is the CI failure in miniature: `Portable` depends on path-tracked `Leaf`, a private depot gets native images, `Portable` is made stale, and the run repeats under the portable target. Without this change it fails on 1.13 with exactly the CI error; with it, it passes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
davidanthoff
added a commit
to julia-actions/julia-run-testitems
that referenced
this pull request
Sep 13, 2026
…depot themselves #12 gave the test processes a portable `JULIA_CPU_TARGET` and broke every Julia 1.13 leg of JuliaWorkspaces.jl #322 and LanguageServer.jl #1474. The variable never changes the process it is set in -- Julia reads it only when spawning precompilation workers -- so a test process accepted the native images the restored depot held from earlier runs, compiled only the stale package under test, and handed it to a worker that refused those same images; 1.13's workers run with `--compiled-modules=strict`, so that was a failed run rather than the nested recompilation older versions did. TestItemControllers 1.11.2 (julia-testitems/TestItemControllers.jl#108) makes environment activation precompile from a child Julia on the target's base CPU whenever the target is portable, which rebuilds such images once and makes the test processes robust to whatever the depot holds. This bumps the manifest to it (the only change `Pkg.update` made) and says in the input description, the toolkit step and the README why setting a target here is safe again -- and that `julia-buildpkg` is not needed alongside this action, while `julia-actions/cache` still is. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The failure
Every Julia 1.13 leg of JuliaWorkspaces.jl #322 and LanguageServer.jl #1474 went red once
julia-run-testitems@v2started giving the test processes a portableJULIA_CPU_TARGET(generic;sandybridge,-xsaveopt,clone_all;haswell,-rdrnd,base(1)on x86_64) so that the cached per-leg depot survives moving between runner CPUs:and, on the 32-bit legs, a cascade of
UndefVarError: URIs2 not defined in JuliaWorkspacesafterrequirefell back to loading the package from source and left the half-evaluated module registered.Why
JULIA_CPU_TARGETnever changes the process it is set in. Julia reads it in two places only: when it spawns a precompilation worker, which gets it as-C(Base.create_expr_cache), and when it names a cache file. The test process keeps running onnativeand judges cache freshness against that (checked: with the variable set,Base.current_image_targets()still names the host CPU while the worker command carries-C 'generic;...'). So it accepted the native images the restored depot held, compiled only the stale package under test in a worker, and that worker — on base targetgeneric— refused the very same native images. Julia ≤ 1.12 workers recompiled the dependency nested (that is why activation took minutes on those legs); 1.13 launches workers with--compiled-modules=strict(base/precompilation.jl:1186), so activation "succeeds" and everyusingfails.The change
Under such a target,
activate_env_requestnow runsPkg.precompile()in a child Julia started with-C <base variant>(-C genericfor the target above) instead of in-process. The child rejects native images the way the workers do and rebuilds them under the portable target once; afterwards the native test process and any worker it spawns agree on every image in the depot, whatever the depot held before and whoever else wrote to it.Base.julia_cmd(; cpu_target), which replicates the cache-affecting flags (--check-bounds,-O,-g,--inline,--pkgimages,--compiled-modules, coverage), plus the expandedLOAD_PATHasJULIA_LOAD_PATH— whatcreate_expr_cachegives Pkg's own workers — with stdout/stderr inherited so its output stays process-level activation output. The request's cancellation token is polled and kills the child.TestEnv.activateruns withJULIA_PKG_PRECOMPILE_AUTO=0(Pkg'sshould_autoprecompile()honours it), then the child. Workspace-member branch: the child instead ofPkg.precompile().native, nothing changes.testprocess/TestItemServer/src/cpu_target_precompile.jlholds the target parser, the predicate and the child runner (Base only, so the controller suite includes it directly); docs paragraph ininternals.md; CHANGELOG entry.Tests
test/test_cpu_target.jl: unit items for the parser, predicate and command/env shape, and an end-to-end item that is the CI failure in miniature —Portabledepends on path-trackedLeaf, a private depot gets native images,Portableis made stale, and the run repeats under the host's portable target. Verified locally on 1.13.0: withTestItemServer.jl/precompile.jlstashed the item fails with exactly the CI error (Precompiled image ... Leaf not available with flags,Failed to precompile Portable); with the change all four items pass, and so dotest_testserver_precompile,test_scratch_env,test_coverage,test_broken_env,test_run_passing,test_no_package,test_process_reuse(34 items).Expect this PR's own 1.13 CI legs to be red
The CI test processes run the released TestItemServer from the toolkit (TestItemControllers 1.11.0), not this branch, so they hit the very bug this fixes. They go green once this is released and
julia-run-testitemspicks the release up (follow-up PR there).Version
Project.tomlstays at1.11.2-DEV; a 1.11.2 release is what the action's follow-up needs (TestItemRuns' compat is"1.11", so 1.12.0 would force a TestItemRuns release too).🤖 Generated with Claude Code