Skip to content

fix: preserve classes across artifacts with shared coordinates - #1809

Closed
andrewparmet wants to merge 2 commits into
autonomousapps:mainfrom
andrewparmet:test-preserve-binary-classes-for-duplicate-coordinates
Closed

fix: preserve classes across artifacts with shared coordinates#1809
andrewparmet wants to merge 2 commits into
autonomousapps:mainfrom
andrewparmet:test-preserve-binary-classes-for-duplicate-coordinates

Conversation

@andrewparmet

@andrewparmet andrewparmet commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Problem

Gradle can resolve multiple physical artifacts for the same logical component and capability. One example is a test-fixtures variant whose Kotlin and Java classes are exposed as separate class directories but carry the same dependency coordinates.

JarExploder.binaryClasses() converted exploded artifacts to a Map<Coordinates, Set<BinaryClass>> with associate. When two artifacts had the same coordinates, the second set replaced the first. Dependency synthesis still retained the union of classes from both artifacts, which left the two views of the dependency inconsistent:

  1. dependency synthesis recorded classes from both physical artifacts
  2. the coordinate-keyed binary class map retained classes from only one artifact
  3. superclass analysis requested a recorded class that was missing from the map and threw NoSuchElementException

Triggering build

This can happen whenever one component exposes separate physical artifacts under the same dependency coordinates.

producer/build.gradle.kts:

plugins {
  kotlin("jvm")
  `java-test-fixtures`
}

The producer contains one fixture in src/testFixtures/kotlin and another in src/testFixtures/java.

Our build also relocates those outputs:

sourceSets.named("testFixtures") {
  java.destinationDirectory.set(layout.buildDirectory.dir("testFixtures-classes/java"))
}
kotlin.sourceSets.named("testFixtures") {
  kotlin.destinationDirectory.set(layout.buildDirectory.dir("testFixtures-classes/kotlin"))
}

We do this to avoid source-set contamination. Without the relocation, DAGP analyzes compiled test-fixture classes as part of the producer's main capability, so dependency advice can depend on whether the fixtures have already been compiled. I have #1810 to remove the need for this workaround.

consumer/build.gradle.kts:

plugins {
  id("com.android.library")
  kotlin("android")
}

dependencies {
  testImplementation(testFixtures(project(":producer")))
}

Analysis resolves the relocated Kotlin and Java fixture outputs as separate class-directory artifacts with the same component coordinates. The relocation does not cause the aggregation bug, but it makes the duplicate-coordinate case observable.

Checking super classes enables the code path that fails today:

dependencyAnalysis {
  usage {
    analysis {
      checkSuperClasses(true)
    }
  }
}

Change

  • merge binary class sets when multiple physical artifacts share dependency coordinates
  • cover the aggregation boundary with a unit test
  • cover the producer/consumer scenario with a functional test

@autonomousapps

autonomousapps commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Thanks for the PR. In the future, please create an issue before filing a PR.

I'm not sure about this solution. I'm also a little confused about the analysis. You say this about your case:

One example is a test-fixtures variant whose Kotlin and Java classes are exposed as separate class directories but carry the same dependency coordinates.

I've never seen such a thing. Could you describe it in more detail? What are you achieving with this, and how are you configuring it? Are you setting separate capabilities on these different things? I think I'm missing something, and an issue that fully describes the use case would be helpful.

Finally, because you're a relatively new contributor to this repository, please attest that you've read the code of conduct and in particular the section strictly prohibiting usage of LLMs for issues and pull requests.

@andrewparmet

andrewparmet commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

I'll start again from an issue! Yes, I have read the code of conduct.

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.

3 participants