-
Notifications
You must be signed in to change notification settings - Fork 14
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
Enforce lock constraints using shared platform #557
Changes from 12 commits
d714215
746e371
0d7eaae
273dd1c
1107402
90b1ed9
00f6c13
c24cbd4
20bac8b
56e7225
31c5e6f
c3ae86f
e489812
a513092
7ff784f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
type: improvement | ||
improvement: | ||
description: Manage lock file constraints using a single gradle "platform", reducing | ||
the number of constraints that have to be created. | ||
links: | ||
- https://github.com/palantir/gradle-consistent-versions/pull/557 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -79,7 +79,9 @@ | |
import org.gradle.api.artifacts.result.ResolvedComponentResult; | ||
import org.gradle.api.artifacts.result.UnresolvedDependencyResult; | ||
import org.gradle.api.attributes.Attribute; | ||
import org.gradle.api.attributes.AttributeCompatibilityRule; | ||
import org.gradle.api.attributes.AttributesSchema; | ||
import org.gradle.api.attributes.CompatibilityCheckDetails; | ||
import org.gradle.api.attributes.Usage; | ||
import org.gradle.api.invocation.Gradle; | ||
import org.gradle.api.logging.Logger; | ||
|
@@ -115,6 +117,7 @@ public class VersionsLockPlugin implements Plugin<Project> { | |
|
||
private static final Attribute<GcvUsage> GCV_USAGE_ATTRIBUTE = | ||
Attribute.of("com.palantir.consistent-versions.usage", GcvUsage.class); | ||
private static final String GCV_LOCKS_CAPABILITY = "gcv:locks:0"; | ||
|
||
public enum GcvUsage implements Named { | ||
/** | ||
|
@@ -180,7 +183,7 @@ public String getName() { | |
@Inject | ||
public VersionsLockPlugin(Gradle gradle, ObjectFactory objectFactory) { | ||
showStacktrace = gradle.getStartParameter().getShowStacktrace(); | ||
internalUsage = objectFactory.named(Usage.class, "consistent-versions-usage"); | ||
internalUsage = objectFactory.named(Usage.class, ConsistentVersionsPlugin.CONSISTENT_VERSIONS_USAGE); | ||
} | ||
|
||
static Path getRootLockFile(Project project) { | ||
|
@@ -196,6 +199,9 @@ public final void apply(Project project) { | |
AttributesSchema attributesSchema = p.getDependencies().getAttributesSchema(); | ||
attributesSchema.attribute(GCV_SCOPE_ATTRIBUTE); | ||
attributesSchema.attribute(GCV_USAGE_ATTRIBUTE); | ||
attributesSchema.attribute(Usage.USAGE_ATTRIBUTE, strategy -> { | ||
strategy.getCompatibilityRules().add(EverythingIsCompatibleWithConsistentVersionsUsage.class); | ||
}); | ||
}); | ||
|
||
Configuration unifiedClasspath = project.getConfigurations() | ||
|
@@ -219,6 +225,23 @@ public final void apply(Project project) { | |
// (but that's internal) | ||
project.getPluginManager().apply("java-base"); | ||
|
||
// Create "platform" configuration in root project, which will hold the strictConstraints | ||
NamedDomainObjectProvider<Configuration> gcvLocksConfiguration = project.getConfigurations() | ||
.register("gcvLocks", conf -> { | ||
conf.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, internalUsage); | ||
conf.getOutgoing().capability(GCV_LOCKS_CAPABILITY); | ||
conf.setCanBeResolved(false); | ||
conf.setVisible(false); | ||
}); | ||
|
||
ProjectDependency locksDependency = | ||
(ProjectDependency) project.getDependencies().create(project); | ||
locksDependency.capabilities(moduleDependencyCapabilitiesHandler -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this necessary given that we already have configured the configuration? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes - we will resolve multiple variants of the same project (root project) so if they don't have different (i.e. explicit and different) capabilities, they will conflict. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh right and you can't depend on an explicit capability just with attributes, you need to specify the capability. If anything, we might not need to specify the attribute directly, but it's a bit more clear this way I think. |
||
moduleDependencyCapabilitiesHandler.requireCapabilities(GCV_LOCKS_CAPABILITY)); | ||
locksDependency.attributes(attrs -> { | ||
attrs.attribute(Usage.USAGE_ATTRIBUTE, internalUsage); | ||
}); | ||
|
||
// afterEvaluate is necessary to ensure all projects' dependencies have been configured, because we | ||
// need to copy them eagerly before we add the constraints from the lock file. | ||
// | ||
|
@@ -265,7 +288,14 @@ public final void apply(Project project) { | |
} | ||
} | ||
|
||
configureAllProjectsUsingConstraints(project, rootLockfile, lockedConfigurations); | ||
// Wire up the locks from the lock file into the strict locks platform. | ||
gcvLocksConfiguration.configure(conf -> { | ||
conf.getDependencyConstraints() | ||
.addAll(constructConstraintsFromLockFile( | ||
rootLockfile, project.getDependencies().getConstraints())); | ||
}); | ||
|
||
configureAllProjectsUsingConstraints(project, rootLockfile, lockedConfigurations, locksDependency); | ||
}); | ||
|
||
TaskProvider<?> verifyLocks = project.getTasks().register("verifyLocks", VerifyLocksTask.class, task -> { | ||
|
@@ -281,6 +311,20 @@ public final void apply(Project project) { | |
}); | ||
} | ||
|
||
static class EverythingIsCompatibleWithConsistentVersionsUsage implements AttributeCompatibilityRule<Usage> { | ||
@Override | ||
public void execute(CompatibilityCheckDetails<Usage> details) { | ||
if (ConsistentVersionsPlugin.CONSISTENT_VERSIONS_USAGE.equals( | ||
details.getProducerValue().getName()) | ||
// This shouldn't be necessary, because we never resolve configurations with this usage. | ||
// However, 5.3 tests fail without it | ||
|| ConsistentVersionsPlugin.CONSISTENT_VERSIONS_USAGE.equals( | ||
details.getConsumerValue().getName())) { | ||
details.compatible(); | ||
} | ||
} | ||
} | ||
|
||
static boolean isIgnoreLockFile(Project project) { | ||
return project.hasProperty("ignoreLockFile"); | ||
} | ||
|
@@ -795,18 +839,21 @@ private String formatUnresolvedDependencyResult(UnresolvedDependencyResult resul | |
} | ||
|
||
private static void configureAllProjectsUsingConstraints( | ||
Project rootProject, Path gradleLockfile, Map<Project, LockedConfigurations> lockedConfigurations) { | ||
List<DependencyConstraint> strictConstraints = constructConstraintsFromLockFile( | ||
gradleLockfile, rootProject.getDependencies().getConstraints()); | ||
Project rootProject, | ||
Path gradleLockfile, | ||
Map<Project, LockedConfigurations> lockedConfigurations, | ||
ProjectDependency locksDependency) { | ||
|
||
List<DependencyConstraint> publishableConstraints = constructPublishableConstraintsFromLockFile( | ||
gradleLockfile, rootProject.getDependencies().getConstraints()); | ||
|
||
rootProject.allprojects(subproject -> configureUsingConstraints( | ||
subproject, strictConstraints, publishableConstraints, lockedConfigurations.get(subproject))); | ||
subproject, locksDependency, publishableConstraints, lockedConfigurations.get(subproject))); | ||
} | ||
|
||
private static void configureUsingConstraints( | ||
Project subproject, | ||
List<DependencyConstraint> lockConstraints, | ||
ProjectDependency locksDependency, | ||
List<DependencyConstraint> publishableConstraints, | ||
LockedConfigurations lockedConfigurations) { | ||
Configuration locksConfiguration = subproject | ||
|
@@ -815,7 +862,7 @@ private static void configureUsingConstraints( | |
locksConf.setVisible(false); | ||
locksConf.setCanBeConsumed(false); | ||
locksConf.setCanBeResolved(false); | ||
lockConstraints.forEach(locksConf.getDependencyConstraints()::add); | ||
locksConf.getDependencies().add(locksDependency); | ||
}); | ||
|
||
Set<Configuration> configurationsToLock = lockedConfigurations.allConfigurations(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,9 @@ | |
import org.gradle.api.artifacts.DependencySet; | ||
import org.gradle.api.artifacts.ExternalDependency; | ||
import org.gradle.api.artifacts.ModuleDependency; | ||
import org.gradle.api.artifacts.ProjectDependency; | ||
import org.gradle.api.artifacts.dsl.DependencyConstraintHandler; | ||
import org.gradle.api.attributes.Usage; | ||
import org.gradle.api.logging.Logger; | ||
import org.gradle.api.logging.Logging; | ||
import org.gradle.api.plugins.JavaPlugin; | ||
|
@@ -58,6 +60,17 @@ public class VersionsPropsPlugin implements Plugin<Project> { | |
@Override | ||
public final void apply(Project project) { | ||
checkPreconditions(); | ||
|
||
// Shared across root project / other project | ||
// This must be usable during VersionsLockPlugin's resolution of unifiedClasspath, so the usage | ||
// must be 'compatible with' (or the same as) the one for the VersionsLockPlugin's own configurations. | ||
Usage gcvVersionsPropsUsage = | ||
project.getObjects().named(Usage.class, ConsistentVersionsPlugin.CONSISTENT_VERSIONS_USAGE); | ||
String gcvVersionsPropsCapability = "gcv:versions-props:0"; | ||
|
||
VersionsProps versionsProps = loadVersionsProps( | ||
project.getRootProject().file("versions.props").toPath()); | ||
|
||
if (project.getRootProject().equals(project)) { | ||
applyToRootProject(project); | ||
|
||
|
@@ -71,29 +84,39 @@ public final void apply(Project project) { | |
.set(project.getLayout().getProjectDirectory().file("versions.props")); | ||
}); | ||
project.getTasks().named("check").configure(task -> task.dependsOn(checkNoUnusedConstraints)); | ||
|
||
// Create "platform" configuration in root project, which will hold the versions props constraints | ||
project.getConfigurations().register("gcvVersionsPropsConstraints", conf -> { | ||
conf.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, gcvVersionsPropsUsage); | ||
conf.getOutgoing().capability(gcvVersionsPropsCapability); | ||
conf.setCanBeResolved(false); | ||
conf.setCanBeConsumed(true); | ||
conf.setVisible(false); | ||
|
||
// Note: don't add constraints to the ConstraintHandler, only call `create` / `platform` on it. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean here? could you explain why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was there before and mostly just meant the function accepting the ConstraintHandler doesn't call |
||
addVersionsPropsConstraints(project.getDependencies().getConstraints(), conf, versionsProps); | ||
}); | ||
} | ||
|
||
VersionRecommendationsExtension extension = | ||
project.getRootProject().getExtensions().getByType(VersionRecommendationsExtension.class); | ||
|
||
VersionsProps versionsProps = loadVersionsProps( | ||
project.getRootProject().file("versions.props").toPath()); | ||
|
||
NamedDomainObjectProvider<Configuration> rootConfiguration = project.getConfigurations() | ||
.register(ROOT_CONFIGURATION_NAME, conf -> { | ||
conf.setCanBeResolved(false); | ||
conf.setCanBeConsumed(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not setting this was an oversight, now fixed |
||
conf.setVisible(false); | ||
|
||
// Wire in the constraints from the main configuration. | ||
conf.getDependencies() | ||
.add(createDepOnRootConstraintsConfiguration( | ||
project, gcvVersionsPropsUsage, gcvVersionsPropsCapability)); | ||
}); | ||
|
||
project.getConfigurations().configureEach(conf -> { | ||
setupConfiguration(project, extension, rootConfiguration.get(), versionsProps, conf); | ||
}); | ||
|
||
// Note: don't add constraints to this, only call `create` / `platform` on it. | ||
DependencyConstraintHandler constraintHandler = | ||
project.getDependencies().getConstraints(); | ||
rootConfiguration.configure(conf -> addVersionsPropsConstraints(constraintHandler, conf, versionsProps)); | ||
|
||
log.info("Configuring rules to assign *-constraints to platforms in {}", project); | ||
project.getDependencies() | ||
.getComponents() | ||
|
@@ -103,10 +126,20 @@ public final void apply(Project project) { | |
configureResolvedVersionsWithVersionMapping(project); | ||
} | ||
|
||
private static ProjectDependency createDepOnRootConstraintsConfiguration( | ||
Project project, Usage usage, String capability) { | ||
ProjectDependency projectDep = | ||
((ProjectDependency) project.getDependencies().create(project.getRootProject())); | ||
projectDep.capabilities(capabilities -> capabilities.requireCapability(capability)); | ||
projectDep.attributes(attrs -> attrs.attribute(Usage.USAGE_ATTRIBUTE, usage)); | ||
return projectDep; | ||
} | ||
|
||
private static void applyToRootProject(Project project) { | ||
project.getPluginManager().apply(LifecycleBasePlugin.class); | ||
project.getExtensions() | ||
.create(VersionRecommendationsExtension.EXTENSION, VersionRecommendationsExtension.class, project); | ||
|
||
project.subprojects(subproject -> subproject.getPluginManager().apply(VersionsPropsPlugin.class)); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we create a self dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not actually "self" but pointing to a particular configuration (indirectly).
This is to pick up the locks configuration which is defined in the root project.