kingpin-apps/swift-package-compat-check
Run the Swift Package Index build matrix against your Swift package locally, so you don't have to push a tag and wait for SPI's CI queue to find out a platform broke.
Requirements
| Requirement | Version | Why | |-------------|---------|-----| | macOS | 15+ | spcc is a macOS-only tool. | | Xcode | 26.4+ | For the Apple-platform cells (macos-spm, macos-xcodebuild, ios, tvos, watchos, visionos). Multiple Xcodes can be selected per Swift version via --xcode-6.X. | | Container runtime | Docker (any modern release), apple/container 0.12+, or Podman | For linux, android, wasm cells. Apple cells don't need any. Default is docker; select another with --container-runtime container\|podman, or omit to auto-detect (see Container runtime below). | | Swift | 6.2+ | Only needed to build spcc from source. |
Installation
brew install kingpin-apps/tap/spccOr build from source with Swift Package Manager:
git clone https://github.com/Kingpin-Apps/swift-package-compat-check.git
cd swift-package-compat-check
swift build -c release
cp .build/release/spcc ~/.local/bin/spccMake sure ~/.local/bin is on your $PATH (or copy somewhere else that is).
Quick start
# Show the matrix that would run without actually building anything
spcc run --dry-run
# Run the full matrix against the package in the current directory
spcc run
# Run a subset for fast iteration
spcc run -p macos-spm,linux -s 6.3
# Run against a package elsewhere
spcc run --path ~/Projects/swift-nacl
# Run `swift test` per cell instead of `swift build`
spcc run --test -p macos-spm,linux -s 6.3
# Install extra system packages the tests need (only applied with --test).
# Host packages go on the Mac via brew (Apple cells); container packages go
# inside each Linux/Android/Wasm container via apt.
spcc run --test --install-host gnupg --install-container "gnupg,libgcrypt20-dev" \
-p macos-spm,linux -s 6.3
# Run each cell's tests serially for suites that share global state (keyrings,
# ports, temp files). Distinct from --max-parallel, which bounds cell concurrency.
spcc run --test --test-no-parallel -p macos-spm,linux -s 6.3
# Load default flags from a config file (--config wins over $SPCC_CONFIG)
spcc run --config ./.spi-compat.toml
SPCC_CONFIG=~/.config/spcc.toml spcc runWhen a cell fails, its log path is printed below the matrix so you can drill in:
Failed cells (1):
✗ wasm × Swift 6.3 /Users/me/.cache/spi-compat-check/logs/swift-nacl/.../wasm-6.3.logSubcommands
| Subcommand | Purpose | |------------|---------| | spcc run [path] | Run the matrix (default subcommand — bare spcc is equivalent). | | spcc clean [path] | Remove the package's caches (Docker volumes + log/derived-data/cloned-packages dirs). | | spcc clean-all [--remove-images] | Wipe every spi-compat cache globally. --remove-images also drops cached SPI builder images (typically 50+ GB). | | spcc list-caches | du-style report of all caches + Docker volumes + builder images. | | spcc images [--remove] | List or remove cached SPI builder images. |
Run spcc <subcommand> --help for the full flag list.
What it actually runs
Each cell of the matrix reproduces SPI's own Build Command panel verbatim:
| Platform | Command | |----------|---------| | linux | docker run … spi-images:basic-X.Y-latest swift build --triple x86_64-unknown-linux-gnu | | macos-spm | xcrun swift build --arch arm64 | | macos-xcodebuild | xcrun xcodebuild build -scheme <s> -destination platform=macOS,arch=arm64 | | ios / tvos / watchos / visionos | xcrun xcodebuild build -scheme <s> -destination generic/platform=<SDK> | | android | docker run … spi-images:android-X.Y-latest swift build --swift-sdk aarch64-unknown-linux-android28 | | wasm | docker run … spi-images:wasm-X.Y-latest swift build --swift-sdk swift-X.Y-RELEASE_wasm |
Apple cells use whichever Xcode xcode-select points at by default. Linux / Android / Wasm cells use SPI's own publicly-hosted builder images at registry.gitlab.com/swiftpackageindex/spi-images:<platform>-X.Y-latest, so the SDKs and apt packages match SPI exactly.
For full documentation including all flags, caching behaviour, and troubleshooting, see the SwiftPackageCompatCheck DocC catalog.
Container runtime
Linux / Android / Wasm cells dispatch through a host-side container runtime — one of docker, container (apple/container, Apple Silicon, Virtualization.framework), or podman. Select one explicitly, or omit the flag to auto-detect whichever is running (priority: container → docker → podman):
# CLI flag
spcc run --container-runtime container # or docker, or podman
# Or persist in .spi-compat.toml
container_runtime = "container"apple/container support is experimental. It reuses the same SPI builder images and produces identical pass/fail results in spcc's smoke tests, but it hasn't yet been validated against as wide a range of real-world packages as Docker — which is why Docker stays the default.
Things to know when using apple/container:
- Image pulls happen as an explicit pre-step (apple/container has no
--pullonrun); concurrent cells share a single pull. - apple/container caps per-container memory at 1 GB by default, which OOM-kills any non-toy Swift build.
spccraises this to 8 GB per cell so builds get a comparable allotment to Docker Desktop's VM. - Pair
--container-runtime containerwith--timeout(e.g.--timeout 1800) when running long matrices unattended. Without a timeout, a stuck cell sits indefinitely rather than failing fast.
Podman is a Docker-compatible alternative and shares Docker's code path (inline --pull, ps --filter label-based timeout kill, volume/images verbs). It's a good fit for light packages and smoke checks, but not for heavy real-world matrices — see the caveats below. Things to know:
- Memory / concurrency. Podman runs all cells in a single
podman machineVM, andspccruns the matrix cells concurrently, so they share that one VM's RAM (unlike apple/container, which gives each cell its own VM).spccsets no per-cell cap for Podman, so a matrix of N concurrent heavy cells will OOM unless the machine has roughly N × 6 GiB. Size it withpodman machine init/set -m <MiB>(the 2 GiB default OOMs even one real build), or serialize with--max-parallel 1. - No Rosetta in containers → slow amd64. Even on the
applehvprovider withrosetta=true, Podman does not mount Rosetta intopodman run --platform linux/amd64containers, so amd64 Swift compilation runs under qemu — measured ~5–6× slower than apple/container. Heavy packages (e.g. a full Cardano-stack build) time out at 1800s under Podman where apple/container finishes in ~5 min. Always pair Podman with--timeoutso a slow cell fails fast instead of grinding. - Public-registry pulls. If Podman inherits a
credsStore/credHelpersfrom~/.docker/config.json, pulls of the public SPI registry can fail with a credential-helper error — bypass with an emptyREGISTRY_AUTH_FILE(see the DocC Troubleshooting guide).
Caches
spcc keeps a stable per-package cache so repeat runs are fast:
~/.cache/spi-compat-check/
├── derived-data/<pkg>/<platform>-<sv>/ ← xcodebuild incremental state
├── cloned-packages/<pkg>/ ← SourcePackages cache shared across xcodebuild cells
└── logs/<pkg>/<RUN_TS>/<platform>-<sv>.logPlus Docker volumes named spi-compat-build-<pkg>-<sv> (Linux) and spi-compat-build-<pkg>-<platform>-<sv> (Android/Wasm) holding the cross-SDK scratch path.
Logs auto-trim to the latest 5 runs per package. Use spcc list-caches to see total disk usage, spcc clean <pkg> to drop one package's caches, spcc clean-all to nuke everything.
Override the cache root with SPI_COMPAT_CACHE=/custom/path spcc run.
Features
- Live updating matrix under a TTY — cells transition
?→⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏(braille spinner) →✓/✗in place, no scrollback. - Scheme auto-detection via
swift package dump-package— skips system C targets likeClibsodiumthat alphabetically win against the real Swift library product. - Per-Swift-version overrides —
--xcode-6.X,--toolchain-6.X,--linux-image-6.X,--android-image-6.X,--wasm-image-6.X,--wasm-sdk-url-6.X. - Bounded concurrent fan-out —
--max-parallel Nruns cells in parallel within each Swift version. Defaults toactiveProcessorCount / 2. - Test-dependency installs —
--install-host(brew, on the Mac for Apple cells) and--install-container(apt, inside each Linux/Android/Wasm container) pull in system packages a package's tests need — e.g.gpgfor swift-gnupg. Applied only with--test. Host installs run once and persist on your machine; container installs are ephemeral. Both accept a comma-separated list and are validated against shell injection. - Serial test execution —
--test-no-parallelruns each cell's tests serially (swift test --no-parallel/xcodebuild test -parallel-testing-enabled NO) for suites that share global state. Orthogonal to--max-parallel, which bounds how many cells run at once. - Timeout safety net —
--timeout SECONDSkills hung containers so a stuck cell fails fast instead of blocking the run. - Qemu IPC retry — the cross-SDK resolver detects transient "failed parsing the Swift compiler output" errors under qemu emulation and retries the build before falling back. Critical for Android/Wasm cells against large packages on Apple Silicon.
- Multi-arch bundle extraction — when an Android SDK bundle ships multiple triples (the finagolfin/swift-android-sdk case),
spccextracts the specific triple matching SPI's intent rather than building for every architecture in the bundle.
Development
just test # Unit tests
just hello # Smoke-test spcc against the bundled HelloWorld fixture
just hello-full # Full 34-cell matrix against HelloWorld (slow on first run)
just build # Debug build
just release # Release build for current host arch
just bump # Cut a release: `cz bump` then push --follow-tagsThe DocC catalog doubles as the full user guide and the API documentation for the SwiftPackageCompatCheck library target.
License
MIT. See LICENSE.
Package Metadata
Repository: kingpin-apps/swift-package-compat-check
Default branch: main
README: README.md