diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/Artifact.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/Artifact.java index ecd0a94b4..92babce14 100644 --- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/Artifact.java +++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/Artifact.java @@ -24,14 +24,21 @@ public class Artifact { private final Coordinates coordinates; private final Set exclusions; + private final boolean forceVersion; public Artifact(Coordinates coordinates, Coordinates... exclusions) { this(coordinates, ImmutableSet.copyOf(exclusions)); } public Artifact(Coordinates coordinates, Collection exclusions) { + this(coordinates, exclusions, false); + } + + public Artifact( + Coordinates coordinates, Collection exclusions, boolean forceVersion) { this.coordinates = Objects.requireNonNull(coordinates); this.exclusions = ImmutableSet.copyOf(exclusions); + this.forceVersion = forceVersion; } public Coordinates getCoordinates() { @@ -42,6 +49,10 @@ public Set getExclusions() { return exclusions; } + public boolean isForceVersion() { + return forceVersion; + } + @Override public String toString() { if (exclusions.isEmpty()) { diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ConfigArtifact.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ConfigArtifact.java index 053a5ad42..18e817584 100644 --- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ConfigArtifact.java +++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ConfigArtifact.java @@ -28,6 +28,7 @@ public class ConfigArtifact { private String packaging; private String version; private Set exclusions; + private boolean force_version; public String getGroupId() { return group; @@ -56,4 +57,8 @@ public Set getExclusions() { return exclusions.stream().map(Coordinates::new).collect(ImmutableSet.toImmutableSet()); } + + public boolean isForceVersion() { + return force_version; + } } diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ResolverConfig.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ResolverConfig.java index a2967027f..c0cd74569 100644 --- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ResolverConfig.java +++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/cmd/ResolverConfig.java @@ -177,11 +177,10 @@ public ResolverConfig(EventListener listener, String... args) throws IOException art.getExtension(), art.getClassifier(), art.getVersion()); - request.addArtifact( - coords.toString(), - art.getExclusions().stream() - .map(c -> c.getGroupId() + ":" + c.getArtifactId()) - .toArray(String[]::new)); + com.github.bazelbuild.rules_jvm_external.resolver.Artifact artifact = + new com.github.bazelbuild.rules_jvm_external.resolver.Artifact( + coords, art.getExclusions(), art.isForceVersion()); + request.addArtifact(artifact); }); } diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/gradle/GradleResolver.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/gradle/GradleResolver.java index 8d928bb75..6ce11c30c 100644 --- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/gradle/GradleResolver.java +++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/gradle/GradleResolver.java @@ -496,10 +496,18 @@ private GradleDependencyImpl createDependency(Artifact artifact) { exclusion -> { exclusions.add(new ExclusionImpl(exclusion.getGroupId(), exclusion.getArtifactId())); }); + + // When force_version is true, use Gradle's !! shorthand which is equivalent to strictly() + // This forces the exact version and prevents transitive dependencies from overriding it + String version = coordinates.getVersion(); + if (artifact.isForceVersion() && version != null && !version.isEmpty()) { + version = version + "!!"; + } + return new GradleDependencyImpl( coordinates.getGroupId(), coordinates.getArtifactId(), - coordinates.getVersion(), + version, exclusions, coordinates.getClassifier(), coordinates.getExtension()); diff --git a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/maven/MavenResolver.java b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/maven/MavenResolver.java index 24ed22917..82bfe74fe 100644 --- a/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/maven/MavenResolver.java +++ b/private/tools/java/com/github/bazelbuild/rules_jvm_external/resolver/maven/MavenResolver.java @@ -118,13 +118,20 @@ private Dependency createBom( private Dependency createDependency( com.github.bazelbuild.rules_jvm_external.resolver.Artifact source) { Coordinates coords = source.getCoordinates(); + + // When force_version is true, use Maven version range syntax [version] to force exact version + String version = coords.getVersion(); + if (source.isForceVersion() && version != null && !version.isEmpty()) { + version = "[" + version + "]"; + } + Artifact artifact = new DefaultArtifact( coords.getGroupId(), coords.getArtifactId(), coords.getClassifier(), coords.getExtension(), - coords.getVersion()); + version); Set excluded = source.getExclusions().stream().map(this::createExclusion).collect(Collectors.toSet()); diff --git a/tests/com/github/bazelbuild/rules_jvm_external/resolver/ResolverTestBase.java b/tests/com/github/bazelbuild/rules_jvm_external/resolver/ResolverTestBase.java index 6e9f318a9..0ca57667a 100644 --- a/tests/com/github/bazelbuild/rules_jvm_external/resolver/ResolverTestBase.java +++ b/tests/com/github/bazelbuild/rules_jvm_external/resolver/ResolverTestBase.java @@ -791,6 +791,47 @@ public boolean checkCredentials(String username, String pwd) { assertEquals(coords, resolved.nodes().iterator().next()); } + @Test + public void shouldRespectForceVersionWhenResolvingConflicts() { + // When force_version is set on a lower version, it should win over the higher version + // that would normally be selected during version conflict resolution. + Coordinates lowerVersion = new Coordinates("com.example:forced:1.0"); + Coordinates higherVersion = new Coordinates("com.example:forced:2.0"); + Coordinates dependsOnLower = new Coordinates("com.example:uses-lower:1.0"); + Coordinates dependsOnHigher = new Coordinates("com.example:uses-higher:1.0"); + + Path repo = + MavenRepo.create() + .add(lowerVersion) + .add(higherVersion) + .add(dependsOnLower, lowerVersion) + .add(dependsOnHigher, higherVersion) + .getPath(); + + // Create a request that forces the lower version using the new forceVersion capability + ResolutionRequest request = new ResolutionRequest().addRepository(repo.toUri()); + request.addArtifact(dependsOnLower.toString()); + // Force the lower version by creating an Artifact with forceVersion=true + Artifact forcedArtifact = new Artifact(lowerVersion, Set.of(), true); + request.addArtifact(forcedArtifact); + request.addArtifact(dependsOnHigher.toString()); + + // Without force_version implementation, this test will fail for Gradle resolver + // because it will resolve to the higher version (2.0) instead of the forced lower version (1.0) + Graph graph = resolver.resolve(request).getResolution(); + + Set forcedNodes = + graph.nodes().stream() + .filter(c -> "forced".equals(c.getArtifactId())) + .collect(Collectors.toSet()); + + assertEquals("Should resolve to exactly one version", 1, forcedNodes.size()); + + assertTrue( + "Should resolve to forced lower version when force_version is set", + forcedNodes.contains(lowerVersion)); + } + protected ResolutionRequest prepareRequestFor(URI repo, Coordinates... coordinates) { ResolutionRequest request = new ResolutionRequest().addRepository(repo); for (Coordinates coordinate : coordinates) { diff --git a/tests/custom_maven_install/regression_testing_gradle_index.json b/tests/custom_maven_install/regression_testing_gradle_index.json index 6c4961a06..c30925b24 100644 --- a/tests/custom_maven_install/regression_testing_gradle_index.json +++ b/tests/custom_maven_install/regression_testing_gradle_index.json @@ -1,9 +1,6 @@ { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", "packages": { - "androidx.annotation:annotation-jvm": [ - "androidx.annotation" - ], "androidx.arch.core:core-common": [ "androidx.arch.core.internal", "androidx.arch.core.util" @@ -37,25 +34,6 @@ "com.google.guava:listenablefuture": [ "com.google.common.util.concurrent" ], - "com.squareup.okhttp3:okhttp": [ - "okhttp3", - "okhttp3.internal", - "okhttp3.internal.authenticator", - "okhttp3.internal.cache", - "okhttp3.internal.cache2", - "okhttp3.internal.concurrent", - "okhttp3.internal.connection", - "okhttp3.internal.http", - "okhttp3.internal.http1", - "okhttp3.internal.http2", - "okhttp3.internal.io", - "okhttp3.internal.platform", - "okhttp3.internal.platform.android", - "okhttp3.internal.proxy", - "okhttp3.internal.publicsuffix", - "okhttp3.internal.tls", - "okhttp3.internal.ws" - ], "com.squareup.okio:okio-jvm": [ "okio", "okio.internal" @@ -136,6 +114,134 @@ ] }, "split_package_classes": { + "androidx.annotation:annotation": { + "androidx.annotation": [ + "AnimRes", + "AnimatorRes", + "AnyRes", + "AnyThread", + "ArrayRes", + "AttrRes", + "BinderThread", + "BoolRes", + "CallSuper", + "CheckResult", + "ChecksSdkIntAtLeast", + "ColorInt", + "ColorLong", + "ColorRes", + "ContentView", + "DimenRes", + "Dimension", + "DoNotInline", + "DrawableRes", + "FloatRange", + "FontRes", + "FractionRes", + "GuardedBy", + "HalfFloat", + "IdRes", + "InspectableProperty", + "IntDef", + "IntRange", + "IntegerRes", + "InterpolatorRes", + "Keep", + "LayoutRes", + "LongDef", + "MainThread", + "MenuRes", + "NavigationRes", + "NonNull", + "Nullable", + "PluralsRes", + "Px", + "RawRes", + "RequiresApi", + "RequiresFeature", + "RequiresPermission", + "RestrictTo", + "Size", + "StringDef", + "StringRes", + "StyleRes", + "StyleableRes", + "TransitionRes", + "UiThread", + "VisibleForTesting", + "WorkerThread", + "XmlRes" + ] + }, + "androidx.annotation:annotation-jvm": { + "androidx.annotation": [ + "AnimRes", + "AnimatorRes", + "AnyRes", + "AnyThread", + "ArrayRes", + "AttrRes", + "BinderThread", + "BoolRes", + "CallSuper", + "CheckResult", + "ChecksSdkIntAtLeast", + "ColorInt", + "ColorLong", + "ColorRes", + "ContentView", + "DeprecatedSinceApi", + "DimenRes", + "Dimension", + "Discouraged", + "DisplayContext", + "DoNotInline", + "DrawableRes", + "EmptySuper", + "FloatRange", + "FontRes", + "FractionRes", + "GravityInt", + "GuardedBy", + "HalfFloat", + "IdRes", + "InspectableProperty", + "IntDef", + "IntRange", + "IntegerRes", + "InterpolatorRes", + "Keep", + "LayoutRes", + "LongDef", + "MainThread", + "MenuRes", + "NavigationRes", + "NonNull", + "NonUiContext", + "Nullable", + "OpenForTesting", + "PluralsRes", + "Px", + "RawRes", + "RequiresApi", + "RequiresExtension", + "RequiresFeature", + "RequiresPermission", + "RestrictTo", + "ReturnThis", + "Size", + "StringDef", + "StringRes", + "StyleRes", + "StyleableRes", + "TransitionRes", + "UiContext", + "UiThread", + "VisibleForTesting", + "WorkerThread", + "XmlRes" + ] + }, "androidx.collection:collection": { "androidx.collection": [ "ArrayMap", diff --git a/tests/custom_maven_install/regression_testing_gradle_install.json b/tests/custom_maven_install/regression_testing_gradle_install.json index 0476c3489..deced3521 100644 --- a/tests/custom_maven_install/regression_testing_gradle_install.json +++ b/tests/custom_maven_install/regression_testing_gradle_install.json @@ -1,11 +1,11 @@ { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", "__INPUT_ARTIFACTS_HASH": 1541018928, - "__RESOLVED_ARTIFACTS_HASH": 128343746, + "__RESOLVED_ARTIFACTS_HASH": 414364530, "artifacts": { "androidx.activity:activity-ktx:aar": { "shasums": { - "jar": "b0b4206ece92919925061fdf5784dd21f0118534609e8f6d9404bdd0f5cb5a3d" + "jar": "eeac7fea684727bfe1f339ee86ccca16518eafb24f02f1997dd7503911f40e63" }, "version": "1.7.2" }, @@ -17,7 +17,7 @@ }, "androidx.annotation:annotation": { "shasums": { - "jar": "fbc64f5c44a7added8b6eab517cf7d70555e25153bf5d44a6ed9b0e5312f7de9" + "jar": "9029262bddce116e6d02be499e4afdba21f24c239087b76b3b57d7e98b490a36" }, "version": "1.6.0" }, @@ -65,13 +65,13 @@ }, "androidx.compose.animation:animation-core:aar": { "shasums": { - "jar": "e1a44dc558d1966e3345f5c51f63afaaf2b5aeea20f478f026594363ae910675" + "jar": "53413f7e572f1410930826d63a027c73288ce4c1269591ebc503a56ba163f75b" }, "version": "1.2.1" }, "androidx.compose.foundation:foundation-layout": { "shasums": { - "jar": "f8cbd92d3585570dce2541aefef0d59b4c591ae225d98921058e484e9abf3e42" + "jar": "79af00811d834b966f383fb39454cff06e4a0045e5180eac5394d72fab9a1c97" }, "version": "1.5.0-beta01" }, @@ -239,13 +239,13 @@ }, "androidx.core:core:aar": { "shasums": { - "jar": "bc3f782e107b2d40cca9f0319d52e7343a0406e06febd42f9f2b509c3123438c" + "jar": "3c60213a2a2116a9314d3f4f9b6e7c32505353fa1143919bfb59366fef825151" }, "version": "1.11.0-beta01" }, "androidx.customview:customview-poolingcontainer:aar": { "shasums": { - "jar": "3584102fc49bf399c56e3b7be4bfe12000c46112320cd8cf85cc0a8f93f3e752" + "jar": "9fa62196c4c4e79b6a58dbb38a8c022bef8db21989d7dae2da0f6f5b66289a9c" }, "version": "1.0.0" }, @@ -257,13 +257,13 @@ }, "androidx.emoji2:emoji2:aar": { "shasums": { - "jar": "f31a06c150ecb03073f55a6f7b0b74a240a6a8d727c14ce76726d020570dfa8c" + "jar": "faf5955d2c9b09bba92e51569f2b928419ded05b5c9649c0453c925c7da62360" }, "version": "1.2.0" }, "androidx.fragment:fragment-ktx:aar": { "shasums": { - "jar": "3e4595cda9bee392715959366d70790704ff240c77656e702f3c8220b3d9245a" + "jar": "49222903181d392cece82b5146767d4c0b0c71287376befe6830ec64a01ca17d" }, "version": "1.6.1" }, @@ -323,7 +323,7 @@ }, "androidx.lifecycle:lifecycle-runtime:aar": { "shasums": { - "jar": "4867fd5279742fba8388821930cb2affe06d81a52814e7e41e70392ea0ef887c" + "jar": "41e71d003d437f169d4e8ada59cb1982f1b47b7d25c23010b8fc81d528a740d5" }, "version": "2.6.1" }, @@ -341,7 +341,7 @@ }, "androidx.lifecycle:lifecycle-viewmodel:aar": { "shasums": { - "jar": "e4ff4338999e1c6c9c724719f5d4aa7dd61bf6f545d5256a27a9d375df9f2330" + "jar": "dc2e8e65db53d21659a27ed93b966dee336c1c2e8e872861677389a99b8b9134" }, "version": "2.6.1" }, @@ -353,13 +353,13 @@ }, "androidx.profileinstaller:profileinstaller:aar": { "shasums": { - "jar": "34e8b2bfc74e23c1525e3da903ae449b7f1b440aef45e18159ee470e91997f48" + "jar": "12cbc01c8a5ae4b412caf3c5a0662ccd28002dedcff1dbe6c2ab3665be9f758c" }, "version": "1.3.0" }, "androidx.savedstate:savedstate-ktx:aar": { "shasums": { - "jar": "8553f87e7136c24ec5243560f48f1c32cba56daa77722f89589a5cafcb8f7894" + "jar": "d09553211f6703b19aefd608d6007c77132d27a281556a8e689d92764ec27d3b" }, "version": "1.2.1" }, @@ -401,7 +401,7 @@ }, "com.almworks.sqlite4java:libsqlite4java-linux-i386:so": { "shasums": { - "jar": "3c93ee3f997e957715fd08b263948d460da000a6e0bb904ae525e790f39429eb" + "jar": "85c80b7c2905db9edabbac0d58c738da3107ae7e09454448c700e157490558ad" }, "version": "1.0.392" }, @@ -419,7 +419,7 @@ }, "com.squareup.okhttp3:okhttp": { "shasums": { - "jar": "b1050081b14bb7a3a7e55a4d3ef01b5dcfabc453b4573a4fc019767191d5f4e0" + "jar": "7c737040a9419522e7c50cc027416a70fe7c7629e52b219935ade6b4119c7d38" }, "version": "4.12.0" }, @@ -526,7 +526,7 @@ "org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21": "org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21", "org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21": "org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21": "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10", - "org.jetbrains.kotlin:kotlin-stdlib:1.8.0": "org.jetbrains.kotlin:kotlin-stdlib:1.8.21", + "org.jetbrains.kotlin:kotlin-stdlib:1.8.0": "org.jetbrains.kotlin:kotlin-stdlib:1.9.10", "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1": "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1": "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" },