Describe the bug
The Bootstrap workflow fails on the 3.18 release branch (e.g. mergify backport branches) with:
Warning:
This package indirectly depends on multiple versions of the same package. This is very likely to cause a compile failure.
package hooks-exe (hooks-exe-3.18-22KHzF4sUT38fGhUvzyePu) requires Cabal-syntax-3.18.1.0-8zQAqSUiwaZ17MHcGFOcie
package cabal-install-solver (cabal-install-solver-3.18.1.0-5LuTmwczm6yHss5JDfGG3K) requires Cabal-syntax-3.18.1.0-8zQAqSUiwaZ17MHcGFOcie
package cabal-install (cabal-install-3.18.1.0) requires Cabal-syntax-3.18.1.0-8zQAqSUiwaZ17MHcGFOcie
package Cabal (Cabal-3.18.1.0-5o8LiszPwViDtqOTbtNLRM) requires Cabal-syntax-3.18.1.0-8zQAqSUiwaZ17MHcGFOcie
package hackage-security (hackage-security-0.6.3.3-HnzyDDEGWK8KBPRiLAacC2) requires Cabal-syntax-3.19.0.0-GjmW8g1FynRA1psf3P3WKB
followed by a compile failure like Couldn't match type 'Distribution.Types.PackageId.PackageIdentifier' ....
The root cause: bootstrap.py only initialises the bootstrap package database if it does not exist yet:
if not PKG_DB.exists():
...
subprocess_run([ghc.ghc_pkg_path, 'init', PKG_DB])
When actions/cache restores a _build directory produced on a different branch (where the local packages have different versions), the stale registrations survive: local Cabal-syntax-3.18.1.0 gets registered alongside the cached Cabal-syntax-3.19.0.0. Setup.hs then resolves the newest version for every package whose bounds are loose enough — currently only hackage-security — so it is compiled against Cabal-syntax-3.19.0.0 while everything else is compiled against Cabal-syntax-3.18.1.0.
The caches collide because the cache key in .github/workflows/bootstrap.yml is
key: bootstrap-${{ ... }}-${{ matrix.ghc }}-${{ hashFiles(format('bootstrap/linux-{0}.json', matrix.ghc)) }}-${{ github.sha }}
restore-keys: bootstrap-${{ ... }}-${{ matrix.ghc }}-${{ hashFiles(...) }}-
and the bootstrap/linux-*.json files are byte-identical between master (local packages = 3.19.0.0) and 3.18 (local packages = 3.18.1.0), since local packages are only referenced by name in the plans.
To Reproduce
$ python3 bootstrap/bootstrap.py -d bootstrap/linux-9.6.7.json fetch
$ python3 bootstrap/bootstrap.py --bootstrap-sources bootstrap-sources.tar.gz
- Run the Bootstrap workflow on
master (local Cabal/Cabal-syntax/cabal-install-solver are 3.19.0.0) → _build incl. packages.conf is cached.
- Run the Bootstrap workflow on the
3.18 branch → restore-keys matches the master cache (identical JSON hash) → stale _build/packages.conf containing Cabal-syntax-3.19.0.0 is restored.
bootstrap.py skips ghc-pkg init and registers local Cabal-syntax-3.18.1.0 next to it.
hackage-security-0.6.3.3 (the only package with bounds loose enough to admit both) is configured against 3.19.0.0, everything else against 3.18.1.0 → the warning above, then a PackageIdentifier type mismatch.
- Example failing run: Bootstrap / ubuntu-latest ghc-9.6.7 (job 99738568748)
Expected behavior
Bootstrapping should produce a consistent package database regardless of the previous content of _build — a stale or foreign package DB must never result in multiple versions of the same package being mixed.
System information
- Operating system: GitHub Actions runner, ubuntu-latest
cabal: built from source — 3.18.1.0 (release branch 3.18 / backport branch mergify/bp/3.18/pr-12276), 3.19.0.0 on master
ghc: 9.6.7, 9.8.4, 9.10.3, 9.12.4 (all matrix jobs are affected)
Additional context
Proposed fix — recreate the bootstrap package database from scratch on every run, making bootstrap.py idempotent w.r.t. a dirty/polluted cache:
diff --git a/bootstrap/bootstrap.py b/bootstrap/bootstrap.py
index 5638b1364..19e88c291 100755
--- a/bootstrap/bootstrap.py
+++ b/bootstrap/bootstrap.py
@@ -275,10 +275,16 @@ UnitId = NewType('UnitId', str)
PlanUnit = NewType('UnitId', str)
def bootstrap(info: BootstrapInfo, ghc: Compiler) -> None:
- if not PKG_DB.exists():
- print(f'Creating package database {PKG_DB}')
- PKG_DB.parent.mkdir(parents=True, exist_ok=True)
- subprocess_run([ghc.ghc_pkg_path, 'init', PKG_DB])
+ # Always recreate the package database from scratch: a stale database
+ # (e.g. restored from a CI cache built from a different branch) can
+ # contain other versions of the local packages, which would lead to
+ # mixing multiple versions of the same package and compile failures.
+ if PKG_DB.exists():
+ print(f'Deleting stale package database {PKG_DB}')
+ shutil.rmtree(PKG_DB)
+ print(f'Creating package database {PKG_DB}')
+ PKG_DB.parent.mkdir(parents=True, exist_ok=True)
+ subprocess_run([ghc.ghc_pkg_path, 'init', PKG_DB])
for dep in info.builtin:
check_builtin(dep, ghc)
Describe the bug
The
Bootstrapworkflow fails on the3.18release branch (e.g. mergify backport branches) with:followed by a compile failure like
Couldn't match type 'Distribution.Types.PackageId.PackageIdentifier' ....The root cause:
bootstrap.pyonly initialises the bootstrap package database if it does not exist yet:When
actions/cacherestores a_builddirectory produced on a different branch (where the local packages have different versions), the stale registrations survive: localCabal-syntax-3.18.1.0gets registered alongside the cachedCabal-syntax-3.19.0.0.Setup.hsthen resolves the newest version for every package whose bounds are loose enough — currently onlyhackage-security— so it is compiled againstCabal-syntax-3.19.0.0while everything else is compiled againstCabal-syntax-3.18.1.0.The caches collide because the cache key in
.github/workflows/bootstrap.ymlisand the
bootstrap/linux-*.jsonfiles are byte-identical betweenmaster(local packages = 3.19.0.0) and3.18(local packages = 3.18.1.0), since local packages are only referenced by name in the plans.To Reproduce
master(localCabal/Cabal-syntax/cabal-install-solverare 3.19.0.0) →_buildincl.packages.confis cached.3.18branch →restore-keysmatches themastercache (identical JSON hash) → stale_build/packages.confcontainingCabal-syntax-3.19.0.0is restored.bootstrap.pyskipsghc-pkg initand registers localCabal-syntax-3.18.1.0next to it.hackage-security-0.6.3.3(the only package with bounds loose enough to admit both) is configured against 3.19.0.0, everything else against 3.18.1.0 → the warning above, then aPackageIdentifiertype mismatch.Expected behavior
Bootstrapping should produce a consistent package database regardless of the previous content of
_build— a stale or foreign package DB must never result in multiple versions of the same package being mixed.System information
cabal: built from source — 3.18.1.0 (release branch3.18/ backport branchmergify/bp/3.18/pr-12276), 3.19.0.0 onmasterghc: 9.6.7, 9.8.4, 9.10.3, 9.12.4 (all matrix jobs are affected)Additional context
Proposed fix — recreate the bootstrap package database from scratch on every run, making
bootstrap.pyidempotent w.r.t. a dirty/polluted cache: