Skip to content

chore(ci): close the gaps left by the Develocity onboarding - #6541

Merged
jamesarich merged 5 commits into
mainfrom
chore/develocity-tuning
Aug 1, 2026
Merged

chore(ci): close the gaps left by the Develocity onboarding#6541
jamesarich merged 5 commits into
mainfrom
chore/develocity-tuning

Conversation

@jamesarich

@jamesarich jamesarich commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #6531. Onboarding to the OSS Community Develocity instance landed the plumbing, but three things were left on the table: the build-logic included build never actually got the remote cache, Build Scans lost the one capture setting that makes cache misses diagnosable, and CI now ships a local build cache that duplicates the remote one. This PR closes those gaps so the sponsorship is doing the work it can do.

🛠️ Improvements

  • build-logic now shares the remote build cache. build-logic is a plugin-included build with its own settings.gradle.kts, so it does not inherit the root build's Develocity configuration. It applies the shared gradle/build-cache.settings.gradle, but that script looks up extensions.findByName("develocity") — which is null there — so the entire remote {} block was skipped and the convention plugins compiled against the local cache only. The pre-Onboard to the OSS Community Develocity Instance #6531 HttpBuildCache config handled this case explicitly (it had an isLogic branch and its own log line); the Develocity port dropped it. Applying the Develocity settings plugin in build-logic/settings.gradle.kts restores parity.

    Verified with ./gradlew help --info:

    # before
    Using local directory build cache for build ':build-logic'
    Using local directory build cache for the root build
    Using remote Develocity build cache for the root build (pull-only, ...)
    
    # after
    Using local directory build cache for build ':build-logic'
    Using remote Develocity build cache for build ':build-logic' (pull-only, ...)   # ← new
    Using local directory build cache for the root build
    Using remote Develocity build cache for the root build (pull-only, ...)
    

    No additional Build Scan is published — the included build mirrors the root's publishing.onlyIf { it.isAuthenticated } guard.

  • Restore capture { fileFingerprints } on CI. Onboard to the OSS Community Develocity Instance #6531 dropped the explicit setting in favour of the plugin default. File fingerprints are what let Develocity's build-comparison view explain a cache miss down to the individual changed input, which is the main reason to reach for a scan in the first place. Re-enabled on CI only (fileFingerprints = isCI), so local builds don't pay the scan payload for a tool that gets used when debugging CI.

  • Stop shipping the local build cache in the Actions cache. gradle-home-cache-includes: caches sweeps in caches/build-cache-1, which since Onboard to the OSS Community Develocity Instance #6531 duplicates the remote cache at community.develocity.cloud. Every one of the ~14 jobs that call the shared gradle-setup action paid tarball upload/download for entries the remote already serves, against a 10 GB repo-wide Actions cache quota that evicts under pressure. Added gradle-home-cache-excludes: caches/build-cache-1; dependency and artifact-transform caches — the expensive part — still ship.

    This one is a trade, not a strict win: it swaps one bulk tarball restore for per-entry HTTP against Develocity. The effect is now measurable in the Build Scan cache-performance view, and the comment in the action says to revert if remote latency costs more than the restore it replaced.

  • Obfuscate username and hostname in Build Scans, from both CI and workstations. community.develocity.cloud is a public OSS instance and the README badge links its scan list, but only IP addresses were being obfuscated.

    The workstation case is the live one: provisioning a local access key is what makes local builds publish at all (publishing.onlyIf { it.isAuthenticated }), so the moment a contributor runs ./gradlew provisionDevelocityAccessKey, every local build would publish their OS username and machine hostname. Now local-dev / local-machine.

    The CI case is a hedge, not a fix for a live leak. Every runner today is GitHub-hosted, so the raw hostname is an ephemeral Azure VM name that leaks nothing and correlates nothing (VMs are never reused). But a self-hosted runner's hostname would be real infrastructure, and whoever adds one will not be thinking about build scans. Now ci / ci-runner.

    Values are deliberately constants rather than anything descriptive: the scan already records operatingSystem ("Linux 7.0.0-28-generic (amd64)" — including architecture) and numberOfCpuCores, and common-custom-user-data already adds CI workflow / CI job / CI step / CI run values, so an "informative" hostname would only duplicate them.

    Reviewer note on structure: the if must stay outside the obfuscation lambdas. Referencing isCI from inside one captures the enclosing settings script object, which the configuration cache cannot serialize (cannot serialize Gradle script object references) — this repo runs with org.gradle.configuration-cache=true. The first attempt at this collapsed the branch into username { if (isCI) ... } and failed the baseline; the constraint is now commented in both files so it doesn't get refactored back.

    Applied in both settings files. In build-logic it is only reachable via a standalone -p build-logic run (when included, the root build publishes the scan), but the exposure would be identical, so the gap is closed there too.

  • Gitignore JVM crash dumps so they stay out of public scans. common-custom-user-data publishes the raw output of git status --porcelain as the Git status custom value, and it offers no opt-out: CaptureGitMetadataAction runs unconditionally, and the obfuscation {} block used above covers only username, hostname, IP addresses and external process names — not custom values. So on a public instance every untracked, non-ignored path in the working tree is visible.

    The :desktopApp:test crash described at the bottom of this PR leaves hs_err_pid*.log dumps behind, and three of them were riding along in every local scan. Ignoring them — plus replay_pid*.log and .attach_pid*, same family — scopes the value back to real working-tree changes; a fresh scan now reports Git status: " M .gitignore" alone. Paths only, never file contents, but worth keeping honest.

Notes for reviewers

  • No test-retry changes. Flaky-test detection was the other thing worth chasing here, but it turns out the repo already has it: configureTestOptions() wires org.gradle.test-retry with maxRetries=2, maxFailures=10, failOnPassedAfterRetry=false across the Android app/library and KMP library convention plugins. Develocity's within-build detection works with the standalone plugin as-is, and cross-build detection needs no client config at all — so the flaky data is already flowing to the Tests dashboard and no code change is warranted. Migrating to the built-in develocity.testRetry would be tidy but is not required, and would risk the screenshot-test opt-out in AndroidScreenshotConventionPlugin; left alone deliberately.
  • failOnPassedAfterRetry stays false. Develocity's guide suggests true to keep a flake as a gate failure, but recording flakes without breaking CI is the more useful posture now that the Tests dashboard actually surfaces them.
  • Obfuscation verified against real published scans, both modes. Queried back via /api/builds/{id}/gradle-attributes: a local build reports local-dev / local-machine, a CI=true build reports ci / ci-runner, and the configuration cache stores cleanly in each. Initial local-only verification is in this comment.
  • The com.gradle.develocity version literal is duplicated across the two settings files, and is now guarded by a CI drift check (third guard in check-changes). It cannot be shared — a settings plugin must be applied in the settings file itself, so a plugins {} block cannot move into an apply(from = …) script. Folding the rest of the develocity { } body into the shared Groovy gradle/build-cache.settings.gradle was considered and rejected: publishing.onlyIf { it.isAuthenticated } does not port literally to Groovy (boolean getter → it.authenticated), and getting it wrong would silently disable the publish guard this PR just tightened. Not worth trading compile-time checking for ~12 lines on that particular block.
  • No PR-comment integration. setup-gradle's add-job-summary-as-pr-comment was considered and skipped: with ~14 jobs behind the shared action, a broad failure would post ~14 comments. The existing add-job-summary: always already carries the scan links.

Testing Performed

  • ./gradlew help --info before and after, confirming which builds resolve the remote cache (output quoted above).
  • Baseline: ./gradlew spotlessApply spotlessCheck detekt assembleDebug test allTests -x :desktopApp:test --continueBUILD SUCCESSFUL, no failing tasks, spotless produced no reformatting. Re-run after the obfuscation commit: still green.
  • Build-config-only change; no production source touched, so no new tests are applicable.

One task was excluded from the local baseline and needs CI to cover it. :desktopApp:test aborts on my Linux workstation with exit value 134 (SIGABRT) — a native SIGSEGV in libgobject-2.0.so / g_object_unref under the JetBrains JRE, almost certainly LinuxNotificationSenderTest touching GLib/GTK on a host with a real desktop session. I confirmed it is pre-existing and unrelated to this change by stashing the diff and running :desktopApp:test --rerun-tasks on the unmodified tree: identical crash, identical signature, and an hs_err log from before this work began. CI covers the task in the shard-app test shard, and that shard passed on this PR — so the gap is closed by CI, not left open. Flagging it because the full local baseline as documented in CLAUDE.md is currently unrunnable on a Linux desktop session, which is worth fixing separately.

🤖 Generated with Claude Code

Follow-up to #6531. Three things the onboarding left on the table:

- build-logic is a plugin-included build with its own settings, so it does
  not inherit the root's Develocity config. It applies the shared
  build-cache script, but that script looks up the `develocity` extension,
  which is null there — so the whole remote block was skipped and the
  convention plugins compiled against the local cache only. The pre-#6531
  HttpBuildCache config handled this explicitly (it had an isLogic branch);
  the port dropped it. Verified with `./gradlew help --info`: ':build-logic'
  now reports a remote Develocity cache alongside the local one.

- Restore `capture { fileFingerprints }`, CI-only. Fingerprints are what
  let Develocity's build comparison explain a cache miss down to the changed
  input; not worth the scan payload on local builds.

- Exclude caches/build-cache-1 from the Actions cache. It duplicates the
  Develocity remote cache, and was being tarballed on every one of the ~14
  jobs behind the shared gradle-setup action.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds Develocity build-scan configuration, enables CI-only file-fingerprint capture, and excludes the local Gradle build cache from GitHub Actions cache storage.

Changes

Develocity and Gradle caching

Layer / File(s) Summary
Develocity build-scan configuration
build-logic/settings.gradle.kts, settings.gradle.kts
Adds Develocity version 4.5.0, configures authenticated build-scan publishing, and enables file-fingerprint capture only in CI.
Gradle Actions cache exclusion
.github/actions/gradle-setup/action.yml
Excludes caches/build-cache-1 from Gradle home cache storage.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Sibling Call Sites And Presence Semantics ✅ Passed The PR changes only Gradle settings and GitHub Actions cache configuration; it changes no nullable field, zero-guard, or physical metric call site, so the sibling-call-site check is not applicable.
Tests Prove The Path, Not The End State ✅ Passed The patch changes only an Actions YAML file and two Gradle settings files; it adds or changes no tests, so the path-vs-end-state test check is not applicable.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request’s main changes to complete Develocity onboarding for CI and build caching.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added build Build system changes chore repo Repository maintenance labels Aug 1, 2026
@jamesarich
jamesarich marked this pull request as ready for review August 1, 2026 18:03
jamesarich and others added 2 commits August 1, 2026 13:28
community.develocity.cloud is a public OSS instance and the README badge
links its scan list. Only IP addresses were obfuscated, so once a
contributor provisions an access key — which is what makes local builds
publish at all, via publishing.onlyIf { it.isAuthenticated } — every local
build would publish their OS username and machine hostname.

CI runners are left as-is: they are disposable and their identity is
already public in the Actions log, which is the more useful signal when
reading a CI scan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Develocity settings plugin is applied in two settings files — the root
build and the build-logic included build — because a settings `plugins {}`
block cannot be sourced from an `apply(from = ...)` script. The version
literal is therefore unavoidably duplicated, and two copies of a version
string drift silently.

Adds a third drift guard alongside the existing check-changes filter and
test-shard guards, asserting both files declare the same version. Verified
both directions: passes on the current tree, and fails with a diagnostic
when the build-logic version is perturbed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jamesarich

Copy link
Copy Markdown
Collaborator Author

Obfuscation verified against a real Build Scan

The PR body notes that the username/hostname obfuscation could not be verified locally, because nothing publishes without an access key. That's now resolved — I provisioned a key (./gradlew provisionDevelocityAccessKey) and published a scan from a local build.

Scan: https://community.develocity.cloud/s/ydiyls2oxv2qo

Queried back through the Develocity API (/api/builds/{id}/gradle-attributes):

{
  "user": "local-dev",
  "host": "local-machine",
  "localHost": "local-machine",
  "ip": null
}

The real OS username and machine hostname do not appear in the published scan. Note that localHostname is redacted too, not just the public one — worth confirming that's the intent, though I'd argue redacting both is the safer default for a public instance.

Develocity plugin version drift guard

Added in c26a44e, following the reviewer note about the duplicated 4.5.0 literal. It sits alongside the two existing drift guards in check-changes and asserts both settings files declare the same Develocity version.

Verified both directions:

# current tree
Develocity plugin version is consistent: 4.5.0
exit 0

# with build-logic perturbed to 4.4.9
Develocity plugin version drift detected:
  settings.gradle.kts: 4.5.0
  build-logic/settings.gradle.kts: 4.4.9
Both settings files must apply the same version.
exit 1

This does not remove the duplication — it can't be removed, since a settings plugins {} block cannot be sourced from an apply(from = ...) script — but it makes the drift a build failure rather than something discovered later.

🤖 Generated with Claude Code

Extends the workstation redaction to CI, so no machine identity is
published to the public OSS instance from either context.

This is a hedge rather than a fix for a live leak: every runner today is
GitHub-hosted, so the raw hostname is an ephemeral Azure VM name that
leaks nothing and correlates nothing, since VMs are never reused. But a
self-hosted runner's hostname would be real infrastructure, and whoever
adds one will not be thinking about build scans.

Values are constants rather than anything descriptive. The scan already
records operatingSystem ("Linux ... (amd64)") and numberOfCpuCores, and
common-custom-user-data already adds CI workflow/job/step/run values, so
an "informative" hostname would only duplicate them.

The `if` must stay outside the obfuscation lambdas: referencing isCI from
inside one captures the enclosing settings script object, which the
configuration cache cannot serialize. Caught by the baseline as "cannot
serialize Gradle script object references"; the constraint is now
commented so it does not get refactored back.

Verified against published scans in both modes — local: local-dev /
local-machine, CI=true: ci / ci-runner — with the configuration cache
storing cleanly in each.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jamesarich

Copy link
Copy Markdown
Collaborator Author

CI obfuscation verified on a real GitHub Actions build

Follow-up to the earlier verification comment, which covered local builds only. 77ded1296 extends the redaction to CI, and this is a scan published by the android-check job on this PR — not a simulated CI=true run.

Scan: https://community.develocity.cloud/s/3ugjngaluoeug

{
  "username": "ci",
  "publicHostname": "ci-runner",
  "localHostname": "ci-runner",
  "operatingSystem": "Linux 6.17.0-1020-azure (amd64)",
  "numberOfCpuCores": 4
}

Two things worth noting from this:

  • The -azure kernel string is the GitHub-hosted VM identifying itself, which confirms the raw hostname really would have been an ephemeral Azure name — leaking nothing today, but the hedge is against a self-hosted runner being added later.
  • operatingSystem (including amd64) and numberOfCpuCores survive untouched. That was the argument for using flat constants instead of an "informative" hostname: the arch and machine class are already captured, so encoding them into the hostname would only duplicate them.

Configuration cache

The first attempt at this commit collapsed the branch into username { if (isCI) ... } and failed the local baseline with cannot serialize Gradle script object references — referencing isCI from inside an obfuscation lambda captures the enclosing settings script. Since that failure occurs when storing the cache at the end of a build, it is the kind of thing that can pass a warm incremental run, so it is worth confirming on CI rather than assuming. From the android-check job log on this commit:

Configuration cache entry stored

No problems reported. The constraint is commented in both settings files so the branch does not get refactored back inside the lambdas.

🤖 Generated with Claude Code

`common-custom-user-data-gradle-plugin` publishes the raw output of
`git status --porcelain` as the `Git status` custom value, and it offers
no opt-out — `CaptureGitMetadataAction` runs unconditionally, and the
`obfuscation {}` block only covers username, hostname, IP addresses and
external process names.

That means every untracked, non-ignored path is visible on the public
community instance. `:desktopApp:test` leaves `hs_err_pid*.log` dumps
behind when the JVM crashes in libgobject, so three of them were riding
along in every local scan.

Ignoring them keeps the value scoped to real working-tree changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jamesarich
jamesarich added this pull request to the merge queue Aug 1, 2026
Merged via the queue into main with commit e2c8d75 Aug 1, 2026
18 checks passed
@jamesarich
jamesarich deleted the chore/develocity-tuning branch August 1, 2026 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Build system changes chore repo Repository maintenance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant