Artifact 684c0056dc26f45ac9c101b6a66ffba73f2e5be2000e59783827dc84d4ca8cdd:
- File
.github/workflows/release.yml
— part of check-in
[8ebc3e4dd8]
at
2026-09-24 01:02:57
on branch trunk
— soak: record a finding and keep sweeping; CI: cap the runners' zig cache
A failing fuzz target is abandoned and the sweep moves on, so one recurring crash no longer hides the targets after it; the exit lists every finding. Each workflow clears its zig-local cache whole at job start once it passes 8 GB, since Zig never prunes it. (user: dmestas, size: 14791) [annotate] [blame] [check-ins using]
name: Zit Release Candidate on: push: tags: - "version-*" workflow_dispatch: inputs: version: description: "Existing Fossil release version, without version- prefix" required: true type: string permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false jobs: source: runs-on: [self-hosted, Linux, X64, linux-x64-zit] timeout-minutes: 10 outputs: version: ${{ steps.version.outputs.version }} steps: - name: Resolve release version id: version env: REQUESTED_VERSION: ${{ inputs.version }} run: | set -euo pipefail if [ "${GITHUB_REF_TYPE}" = "tag" ]; then case "${GITHUB_REF_NAME}" in version-*) version="${GITHUB_REF_NAME#version-}" ;; *) echo "release workflow requires a version-* tag" >&2; exit 1 ;; esac else version="${REQUESTED_VERSION}" fi if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "invalid release version: ${version}" >&2 exit 1 fi tag="version-${version}" echo "version=${version}" >> "${GITHUB_OUTPUT}" echo "tag=${tag}" >> "${GITHUB_OUTPUT}" - name: Materialize exact Fossil release source env: VERSION: ${{ steps.version.outputs.version }} TAG: ${{ steps.version.outputs.tag }} run: | set -euo pipefail command -v fossil >/dev/null fossil version version="${VERSION}" tag="version-${version}" repo="${RUNNER_TEMP}/zit-${version}.fossil" checkout="${RUNNER_TEMP}/zit-${version}-checkout" rm -rf "${repo}" "${checkout}" fossil clone https://fossil.craftdesign.group/zit "${repo}" mkdir -p "${checkout}" cd "${checkout}" fossil open "${repo}" "${tag}" fossil info current | tee "${RUNNER_TEMP}/fossil-info.txt" grep -F ".version = \"${version}\"" build.zig.zon >/dev/null grep -F "zit ${version} (targets Fossil RFC rev " src/main.zig >/dev/null fossil tarball --name "zit-${version}" "${tag}" \ "${RUNNER_TEMP}/zit-${version}-source.tar.gz" -R "${repo}" cd "${RUNNER_TEMP}" sha256sum "zit-${version}-source.tar.gz" > "zit-${version}-source.tar.gz.sha256" - name: Upload exact release source uses: actions/upload-artifact@v4 with: name: release-source path: | ${{ runner.temp }}/zit-${{ steps.version.outputs.version }}-source.tar.gz ${{ runner.temp }}/zit-${{ steps.version.outputs.version }}-source.tar.gz.sha256 ${{ runner.temp }}/fossil-info.txt if-no-files-found: error retention-days: 30 build: needs: source timeout-minutes: 60 strategy: fail-fast: false matrix: include: - os: macOS arch: ARM64 runner: mac-mini-zit artifact: macos-aarch64 zig_target: aarch64-macos zig_package_target: aarch64-macos zig_shasum: b23d70deaa879b5c2d486ed3316f7eaa53e84acf6fc9cc747de152450d401489 smoke_repetitions: 3 - os: Linux arch: X64 runner: linux-x64-zit artifact: linux-x86_64 zig_target: x86_64-linux zig_package_target: x86_64-linux zig_shasum: 70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00 smoke_repetitions: 10 - os: Linux arch: ARM64 runner: linux-arm64-zit artifact: linux-aarch64 zig_target: aarch64-linux zig_package_target: aarch64-linux zig_shasum: ea4b09bfb22ec6f6c6ceac57ab63efb6b46e17ab08d21f69f3a48b38e1534f17 smoke_repetitions: 10 runs-on: [self-hosted, "${{ matrix.os }}", "${{ matrix.arch }}", "${{ matrix.runner }}"] env: ZIG_VERSION: 0.16.0 ZIG_TARGET: ${{ matrix.zig_target }} ZIG_PACKAGE_TARGET: ${{ matrix.zig_package_target }} ZIG_SHASUM: ${{ matrix.zig_shasum }} VERSION: ${{ needs.source.outputs.version }} ARTIFACT: ${{ matrix.artifact }} SMOKE_REPETITIONS: ${{ matrix.smoke_repetitions }} steps: - name: Download exact release source uses: actions/download-artifact@v4 with: name: release-source path: ${{ runner.temp }}/release-source - name: Verify and extract exact release source run: | set -euo pipefail cd "${RUNNER_TEMP}/release-source" if command -v sha256sum >/dev/null 2>&1; then sha256sum -c "zit-${VERSION}-source.tar.gz.sha256" else shasum -a 256 -c "zit-${VERSION}-source.tar.gz.sha256" fi rm -rf "${RUNNER_TEMP}/zit-${VERSION}" tar -xzf "zit-${VERSION}-source.tar.gz" -C "${RUNNER_TEMP}" - name: Install Zig 0.16.0 run: | set -euo pipefail # Same persistent, digest-verified cache as ci.yml: the runners are # self-hosted, and a warm content-addressed Zig cache outside the # per-run temp turns this build's unit and end-to-end steps from cold # compiles into warm ones. The tarball is refetched whenever the # kept copy fails the pinned digest, never trusted for being there. cache="${HOME}/.cache/zit-ci" # Zig never prunes its compilation cache: every source change leaves # a generation behind, and these runners grew 9-14 GB of it in a week. # Past the cap it is cleared WHOLE, at job start, when nothing else is # building on this single-slot runner. Not pruned by age: Zig does not # touch an entry it reuses, so an old entry may still be live, and a # manifest naming a deleted output costs more than one cold compile. cap_kb=$((8 * 1024 * 1024)) if [ -d "${cache}/zig-local" ]; then used_kb=$(du -sk "${cache}/zig-local" | awk '{print $1}') if [ "${used_kb}" -gt "${cap_kb}" ]; then echo "zig cache is ${used_kb} KB, over the ${cap_kb} KB cap; clearing it" rm -rf "${cache}/zig-local" fi fi mkdir -p "${cache}/zig-local" "${cache}/zig-global" "${cache}/toolchains" echo "ZIG_LOCAL_CACHE_DIR=${cache}/zig-local" >> "$GITHUB_ENV" echo "ZIG_GLOBAL_CACHE_DIR=${cache}/zig-global" >> "$GITHUB_ENV" cd "${RUNNER_TEMP}" tarball="zig-${ZIG_PACKAGE_TARGET}-${ZIG_VERSION}.tar.xz" kept="${cache}/toolchains/${tarball}" digest() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' else shasum -a 256 "$1" | awk '{print $1}' fi } if [ ! -f "${kept}" ] || [ "$(digest "${kept}")" != "${ZIG_SHASUM}" ]; then rm -f "${kept}" curl -fsSL -o "${kept}.part" "https://ziglang.org/download/${ZIG_VERSION}/${tarball}" mv "${kept}.part" "${kept}" fi actual="$(digest "${kept}")" if [ "${actual}" != "${ZIG_SHASUM}" ]; then echo "zig tarball digest mismatch: expected ${ZIG_SHASUM}, got ${actual}" >&2 rm -f "${kept}" exit 1 fi rm -rf "zig-${ZIG_PACKAGE_TARGET}-${ZIG_VERSION}" tar -xf "${kept}" echo "${RUNNER_TEMP}/zig-${ZIG_PACKAGE_TARGET}-${ZIG_VERSION}" >> "${GITHUB_PATH}" - name: Format check working-directory: ${{ runner.temp }}/zit-${{ needs.source.outputs.version }} run: zig fmt --check build.zig src - name: Build portable ReleaseFast binary working-directory: ${{ runner.temp }}/zit-${{ needs.source.outputs.version }} run: zig build --release=fast -Dtarget="${ZIG_TARGET}" - name: Unit tests working-directory: ${{ runner.temp }}/zit-${{ needs.source.outputs.version }} run: zig build test - name: End-to-end tests working-directory: ${{ runner.temp }}/zit-${{ needs.source.outputs.version }} run: zig build e2e - name: Deterministic simulation working-directory: ${{ runner.temp }}/zit-${{ needs.source.outputs.version }} run: zig build dst -- --seed 1 --steps 1000 - name: Seeded fuzz sweep working-directory: ${{ runner.temp }}/zit-${{ needs.source.outputs.version }} run: | set -euo pipefail targets="$(zig build fuzz -- --list)" [ -n "${targets}" ] || { echo "no fuzz targets enumerated" >&2; exit 1; } for target in ${targets}; do zig build fuzz -- "${target}" --seed 1 --rounds 100 done - name: Fossil interoperability smoke working-directory: ${{ runner.temp }}/zit-${{ needs.source.outputs.version }} run: | set -euo pipefail for n in $(seq 1 "${SMOKE_REPETITIONS}"); do echo "smoke ${n}/${SMOKE_REPETITIONS}" zig build smoke done - name: Rebuild and package qualified ReleaseFast binary working-directory: ${{ runner.temp }}/zit-${{ needs.source.outputs.version }} run: | set -euo pipefail zig build --release=fast -Dtarget="${ZIG_TARGET}" ./zig-out/bin/zit version dist="${RUNNER_TEMP}/dist-${ARTIFACT}" stage="${dist}/zit-${VERSION}" archive="zit-${VERSION}-${ARTIFACT}.tar.gz" rm -rf "${dist}" mkdir -p "${stage}" install -m 0755 zig-out/bin/zit "${stage}/zit" cp README.md LICENSE "${stage}/" cp "docs/RELEASE-NOTES-${VERSION}.md" "${stage}/RELEASE-NOTES.md" tar -C "${dist}" -czf "${dist}/${archive}" "zit-${VERSION}" if command -v sha256sum >/dev/null 2>&1; then binary_sha="$(sha256sum "${stage}/zit" | awk '{print $1}')" archive_sha="$(sha256sum "${dist}/${archive}" | awk '{print $1}')" else binary_sha="$(shasum -a 256 "${stage}/zit" | awk '{print $1}')" archive_sha="$(shasum -a 256 "${dist}/${archive}" | awk '{print $1}')" fi printf '%s %s\n' "${archive_sha}" "${archive}" > "${dist}/${archive}.sha256" python3 - "${dist}/${archive}.metadata.json" "${archive}" "${ARTIFACT}" \ "${ZIG_TARGET}" "${archive_sha}" "${binary_sha}" <<'PY' import json, pathlib, sys output, archive, artifact, target, archive_sha, binary_sha = sys.argv[1:] data = { "artifact": artifact, "binary_sha256": binary_sha, "bytes": pathlib.Path(output).with_name(archive).stat().st_size, "name": archive, "qualification": "passed", "sha256": archive_sha, "target": target, } pathlib.Path(output).write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") PY - name: Upload qualified binary package uses: actions/upload-artifact@v4 with: name: release-${{ matrix.artifact }} path: | ${{ runner.temp }}/dist-${{ matrix.artifact }}/zit-${{ needs.source.outputs.version }}-${{ matrix.artifact }}.tar.gz ${{ runner.temp }}/dist-${{ matrix.artifact }}/zit-${{ needs.source.outputs.version }}-${{ matrix.artifact }}.tar.gz.sha256 ${{ runner.temp }}/dist-${{ matrix.artifact }}/zit-${{ needs.source.outputs.version }}-${{ matrix.artifact }}.tar.gz.metadata.json if-no-files-found: error retention-days: 30 qualify-linux: needs: [source, build] runs-on: ubuntu-24.04 timeout-minutes: 10 strategy: fail-fast: false matrix: include: - artifact: linux-x86_64 emulator: x86_64 - artifact: linux-aarch64 emulator: aarch64 env: VERSION: ${{ needs.source.outputs.version }} ARTIFACT: ${{ matrix.artifact }} steps: - name: Download packaged binary uses: actions/download-artifact@v4 with: name: release-${{ matrix.artifact }} path: dist - name: Install deterministic CPU emulators run: | sudo apt-get update sudo apt-get install -y qemu-user - name: Verify package digest and low-feature execution env: EMULATOR: ${{ matrix.emulator }} run: | set -euo pipefail cd dist archive="zit-${VERSION}-${ARTIFACT}.tar.gz" sha256sum -c "${archive}.sha256" mkdir package tar -xzf "${archive}" -C package binary="${PWD}/package/zit-${VERSION}/zit" if [ "${EMULATOR}" = "x86_64" ]; then "${binary}" version qemu-x86_64 -cpu Nehalem "${binary}" version else qemu-aarch64 -cpu cortex-a53 "${binary}" version fi qualify-macos: needs: [source, build] runs-on: [self-hosted, macOS, ARM64, mac-mini-zit] timeout-minutes: 5 env: VERSION: ${{ needs.source.outputs.version }} steps: - name: Download packaged binary uses: actions/download-artifact@v4 with: name: release-macos-aarch64 path: dist - name: Verify packaged macOS binary run: | set -euo pipefail cd dist archive="zit-${VERSION}-macos-aarch64.tar.gz" shasum -a 256 -c "${archive}.sha256" mkdir package tar -xzf "${archive}" -C package "${PWD}/package/zit-${VERSION}/zit" version bundle: needs: [source, qualify-linux, qualify-macos] runs-on: ubuntu-24.04 timeout-minutes: 5 steps: - name: Download release source uses: actions/download-artifact@v4 with: name: release-source path: candidate - name: Download qualified binary packages uses: actions/download-artifact@v4 with: pattern: release-*-* path: candidate merge-multiple: true - name: Assemble candidate checksums run: | set -euo pipefail cd candidate cat ./*.tar.gz.sha256 | sort -k2 > SHA256SUMS sha256sum -c SHA256SUMS - name: Upload publication candidate uses: actions/upload-artifact@v4 with: name: zit-${{ needs.source.outputs.version }}-publication-candidate path: candidate if-no-files-found: error retention-days: 30