Skip to content

Fix CI toolchain caching#401

Open
holodorum wants to merge 4 commits into
kson-org:mainfrom
holodorum:ci-toolchain-caching
Open

Fix CI toolchain caching#401
holodorum wants to merge 4 commits into
kson-org:mainfrom
holodorum:ci-toolchain-caching

Conversation

@holodorum

Copy link
Copy Markdown
Collaborator

Two of the CI cache entries never stored anything, and the jobs with the biggest downloads had no caching at all. This PR fixes both:

  • Remove the dead jdk cache: ./gradle/jdk and ./buildSrc/gradle/jdk no longer exist — the jvm wrapper installs the GraalVM JDK to ~/.gradle/jdks/, which the existing ~/.gradle dependencies cache already covers — so this entry saved an empty archive on every run.
  • Add working toolchain caches: rattler (pixi conda packages) keyed on kson-lib/pixi.lock, and uv (Python packages + CPython downloads) keyed on lib-python/uv.lock, each with Linux/macOS/Windows cache paths, following the multi-path pattern of the existing vscode cache.
  • Cache the Python sdist jobs, which previously restored nothing despite pip install of the sdist running a full bundled-Gradle + GraalVM native-image build, plus uv/cibuildwheel for wheels. They now restore/save gradle deps + rattler + uv through new shared restore-build-caches/save-build-caches commands; the existing gradle-caches commands layer vscode/pnpm on top of the same trio. The Linux Python jobs share linux-amd64 cache keys with the every-commit build-linux-amd64 job, so release runs start with warm caches.

.circleci/config.yml is regenerated from config.kson via the gradle transpile task.

Supersedes #400.

holodorum added 3 commits July 4, 2026 00:36
The jdk cache entries pointed at ./gradle/jdk and ./buildSrc/gradle/jdk,
which no longer exist (the jvm wrapper installs the GraalVM JDK to
~/.gradle/jdks, already covered by the dependencies cache), so every run
saved an empty archive. Remove them and add caches that hit: rattler
(pixi's conda package cache, keyed on kson-lib/pixi.lock) and uv (Python
packages and CPython downloads, keyed on lib-python/uv.lock), each with
Linux/macOS/Windows paths. The Python sdist jobs previously restored no
caches at all despite running a full bundled-Gradle + GraalVM
native-image build plus uv/cibuildwheel; they now restore/save gradle
deps, rattler, and uv via new shared restore-build-caches and
save-build-caches commands, with the Linux jobs sharing linux-amd64 keys
with the every-commit build job so release runs start warm.

No tests: this is CI configuration, verified by regenerating
.circleci/config.yml from config.kson via the gradle transpile task.
Enable Gradle's build cache CI-only by setting
GRADLE_OPTS=-Dorg.gradle.caching=true in the environment of every
Gradle-running job, letting CI reuse compile and test task outputs across
runs (build-linux-amd64 spends most of its time in `./gradlew check`).
Keeping this in GRADLE_OPTS rather than gradle.properties leaves local dev
behavior untouched, and the env var still reaches the bundled-Gradle build
that `pip install` of the sdist runs and the separate `cd buildSrc &&
./gradlew check`. Persist the cache in the shared
restore-build-caches/save-build-caches commands: save
`~/.gradle/caches/build-cache-1` under a per-commit key (`{{ .Revision }}`)
and restore by prefix so each build starts from the most recent snapshot. A
per-commit key is required because the dependencies cache keys on the
build.gradle.kts checksum and CircleCI writes any given key only once, so
that cache can never carry an evolving build cache. No tests: this is CI
configuration, verified by regenerating config.yml via the transpile task
and by confirming locally that GRADLE_OPTS makes :kson-lib:compileKotlinJvm
report FROM-CACHE while a plain invocation still rebuilds.
Enabling Gradle's build cache in CI exposed a latent bug in the
nl.ochagavia.krossover plugin: its KSP processor writes
build/kotlin/krossover/metadata/api.json as a side effect that is not among
kspKotlinJvm's declared outputs. Cold builds work because the task executes and
the file appears, but on a warm cache kspKotlinJvm is served FROM-CACHE and the
side-effect file is never restored, so generateJniConfigJvm then fails with
"property 'publicApiMetadataFile' specifies file '...api.json' which doesn't
exist". Declaring api.json as an additional output of kspKotlinJvm lets the
build cache capture and restore it on a cache hit, keeping the task cacheable.
Reproduced locally against an isolated build cache: prime, then wipe
kson-lib/build and rerun :kson-lib:generateJniConfigJvm, which fails before this
change (kspKotlinJvm FROM-CACHE, api.json missing) and passes after (api.json
restored from the cache entry); a broader warm-cache run of
:generateJniBindingsJvm confirmed api.json is the only undeclared side-effect in
the krossover chain. The real fix belongs upstream in the plugin. No automated
test is added: exercising this requires a full cold-then-warm Gradle run, which
the manual reproduction covers and which has no cheap unit-test surface.
@dmarcotte

Copy link
Copy Markdown
Contributor

Nice! Thanks, @holodorum!

Gradle caches go stale depending on clarified input and ouptut of task.
If we accidentally did not specify an input or not all inputs the task
won't be run again or will be run with stale a stale cache.

I'm not sure if we can fully safeguard against this. This would limit the
consequence by only using the Gradle caching option only for the check
job, not for any of the release jobs.
@holodorum holodorum force-pushed the ci-toolchain-caching branch from f7b28d5 to 69a2897 Compare July 6, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants