Skip to content

feat: suppress removal advice for transitively-exposed dependencies - #1905

Open
marceltft wants to merge 1 commit into
autonomousapps:mainfrom
appian:AP-70473-transitive-analysis-fix
Open

marceltft wants to merge 1 commit into
autonomousapps:mainfrom
appian:AP-70473-transitive-analysis-fix

Conversation

@marceltft

Copy link
Copy Markdown

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 :middle declares api project(':producer') but doesn't reference it. :consumer depends on :middle and uses a type that :producer exposes through :middle.
Looking at :middle on its own, :producer looks unused, so buildHealth tells you to remove it.
Do that and :consumer stops compiling.

This shows up a lot in multi-module builds that layer dependencies with api and implementation, 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, generateBuildHealth works exactly like it does today.

Contributor Checklist

  • I have read the Contributing guide.
  • I have read the Code of Conduct.
  • No part of this pull request was created with an LLM/AI.
  • All contributed code can be distributed under the terms of the Apache License 2.0, e.g. the code was written by yourself or the original code is licensed under a license compatible to Apache License 2.0.
  • Check "Allow edit from maintainers" option in pull request so that additional changes can be pushed by project maintainers.
  • Provide functional tests (under src/functionalTest) to verify changes from a user perspective.
  • Provide unit tests (under src/test) to verify logic.
  • Ensure that unit tests pass: ./gradlew test.
  • Ensure that functional tests pass: ./gradlew :functionalTest -DfuncTest.quick.

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.
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.

1 participant