perf(ci): cache pub get separately from source to fix downstream cache misses

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.
This commit is contained in:
Thomas SharedInbox
2026-05-19 16:59:19 +02:00
parent 1e0679c324
commit dd9bd24f09
+18 -4
View File
@@ -190,14 +190,28 @@ func (m *Ci) Base() *dagger.Container {
WithExec([]string{"/bin/sh", "-c", `yes | sdkmanager "ndk;28.2.13676358" "cmake;3.22.1" "build-tools;35.0.0" "platforms;android-34"`})
}
// setup mounts a source directory into Base and runs pub get + build_runner.
// pubGetLayer runs flutter pub get with only pubspec.yaml + pubspec.lock as
// inputs, then normalises the "generated" timestamp in package_config.json so
// the snapshot is byte-for-byte stable across runs. The layer is only
// re-executed when pubspec.yaml or pubspec.lock changes.
func (m *Ci) pubGetLayer() *dagger.Container {
pubspecOnly := m.Source.Filter(dagger.DirectoryFilterOpts{
Include: []string{"pubspec.yaml", "pubspec.lock"},
})
return m.Base().
WithDirectory("/src", pubspecOnly).
WithWorkdir("/src").
WithExec([]string{"flutter", "pub", "get"}).
WithExec([]string{"/bin/sh", "-c",
`sed -i 's/"generated": *"[^"]*"/"generated": "1970-01-01T00:00:00.000000Z"/' .dart_tool/package_config.json`})
}
// setup overlays source files onto the cached pub-get layer and runs build_runner.
// Each caller passes only the files it actually needs so Dagger's content-addressed
// cache is scoped correctly: changing website/ won't bust the Android build cache.
func (m *Ci) setup(src *dagger.Directory) *dagger.Container {
return m.Base().
return m.pubGetLayer().
WithDirectory("/src", src).
WithWorkdir("/src").
WithExec([]string{"flutter", "pub", "get"}).
WithExec([]string{"flutter", "pub", "run", "build_runner", "build"})
}