Skip to content

Conversation

lmcrean
Copy link
Contributor

@lmcrean lmcrean commented Sep 14, 2025

fixes #6666

Problem

Users can accidentally include duplicate Guava artifacts (guava-jdk5, guava-base, sisu-guava, etc.) alongside the main Guava library, causing classpath conflicts and runtime issues that are difficult to debug.

Solution

Declare that Guava provides the capabilities of known duplicate artifacts in module.json, following the existing google-collections pattern. This enables Gradle to detect and report conflicts at build time.

Changes

  • Added capability declarations for 4 duplicate Guava artifacts in all 4 variant sections of module.json:
    • com.google.guava:guava-base
    • com.google.guava:guava-jdk5
    • org.sonatype.sisu:sisu-guava
    • org.hudsonci.lib.guava:guava

Note: The servicemix capability was removed after testing revealed it caused conflicts between Gradle variants.

Testing

# Build and install locally with module metadata
./mvnw install -pl guava -DskipTests -q

# Verify module metadata contains capability declarations
grep -E "(guava-base|guava-jdk5|sisu-guava|hudsonci)" \
  ~/.m2/repository/com/google/guava/guava/999.0.0-HEAD-jre-SNAPSHOT/guava-999.0.0-HEAD-jre-SNAPSHOT.module
# Expected: 20 lines (3 artifacts × 4 = 12, hudsonci × 4 = 4, total = 16 capability lines + 4 group lines)

# For Gradle users - test conflict detection after release
# Note: This will only show conflicts once the changes are in a released version
# The SNAPSHOT version may not resolve correctly from mavenLocal()
mkdir test-guava-conflict && cd test-guava-conflict
cat > build.gradle << 'EOF'
plugins { id 'java' }
repositories { mavenCentral() }
dependencies {
    implementation 'com.google.guava:guava:NEXT_RELEASE_VERSION'
    implementation 'com.google.guava:guava-jdk5:17.0'
}
EOF
gradle dependencies --configuration compileClasspath
# Expected after release: Capability conflict error
# Current behavior (without these changes): Both dependencies resolve without conflict

Breaking Changes

Builds that currently (incorrectly) include both Guava and duplicate artifacts will now fail with a capability conflict error. Users must resolve by excluding the duplicate artifact or using Gradle's capability resolution.

Why this breaking change is necessary:

  • Prevents silent runtime failures (NoSuchMethodError, ClassNotFoundException)
  • Having duplicate Guava classes leads to unpredictable classloading behavior
  • Build-time failure is preferable to production runtime failure
  • Follows the established pattern already used for google-collections
  • Simple fix: exclude the duplicate or explicitly choose which one to use
  • "Fail fast, fail loud, fail at build time - not in production"

This update introduces several new Guava-related dependencies in the module.json file, including guava-base, guava-jdk5, sisu-guava, and others. These additions enhance the project's support for Guava libraries.

RELNOTES=n/a
@cpovirk cpovirk self-assigned this Sep 14, 2025
copybara-service bot pushed a commit that referenced this pull request Sep 14, 2025
Fixes #7990
Fixes #6666

RELNOTES=n/a
PiperOrigin-RevId: 806974529
@cpovirk
Copy link
Member

cpovirk commented Sep 14, 2025

Thanks. I took the liberty of pulling some more items from #6666 in #7991. (Because of the specifics of how our process works, we always submit an internal CL, which generates a new GitHub PR, but it still gets attributed to the original contributor.) If you don't want your name to show up on the additions there, let me know, and I can add them in a separate change.

@cpovirk
Copy link
Member

cpovirk commented Sep 15, 2025

Huh, somehow this breaks the Gradle integration test:

> Task :androidAndroidConstraintCompileClasspathJava:testClasspath FAILED
 
FAILURE: Build failed with an exception.
 
* What went wrong:
Execution failed for task ':androidAndroidConstraintCompileClasspathJava:testClasspath'.
> Could not resolve all files for configuration ':androidAndroidConstraintCompileClasspathJava:compileClasspath'.
   > Could not resolve com.google.collections:google-collections:1.0.
     Required by:
         project :androidAndroidConstraintCompileClasspathJava
      > Module 'com.google.guava:guava' has been rejected:
           Cannot select module with conflict on capability 'org.apache.servicemix.bundles:org.apache.servicemix.bundles.guava:999.0.0-HEAD-android-SNAPSHOT' also provided by [com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(jreApiElements), com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(androidApiElements)]
   > Could not resolve com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT.
     Required by:
         project :androidAndroidConstraintCompileClasspathJava
      > Module 'com.google.guava:guava' has been rejected:
           Cannot select module with conflict on capability 'org.apache.servicemix.bundles:org.apache.servicemix.bundles.guava:999.0.0-HEAD-android-SNAPSHOT' also provided by [com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(jreApiElements), com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(androidApiElements)]
   > Could not resolve com.google.guava:guava.
     Required by:
         project :androidAndroidConstraintCompileClasspathJava
      > Module 'com.google.guava:guava' has been rejected:
           Cannot select module with conflict on capability 'org.apache.servicemix.bundles:org.apache.servicemix.bundles.guava:999.0.0-HEAD-android-SNAPSHOT' also provided by [com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(jreApiElements), com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(androidApiElements)]

Any theories?

@cpovirk
Copy link
Member

cpovirk commented Sep 15, 2025

Sorry, I forgot to say:

That's https://github.com/google/guava/blob/master/util/gradle_integration_tests.sh. You might need to set JAVA_HOME to JDK11 or at least some version close to that.

@lmcrean
Copy link
Contributor Author

lmcrean commented Sep 21, 2025

@cpovirk Thanks for pointing me to the Gradle integration test

Any Theories?

What I found: The servicemix capability declaration creates an internal conflict within Guava's module metadata. Since all variants (jreApiElements, androidApiElements, etc.) declare they provide the same capability, Gradle rejects the entire module before it can apply variant selection rules.

I noticed the build config has special capability resolution logic for google-collections but not for servicemix:
https://github.com/google/guava/blob/master/integration-tests/gradle/build.gradle.kts#L115-L126

withCapability("com.google.collections:google-collections") {
  candidates
    .find {
      val idField = it.javaClass.getDeclaredMethod("getId")
      (idField.invoke(it) as ModuleComponentIdentifier).module == "guava"
    }
    ?.apply { select(this) }
}

Rather than adding similar logic for servicemix, I've removed its capability declaration while keeping the other 4 capabilities.

The specific failing test now passes:

> Task :androidAndroidConstraintCompileClasspathJava:testClasspath
BUILD SUCCESSFUL in 12s

This seems like a reasonable compromise - we still get duplicate detection for the other artifacts while avoiding the variant conflict issue.

@cpovirk
Copy link
Member

cpovirk commented Sep 25, 2025

Thanks! Hmm, I wonder what's special about that one? Really, I wonder if that's the one that's behaving as expected: Maybe Gradle should be forcing us to resolve the conflict for all the libraries? Maybe, to make that happen, we need to list the various conflicting libraries as dependencies in the integration tests and probably as extraLegacyDependencies, too? And then it would force us to make the capability-resolution edit that you're suggesting?

But wait, maybe we're seeing the conflict even in the PR's current form?

Execution failed for task ':androidAndroidConstraintCompileClasspathJava:testClasspath'.
> Could not resolve all files for configuration ':androidAndroidConstraintCompileClasspathJava:compileClasspath'.
   > Could not resolve com.google.collections:google-collections:1.0.
     Required by:
         project :androidAndroidConstraintCompileClasspathJava
      > Module 'com.google.guava:guava' has been rejected:
           Cannot select module with conflict on capability 'org.hudsonci.lib.guava:guava:999.0.0-HEAD-android-SNAPSHOT' also provided by [com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(jreApiElements), com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(androidApiElements)]
   > Could not resolve com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT.
     Required by:
         project :androidAndroidConstraintCompileClasspathJava
      > Module 'com.google.guava:guava' has been rejected:
           Cannot select module with conflict on capability 'org.hudsonci.lib.guava:guava:999.0.0-HEAD-android-SNAPSHOT' also provided by [com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(jreApiElements), com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(androidApiElements)]
   > Could not resolve com.google.guava:guava.
     Required by:
         project :androidAndroidConstraintCompileClasspathJava
      > Module 'com.google.guava:guava' has been rejected:
           Cannot select module with conflict on capability 'org.hudsonci.lib.guava:guava:999.0.0-HEAD-android-SNAPSHOT' also provided by [com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(jreApiElements), com.google.guava:guava:999.0.0-HEAD-android-SNAPSHOT(androidApiElements)]

I wonder if the single conflict created by pulling in google-collections turns out to be enough to trigger the whole thing? If so, maybe we need only the capability-resolution edit that you're suggesting?

@cpovirk cpovirk added P3 no SLO and removed P2 labels Oct 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Look for other Maven artifacts that contain Guava classes, and list them in our metadata
2 participants