Skip to content
Open
56 changes: 45 additions & 11 deletions src/main/java/com/palantir/gradle/versions/VersionsLockPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.artifacts.ExternalModuleDependency;
import org.gradle.api.artifacts.ModuleIdentifier;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.artifacts.VersionConstraint;
import org.gradle.api.artifacts.component.ComponentSelector;
Expand Down Expand Up @@ -133,6 +134,7 @@ public abstract class VersionsLockPlugin implements Plugin<Project> {
new TaskNameMatcher(WRITE_VERSIONS_LOCKS_TASK);
private static final String PUBLISH_LOCAL_CONSTRAINTS_PROPERTY =
"com.palantir.gradle.versions.publishLocalConstraints";
private static final String FILTER_LOCK_FILE_CONSTRAINTS = "com.palantir.gradle.versions.filterLockFileConstraints";
private static final String GCV_PUBLISH_CONSTRAINTS_CONFIGURATION_NAME = "gcvPublishConstraints";

public enum GcvUsage implements Named {
Expand Down Expand Up @@ -954,30 +956,27 @@ private static void configureAllProjectsUsingConstraints(
Map<Project, LockedConfigurations> lockedConfigurations,
ProjectDependency locksDependency) {

List<DependencyConstraint> publishableConstraints = constructPublishableConstraintsFromLockFile(
List<DependencyConstraint> lockFileConstraints = constructConstraintsFromLockfile(
rootProject, gradleLockfile, rootProject.getDependencies().getConstraints()::create);

rootProject.allprojects(subproject -> {
// Avoid including the current project as a constraint -- it must already be present to provide constraints
List<DependencyConstraint> localProjectConstraints = constructPublishableConstraintsFromLocalProjects(
subproject, rootProject.getDependencies().getConstraints()::create);
ImmutableList<DependencyConstraint> publishableConstraintsForSubproject =
ImmutableList.<DependencyConstraint>builder()
.addAll(localProjectConstraints)
.addAll(publishableConstraints)
.build();
configureUsingConstraints(
subproject,
locksDependency,
publishableConstraintsForSubproject,
lockFileConstraints,
localProjectConstraints,
lockedConfigurations.get(subproject));
});
}

private static void configureUsingConstraints(
Project subproject,
ProjectDependency locksDependency,
List<DependencyConstraint> publishableConstraints,
List<DependencyConstraint> lockFileConstraints,
List<DependencyConstraint> localProjectConstraints,
LockedConfigurations lockedConfigurations) {
subproject.getConfigurations().named(LOCK_CONSTRAINTS_CONFIGURATION_NAME, conf -> conf.getDependencies()
.add(locksDependency));
Expand All @@ -986,11 +985,46 @@ private static void configureUsingConstraints(
log.info("Configuring locks for {}. Locked configurations: {}", subproject.getPath(), configurationsToLock);
configurationsToLock.forEach(VersionsLockPlugin::ensureNoFailOnVersionConflict);

subproject.getConfigurations().named(GCV_PUBLISH_CONSTRAINTS_CONFIGURATION_NAME, conf -> {
conf.getDependencyConstraints().addAll(publishableConstraints);
subproject.getPluginManager().withPlugin("java", _plugin -> {
subproject.getConfigurations().named(GCV_PUBLISH_CONSTRAINTS_CONFIGURATION_NAME, conf -> {
conf.getDependencyConstraints().addAll(localProjectConstraints);
conf.getDependencyConstraints()
.addAllLater(maybeFilterConstraintsByUsage(subproject, lockFileConstraints));
});
});
}

private static Provider<Collection<DependencyConstraint>> maybeFilterConstraintsByUsage(
Project project, List<DependencyConstraint> constraints) {

NamedDomainObjectProvider<Configuration> compileClasspath =
project.getConfigurations().named(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME);
NamedDomainObjectProvider<Configuration> runtimeClasspath =
project.getConfigurations().named(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);

return compileClasspath.zip(runtimeClasspath, (compile, runtime) -> {
if (!filterLockFileConstraints(project)) {
return constraints;
}

Set<ModuleIdentifier> usedModules = Stream.of(compile, runtime)
.flatMap(config -> config.getIncoming().getResolutionResult().getAllComponents().stream())
.map(ResolvedComponentResult::getModuleVersion)
.filter(Objects::nonNull)
.map(ModuleVersionIdentifier::getModule)
.collect(Collectors.toSet());

return constraints.stream()
.filter(constraint -> usedModules.contains(constraint.getModule()))
.collect(Collectors.toList());
});
}

private static boolean filterLockFileConstraints(Project project) {
return project.hasProperty(FILTER_LOCK_FILE_CONSTRAINTS)
&& "true".equals(project.property(FILTER_LOCK_FILE_CONSTRAINTS));
}

private static LockedConfigurations computeConfigurationsToLock(Project project, VersionsLockExtension ext) {
Preconditions.checkState(
project.getState().getExecuted(),
Expand Down Expand Up @@ -1077,7 +1111,7 @@ private static List<DependencyConstraint> constructConstraintsFromLockFile(
.collect(Collectors.toList());
}

private static List<DependencyConstraint> constructPublishableConstraintsFromLockFile(
private static List<DependencyConstraint> constructConstraintsFromLockfile(
Project rootProject, Path gradleLockfile, DependencyConstraintCreator constraintCreator) {
LockState lockState = new ConflictSafeLockFile(gradleLockfile).readLocks();
// We only publish the production locks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -856,10 +856,118 @@ class VersionsLockPluginIntegrationSpec extends IntegrationSpec {
gradleVersionNumber << GRADLE_VERSIONS
}

def "#gradleVersionNumber: published constraints are derived from lock file (with local constraints)"() {
def "#gradleVersionNumber: published constraints are derived from lock file (with local constraints and with filtering)"() {
setup:
// Test with local constraints enabled
file('gradle.properties') << 'com.palantir.gradle.versions.publishLocalConstraints = true'
file('gradle.properties') << '''
com.palantir.gradle.versions.publishLocalConstraints = true
com.palantir.gradle.versions.filterLockFileConstraints = true
'''
gradleVersion = gradleVersionNumber

buildFile << """
allprojects {
apply plugin: 'java'
}
""".stripIndent(true)

String publish = """
apply plugin: 'maven-publish'
group = 'com.palantir.published-constraints'
version = '1.2.3'
publishing.publications {
maven(MavenPublication) {
from components.java
}
}
""".stripIndent(true)

addSubproject('foo', """
$publish
dependencies {
implementation 'ch.qos.logback:logback-classic:1.2.3'
}
""".stripIndent(true))

addSubproject('bar', """
$publish
dependencies {
implementation 'junit:junit:4.10'
}
""".stripIndent(true))

if (GradleVersion.version(gradleVersionNumber) < GradleVersion.version("6.0")) {
settingsFile << """
enableFeaturePreview('GRADLE_METADATA')
""".stripIndent(true)
}

runTasks('--write-locks')

when:
runTasks('generatePomFileForMavenPublication', 'generateMetadataFileForMavenPublication')

def junitDep = new MetadataFile.Dependency(
group: 'junit',
module: 'junit',
version: [requires: '4.10'])
def logbackDep = new MetadataFile.Dependency(
group: 'ch.qos.logback',
module: 'logback-classic',
version: [requires: '1.2.3'])
def slf4jDep = new MetadataFile.Dependency(
group: 'org.slf4j',
module: 'slf4j-api',
version: [requires: '1.7.25'])
def fooDep = new MetadataFile.Dependency(
group: 'com.palantir.published-constraints',
module: 'foo',
version: [requires: '1.2.3'])
def barDep = new MetadataFile.Dependency(
group: 'com.palantir.published-constraints',
module: 'bar',
version: [requires: '1.2.3'])

then: "foo's metadata file has the right dependency constraints"
def fooMetadataFilename = new File(projectDir, "foo/build/publications/maven/module.json")
def fooMetadata = new ObjectMapper().readValue(fooMetadataFilename, MetadataFile)

fooMetadata.variants == [
new MetadataFile.Variant(
name: 'runtimeElements',
dependencies: [logbackDep],
dependencyConstraints: [barDep, logbackDep, slf4jDep]),
new MetadataFile.Variant(
name: 'apiElements',
dependencies: null,
dependencyConstraints: [barDep, logbackDep, slf4jDep])
] as Set

and: "bar's metadata file has the right dependency constraints"
def barMetadataFilename = new File(projectDir, "bar/build/publications/maven/module.json")
def barMetadata = new ObjectMapper().readValue(barMetadataFilename, MetadataFile)

barMetadata.variants == [
new MetadataFile.Variant(
name: 'runtimeElements',
dependencies: [junitDep],
dependencyConstraints: [fooDep, junitDep]),
new MetadataFile.Variant(
name: 'apiElements',
dependencies: null,
dependencyConstraints: [fooDep, junitDep]),
] as Set

where:
gradleVersionNumber << GRADLE_VERSIONS
}

def "#gradleVersionNumber: published constraints are derived from lock file (with local constraints and without filtering)"() {
setup:
// Test with local constraints enabled
file('gradle.properties') << '''
com.palantir.gradle.versions.publishLocalConstraints = true
'''
gradleVersion = gradleVersionNumber

buildFile << """
Expand Down Expand Up @@ -959,7 +1067,102 @@ class VersionsLockPluginIntegrationSpec extends IntegrationSpec {
gradleVersionNumber << GRADLE_VERSIONS
}

def "#gradleVersionNumber: published constraints are derived from lock file (without local constraints)"() {
def "#gradleVersionNumber: published constraints are derived from lock file (without local constraints and with filtering)"() {
setup:
// Test with filtering of lock file constraints enabled
file('gradle.properties') << 'com.palantir.gradle.versions.filterLockFileConstraints = true'
gradleVersion = gradleVersionNumber

buildFile << """
allprojects {
apply plugin: 'java'
}
""".stripIndent(true)

String publish = """
apply plugin: 'maven-publish'
group = 'com.palantir.published-constraints'
version = '1.2.3'
publishing.publications {
maven(MavenPublication) {
from components.java
}
}
""".stripIndent(true)

addSubproject('foo', """
$publish
dependencies {
implementation 'ch.qos.logback:logback-classic:1.2.3'
}
""".stripIndent(true))

addSubproject('bar', """
$publish
dependencies {
implementation 'junit:junit:4.10'
}
""".stripIndent(true))

if (GradleVersion.version(gradleVersionNumber) < GradleVersion.version("6.0")) {
settingsFile << """
enableFeaturePreview('GRADLE_METADATA')
""".stripIndent(true)
}
Comment on lines +1107 to +1111
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is never going to be true, minimum version is gradle 7.6.4

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, this was a copy pasted test

Copy link

@kelvinou01 kelvinou01 Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should i remove all checks for 6.0?


runTasks('--write-locks')

when:
runTasks('generatePomFileForMavenPublication', 'generateMetadataFileForMavenPublication')

def junitDep = new MetadataFile.Dependency(
group: 'junit',
module: 'junit',
version: [requires: '4.10'])
def logbackDep = new MetadataFile.Dependency(
group: 'ch.qos.logback',
module: 'logback-classic',
version: [requires: '1.2.3'])
def slf4jDep = new MetadataFile.Dependency(
group: 'org.slf4j',
module: 'slf4j-api',
version: [requires: '1.7.25'])

then: "foo's metadata file has the right dependency constraints"
def fooMetadataFilename = new File(projectDir, "foo/build/publications/maven/module.json")
def fooMetadata = new ObjectMapper().readValue(fooMetadataFilename, MetadataFile)

fooMetadata.variants == [
new MetadataFile.Variant(
name: 'runtimeElements',
dependencies: [logbackDep],
dependencyConstraints: [logbackDep, slf4jDep]),
new MetadataFile.Variant(
name: 'apiElements',
dependencies: null,
dependencyConstraints: [logbackDep, slf4jDep])
] as Set

and: "bar's metadata file has the right dependency constraints"
def barMetadataFilename = new File(projectDir, "bar/build/publications/maven/module.json")
def barMetadata = new ObjectMapper().readValue(barMetadataFilename, MetadataFile)

barMetadata.variants == [
new MetadataFile.Variant(
name: 'runtimeElements',
dependencies: [junitDep],
dependencyConstraints: [junitDep]),
new MetadataFile.Variant(
name: 'apiElements',
dependencies: null,
dependencyConstraints: [junitDep]),
] as Set

where:
gradleVersionNumber << GRADLE_VERSIONS
}

def "#gradleVersionNumber: published constraints are derived from lock file (without local constraints and without filtering)"() {
setup:
gradleVersion = gradleVersionNumber

Expand Down