From d77da7cc34505bc7bb1f1306f277649a51774ac7 Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Sat, 10 Oct 2026 14:19:44 +0700 Subject: [PATCH] refactor: migrate to gitea actions --- .dockerignore | 5 ++ .gitea/workflows/build.yml | 80 +++++++++++++++++++++++ Dockerfile | 34 ++++++++-- README.md | 129 ++++++++++++++++++++++++++++++++++++- ci/VALIDATION.md | 89 +++++++++++++++++++++++++ ci/build-rust.sh | 14 ++++ ci/buildkitd.toml | 6 ++ ci/cache.compose.yml | 40 ++++++++++++ ci/matrix.py | 40 ++++++++++++ ci/setup-builder.sh | 11 ++++ ci/verify-cache.sh | 39 +++++++++++ 11 files changed, 480 insertions(+), 7 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/build.yml create mode 100644 ci/VALIDATION.md create mode 100644 ci/build-rust.sh create mode 100644 ci/buildkitd.toml create mode 100644 ci/cache.compose.yml create mode 100644 ci/matrix.py create mode 100644 ci/setup-builder.sh create mode 100644 ci/verify-cache.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9fa55d5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.gitea +Jenkinsfile +README.md +**/__pycache__ diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..cb6003e --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,80 @@ +name: Build OpenWrt Rust SDK images + +on: + workflow_dispatch: + inputs: + targets: + description: 'Comma-separated targets, or all' + default: 'x86/64,mediatek/filogic,octeon/generic' + required: true + type: string + publish: + description: 'Publish the original Docker Hub tags (requires approval)' + default: false + type: boolean + +permissions: + contents: read + +jobs: + matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.resolve.outputs.matrix }} + steps: + - uses: builtin:checkout + - name: Resolve targets and OpenWrt versions + id: resolve + env: + TARGETS: ${{ inputs.targets }} + run: python3 ci/matrix.py + + build: + needs: matrix + runs-on: ubuntu-latest + timeout-minutes: 360 + strategy: + fail-fast: true + max-parallel: 1 + matrix: ${{ fromJSON(needs.matrix.outputs.matrix) }} + env: + SAFE_TARGET: ${{ matrix.target }} + VERSION: ${{ matrix.version }} + steps: + - uses: builtin:checkout + - name: Prepare persistent cache and bounded builder + run: sh ci/setup-builder.sh + - name: Build SDK image + run: | + docker buildx build --builder openwrt-sdk-rust --load --progress plain \ + --network host --allow network.host \ + --no-cache-filter configured,sdk \ + --build-arg SAFE_TARGET="$SAFE_TARGET" \ + --build-arg VERSION="$VERSION" \ + --build-arg JOBS=8 \ + --build-arg SCCACHE_REDIS_ENDPOINT=redis://127.0.0.1:6379 \ + --tag "lochnair/openwrt-sdk-rust:${SAFE_TARGET}-${VERSION}" . + - name: Verify SDK image + run: | + docker run --rm "lochnair/openwrt-sdk-rust:${SAFE_TARGET}-${VERSION}" sh -ec ' + sccache --version + grep -qx "CONFIG_RUST_SCCACHE=y" .config + rustc=$(find staging_dir -type f -path "*/host/bin/rustc") + test -x "$rustc" + "$rustc" -vV + "$(dirname "$rustc")/cargo" --version + ' + sh ci/verify-cache.sh "lochnair/openwrt-sdk-rust:${SAFE_TARGET}-${VERSION}" + - name: Publish Docker Hub tag + if: inputs.publish == true + env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + shell: bash + run: | + set -euo pipefail + DOCKER_CONFIG=$(mktemp -d) + export DOCKER_CONFIG + trap 'rm -rf "$DOCKER_CONFIG"' EXIT + printf '%s' "$DOCKERHUB_TOKEN" | docker login --username "$DOCKERHUB_USERNAME" --password-stdin + docker push "lochnair/openwrt-sdk-rust:${SAFE_TARGET}-${VERSION}" diff --git a/Dockerfile b/Dockerfile index ee2cfa2..0d2984f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,35 @@ ARG SAFE_TARGET ARG VERSION -FROM ghcr.io/openwrt/sdk:${SAFE_TARGET}-${VERSION} +FROM ghcr.io/openwrt/sdk:${SAFE_TARGET}-${VERSION} AS tooling +# The SDK runs on amd64 even when compiling for another OpenWrt target. +USER root +RUN curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.18.0/sccache-v0.18.0-x86_64-unknown-linux-musl.tar.gz -o /tmp/sccache.tar.gz && \ + echo '45f1447fbe231e3037bde351ef70677dd212216c8d62ae7ca409fecc4d6acc89 /tmp/sccache.tar.gz' | sha256sum -c - && \ + tar -xzf /tmp/sccache.tar.gz -C /tmp && \ + install -m 0755 /tmp/sccache-v0.18.0-x86_64-unknown-linux-musl/sccache /usr/local/bin/sccache && \ + rm -rf /tmp/sccache.tar.gz /tmp/sccache-v0.18.0-x86_64-unknown-linux-musl +USER buildbot + +FROM tooling AS configured +ARG SAFE_TARGET +ARG VERSION +ENV SCCACHE_REDIS_KEY_PREFIX=openwrt-rust:${SAFE_TARGET}-${VERSION}: \ + CARGO_INCREMENTAL=0 +COPY --chmod=0755 ci/build-rust.sh /usr/local/bin/build-rust RUN ./scripts/feeds update packages && \ ./scripts/feeds install rust && \ + printf '\nCONFIG_RUST_SCCACHE=y\n' >> .config && \ make defconfig && \ - sed -i 's/--set=llvm.download-ci-llvm=true/--set=llvm.download-ci-llvm=false/g' feeds/packages/lang/rust/Makefile; \ - { make package/rust/host/compile -j$(nproc) || \ - make package/rust/host/compile -j1 V=sc; } && \ - rm -rf build_dir/host/rustc-*/rustc-*/build \ - dl/rustc-*.tar.* + grep -qx 'CONFIG_RUST_SCCACHE=y' .config && \ + sed -i 's/--set=llvm.download-ci-llvm=true/--set=llvm.download-ci-llvm=false/g' feeds/packages/lang/rust/Makefile && \ + sed -i '/--set=llvm.download-ci-llvm=false/a\ --set=build.ccache=sccache \\' feeds/packages/lang/rust/Makefile && \ + sed -i '/^define Host\/Compile/i RUST_BUILD_JOBS ?= 8' feeds/packages/lang/rust/Makefile && \ + sed -i 's/--build-dir /--jobs=$(RUST_BUILD_JOBS) --build-dir /' feeds/packages/lang/rust/Makefile + +FROM configured AS sdk +ARG JOBS=8 +# A non-secret endpoint on the runner; do not persist it in the published image. +ARG SCCACHE_REDIS_ENDPOINT +RUN build-rust diff --git a/README.md b/README.md index db051c1..1037877 100644 --- a/README.md +++ b/README.md @@ -1 +1,128 @@ -# openwrt-sdk-rust \ No newline at end of file +# openwrt-sdk-rust + +Builds the OpenWrt SDK with its Rust host toolchain installed. Gitea Actions +resolves `stable_version` and `oldstable_version` from OpenWrt's `.versions.json`. +The default targets remain `x86/64,mediatek/filogic,octeon/generic`; dispatch with +`targets=all` to use upstream `dump-target-info.pl`, or supply a comma-separated +list. Images still use `ghcr.io/openwrt/sdk:-` and +publish to `lochnair/openwrt-sdk-rust:-` on Docker Hub. +No `latest` tag or additional registry is introduced. + +## Runner setup and cutover + +The workflow uses `ubuntu-latest` on the existing TheBeast runner, currently +the only runner. Its job image and access to the persistent Docker daemon were +verified; no additional runner, label, capacity change, or restart is required. +`builtin:checkout` uses the existing Runner 5 checkout support. + +Enable repository Actions and set `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` +secrets with push permission to `lochnair/openwrt-sdk-rust`. Jenkins' credential +ID is not reusable in Gitea. The workflow is manually dispatched, matching the +parameterized Jenkins job; there are no PR, push, or scheduled publication +triggers. `publish=false` is the safe validation default. Dispatch +`publish=true` only after publication is approved. Login uses a temporary job +directory, and each successfully built and checked image is pushed under its +original tag. Before any push, `ci/verify-cache.sh` uses the installed SDK rustc +in two disposable containers and requires a Redis miss followed by a hit. + +Run a non-publishing dispatch for the default six combinations, then an +approved publishing dispatch. Check the pulled image's Rust/Cargo installation +and downstream package build before disabling the old Jenkins job and deleting +`Jenkinsfile`. Do not retire Jenkins before this acceptance run. No Jenkins +plugins or credentials are used by the new workflow. + +## Persistent compilation cache + +`ci/setup-builder.sh` provisions one Redis service and one reusable BuildKit +builder on that daemon. Redis uses the named volume `openwrt-rust-sccache`, AOF +persistence (`appendfsync everysec`), a 6 GiB LRU cache, an 8 GiB memory limit, +and one CPU. Jobs and build containers can disappear without losing it. A +machine crash can lose up to the most recent second of cache writes; eviction +or lost entries cause recompilation. Do not run `docker compose down -v` or +prune this volume. BuildKit's layer cache is separate and may be garbage +collected without clearing sccache. + +The cache joins the existing `gitea-runner-jobs` network and exposes port 6379 +only on loopback. BuildKit uses host networking to reach it; package containers +use its Docker DNS name. This is a trusted-runner cache: do not attach untrusted +PR containers or public networks to it. No password or endpoint is baked into +published SDK images. Consumers outside this runner can use their own Redis +endpoint. Each target/OpenWrt version has its own key prefix; sccache also +hashes Rust compiler version and shared libraries, compilation arguments +(including target and flags), source and dependency contents. Different Rust +versions/toolchains therefore miss rather than reuse incompatible objects. + +The shared BuildKit worker executes one build step at a time, even across +simultaneous dispatches. It is limited to eight CPUs and 20 GiB RAM with no +swap. Make and Rust bootstrap both receive eight jobs; the original `-j1 V=sc` +diagnostic retry also reduces bootstrap/Ninja to one job. Rust +build-directory/archive cleanup remains. BuildKit +layer storage has a 50 GiB GC budget. Matrix jobs also run one at a time and +stop scheduling on a failure, as Jenkins' sequential stage loop did. + +OpenWrt integration was inspected in the SDK's pinned packages feed: + +- `rust-values.mk` **unexports `RUSTC_WRAPPER`**. Setting it in the workflow + alone does not enable caching. `CONFIG_RUST_SCCACHE=y` makes OpenWrt inject + `CARGO_INCREMENTAL=0`, `RUSTC_WRAPPER=sccache`, and `SCCACHE_DIR` into host + and package Cargo invocations. The Dockerfile enables and checks that option. +- The base SDK has no sccache binary. We install the pinned 0.18.0 amd64-musl + release and verify its SHA-256 before using it. +- Rust bootstrap wraps rustc itself. We also set `build.ccache=sccache` in + its configure arguments so stage-zero compilation and LLVM use sccache, + retaining `llvm.download-ci-llvm=false`. The inherited wrapper is forwarded + by bootstrap through `RUSTC_WRAPPER_REAL` where applicable. +- sccache is a compiler cache, not a Cargo target-directory cache. Linked + binaries, proc macros, some bootstrap work and incremental compilation are + not cacheable. SDK bootstrap and downstream dependencies can share storage + without necessarily sharing every compiled object. Every actual SDK build + prints statistics on success or failure; configured/compile layers are + rebuilt so stale Docker layer hits do not hide those statistics. + +Sources: [OpenWrt Rust configuration](https://github.com/openwrt/packages/blob/openwrt-24.10/lang/rust/rust-values.mk), +[Rust bootstrap](https://github.com/rust-lang/rust/blob/1.90.0/src/bootstrap/src/core/build_steps/compile.rs), +[sccache Redis](https://github.com/mozilla/sccache/blob/v0.18.0/docs/Redis.md), +[Rust cache limitations](https://github.com/mozilla/sccache/blob/v0.18.0/docs/Rust.md). + +## Downstream OpenWrt packages + +Use the same persistent daemon and target/version image. For example, after +installing the `sqm-autorate-rust` feed/package in the SDK: + +```sh +docker run --rm --network gitea-runner-jobs --cpus 8 \ + --memory 20g --memory-swap 20g \ + -e SCCACHE_REDIS_ENDPOINT=redis://openwrt-rust-cache:6379 \ + lochnair/openwrt-sdk-rust:x86-64-24.10.8 sh -ec ' + # Install/update the downstream feed here before defconfig. + make defconfig + grep -qx "CONFIG_RUST_SCCACHE=y" .config + sccache --start-server + trap "sccache --show-stats; sccache --stop-server" EXIT + sccache --zero-stats + make package/sqm-autorate-rust/compile -j8 V=sc + ' +``` + +Keep the image's cache prefix and enable `CONFIG_RUST_SCCACHE` after any +downstream configuration replacement. Cache statistics belong to the current +container's sccache server; `--zero-stats` does not erase cached objects. +`sqm-autorate-rust` currently uses cross-rs to publish static binaries, not an +OpenWrt SDK package workflow; that unrelated pipeline is unchanged. The package +Makefile in `openwrt-feeds` already uses OpenWrt's `rust-package.mk`, so an SDK +package job can use this cache without patching the package. + +For a local SDK build on the prepared runner: + +```sh +sh ci/setup-builder.sh +docker buildx build --builder openwrt-sdk-rust --load --progress plain \ + --network host --allow network.host --no-cache-filter configured,sdk \ + --build-arg SAFE_TARGET=x86-64 --build-arg VERSION=24.10.8 \ + --build-arg JOBS=8 \ + --build-arg SCCACHE_REDIS_ENDPOINT=redis://127.0.0.1:6379 \ + -t lochnair/openwrt-sdk-rust:x86-64-24.10.8 . +``` + +See [validation results](ci/VALIDATION.md) for the checks performed and the +remaining cutover acceptance. diff --git a/ci/VALIDATION.md b/ci/VALIDATION.md new file mode 100644 index 0000000..9e5b062 --- /dev/null +++ b/ci/VALIDATION.md @@ -0,0 +1,89 @@ +# Migration validation — 2026-10-10 + +Inspected the original Jenkinsfile, Dockerfile, downstream `sqm-autorate-rust` +workflows and package Makefile, Terraform runner definition, and live TheBeast +runner configuration before changing the repository. + +## Passed + +- YAML parsing; TOML parsing; `sh -n`; ShellCheck; `git diff --check`. +- actionlint 1.7.12, excluding only its unsupported `builtin:checkout` action + syntax. The workflow uses the existing `ubuntu-latest` label. Gitea's documentation and the + existing downstream workflow confirm built-in checkout support. +- Default matrix resolves six entries for stable 25.12.5 and oldstable 24.10.8. + `targets=all` ran upstream target discovery inside the actual job image and + resolved 194 entries. Empty/invalid targets fail. Every default base-image + tag was read successfully from GHCR. +- Docker Compose configuration accepted; Redis healthy with its named volume, + AOF enabled and successful AOF writes. Service and bounded builder were + provisioned on TheBeast without changing the runner configuration. +- Builder inspected: eight-CPU quota, 20 GiB memory/swap limit, one BuildKit + worker step, 50 GiB layer GC budget. A second disposable job container with + fresh Docker client configuration reused the existing daemon and builder. +- Dockerfile build checks completed. Two expected advisory warnings: mandatory + base-image arguments have no defaults, and Docker's secret-name heuristic + mistakes the public `SCCACHE_REDIS_KEY_PREFIX` for a secret. +- `configured` Dockerfile stage built for x86/64 on both OpenWrt releases. + Pinned feeds select Rust 1.94.0 on 25.12.5 and 1.90.0 on 24.10.8. Both retain + `CONFIG_RUST_SCCACHE=y` through defconfig and contain the explicit bootstrap + `build.ccache=sccache` setting with LLVM CI downloads disabled. +- Actual OpenWrt make-variable expansion injects sccache into host and package + compilation, despite unexporting an inherited wrapper. +- Real Cargo library compilation in Docker BuildKit: one Redis miss. A fresh + runtime container using the Docker-network endpoint: one hit, zero misses. +- Recreated the newly provisioned Redis container using the same named volume; + a fresh compiler container still recorded one hit, zero misses. No existing + infrastructure containers were restarted. +- Same source/cache namespace with GNU versus musl target: first musl build + missed; repetition hit. Rust 1.90.0 versus 1.91.0: first 1.91 build missed; + repetition hit. All probes reported zero cache read/write errors. +- A disposable OpenWrt package used the real `Build/Compile/Cargo` helper, + SDK GCC/musl linker, OpenWrt LTO/optimization flags and a local Rust library + dependency. It built successfully in two separate containers: one dependency + miss, then one hit. The linked executable correctly remained non-cacheable. + This used a prebuilt Rust 1.90 toolchain for the probe, not a freshly compiled + SDK toolchain; it is not a full SDK/source-bootstrap acceptance result. +- Retained `ci/verify-cache.sh` passed its separate-container miss/hit checks + against that prebuilt validation toolchain. Its original hardcoded SDK rustc + path was later corrected after the live mediatek/filogic build exposed the + target-specific staging layout; see the follow-up below. +- Publication configuration checked: original Docker Hub namespace/tag format, + verification before push, explicit publish input, temporary credential + directory, no automatic publication trigger. No registry login or push was + attempted. + +## Remaining acceptance + +1. Enable repository Actions and configure the two Docker Hub secrets. No + runner configuration changes are required. +2. Complete a non-publishing Gitea dispatch for the full default matrix. + The mediatek/filogic 24.10.8 job completed Rust source bootstrap and image + export; its corrected verification passed on the built image. Acceptance + for the other combinations remains pending. +3. With explicit publication approval, run the publishing dispatch, pull the + resulting tags and build the downstream OpenWrt package with the new image. + Actual Docker Hub authorization and publication remain unverified. +4. After acceptance, disable the old Jenkins job and remove `Jenkinsfile`. + It is deliberately retained until the replacement passes these checks. + +Remote test logs and disposable probe sources are under +`/tmp/openwrt-sdk-rust-migration` in TheBeast's `gitea-runner` instance. The +Redis volume/service and BuildKit builder remain available for the replacement +pipeline; no cache volume or existing infrastructure was deleted. + +## Follow-up: mediatek/filogic 24.10.8 verification + +The supplied job 1602 log shows full SDK compilation and image export completed +successfully. Verification failed with exit 127 because it assumed +`staging_dir/host/bin/rustc`. Inspection of that exact built image reproduced +the failure and located Rust/Cargo in +`staging_dir/target-aarch64_cortex-a53_musl/host/bin` instead. Both the workflow +and retained cache probe now resolve the unique executable compiler from the +SDK staging tree, rather than assuming a shared host directory. + +Reran the corrected verification on that image: Rust 1.90.0 and Cargo 1.90.0 +execute successfully. The retained cache probe reports one Redis miss in its +first container and one hit in a fresh second container, with zero cache +read/write errors. ShellCheck, actionlint (excluding only Gitea's built-in +checkout syntax), and `git diff --check` pass. No rebuild or publication was +performed for this fix. diff --git a/ci/build-rust.sh b/ci/build-rust.sh new file mode 100644 index 0000000..4bd0842 --- /dev/null +++ b/ci/build-rust.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -eu + +: "${SCCACHE_REDIS_ENDPOINT:?Set the persistent sccache Redis endpoint}" +: "${JOBS:=8}" +case "$JOBS" in ''|*[!0-9]*|0) echo 'JOBS must be a positive integer' >&2; exit 1 ;; esac + +sccache --start-server +trap 'sccache --show-stats; sccache --stop-server' EXIT +sccache --zero-stats +# Bootstrap/Ninja does not inherit Make's job count. Bound both explicitly. +make package/rust/host/compile -j"$JOBS" RUST_BUILD_JOBS="$JOBS" || \ + make package/rust/host/compile -j1 RUST_BUILD_JOBS=1 V=sc +rm -rf build_dir/host/rustc-*/rustc-*/build dl/rustc-*.tar.* diff --git a/ci/buildkitd.toml b/ci/buildkitd.toml new file mode 100644 index 0000000..fddf579 --- /dev/null +++ b/ci/buildkitd.toml @@ -0,0 +1,6 @@ +# Shared by all SDK jobs on this daemon, including simultaneous dispatches. +[worker.oci] + max-parallelism = 1 + gc = true + reservedSpace = "10GB" + maxUsedSpace = "50GB" diff --git a/ci/cache.compose.yml b/ci/cache.compose.yml new file mode 100644 index 0000000..21eb7a6 --- /dev/null +++ b/ci/cache.compose.yml @@ -0,0 +1,40 @@ +services: + cache: + image: redis:7.4.6-alpine + container_name: openwrt-rust-cache + restart: unless-stopped + command: + - redis-server + - --appendonly + - 'yes' + - --appendfsync + - everysec + - --maxmemory + - 6gb + - --maxmemory-policy + - allkeys-lru + - --save + - '' + mem_limit: 8g + memswap_limit: 8g + cpus: 1 + ports: + - '127.0.0.1:6379:6379' + volumes: + - cache:/data + networks: + - jobs + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 5s + timeout: 3s + retries: 12 + +volumes: + cache: + name: openwrt-rust-sccache + +networks: + jobs: + external: true + name: gitea-runner-jobs diff --git a/ci/matrix.py b/ci/matrix.py new file mode 100644 index 0000000..3754704 --- /dev/null +++ b/ci/matrix.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Resolve the same stable/oldstable x target matrix as Jenkins.""" +import json +import os +import re +import subprocess +import tempfile +import urllib.request + + +def resolve(versions, targets): + versions = [versions[k] for k in ("stable_version", "oldstable_version") if versions.get(k)] + if not versions: + raise ValueError("OpenWrt returned no stable or oldstable version") + targets = [t.strip() for t in targets.split(",") if t.strip()] + if not targets: + raise ValueError("No targets selected") + for target in targets: + if not re.fullmatch(r"[a-z0-9_+-]+/[a-z0-9_+-]+", target): + raise ValueError(f"Invalid target: {target!r}") + for version in versions: + if not re.fullmatch(r"[a-zA-Z0-9_.-]+", version): + raise ValueError(f"Invalid version: {version!r}") + return {"include": [{"target": t.replace("/", "-"), "version": v} + for v in versions for t in dict.fromkeys(targets)]} + + +if __name__ == "__main__": + with urllib.request.urlopen("https://downloads.openwrt.org/.versions.json", timeout=60) as response: + versions = json.load(response) + targets = os.environ.get("TARGETS", "x86/64,mediatek/filogic,octeon/generic") + if targets.strip() == "all": + with tempfile.TemporaryDirectory(prefix="openwrt-targets-") as source: + subprocess.run(["git", "clone", "--depth", "1", "https://github.com/openwrt/openwrt.git", source], check=True) + output = subprocess.check_output(["perl", "scripts/dump-target-info.pl", "targets"], cwd=source, text=True) + targets = ",".join(line.split()[0] for line in output.splitlines() if line.strip()) + matrix = json.dumps(resolve(versions, targets), separators=(",", ":")) + print(matrix) + with open(os.environ["GITHUB_OUTPUT"], "a") as output: + output.write(f"matrix={matrix}\n") diff --git a/ci/setup-builder.sh b/ci/setup-builder.sh new file mode 100644 index 0000000..1be03b5 --- /dev/null +++ b/ci/setup-builder.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu +docker compose -p openwrt-rust -f ci/cache.compose.yml up -d --wait +if ! docker buildx inspect openwrt-sdk-rust >/dev/null 2>&1; then + docker buildx create --name openwrt-sdk-rust --driver docker-container \ + --driver-opt network=host,memory=20g,memory-swap=20g,cpu-period=100000,cpu-quota=800000 \ + --buildkitd-config ci/buildkitd.toml \ + --buildkitd-flags '--allow-insecure-entitlement network.host' || \ + docker buildx inspect openwrt-sdk-rust +fi +docker buildx inspect openwrt-sdk-rust --bootstrap diff --git a/ci/verify-cache.sh b/ci/verify-cache.sh new file mode 100644 index 0000000..854e895 --- /dev/null +++ b/ci/verify-cache.sh @@ -0,0 +1,39 @@ +#!/bin/sh +# Compile in two disposable containers: the second must read the first's cache. +set -eu +image=${1:?Usage: verify-cache.sh IMAGE} +nonce=$(mktemp -d) +trap 'rmdir "$nonce"' EXIT +prefix="validation:${image##*:}:$(basename "$nonce"):" +for expected in miss hit; do + docker run --rm -i --network gitea-runner-jobs --cpus 2 \ + --memory 2g --memory-swap 2g \ + -e SCCACHE_REDIS_ENDPOINT=redis://openwrt-rust-cache:6379 \ + -e SCCACHE_REDIS_KEY_PREFIX="$prefix" -e EXPECTED="$expected" \ + "$image" sh -es <<'PROBE' +rustc=$(find /builder/staging_dir -type f -path '*/host/bin/rustc') +test -x "$rustc" +mkdir /tmp/sccache-probe +cd /tmp/sccache-probe +printf 'pub fn probe(n: u64) -> u64 { n.wrapping_mul(17) }\n' > probe.rs +sccache --start-server +trap 'sccache --stop-server' EXIT +sccache --zero-stats +sccache "$rustc" --crate-name ci_sccache_probe \ + --crate-type rlib --emit link,dep-info --out-dir . probe.rs +sccache --show-stats +sccache --show-stats --stats-format json > stats.json +python3 - <<'PY' +import json +import os + +data = json.load(open("stats.json")) +assert data["cache_location"].startswith("redis,"), data +stats = data["stats"] +field = "cache_hits" if os.environ["EXPECTED"] == "hit" else "cache_misses" +assert sum(stats[field]["counts"].values()) == 1, data +assert stats["cache_read_errors"] == stats["cache_write_errors"] == 0, data +print("Verified persistent cache", os.environ["EXPECTED"]) +PY +PROBE +done