feat(ci): android gets a baked builder image; apple gets sccache and a pinned DerivedData

Android was the last job still downloading its world every run: ~3 GB of
SDK/NDK/CMake from Google, a from-source cargo-ndk build, and one monolithic
cache key where a gradle edit invalidated the cargo registry and the Rust
target/ with it. punktfunk-android-ci (content-keyed, LAN registry) bakes
JDK 21 + SDK + NDK + cargo-ndk + sccache; the workflow shrinks to checkout →
two caches → gradle. The cargo-home cache joins the fleet-wide namespace
(same lockfile, same layout — it was the same bytes under a private key),
gradle gets its own key shared with android-screenshots (which stored the
identical content under a second name), and target/ leaves the cache —
sccache covers the three ABI builds now.

Apple: sccache (self-healing ~/.local/bin install + already provisioned on
the mini) covers every cargo invocation build-xcframework.sh makes across
the swift job, the screenshots job and release.yml — three jobs that each
recompiled the same core. screenshots.sh learns PF_SHOT_DERIVED_DATA so CI
pins one stable DerivedData root instead of cold-building into two throwaway
mktemp trees per run (release.yml's disease, same cure), and the Simulators
get shut down after capture (the 846-leaked-sims lesson).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 21:20:59 +02:00
co-authored by Claude Fable 5
parent fcdb4d147d
commit 9cda05dffb
7 changed files with 207 additions and 69 deletions
+9 -18
View File
@@ -22,25 +22,15 @@ jobs:
screenshots: screenshots:
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
# JDK 21 + SDK baked (AGP 9.3 + Robolectric's SDK-36 android-all jar both want 1721).
# The tests are pure JVM (no NDK), but sharing android.yml's image means one image to
# keep warm instead of a per-run setup-java + sdkmanager download pair.
container:
image: 192.168.1.58:5010/punktfunk-android-ci:latest
timeout-minutes: 45 timeout-minutes: 45
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: JDK 21 (AGP 9.3 + Robolectric's SDK-36 android-all jar both want 1721)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Android SDK
# SHA-pinned for parity with android.yml (third-party action). v3 = 9fc6c4e.
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3
# No NDK/CMake — the screenshot unit tests are pure JVM. compileSdk 37 auto-downloads via AGP
# if the platform channel lacks it (same note as android.yml).
- name: platform-tools + platform 36 + build-tools
run: sdkmanager "platform-tools" "platforms;android-36" "build-tools;37.0.0"
- name: Cache (gradle) - name: Cache (gradle)
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
@@ -49,9 +39,10 @@ jobs:
~/.gradle/wrapper ~/.gradle/wrapper
# gradle-wrapper.properties is in the key on purpose: `~/.gradle/wrapper` caches the # gradle-wrapper.properties is in the key on purpose: `~/.gradle/wrapper` caches the
# Gradle DISTRIBUTION, so a wrapper bump with no .gradle.kts change would otherwise # Gradle DISTRIBUTION, so a wrapper bump with no .gradle.kts change would otherwise
# restore a key that can never hold the new one. # restore a key that can never hold the new one. Namespace shared with android.yml —
key: android-screenshots-${{ hashFiles('clients/android/**/*.gradle.kts', 'clients/android/gradle/wrapper/gradle-wrapper.properties') }} # it is the same content; two keys just stored it twice in the central cache.
restore-keys: android-screenshots- key: gradle-${{ hashFiles('clients/android/**/*.gradle.kts', 'clients/android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: gradle-
# Roborazzi renders Compose on the JVM (Robolectric Native Graphics). `-PskipRustBuild` keeps # Roborazzi renders Compose on the JVM (Robolectric Native Graphics). `-PskipRustBuild` keeps
# the cargo-ndk native build out of the graph — the tests never load libpunktfunk_android.so. # the cargo-ndk native build out of the graph — the tests never load libpunktfunk_android.so.
+46 -50
View File
@@ -2,11 +2,11 @@
# cargo-ndk for all three shipping ABIs and assembles the debug APK (clients/android). Mirrors apple.yml # cargo-ndk for all three shipping ABIs and assembles the debug APK (clients/android). Mirrors apple.yml
# but on a Linux runner — the NDK is cross-platform, so no self-hosted host is needed. # but on a Linux runner — the NDK is cross-platform, so no self-hosted host is needed.
# #
# Prereq: the runner needs ~6 GB free + internet (it pulls the Android SDK/NDK and the Gradle # Runs in the punktfunk-android-ci builder image (ci/android-ci.Dockerfile, content-keyed on
# distribution in-job). If android-actions/setup-android is not mirrored on this Gitea instance, # the LAN registry): JDK 21, the Android SDK/NDK/CMake pins, cargo-ndk and sccache are all
# replace that step with a manual cmdline-tools download, or bake an `android-ci` image like # baked, so the multi-GB per-run Google downloads this job used to make are gone. Emulator
# ci/rust-ci.Dockerfile. Emulator instrumentation tests are deferred until a KVM-capable runner # instrumentation tests are deferred until a KVM-capable runner exists (they self-skip
# exists (they self-skip otherwise, like apple.yml's RemoteFirstLightTests). # otherwise, like apple.yml's RemoteFirstLightTests).
name: android name: android
# One pending run per workflow+ref: a newer push supersedes the queued/running one and cancels # One pending run per workflow+ref: a newer push supersedes the queued/running one and cancels
# it (a canary only needs the latest commit; each release tag is its own ref so tag runs never # it (a canary only needs the latest commit; each release tag is its own ref so tag runs never
@@ -46,66 +46,62 @@ on:
- '.gitea/workflows/android.yml' - '.gitea/workflows/android.yml'
workflow_dispatch: workflow_dispatch:
# Shared compile cache: sccache -> RustFS S3 (storage.unom.io, LAN-pinned via ci-core's
# unbound). The NDK clang targets get their own key universes automatically (keys embed
# compiler hash + target), so the three ABI builds share the bucket with everything else.
env:
RUSTC_WRAPPER: sccache
SCCACHE_BUCKET: unom-ci-sccache
SCCACHE_ENDPOINT: https://storage.unom.io
SCCACHE_REGION: home-central
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_SECRET_ACCESS_KEY }}
# sccache and incremental compilation are mutually exclusive; CI wants the shared
# cache, dev boxes keep incremental.
CARGO_INCREMENTAL: "0"
jobs: jobs:
android: android:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
container:
image: 192.168.1.58:5010/punktfunk-android-ci:latest
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: JDK 21 (AGP 9.3 runs on JDK 1721, not the host default) # Everything below the checkout used to be four download steps (JDK, SDK,
uses: actions/setup-java@v4 # NDK+CMake, cargo-ndk — the flakiest, heaviest part of the job); it is all baked
with: # into the image now. This guard only re-asserts the Android targets so a
distribution: temurin # rust-toolchain.toml pin bump keeps working against an older image (:latest lags
java-version: "21" # one image rebuild, same bootstrap note as ci.yml's dep steps).
- name: Rust Android targets (no-op unless the toolchain pin outran the image)
run: rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
- name: Rust toolchain + Android targets (self-healing on a fresh runner) # Same key namespace as ci.yml/deb.yml ON PURPOSE: identical Cargo.lock, identical
run: | # CARGO_HOME layout (/usr/local/cargo), so the registry/git downloads dedupe with
if ! command -v rustup >/dev/null && [ ! -x "$HOME/.cargo/bin/rustup" ]; then # the rest of the fleet in the central cache. target/ is deliberately NOT cached
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ # anymore — sccache covers recompilation without shipping multi-GB tars per run.
| sh -s -- -y --no-modify-path --profile minimal - name: Cache (cargo registry)
fi uses: actions/cache@v4
RUSTUP="$(command -v rustup || echo "$HOME/.cargo/bin/rustup")"
dirname "$RUSTUP" >> "$GITHUB_PATH"
"$RUSTUP" target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
- name: Android SDK
# SHA-pinned: this workflow's release job carries the signing keystore + Play service-account
# secrets, so a moved tag on a third-party action could exfiltrate them. v3 = 9fc6c4e.
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3
with: with:
# Only platform-tools — NOT the action's default legacy `tools`, whose dependency chain path: |
# drags in the ~250 MB emulator nobody here runs (instrumentation tests are deferred). /usr/local/cargo/registry
# That download was the single flakiest piece of this job: the shared runner fleet drops /usr/local/cargo/git
# packets under parallel-job load and sdkmanager's streamed unzip turns a truncated key: cargo-home-${{ hashFiles('Cargo.lock') }}
# stream into "Error on ZipFile unknown archive" (observed 2026-07-22, twice). restore-keys: cargo-home-
packages: platform-tools
- name: NDK r30 + platform 36 + build-tools + CMake (libopus cross-build)
# cmake;3.22.1 installs cmake + ninja under $ANDROID_SDK/cmake/3.22.1/bin — the exact path
# kit/build.gradle.kts prepends to PATH for cargo-ndk's audiopus_sys (libopus) CMake build.
# Note: platforms;android-37 is sometimes missing from standard channels; AGP will
# auto-download it if needed during the build.
# retry.sh: sdkmanager is a single-shot multi-hundred-MB fetch, exactly the class the
# helper exists for (fleet-load packet drops truncate the stream mid-unzip); a failed
# attempt leaves no partial package behind, so a plain re-invoke is safe.
run: bash scripts/ci/retry.sh 4 sdkmanager "platform-tools" "platforms;android-36" "build-tools;37.0.0" "ndk;30.0.14904198" "cmake;3.22.1"
- name: Caches (cargo + gradle) - name: Cache (gradle)
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
~/.cargo/registry
~/.cargo/git
~/.gradle/caches ~/.gradle/caches
~/.gradle/wrapper ~/.gradle/wrapper
target # gradle-wrapper.properties is in the key on purpose: `~/.gradle/wrapper` caches the
# gradle-wrapper.properties is in the key on purpose — see android-screenshots.yml. # Gradle DISTRIBUTION, so a wrapper bump with no .gradle.kts change would otherwise
key: android-${{ hashFiles('Cargo.lock', 'clients/android/**/*.gradle.kts', 'clients/android/gradle/wrapper/gradle-wrapper.properties') }} # restore a key that can never hold the new one. Namespace shared with
restore-keys: android- # android-screenshots.yml — same content, one copy in the central store.
key: gradle-${{ hashFiles('clients/android/**/*.gradle.kts', 'clients/android/gradle/wrapper/gradle-wrapper.properties') }}
- name: cargo-ndk restore-keys: gradle-
run: command -v cargo-ndk >/dev/null || cargo install cargo-ndk
- name: assembleDebug (cargo-ndk → jniLibs → APK) - name: assembleDebug (cargo-ndk → jniLibs → APK)
working-directory: clients/android working-directory: clients/android
+57
View File
@@ -44,6 +44,21 @@ on:
- '.gitea/workflows/apple.yml' - '.gitea/workflows/apple.yml'
workflow_dispatch: workflow_dispatch:
# Shared compile cache: sccache -> RustFS S3 (storage.unom.io — the mini resolves it via
# the router, i.e. the hairpin path whose TLS always validated). Covers every cargo/rustc
# invocation build-xcframework.sh makes, incl. the tvOS -Zbuild-std std builds; the Swift
# side stays on DerivedData (sccache doesn't cache swiftc).
env:
RUSTC_WRAPPER: sccache
SCCACHE_BUCKET: unom-ci-sccache
SCCACHE_ENDPOINT: https://storage.unom.io
SCCACHE_REGION: home-central
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_SECRET_ACCESS_KEY }}
# sccache and incremental compilation are mutually exclusive; the shared cache makes the
# runner's persistent target/ disposable instead of precious.
CARGO_INCREMENTAL: "0"
jobs: jobs:
# SECURITY: builds/tests PULL-REQUEST code on the host-mode, persistent `macos-arm64` runner shared # SECURITY: builds/tests PULL-REQUEST code on the host-mode, persistent `macos-arm64` runner shared
# with the release-signing job (release.yml, which loads the App Store Connect key). Untrusted PR # with the release-signing job (release.yml, which loads the App Store Connect key). Untrusted PR
@@ -70,6 +85,18 @@ jobs:
dirname "$RUSTUP" >> "$GITHUB_PATH" dirname "$RUSTUP" >> "$GITHUB_PATH"
"$RUSTUP" target add aarch64-apple-darwin x86_64-apple-darwin "$RUSTUP" target add aarch64-apple-darwin x86_64-apple-darwin
# Shared compile cache. ~/.local/bin is on the runner daemon's PATH; GITHUB_PATH is
# belt-and-braces. bsdtar (macOS) globs by default — no --wildcards.
- name: sccache (self-healing install)
run: |
if ! command -v sccache >/dev/null; then
mkdir -p "$HOME/.local/bin"
curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.10.0/sccache-v0.10.0-aarch64-apple-darwin.tar.gz \
| tar -xz --strip-components=1 -C "$HOME/.local/bin" '*/sccache'
fi
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
sccache --version
# `punktfunk-core` now decodes Opus in-core for the Apple client (surround), pulling # `punktfunk-core` now decodes Opus in-core for the Apple client (surround), pulling
# `audiopus_sys`, which builds a vendored static libopus via CMake when pkg-config can't find a # `audiopus_sys`, which builds a vendored static libopus via CMake when pkg-config can't find a
# system Opus — so the xcframework is self-contained (no runtime libopus.dylib on end-user Macs). # system Opus — so the xcframework is self-contained (no runtime libopus.dylib on end-user Macs).
@@ -128,6 +155,18 @@ jobs:
"$RUSTUP" target add aarch64-apple-darwin x86_64-apple-darwin \ "$RUSTUP" target add aarch64-apple-darwin x86_64-apple-darwin \
aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
# Shared compile cache. ~/.local/bin is on the runner daemon's PATH; GITHUB_PATH is
# belt-and-braces. bsdtar (macOS) globs by default — no --wildcards.
- name: sccache (self-healing install)
run: |
if ! command -v sccache >/dev/null; then
mkdir -p "$HOME/.local/bin"
curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.10.0/sccache-v0.10.0-aarch64-apple-darwin.tar.gz \
| tar -xz --strip-components=1 -C "$HOME/.local/bin" '*/sccache'
fi
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
sccache --version
# See the swift job: audiopus_sys (via the in-core Opus decode) builds vendored libopus with CMake. # See the swift job: audiopus_sys (via the in-core Opus decode) builds vendored libopus with CMake.
- name: CMake (for the vendored libopus audiopus_sys builds) - name: CMake (for the vendored libopus audiopus_sys builds)
run: | run: |
@@ -144,6 +183,20 @@ jobs:
# inherits this from the env during the xcframework build). # inherits this from the env during the xcframework build).
echo "CMAKE_POLICY_VERSION_MINIMUM=3.5" >> "$GITHUB_ENV" echo "CMAKE_POLICY_VERSION_MINIMUM=3.5" >> "$GITHUB_ENV"
- name: Pin + prune DerivedData (same disease release.yml already cures)
# screenshots.sh builds into a throwaway mktemp DerivedData per invocation — two
# fresh ~1 GB trees per run, zero reuse. Pin one stable root (PF_SHOT_DERIVED_DATA,
# honored by the script) so repeat runs are incremental, and GC anything a week old
# in the default DerivedData root that no pin owns.
run: |
DD="$HOME/ci/derived-data/screenshots"
mkdir -p "$DD"
echo "PF_SHOT_DERIVED_DATA=$DD" >> "$GITHUB_ENV"
if [ -d "$HOME/Library/Developer/Xcode/DerivedData" ]; then
find "$HOME/Library/Developer/Xcode/DerivedData" -mindepth 1 -maxdepth 1 \
-mtime +7 -exec rm -rf {} + 2>/dev/null || true
fi
- name: Build PunktfunkCore.xcframework (mac + iOS slices) - name: Build PunktfunkCore.xcframework (mac + iOS slices)
run: BUILD_IOS=1 bash scripts/build-xcframework.sh run: BUILD_IOS=1 bash scripts/build-xcframework.sh
@@ -157,6 +210,10 @@ jobs:
bash tools/screenshots.sh ipad || echo "::warning::iPad 13\" screenshots skipped" bash tools/screenshots.sh ipad || echo "::warning::iPad 13\" screenshots skipped"
echo "Produced:"; ls -la screenshots || true echo "Produced:"; ls -la screenshots || true
- name: Shut the Simulators down (leaked booted sims once piled up 846 deep)
if: always()
run: xcrun simctl shutdown all || true
- name: Upload screenshots (zip artifact) - name: Upload screenshots (zip artifact)
if: always() if: always()
# v3, not v4: Gitea's artifact backend identifies as GHES, which @actions/artifact v2+ # v3, not v4: Gitea's artifact backend identifies as GHES, which @actions/artifact v2+
+5
View File
@@ -65,6 +65,11 @@ jobs:
dockerfile: ci/fedora-rpm.Dockerfile dockerfile: ci/fedora-rpm.Dockerfile
buildargs: --build-arg FEDORA_VERSION=44 buildargs: --build-arg FEDORA_VERSION=44
keysuffix: -f44 keysuffix: -f44
# Android builder (JDK + SDK/NDK + cargo-ndk + sccache) — android.yml and
# android-screenshots.yml run in it; ~3 GB of per-run Google downloads became
# image layers.
- image: punktfunk-android-ci
dockerfile: ci/android-ci.Dockerfile
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+27
View File
@@ -88,6 +88,21 @@ on:
required: false required: false
default: "true" default: "true"
# Shared compile cache: sccache -> RustFS S3 (storage.unom.io — the mini resolves it via
# the router, i.e. the hairpin path whose TLS always validated). Covers every cargo/rustc
# invocation build-xcframework.sh makes, incl. the tvOS -Zbuild-std std builds; the Swift
# side stays on DerivedData (sccache doesn't cache swiftc).
env:
RUSTC_WRAPPER: sccache
SCCACHE_BUCKET: unom-ci-sccache
SCCACHE_ENDPOINT: https://storage.unom.io
SCCACHE_REGION: home-central
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_SECRET_ACCESS_KEY }}
# sccache and incremental compilation are mutually exclusive; the shared cache makes the
# runner's persistent target/ disposable instead of precious.
CARGO_INCREMENTAL: "0"
jobs: jobs:
apple: apple:
runs-on: macos-arm64 runs-on: macos-arm64
@@ -157,6 +172,18 @@ jobs:
# inherits this from the env during the xcframework build). # inherits this from the env during the xcframework build).
echo "CMAKE_POLICY_VERSION_MINIMUM=3.5" >> "$GITHUB_ENV" echo "CMAKE_POLICY_VERSION_MINIMUM=3.5" >> "$GITHUB_ENV"
# Shared compile cache. ~/.local/bin is on the runner daemon's PATH; GITHUB_PATH is
# belt-and-braces. bsdtar (macOS) globs by default — no --wildcards.
- name: sccache (self-healing install)
run: |
if ! command -v sccache >/dev/null; then
mkdir -p "$HOME/.local/bin"
curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.10.0/sccache-v0.10.0-aarch64-apple-darwin.tar.gz \
| tar -xz --strip-components=1 -C "$HOME/.local/bin" '*/sccache'
fi
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
sccache --version
- name: Pin + prune Xcode DerivedData - name: Pin + prune Xcode DerivedData
# Without -derivedDataPath, xcodebuild derives its DerivedData directory name from the # Without -derivedDataPath, xcodebuild derives its DerivedData directory name from the
# PROJECT'S ABSOLUTE PATH — and act_runner rotates its workspace # PROJECT'S ABSOLUTE PATH — and act_runner rotates its workspace
+59
View File
@@ -0,0 +1,59 @@
# Android CI builder: JDK 21 + Android SDK/NDK/CMake + pinned Rust with the three shipping
# Android targets + cargo-ndk + sccache. Everything android.yml used to download per run
# (~3 GB of NDK + SDK packages from Google, plus a from-source cargo-ndk build) is baked
# here instead; the image is content-keyed and rebuilt only when the ci/ tree changes
# (docker.yml `builders`).
#
# docker build -f ci/android-ci.Dockerfile -t punktfunk-android-ci ci
#
# Version pins mirror what android.yml installed via sdkmanager: AGP 9.3 wants JDK 1721;
# cmake;3.22.1 because kit/build.gradle.kts prepends $ANDROID_SDK/cmake/3.22.1/bin to PATH
# for cargo-ndk's audiopus_sys (libopus) CMake build; platforms;android-37 is deliberately
# absent (AGP auto-downloads it if a build ever needs it — same note as the old workflow).
FROM ubuntu:26.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl git unzip zip python3 openjdk-21-jdk-headless \
build-essential pkg-config \
&& rm -rf /var/lib/apt/lists/*
ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
# Android SDK: cmdline-tools must land under cmdline-tools/latest for sdkmanager to
# find its own root.
ENV ANDROID_HOME=/opt/android-sdk \
ANDROID_SDK_ROOT=/opt/android-sdk
ARG CMDLINE_TOOLS=13114758
RUN mkdir -p "$ANDROID_HOME/cmdline-tools" \
&& curl -fsSL -o /tmp/clt.zip "https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS}_latest.zip" \
&& unzip -q /tmp/clt.zip -d "$ANDROID_HOME/cmdline-tools" \
&& mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" \
&& rm /tmp/clt.zip
ENV PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH
RUN yes | sdkmanager --licenses >/dev/null \
&& sdkmanager "platform-tools" "platforms;android-36" "build-tools;37.0.0" \
"ndk;30.0.14904198" "cmake;3.22.1" \
&& chmod -R a+rX "$ANDROID_HOME"
# Toolchain shared across CI users (jobs may run as different uids) — same shape as
# rust-ci.Dockerfile, plus the Android cross targets and cargo-ndk. The registry/git
# download caches are stripped after the cargo-ndk install: jobs restore those from the
# shared actions cache, and baking them would only bloat every pull.
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --profile minimal \
&& rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android \
&& cargo install cargo-ndk --locked \
&& rm -rf "$CARGO_HOME/registry" "$CARGO_HOME/git" \
&& chmod -R a+w "$RUSTUP_HOME" "$CARGO_HOME" \
&& rustc --version && cargo-ndk --version
# Shared compile cache: jobs set RUSTC_WRAPPER=sccache (backend = RustFS S3 on the LAN,
# see .gitea/workflows — the env lives there so dev use of this image stays uncached).
ARG SCCACHE_VERSION=0.10.0
RUN curl -fsSL "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
| tar -xz --wildcards --strip-components=1 -C /usr/local/bin '*/sccache' \
&& sccache --version
+4 -1
View File
@@ -116,7 +116,10 @@ shoot_sim() {
xcrun simctl bootstatus "$udid" -b >/dev/null 2>&1 || true xcrun simctl bootstatus "$udid" -b >/dev/null 2>&1 || true
log "$prefix — building ($scheme)…" log "$prefix — building ($scheme)…"
local dd; dd="$(mktemp -d)" # PF_SHOT_DERIVED_DATA (optional): a STABLE DerivedData root, so repeat runs reuse the
# incremental build instead of cold-building into a throwaway tmpdir — CI pins this
# (apple.yml); local runs keep the self-cleaning mktemp default.
local dd; dd="${PF_SHOT_DERIVED_DATA:-$(mktemp -d)}"; mkdir -p "$dd"
xcodebuild -project Punktfunk.xcodeproj -scheme "$scheme" -configuration Debug \ xcodebuild -project Punktfunk.xcodeproj -scheme "$scheme" -configuration Debug \
-sdk "$sdk" -destination "id=$udid" -derivedDataPath "$dd" \ -sdk "$sdk" -destination "id=$udid" -derivedDataPath "$dd" \
CODE_SIGNING_ALLOWED=NO build >/dev/null \ CODE_SIGNING_ALLOWED=NO build >/dev/null \