Skip to content

ci(wheels): cache SHA-verified source distfiles + vcpkg assets on every build - #970

Merged
MAfarrag merged 8 commits into
mainfrom
ci/cache-source-distfiles
Aug 12, 2026
Merged

ci(wheels): cache SHA-verified source distfiles + vcpkg assets on every build#970
MAfarrag merged 8 commits into
mainfrom
ci/cache-source-distfiles

Conversation

@MAfarrag

@MAfarrag MAfarrag commented Aug 12, 2026

Copy link
Copy Markdown
Member

Description

Every bundle run — releases included — re-downloads all source tarballs from ~30 upstream hosts, so a single 503/429
from any one fails the whole build (the lcms2 GitHub 503 in run 31621639572; the earlier DKRZ libaec 429). The
existing caches store compiled binaries and are (correctly) skipped on the release path to avoid
cache-poisoning-to-release. Source distfiles are a different, safe thing to cache: they are integrity-verified
before use
, so a tampered/corrupt cache entry is rejected and re-downloaded rather than baked into a wheel.

  • Linux from-source (build-gdal-stack.sh + config.sh): exports a DISTFILES_DIR (a sibling of the
    compiled-stack cache on the /host mount); fetch() reuses a cached tarball only when it matches the pinned
    SHA256, else downloads and persists it. A new un-gated actions/cache (arch-independent key on config.sh,
    shared by the glibc + musl jobs) keeps the ~26 tarballs across runs — including releases — so the common path
    makes zero upstream requests.
  • win_arm64 vcpkg: an un-gated actions/cache of C:\vcpkg\downloads persists vcpkg's SHA512-verified
    port distfiles (vcpkg re-verifies on use).

Both invalidate only when the pins change (config.sh / vcpkg.json hash). GHA cache is best-effort (branch scope,
eviction), so a first run after a bump still downloads; a persistently-down host still needs a mirror/overlay.

Issues

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

  • bash -n clean on both shell scripts; workflow YAML parses; no lines over 120.

  • Simulated fetch(): cache-hit path taken on a SHA256 match; empty DISTFILES_DIR reverts to the exact old
    download-only behavior (safe fallback).

  • A bundle-pypi-wheels dry-run seeds the caches on this run; a second dry-run confirms the Linux build reuses the
    cached distfiles ("using cached ... from ...") and makes no upstream downloads.

  • Shell syntax + YAML + fetch() cache logic validated locally

  • Dry-run seed + hit confirmed in CI

Checklist:

  • updated version number in pyproject.toml. (commitizen-managed; not applicable)
  • added changes to History.rst. (changelog is commitizen-generated; not applicable)
  • updated the latest version in README file. (not applicable)
  • I have added tests that prove my fix is effective. (CI-build change; validated by the dispatched bundle build)
  • New and existing unit tests pass locally with my changes. (no source touched)
  • documentation are updated. (no user-facing surface)

…ry build

Every bundle run — releases included — re-downloaded all source tarballs from
~30 upstream hosts, so a single 503/429 from any one failed the build (the
lcms2 GitHub 503 in run 31621639572; the earlier DKRZ libaec 429). The
existing caches store COMPILED binaries and are (correctly) skipped on the
release path to avoid cache-poisoning-to-release. Source distfiles are a
different, safe thing to cache: they are integrity-verified before use, so a
tampered/corrupt cache entry is rejected and re-downloaded rather than baked
into a wheel.

- Linux from-source: build-gdal-stack.sh exports a DISTFILES_DIR (a sibling of
  the compiled-stack cache on the /host mount); config.sh's fetch() reuses a
  cached tarball only when it matches the pinned SHA256, else downloads and
  persists it. A new, UN-gated actions/cache (arch-independent key on
  config.sh) keeps the tarballs across runs — including releases — so the
  common path makes zero upstream requests.
- win_arm64 vcpkg: an UN-gated actions/cache of C:\vcpkg\downloads persists
  the SHA512-verified port distfiles (vcpkg re-verifies on use), the same
  safety property.

Both invalidate only when the pins change (config.sh / vcpkg.json hash). GHA
cache is best-effort (branch scope, eviction), so a first run after a bump
still downloads; a persistently-down host still needs a mirror/overlay.
…autysh

beautysh could not parse the backslash-continued [[ ]] condition
(indent/outdent mismatch). Compute the cached tarball's SHA into a variable
first, then branch on a single-line condition — identical behavior, no
continuation.
…sent

fetch()'s persist guard wrote the cache only when the cached file was absent.
A cached tarball whose SHA no longer matches its pin (upstream re-upload of
the same version, mirror swap, corrected pin, or a partial write) correctly
falls through to a fresh wget + SHA-verify, but the guard then skipped the
write-back, so the stale file stayed cached forever and that dep was
re-downloaded from its (possibly flaky) upstream on every build — the exact
fragility this cache removes, silently defeated for that dep. Since the
tarball is SHA-verified immediately above, overwrite unconditionally so the
cache self-heals.
…can't seed a partial

actions/cache saves at post-job even when the build failed, and the four
Linux jobs share one write-once distfiles key — so a job aborting mid-fetch
(the flaky-host case this cache targets) would seed the shared key with a
partial tarball set that never self-heals until config.sh's hash rotates. The
same partial-save-on-failure applies to the win_arm64 vcpkg downloads cache
(which is why its seed never took today — every failed run saved a partial).

Replace the combined actions/cache with actions/cache/restore (before the
build) + actions/cache/save gated on success() and a non-exact-key restore,
for all three new caches. A failed or partial fetch no longer seeds the
immutable key; only a fully-built run does.
The vcpkg downloads cache keyed on ci/vcpkg.json only, but the overlay port
ci/vcpkg-ports/libaec redirects libaec off the DKRZ host that the cache's own
comment cites as its reason to exist. If that portfile's URL/version/SHA512
changes, the download set changes but the key would not rotate. Add
ci/vcpkg-ports/** to the key hash (restore + save kept in step) so the cache
tracks the overlay. Self-healing via vcpkg's SHA512 re-verify regardless, so
this is a freshness fix, not a correctness one.
…che-hit

The distfile/downloads dir is only populated by config.sh's fetch() loop (or
vcpkg install), which build-gdal-stack.sh skips on a compiled-stack cache hit
(mkdir -p still leaves the dir empty). In the reachable state 'compiled cache
hit + distfile exact-key miss' (the two entries evict independently under
GitHub's 10 GB LRU pool), the save fired on the restore-keys prefix match
(cache-hit != 'true') and persisted an empty/stale set under the immutable
exact key. The release path (compiled cache gated off) then restored that
exact key with cache-hit == 'true', skipped its own save, and was forced to
re-download every tarball from its upstream forever — silently reverting
releases to the flaky-download behavior this cache exists to eliminate.

Give each compiled/installed cache step an id and AND its
cache-hit != 'true' into the distfile/downloads save condition, so the save
fires only when the source build actually ran and populated the dir. On the
release path the compiled step is skipped, so its empty cache-hit output keeps
the distfile save active there, as intended. No wheel-correctness change (the
SHA re-verify already guaranteed that); this restores the resilience benefit
on the release path.
…the SHA pin

The distfile cache key hashes config.sh only, while the GDAL tarball name also
depends on GDAL_VERSION defaulted in build-gdal-stack.sh. This is safe only
because the GDAL SHA is pinned inline in config.sh (a bump rotates the key);
record that in the key comment so a future maintainer who decouples
GDAL_VERSION into a workflow input remembers to add build-gdal-stack.sh to the
key.
…ing dirs

The from-source GDAL build persists both dirs into the runner workspace (the
compiled-stack tar and the SHA-verified source distfiles). Like the existing
un-ignored .srcbuild-cache, they were untracked but not ignored; ignore both so
neither can be swept into an sdist or trip a future working-tree-cleanliness
gate.
@sonarqubecloud

Copy link
Copy Markdown

@MAfarrag
MAfarrag merged commit 99c6f75 into main Aug 12, 2026
49 of 51 checks passed
@MAfarrag
MAfarrag deleted the ci/cache-source-distfiles branch August 12, 2026 22:36
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.

ci(wheels): cache SHA-verified source distfiles + vcpkg assets to survive upstream host outages

1 participant