Skip to content

feat[oss-licenses]: Add full Gradle Configuration Cache support#365

Merged
timothyfroehlich merged 12 commits into
google:mainfrom
timothyfroehlich:froeht/config-cache
Mar 3, 2026
Merged

feat[oss-licenses]: Add full Gradle Configuration Cache support#365
timothyfroehlich merged 12 commits into
google:mainfrom
timothyfroehlich:froeht/config-cache

Conversation

@timothyfroehlich

@timothyfroehlich timothyfroehlich commented Mar 3, 2026

Copy link
Copy Markdown
Member

Description

This PR introduces full support for Gradle's Configuration Cache to the oss-licenses-plugin, allowing for significant build speed improvements in subsequent runs.

Key Architectural Changes

  1. Unified Lazy Dependency Resolution:

    • Rewrote DependencyUtil.groovy to perform artifact resolution during the configuration phase instead of the execution phase.
    • Replicated internal Android Gradle Plugin (AGP) logic using ArtifactView and ArtifactResolutionQuery against the variant's runtimeConfiguration.
    • Consolidated the resolution of physical library files (JAR/AAR) and POM metadata into a single lazy Provider.
    • Injected these mappings into LicensesTask via a new @Input property (MapProperty<String, ArtifactFiles> artifactFiles), ensuring the task remains a pure, cacheable function.
  2. Test Infrastructure & Parallelization:

    • Improved Parallelism: Refactored EndToEndTest.kt to remove the previous @Parameterized setup. The tests are now "unrolled" into concrete subclasses, allowing Gradle to execute them in parallel, significantly reducing CI time.
    • Fixed Script Capture: Resolved an issue in build.gradle.kts where the test task's doFirst closure was capturing the Gradle script object, which blocked configuration caching.
    • New Regression Suite: Added DependencyResolutionTest.java to programmatically verify that the resolution engine correctly handles version conflicts, transitives, and scoped dependencies.
    • Absent Report Handling: Added testAbsentDependencyReport to verify correct fallback behavior on debug variants where the AGP dependency report is absent.
  3. Version & Tooling:

    • Bumped version to 0.11.0 for release.
    • Updated AGP to 9.0.1 and Gradle Wrapper to 9.3.1.
    • Maintained Java 17 toolchain parity while ensuring compatibility with legacy Gradle versions (down to 7.5).

Related Changes

  • Preserved 100% behavioral parity with existing license text parsing and deduplication logic.
  • Added ArtifactFiles.groovy data class.
  • Suppressed legacy Protobuf security warnings caused by AGP dependencies.

* Update Android Gradle Plugin to 9.0.1
* Update Gradle Wrapper to 9.3.1
* Update Kotlin plugin to 2.3.10
* Update Protobuf, Guava, Truth, and Gson
* Bump version to 0.10.11-SNAPSHOT
* Remove `markNotCompatibleWithConfigurationCache` from `OssLicensesPlugin`
* Add `testConfigurationCache` to `EndToEndTest` to verify cache reuse
* Suppress Protobuf security warnings in `gradle.properties` caused by AGP dependencies
* Refactor `DependencyUtil` to resolve artifacts using `ArtifactView` and `ArtifactResolutionQuery` instead of accessing `ResolvedConfiguration` directly.
* Pass resolved artifacts as inputs to `LicensesTask` via a new `artifactFiles` property to avoid project access during execution.
* Add `ArtifactFiles` data class to hold resolved POM and library files.
* Update `OssLicensesPlugin` to provide lazy artifact resolution.
* Update `EndToEndTest` to verify configuration cache compatibility.
@timothyfroehlich timothyfroehlich changed the title feat: Add full Gradle Configuration Cache support feat[oss-licences]: Add full Gradle Configuration Cache support Mar 3, 2026
@timothyfroehlich timothyfroehlich changed the title feat[oss-licences]: Add full Gradle Configuration Cache support feat[oss-licenses]: Add full Gradle Configuration Cache support Mar 3, 2026
xyarco
xyarco previously approved these changes Mar 3, 2026
@timothyfroehlich timothyfroehlich requested a review from rlazo March 3, 2026 16:44
rlazo
rlazo previously approved these changes Mar 3, 2026

@rlazo rlazo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with nits

@timothyfroehlich timothyfroehlich dismissed stale reviews from rlazo and xyarco via 1aff4c7 March 3, 2026 19:46
This commit updates the copyright headers for all modified Google-owned
source files to include the 2026 year range, as per Google's open-source
policy. For files missing headers, the original publication year was
retrieved from Git history and used as the start of the range.
@timothyfroehlich timothyfroehlich merged commit dc6599f into google:main Mar 3, 2026
6 checks passed
markNotCompatibleWithConfigurationCache(it)
it.dependenciesJson.set(dependencyTask.flatMap { it.dependenciesJson })

it.artifactFiles.set(project.provider {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapping into a project.provider is usually a code smell for the underlying provider not being lazy.

Why does DependencyUtil.resolveArtifacts not return a lazy FileCollection?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should. I'll follow up with a fix.

timothyfroehlich added a commit that referenced this pull request Mar 10, 2026
This commit modernizes the oss-licenses-plugin for Gradle 9.4 compatibility and enables full support for the Gradle Build Cache.

Key issues resolved:
- Implicit Dependency Failures: Gradle 9.x enforces strict task graph validation. The redundant LicensesCleanUpTask was removed because it conflicted with generation tasks over the build directory. Standard 'clean' now handles this idiomatically.
- Plugin Validation Errors: Modernized tasks to pass 'validatePlugins' by opting into @CacheableTask and implementing property normalization with @PathSensitive.
- Technical Debt: Refactored artifact resolution to use the Lazy Provider API, addressing feedback from PR #365 and improving configuration phase performance.
- Test Matrix: Modernized E2E tests to include AGP 9.0/9.1/9.2 and enabled strict configuration cache validation across the matrix.

Closes #356, #300, #299, #246.
mikepenz added a commit to mikepenz/AboutLibraries that referenced this pull request Apr 13, 2026
…solation compatible

Refactor dependency resolution so all `Project` access happens during the
configuration phase, never at task execution time. The plugin now passes
both `--configuration-cache` and `org.gradle.unsafe.isolated-projects=true`
without discarding the cache entry, while producing byte-for-byte identical
output to the previous implementation.

Approach mirrors google/play-services-plugins#365 (oss-licenses-plugin):

- `BaseAboutLibrariesTask` exposes `configToCoordinateKeys` (`@Input
  MapProperty<String, String>`) populated lazily via
  `resolutionResult.rootComponent.map {}`, encoding coordinates as plain
  strings so the value type stays config-cache serialisable.
- `pomFileMap` (`@Internal MapProperty<String, String>`) holds
  `<group:artifact:version> -> POM file path`, populated via a
  `project.provider {}` that runs `resolvePomFiles` during config-cache
  store. `pomFiles` (`@InputFiles ConfigurableFileCollection`) is derived
  from the same `MapProperty` so the heavy resolution executes exactly
  once per build, while still tracking POM content for UP-TO-DATE checks.
- `resolvePomFiles` walks every selected configuration's
  `ResolvedComponentResult` to gather coordinates (incl. platform/BOM
  dependencies), fetches direct POMs in a single detached configuration,
  then iteratively resolves parent POMs ONE AT A TIME via per-coordinate
  detached configurations. The one-at-a-time fetch is required because
  Gradle silently dedupes overlapping `group:artifact` versions inside a
  single detached configuration, which previously caused parent metadata
  loss for transitives like `guava-parent:26.0-android` vs
  `guava-parent:33.3.1-jre`.
- `MavenXpp3Reader` is used during config phase to extract `<parent>`
  coordinates so the entire parent-POM hierarchy is pre-resolved. The
  execution-time `Maven Model Builder` then loads parents from the
  pre-populated `pomFileMap` instead of an on-demand `ModelResolver` that
  needed `project.dependencies` / `project.configurations`.
- `DependencyCollector` is split into `loadDependencyCoordinates(root)`
  (config time, pure walk over the resolution tree) and
  `loadDependenciesFromCoordinates(coords, pomFileMap)` (execution time,
  pure POM parsing). A defensive `syntheticModelSource` fallback handles
  the rare case of a parent POM that could not be resolved.
- `exclusionPatterns` switches from `SetProperty<Pattern>` to
  `SetProperty<String>` because `java.util.regex.Pattern` is not
  config-cache serialisable; patterns are compiled to `Regex` once per
  `process()` invocation.
- `AboutLibrariesIdTask` reads `configurationNames` instead of the now-
  removed `variantToDependencyData` property.
- `AboutLibrariesExportComplianceTask` moves three previously class-level
  mutable `HashSet`/`HashMap` fields into `action()` locals so the task is
  side-effect-free.
- `Configuration.isTest` no longer traverses `config.hierarchy`, removing
  configuration-graph realisation from the cheap filter step.

Test coverage (37 tests / 0 failures across 6 suites):

- `OutputCorrectnessTest` (new, 13 tests): parent-POM inheritance,
  multi-version parent POM regression, BOM include/exclude with
  `includePlatform`, `compileOnly` / `runtimeOnly` / `api` collection,
  sub-project skipping, deterministic output across runs, exact-version
  pinning, exclusion-regex (incl. alternation), full-field smoke test.
- `ConfigurationCacheTest` adds: multi-version parent POM cache survival,
  end-to-end project-isolation test (`org.gradle.unsafe.isolated-projects`
  + `--configuration-cache`).
- `PerformanceTest` threshold raised to 30s for cold-daemon environments.
- All test files updated to gson 2.11.0 / slf4j 2.0.16 / jackson 2.18.2.
- `FunctionalTest` filename corrected to `aboutlibraries.json`.

End-to-end verification: `gradle exportLibraryDefinitions
--configuration-cache` with `org.gradle.unsafe.isolated-projects=true`
on a 25-library project (incl. platform BOM, compileOnly, runtimeOnly)
produces an MD5-identical 697-line JSON to the OLD implementation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mikepenz added a commit to mikepenz/AboutLibraries that referenced this pull request Apr 13, 2026
…solation compatible

Refactor dependency resolution so all `Project` access happens during the
configuration phase, never at task execution time. The plugin now passes
both `--configuration-cache` and `org.gradle.unsafe.isolated-projects=true`
without discarding the cache entry, while producing byte-for-byte identical
output to the previous implementation.

Approach mirrors google/play-services-plugins#365 (oss-licenses-plugin):

- `BaseAboutLibrariesTask` exposes `configToCoordinateKeys` (`@Input
  MapProperty<String, String>`) populated lazily via
  `resolutionResult.rootComponent.map {}`, encoding coordinates as plain
  strings so the value type stays config-cache serialisable.
- `pomFileMap` (`@Internal MapProperty<String, String>`) holds
  `<group:artifact:version> -> POM file path`, populated via a
  `project.provider {}` that runs `resolvePomFiles` during config-cache
  store. `pomFiles` (`@InputFiles ConfigurableFileCollection`) is derived
  from the same `MapProperty` so the heavy resolution executes exactly
  once per build, while still tracking POM content for UP-TO-DATE checks.
- `resolvePomFiles` walks every selected configuration's
  `ResolvedComponentResult` to gather coordinates (incl. platform/BOM
  dependencies), fetches direct POMs in a single detached configuration,
  then iteratively resolves parent POMs ONE AT A TIME via per-coordinate
  detached configurations. The one-at-a-time fetch is required because
  Gradle silently dedupes overlapping `group:artifact` versions inside a
  single detached configuration, which previously caused parent metadata
  loss for transitives like `guava-parent:26.0-android` vs
  `guava-parent:33.3.1-jre`.
- `MavenXpp3Reader` is used during config phase to extract `<parent>`
  coordinates so the entire parent-POM hierarchy is pre-resolved. The
  execution-time `Maven Model Builder` then loads parents from the
  pre-populated `pomFileMap` instead of an on-demand `ModelResolver` that
  needed `project.dependencies` / `project.configurations`.
- `DependencyCollector` is split into `loadDependencyCoordinates(root)`
  (config time, pure walk over the resolution tree) and
  `loadDependenciesFromCoordinates(coords, pomFileMap)` (execution time,
  pure POM parsing). A defensive `syntheticModelSource` fallback handles
  the rare case of a parent POM that could not be resolved.
- `exclusionPatterns` switches from `SetProperty<Pattern>` to
  `SetProperty<String>` because `java.util.regex.Pattern` is not
  config-cache serialisable; patterns are compiled to `Regex` once per
  `process()` invocation.
- `AboutLibrariesIdTask` reads `configurationNames` instead of the now-
  removed `variantToDependencyData` property.
- `AboutLibrariesExportComplianceTask` moves three previously class-level
  mutable `HashSet`/`HashMap` fields into `action()` locals so the task is
  side-effect-free.
- `Configuration.isTest` no longer traverses `config.hierarchy`, removing
  configuration-graph realisation from the cheap filter step.

Test coverage (37 tests / 0 failures across 6 suites):

- `OutputCorrectnessTest` (new, 13 tests): parent-POM inheritance,
  multi-version parent POM regression, BOM include/exclude with
  `includePlatform`, `compileOnly` / `runtimeOnly` / `api` collection,
  sub-project skipping, deterministic output across runs, exact-version
  pinning, exclusion-regex (incl. alternation), full-field smoke test.
- `ConfigurationCacheTest` adds: multi-version parent POM cache survival,
  end-to-end project-isolation test (`org.gradle.unsafe.isolated-projects`
  + `--configuration-cache`).
- `PerformanceTest` threshold raised to 30s for cold-daemon environments.
- All test files updated to gson 2.11.0 / slf4j 2.0.16 / jackson 2.18.2.
- `FunctionalTest` filename corrected to `aboutlibraries.json`.

End-to-end verification: `gradle exportLibraryDefinitions
--configuration-cache` with `org.gradle.unsafe.isolated-projects=true`
on a 25-library project (incl. platform BOM, compileOnly, runtimeOnly)
produces an MD5-identical 697-line JSON to the OLD implementation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@timothyfroehlich timothyfroehlich deleted the froeht/config-cache branch April 20, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants