Conversation
Per-project analysis can't see when a declared-but-unreferenced dependency is required by a downstream consumer. If :middle declares `api project(':producer')` without referencing it, and :consumer uses a type :producer publishes through :middle, removing :producer from :middle breaks :consumer. The per-project view flags :producer as unused; it isn't.
Add an opt-in root task that builds a cross-project TransitiveExposureIndex and filters out these false-positive removals. Off by default; enable with -Ddependency.analysis.transitive.exposure=true. When disabled, generateBuildHealth consumes the per-project artifacts exactly as before.
The graph reasoning lives in TransitiveExposureIndex (no Gradle types, unit-tested in isolation); the task is wiring. Also covers two related cases the downstream check alone misses: ABI leakage upstream, and assembly/fat-jar modules that declare bundled deps on purpose.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Per-project analysis can't see when a declared-but-unreferenced dependency is required by a downstream consumer. If :middle declares
api project(':producer')without referencing it, and :consumer uses a type :producer publishes through :middle, removing :producer from :middle breaks :consumer. The per-project view flags :producer as unused; it isn't.Add an opt-in root task that builds a cross-project TransitiveExposureIndex and filters out these false-positive removals. Off by default; enable with -Ddependency.analysis.transitive.exposure=true. When disabled, generateBuildHealth consumes the per-project artifacts exactly as before.
The graph reasoning lives in TransitiveExposureIndex (no Gradle types, unit-tested in isolation); the task is wiring. Also covers two related cases the downstream check alone misses: ABI leakage upstream, and assembly/fat-jar modules that declare bundled deps on purpose.
Context
Dependency analysis runs per project, so it can't tell when a dependency a project declares but never directly uses is actually needed.
Lets just say that
:middledeclaresapi project(':producer')but doesn't reference it.:consumerdepends on:middleand uses a type that:producerexposes through:middle.Looking at
:middleon its own,:producerlooks unused, so buildHealth tells you to remove it.Do that and
:consumerstops compiling.This shows up a lot in multi-module builds that layer dependencies with
apiandimplementation, especially big monorepos.Right now you can't trust a removal suggestion without checking every downstream consumer by hand first.
This adds an opt-in root task that builds a cross project index and drops these false positives. It's off by default.
Turn it on with
-Ddependency.analysis.transitive.exposure=true. When it's off,generateBuildHealthworks exactly like it does today.Contributor Checklist
src/functionalTest) to verify changes from a user perspective.src/test) to verify logic../gradlew test../gradlew :functionalTest -DfuncTest.quick.