Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3e44fc0
we have something working
FinlayRJW Sep 16, 2025
f71953e
working better?
FinlayRJW Sep 16, 2025
fac500f
Mostly working
FinlayRJW Sep 16, 2025
d7cc641
getting warmer
FinlayRJW Sep 16, 2025
922c2c5
move tests
FinlayRJW Sep 16, 2025
d4ad93d
full tests
FinlayRJW Sep 16, 2025
0cccf34
add back nasty
FinlayRJW Sep 16, 2025
d0830d5
filter with faults
FinlayRJW Sep 17, 2025
27408bb
all working
FinlayRJW Sep 17, 2025
54aa4ae
better still
FinlayRJW Sep 17, 2025
0226d00
enable filtering
FinlayRJW Sep 17, 2025
8e11a7e
warmer
FinlayRJW Sep 17, 2025
7882acd
warmer
FinlayRJW Sep 17, 2025
7e1a7b0
warmer
FinlayRJW Sep 17, 2025
169a63b
working
FinlayRJW Sep 17, 2025
5596b28
all tests
FinlayRJW Sep 17, 2025
18aa028
Update src/test/groovy/com/palantir/gradle/versions/VersionsLockPlugi…
FinlayRJW Sep 17, 2025
1613e90
proper tests
FinlayRJW Sep 19, 2025
0f84287
merge
FinlayRJW Sep 19, 2025
d0326a0
combine
FinlayRJW Sep 19, 2025
d9a9ed9
combine
FinlayRJW Sep 19, 2025
3c61557
why not working?
FinlayRJW Sep 19, 2025
05131ff
dont change this test
FinlayRJW Sep 19, 2025
773eb3c
dont change this test
FinlayRJW Sep 19, 2025
d24e3d3
tidy tests
FinlayRJW Sep 19, 2025
38f872c
stable tests
FinlayRJW Sep 19, 2025
da1429f
strcitly
FinlayRJW Sep 19, 2025
f76a08a
tidy
FinlayRJW Sep 19, 2025
52a6438
working
FinlayRJW Sep 19, 2025
d7d9f8f
check working
FinlayRJW Sep 19, 2025
d74c1c9
not strict
FinlayRJW Sep 19, 2025
663b187
tidy tests
FinlayRJW Sep 19, 2025
e803374
clean
FinlayRJW Sep 19, 2025
da10848
Merge branch 'finlayw/enable_filtering' of github.com:palantir/gradle…
FinlayRJW Sep 19, 2025
bf1f071
merge
FinlayRJW Sep 19, 2025
251aab9
spotless
FinlayRJW Sep 19, 2025
9633986
check better
FinlayRJW Sep 19, 2025
b20342d
better
FinlayRJW Sep 19, 2025
9f4622e
all cases
FinlayRJW Sep 19, 2025
d8e013b
tidy tests
FinlayRJW Sep 19, 2025
f145cc6
tidy tests
FinlayRJW Sep 19, 2025
f6dcc67
remove brittle checks
FinlayRJW Sep 19, 2025
12ba046
a little neater
FinlayRJW Sep 19, 2025
7374108
bom
FinlayRJW Sep 19, 2025
f32a767
bom correct
FinlayRJW Sep 19, 2025
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
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ gradlePlugin {
description = displayName
tags.set(['versions'])
}
gradleModuleMetadataConstraintsPlugin {
id = 'com.palantir.gradle-module-metadata-constraints-plugin'
implementationClass = 'com.palantir.gradle.versions.GradleModuleMetadataConstraintsPlugin'
displayName = 'Plugin to apply constraints to the Gradle Module Metadata.'
description = displayName
tags.set(['versions'])
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public final void apply(Project project) {
project.getPluginManager().apply(VersionsPropsPlugin.class);
project.getPluginManager().apply(GetVersionPlugin.class);
project.getPluginManager().apply(VersionsPropsIdeaPlugin.class);
project.getPluginManager().apply(GradleModuleMetadataConstraintsPlugin.class);

project.getPluginManager().apply(IdeaConfigurationPlugin.class);
IdeaConfigurationExtension extension = project.getExtensions().getByType(IdeaConfigurationExtension.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* (c) Copyright 2025 Palantir Technologies Inc. All rights reserved.
*
* 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 com.palantir.gradle.versions;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.plugins.JavaPlugin;

public abstract class GradleModuleMetadataConstraintsPlugin implements Plugin<Project> {

private static final String PUBLISH_PLATFORM_CONSTRAINTS_PROPERTY =
"com.palantir.gradle.versions.publishPlatformConstraints";

@Override
public final void apply(Project rootProject) {
if (!rootProject.equals(rootProject.getRootProject())) {
throw new IllegalStateException(
"GradleModuleMetadataConstraintsPlugin must be applied to the root project");
}

rootProject.afterEvaluate(project -> {
if (publishPlatformConstraints(project)) {
enforceVersionAlignmentAcrossGroups(project);
}
});
}

public static boolean publishPlatformConstraints(Project project) {
return project.hasProperty(PUBLISH_PLATFORM_CONSTRAINTS_PROPERTY)
&& "true".equals(project.property(PUBLISH_PLATFORM_CONSTRAINTS_PROPERTY));
}

private void enforceVersionAlignmentAcrossGroups(Project rootProject) {
Map<String, Set<Project>> projectsByGroup = rootProject.getAllprojects().stream()
.filter(VersionsLockPlugin::isJavaLibrary)
.collect(Collectors.groupingBy(p -> String.valueOf(p.getGroup()), Collectors.toSet()));

projectsByGroup.values().stream()
.filter(group -> group.size() > 1)
.forEach(this::applyVersionConstraintsToGroup);
}

private void applyVersionConstraintsToGroup(Set<Project> projectGroup) {
List<Project> sortedProjects = projectGroup.stream()
.sorted(Comparator.comparing(Project::getPath))
.toList();

sortedProjects.forEach(project -> {
Configuration constraintsConfig = createConstraintsConfiguration(project);
addSiblingVersionConstraints(project, sortedProjects, constraintsConfig);
applyConstraintsToJavaProject(project, constraintsConfig);
});
}

private Configuration createConstraintsConfiguration(Project project) {
Configuration config = project.getConfigurations().maybeCreate("acrossGroupVersionConstraints");
config.setDescription("Enforces version alignment for modules within the same group");
config.setCanBeResolved(false);
config.setCanBeConsumed(false);
config.setVisible(false);
return config;
}

private void addSiblingVersionConstraints(
Project project, List<Project> allGroupProjects, Configuration constraintsConfig) {
allGroupProjects.stream().filter(sibling -> !sibling.equals(project)).forEach(sibling -> {
String gav = sibling.getGroup() + ":" + sibling.getName();
constraintsConfig
.getDependencyConstraints()
.add(project.getDependencies().getConstraints().create(gav, constraint -> {
constraint.version(v -> v.strictly(sibling.getVersion().toString()));
constraint.because("All modules in group must use the same version");
}));
});
}

private void applyConstraintsToJavaProject(Project project, Configuration constraintsConfig) {
if (!project.getPluginManager().hasPlugin("java")) {
return;
}

project.getConfigurations()
.named(JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME)
.configure(conf -> conf.extendsFrom(constraintsConfig));

project.getConfigurations()
.named(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME)
.configure(conf -> conf.extendsFrom(constraintsConfig));
}
}
70 changes: 52 additions & 18 deletions src/main/java/com/palantir/gradle/versions/VersionsLockPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,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 All @@ -96,6 +97,7 @@
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.publish.Publication;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.ivy.IvyPublication;
Expand Down Expand Up @@ -133,6 +135,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";

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

List<DependencyConstraint> publishableConstraints = constructPublishableConstraintsFromLockFile(
List<DependencyConstraint> lockFileConstraints = constructPublishableConstraintsFromLockFile(
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) {
@SuppressWarnings("for-rollout:ConfigurationAvoidanceRegistration")
Configuration locksConfiguration = subproject
Expand All @@ -959,29 +959,58 @@ private static void configureUsingConstraints(
VersionsLockPlugin.ensureNoFailOnVersionConflict(conf);
});

NamedDomainObjectProvider<Configuration> publishConstraints = subproject
.getConfigurations()
.register("gcvPublishConstraints", conf -> {
conf.setDescription("Publishable constraints from the GCV versions.lock file");
conf.setCanBeResolved(false);
conf.setCanBeConsumed(false);
conf.getDependencyConstraints().addAll(publishableConstraints);
});

// Enrich the configurations being published as part of the java component (components.java)
// with constraints generated from the lock file.
subproject.getPluginManager().withPlugin("java", _plugin -> {
NamedDomainObjectProvider<Configuration> publishConstraints = subproject
.getConfigurations()
.register("gcvPublishConstraints", conf -> {
conf.setDescription("Publishable constraints from the GCV versions.lock file");
conf.setCanBeResolved(false);
conf.setCanBeConsumed(false);
conf.getDependencyConstraints().addAll(localProjectConstraints);
conf.getDependencyConstraints()
.addAllLater(maybeFilterConstraintsByUsage(subproject, lockFileConstraints));
});

subproject
.getConfigurations()
.named(JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME)
.configure(conf -> conf.extendsFrom(publishConstraints.get()));

subproject
.getConfigurations()
.named(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME)
.configure(conf -> conf.extendsFrom(publishConstraints.get()));
});
}

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 LockedConfigurations computeConfigurationsToLock(Project project, VersionsLockExtension ext) {
Preconditions.checkState(
project.getState().getExecuted(),
Expand Down Expand Up @@ -1107,7 +1136,12 @@ private static boolean publishLocalConstraints(Project project) {
&& "true".equals(project.property(PUBLISH_LOCAL_CONSTRAINTS_PROPERTY));
}

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

static boolean isJavaLibrary(Project project) {
if (project.getPluginManager().hasPlugin("nebula.maven-publish")) {
// 'nebula.maven-publish' creates publications lazily which causes inconsistencies based
// on ordering.
Expand Down
Loading