WithMountedCache requires a directory. /root/.flutter in the cirruslabs/flutter
image is a plain text file (Flutter SDK marker), causing "not a directory" at
container startup. Reverts to the pre-365 Base() so run-364 exec cache entries
are still valid.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Flutter writes tool state to /root/.flutter on every invocation. Without a
cache mount this ends up in the pub-get snapshot, making it large and prone
to GC eviction. Moving it to a cache volume keeps the snapshot tiny so
BuildKit's exec cache for pub get survives between CI runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
flutter pub get writes a date_created timestamp into .flutter-plugins-
dependencies in addition to the generated field in package_config.json.
Both files are part of the pub-get execution snapshot, so both timestamps
must be removed to make the layer deterministic and cacheable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove non-deterministic "generated" and "generatorVersion" fields from
.dart_tool/package_config.json after flutter pub get, so the snapshot
hash is stable across runs and all downstream test steps can be cached.
Mount only .dart_tool/build as a mutable cache volume so the incremental
build graph persists without polluting the deterministic snapshot.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
flutter pub get embeds a timestamp in .dart_tool/package_config.json, making
its output snapshot non-deterministic and busting the cache for dart format,
flutter analyze, unit tests, mocks, and integration tests on every run.
Fix: isolate pub get into its own layer using only pubspec.yaml + pubspec.lock
as inputs, then normalise the generated timestamp. setup() now overlays the
full source on top of this stable layer before running build_runner.
Result: on an empty commit, all steps downstream of pub get should be cached.
Cache volumes for NDK/CMake proved unreliable on the remote Dagger
engine: the android-ndk-cache volume was empty on each run, causing
Gradle to re-download NDK + CMake + build-tools + platform during every
`flutter build appbundle` (~3-4 min of extra downloads).
Pre-install all four SDK components via sdkmanager in Base() so Dagger's
execution cache captures them. Base() is CACHED on subsequent runs with
identical inputs, eliminating the per-run SDK downloads.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Base() no longer mounts m.Source. Each function gets only the files it
needs via a narrow filter, so Dagger's content-addressed cache is scoped
correctly: changing website/, scripts/, or stalwart-dev/ no longer
invalidates the Android or Linux build cache.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _parse now handles wire types 1 (fixed64) and 5 (fixed32) so it doesn't
crash on unknown fields in the manifest proto
- _patch_prim patches both int_decimal_value (field 6) and int_hexadecimal_value
(field 7) — AAPT2 may use either
- patch() reads versionCode before and after patching and exits with a clear
error if the patch didn't take effect
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old workflow built with build-android-bundle (debug-signed) then uploaded
separately. publish-android stamps the versionCode, re-signs with the release
keystore, and uploads in one Dagger call.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BuildAndroidRelease() drops all params and builds with --build-number 1
(no keystore injected, Gradle uses debug signing). The command is now
stable across all commits — full Dagger cache hit whenever source is
unchanged.
Three new Dagger functions handle the post-cache steps:
- StampAndroidVersionCode(aab, versionCode): pure-stdlib Python patches
the AAB's compiled manifest proto (android:versionCode resource ID
0x0101021b) and strips META-INF/ to clear the old signature.
- SignAndroidBundle(aab, keystoreBase64, keystorePassword): decodes the
base64 keystore secret and re-signs with jarsigner.
- PublishAndroid(ctx, playStoreConfig, keystoreBase64, keystorePassword):
chains all three + UploadToPlayStore, computing time.Now().Unix() as
the versionCode internally.
Taskfile: build-android-bundle simplified (no keystore params); publish-
android now calls publish-android in a single Dagger call instead of the
two-step build-then-upload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Locked exclusive cache access caused concurrent Dagger operations inside
Check() to deadlock waiting on each other, resulting in a 60-minute timeout.
Shared mode is correct here — cache volumes are pre-warmed so pub get is fast.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dagger.Locked is not exported in this SDK version; the correct
constant is dagger.CacheSharingModeLocked.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
flutter pub get was not being cached by Dagger because the pub-cache
CacheVolume used Shared mode: concurrent writes from the check and
deploy-playstore jobs made the mount non-deterministic, causing a cache
miss on every run. Locked mode gives each operation exclusive access so
the output snapshot is stable and Dagger can cache subsequent steps.
Also add --no-pub to both flutter build commands: pub get already ran
explicitly in Setup(), so skipping it again inside the build step avoids
a duplicate network-touching operation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
$(date +%s) changed every run, making the flutter build WithExec args
unique each time and busting the Dagger layer cache (500s build every run).
$(git log -1 --format=%ct HEAD) is stable for the same commit, so a
retry of a failed upload gets a full cache hit on the build step.
Still monotonically increasing across commits, satisfying Play Store.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old fvm-based task had the same name as the new Dagger-based one,
causing go-task to error immediately (1-second CI failure).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pass --build-number $(date +%s) to flutter build for both APK and AAB
so each CI run gets a unique version code (fixes "already been used" error)
- Extract UploadToPlayStore(aab, playStoreConfig) as its own Dagger function
so the build and upload are independently callable
- Add build-android-bundle task (exports AAB via dagger export) and
upload-android-bundle task (calls UploadToPlayStore with the local file)
- CI deploy-playstore job now has two steps: Build Android Bundle and
Upload to Play Store, so a failed upload can be retried without rebuilding
- deploy-apk also gets --build-number to avoid version code collisions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both BuildAndroidApk and BuildAndroidRelease were using the debug
signing config because the keystore and password were never forwarded
into the Dagger container. Add setupKeystore() helper that decodes
ANDROID_KEYSTORE_BASE64 into android/app/upload-keystore.jks and
sets ANDROID_KEYSTORE_PASSWORD, then wire both secrets through
DeployApk, PublishAndroid, and the Taskfile/CI env blocks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Decodes ANDROID_KEYSTORE_BASE64 and prints the SHA1 fingerprint via
keytool before invoking the Dagger build, to confirm which key is in
the secret vs. what the build actually uses.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without the response body we can't tell why Google Play rejects the
upload. Logs the status code and first 500 bytes of the response for
both the init POST and the upload PUT on each failed attempt. Also
moves the init call inside the try/except so init failures are retried.
The resumable upload URL returned by Google Play is session-specific and
expires after a failed attempt. Retrying with the same URL always fails.
Also broadens the caught exception from HTTPError to RequestException so
timeouts and connection errors are retried too.
The ubuntu-latest pool now includes nodes that run Docker containers with
user namespace isolation, causing chown of the workspace to fail before
checkout can run. The codeberg-small label routes consistently to the
actions-tiny nodes (act-latest image, no user namespace restriction) where
Dagger CI succeeded previously.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The ubuntu-latest runner uses Docker containers (ghcr.io/catthehacker/ubuntu:act-22.04)
which don't have task or dagger pre-installed. These steps were mistakenly removed when
switching from the dagger-dagger host runner back to ubuntu-latest.
Also adds DAGGER_NO_NAG=1 to all dagger-invoking steps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add -q (quiet) flag to all dagger call invocations to suppress INFO-level
engine messages while keeping warnings and errors visible. Set DAGGER_NO_NAG=1
globally to suppress the Dagger Cloud tracing nag line. --progress=plain
is retained on all calls as required.