Skip to content

Add global consistent resolution feature #138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# JVM Dependency Conflict Resolution Gradle plugin - Changelog

## Version 2.1
* [New] [102](https://github.com/gradlex-org/jvm-dependency-conflict-resolution/issues/102) Global consistent resolution feature
* [New Rule] [#125](https://github.com/gradlex-org/jvm-dependency-conflict-resolution/issues/125) mysql:mysql-connector-java / com.mysql:mysql-connector-j (Thanks [Eduardo Acosta Miguens](https://github.com/eduacostam)!)
* [New Rule] [#131](https://github.com/gradlex-org/jvm-dependency-conflict-resolution/issues/131) org.json:json / com.vaadin.external.google:android-json (Thanks [Piotr Kubowicz](https://github.com/pkubowicz)!)
* [Adjusted Rule] [#130](https://github.com/gradlex-org/jvm-dependency-conflict-resolution/issues/130) javax-transaction-api rule now also covers jboss-transaction-api artifacts (Thanks [Piotr Kubowicz](https://github.com/pkubowicz) for reporting)
Expand Down
41 changes: 41 additions & 0 deletions src/docs/asciidoc/parts/resolution.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ jvmDependencyConflicts {
// Patch or extend wrong metadata
module("com.googlecode.json-simple:json-simple") { removeDependency("junit:junit") }
}

consistentResolution {
// The runtime classpath of ':app' is always respected in version conflict detection and resolution
providesVersions(":app")
}
}
----

Expand Down Expand Up @@ -261,3 +266,39 @@ jvmDependencyConflicts {
| Set the status of pre-release versions that are identified by one of the _marker string_ (e.g. `-rc`, `-m`) to `integration` (will then not be considered when using `latest.release` as version).

|===


[[consistent-resolution-block]]
== Configure gloabl consistent resolution

The `consistentResolution` section of `jvmDependencyConflicts` allows to configure
https://docs.gradle.org/current/userguide/resolution_strategy_tuning.html#resolution_consistency[consistent resolution]
for all modules (subprojects) of your build.
By configuring which projects aggregate the final _software product_ (applications or services that are delivered) you make sure
that the same versions of all third party dependencies you deliver as part of your product are also used when compiling and testing
parts of your softare (single subprojects) in isolation.

[source,groovy]
----
jvmDependencyConflicts {
consistentResolution {
// The runtime classpaths of the configured projects are always respected in
// version conflict detection and resolution
providesVersions(":app")
providesVersions(":service")
// If the build has a platform project, use it to provision additional version information
platform(":versions")
}
}
----

|===
| Method | Documentation

| `providesVersions(dependency)`
| Respect runtime classpaths of given project in all version conflict detection and resolution.

| `platform(dependency)`
| A platform/BOM to provide versions not available through consistent resolution alone.

|===
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.gradle.api.artifacts.dsl.ComponentMetadataHandler;
import org.gradle.api.initialization.Settings;
import org.gradle.api.plugins.ExtensionAware;
import org.gradle.api.plugins.JvmEcosystemPlugin;
import org.gradle.util.GradleVersion;
import org.gradlex.jvm.dependency.conflict.detection.rules.AlignmentDefinition;
import org.gradlex.jvm.dependency.conflict.detection.rules.CapabilityDefinition;
Expand All @@ -40,7 +41,7 @@ public void apply(ExtensionAware projectOrSettings) {
ComponentMetadataHandler components;
if (projectOrSettings instanceof Project) {
// Make sure 'jvm-ecosystem' is applied which adds the schemas for the attributes this plugin relies on
((Project) projectOrSettings).getPlugins().apply("jvm-ecosystem");
((Project) projectOrSettings).getPlugins().apply(JvmEcosystemPlugin.class);
components = ((Project) projectOrSettings).getDependencies().getComponents();
} else if (projectOrSettings instanceof Settings) {
//noinspection UnstableApiUsage
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright the GradleX team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.gradlex.jvm.dependency.conflict.resolution;

import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.attributes.Bundling;
import org.gradle.api.attributes.Category;
import org.gradle.api.attributes.LibraryElements;
import org.gradle.api.attributes.Usage;
import org.gradle.api.attributes.java.TargetJvmEnvironment;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.util.GradleVersion;

import javax.inject.Inject;
import java.util.Collections;

import static org.gradlex.jvm.dependency.conflict.resolution.JvmDependencyConflictResolutionPlugin.INTERNAL_CONFIGURATION_NAME;
import static org.gradlex.jvm.dependency.conflict.resolution.JvmDependencyConflictResolutionPlugin.MAIN_RUNTIME_CLASSPATH_CONFIGURATION_NAME;

public abstract class ConsistentResolution {

private final SourceSetContainer sourceSets;

@Inject
public ConsistentResolution(SourceSetContainer sourceSets) {
this.sourceSets = sourceSets;
}

@Inject
protected abstract ObjectFactory getObjects();

@Inject
protected abstract ConfigurationContainer getConfigurations();

@Inject
protected abstract DependencyHandler getDependencies();

/**
* The runtime classpath of the given project always respected in version conflict detection and resolution.
*/
public Configuration providesVersions(String versionProvidingProject) {
Configuration mainRuntimeClasspath = maybeCreateMainRuntimeClasspathConfiguration();
getDependencies().add(mainRuntimeClasspath.getName(), createDependency(versionProvidingProject));
return mainRuntimeClasspath;
}

/**
* A platform/BOM (<a href="https://docs.gradle.org/current/userguide/java_platform_plugin.html">Java Platform Plugin</a>)
* used to provide versions not available through consistent resolution alone.
* Useful if additional dependencies are needed only for tests.
*/
public void platform(String platform) {
Configuration internal = maybeCreateInternalConfiguration();
internal.withDependencies(d -> {
Dependency platformDependency = getDependencies().platform(createDependency(platform));
d.add(platformDependency);
});

sourceSets.configureEach(sourceSet -> {
ConfigurationContainer configurations = getConfigurations();
configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()).extendsFrom(internal);
configurations.getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(internal);
configurations.getByName(sourceSet.getAnnotationProcessorConfigurationName()).extendsFrom(internal);
});
}

private Configuration maybeCreateMainRuntimeClasspathConfiguration() {
Configuration existing = getConfigurations().findByName(MAIN_RUNTIME_CLASSPATH_CONFIGURATION_NAME);
if (existing != null) {
return existing;
}

Configuration mainRuntimeClasspath = getConfigurations().create(MAIN_RUNTIME_CLASSPATH_CONFIGURATION_NAME, c -> {
ObjectFactory objects = getObjects();
c.setCanBeResolved(true);
c.setCanBeConsumed(false);
c.extendsFrom(maybeCreateInternalConfiguration());
c.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.JAVA_RUNTIME));
c.getAttributes().attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.class, Category.LIBRARY));
c.getAttributes().attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
c.getAttributes().attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.class, Bundling.EXTERNAL));
if (GradleVersion.current().compareTo(GradleVersion.version("7.0")) >= 0) {
c.getAttributes().attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
objects.named(TargetJvmEnvironment.class, TargetJvmEnvironment.STANDARD_JVM));
}
});
sourceSets.configureEach(sourceSet -> {
ConfigurationContainer configurations = getConfigurations();
Configuration runtime = configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName());
Configuration compile = configurations.getByName(sourceSet.getCompileClasspathConfigurationName());
Configuration processor = configurations.getByName(sourceSet.getAnnotationProcessorConfigurationName());
runtime.shouldResolveConsistentlyWith(mainRuntimeClasspath);
compile.shouldResolveConsistentlyWith(runtime);
processor.shouldResolveConsistentlyWith(runtime);
});
return mainRuntimeClasspath;
}

private Configuration maybeCreateInternalConfiguration() {
Configuration internal = getConfigurations().findByName(INTERNAL_CONFIGURATION_NAME);
if (internal != null) {
return internal;
}
return getConfigurations().create(INTERNAL_CONFIGURATION_NAME, c -> {
c.setCanBeResolved(false);
c.setCanBeConsumed(false);
});
}

private Dependency createDependency(String project) {
boolean isProjectInBuild = project.startsWith(":");
return getDependencies().create(isProjectInBuild
? getDependencies().project(Collections.singletonMap("path", project))
: project);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.plugins.JvmEcosystemPlugin;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradlex.jvm.dependency.conflict.detection.rules.CapabilityDefinition;

import java.util.Optional;
Expand All @@ -32,12 +34,21 @@
import static org.gradlex.jvm.dependency.conflict.resolution.DefaultResolutionStrategy.HIGHEST_VERSION;

public abstract class JvmDependencyConflictResolutionPlugin implements Plugin<Project> {
public static final String MAIN_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "mainRuntimeClasspath";
public static final String INTERNAL_CONFIGURATION_NAME = "internal";

@Override
public void apply(Project project) {
JvmDependencyConflictDetectionPluginApplication.of(project).handleRulesMode();

JvmDependencyConflictsExtension jvmDependencyConflicts = project.getExtensions().create("jvmDependencyConflicts", JvmDependencyConflictsExtension.class);
// Make sure 'jvm-ecosystem' is applied which adds the schemas for the attributes this plugin relies on
project.getPlugins().apply(JvmEcosystemPlugin.class);

JvmDependencyConflictsExtension jvmDependencyConflicts = project.getExtensions().create(
"jvmDependencyConflicts",
JvmDependencyConflictsExtension.class,
project.getExtensions().getByType(SourceSetContainer.class)
);

configureResolutionStrategies(project.getConfigurations(), jvmDependencyConflicts);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.gradle.api.Action;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.tasks.SourceSetContainer;

import javax.inject.Inject;

Expand All @@ -26,16 +27,19 @@ public abstract class JvmDependencyConflictsExtension {
private final ConflictResolution conflictResolution;
private final Logging logging;
private final Patch patch;
private final ConsistentResolution consistentResolution;

@Inject
protected abstract ObjectFactory getObjects();

public JvmDependencyConflictsExtension() {
public JvmDependencyConflictsExtension(SourceSetContainer sourceSets) {
conflictResolution = getObjects().newInstance(ConflictResolution.class);
logging = getObjects().newInstance(Logging.class);
patch = getObjects().newInstance(Patch.class);
consistentResolution = getObjects().newInstance(ConsistentResolution.class, sourceSets);
}

@Inject
protected abstract ObjectFactory getObjects();

public ConflictResolution getConflictResolution() {
return conflictResolution;
}
Expand All @@ -59,4 +63,12 @@ public Patch getPatch() {
public void patch(Action<Patch> action) {
action.execute(patch);
}

public ConsistentResolution getConsistentResolution() {
return consistentResolution;
}

public void consistentResolution(Action<ConsistentResolution> action) {
action.execute(consistentResolution);
}
}
Loading
Loading