jdaugherty commented on code in PR #43:
URL:
https://github.com/apache/grails-gradle-publish/pull/43#discussion_r4190489945
##########
plugin/src/main/groovy/org/apache/grails/gradle/publish/GrailsPublishGradlePlugin.groovy:
##########
@@ -1080,20 +1056,65 @@ Note: properties are read as Gradle properties (the
root project's gradle.proper
}.join('\n') + '\n'
}
- /** The versions of the artifacts resolved by the named configurations,
keyed by `group:name` */
+ /**
+ * The versions of the components resolved by the named configurations,
keyed by `group:name`. Nothing is resolved
+ * until the provider is queried, and nothing at all while
`transitiveDependencies` is disabled. A task action reads
+ * the versions through this provider instead of the project: with the
configuration cache, the provider is
+ * evaluated when the cache entry is stored, and a build reusing the entry
reads the stored versions.
+ */
+ protected static Provider<Map<String, String>>
resolvedVersionsProvider(Project project, Provider<Boolean> enabled,
List<String> configurationNames) {
+ ConfigurationContainer configurations = project.configurations
+ List<String> names = new ArrayList<>(configurationNames)
+ project.provider {
+ enabled.get() ? resolvedVersions(configurations, names) : [:] as
Map<String, String>
+ }
+ }
+
+ /** The versions of the components resolved by the named configurations,
keyed by `group:name` */
protected static Map<String, String> resolvedVersions(Project project,
List<String> configurationNames) {
+ resolvedVersions(project.configurations, configurationNames)
+ }
+
+ /**
+ * The versions of the components resolved by the named configurations,
keyed by `group:name`; for a module
+ * resolved by more than one of them, the version of the configuration
listed first. Configurations that do not
+ * exist are skipped.
+ */
+ protected static Map<String, String>
resolvedVersions(ConfigurationContainer configurations, List<String>
configurationNames) {
Map<String, String> versions = [:]
for (String configurationName : configurationNames) {
- def configuration =
project.configurations.findByName(configurationName)
+ Configuration configuration =
configurations.findByName(configurationName)
if (configuration != null) {
- for (ResolvedArtifact artifact :
configuration.resolvedConfiguration.resolvedArtifacts) {
-
versions.putIfAbsent("${artifact.moduleVersion.id.group}:${artifact.moduleVersion.id.name}"
as String, artifact.moduleVersion.id.version)
- }
+
collectResolvedVersions(configuration.incoming.resolutionResult.rootComponent.get(),
versions)
}
}
versions
}
+ /** Adds the version of every component the root component depends on,
directly or transitively */
+ private static void collectResolvedVersions(ResolvedComponentResult root,
Map<String, String> versions) {
+ Set<ResolvedComponentResult> visited = [] as
Set<ResolvedComponentResult>
+ Deque<ResolvedComponentResult> pending = new ArrayDeque<>()
+ pending.add(root)
+ while (!pending.isEmpty()) {
+ ResolvedComponentResult component = pending.poll()
+ for (DependencyResult dependency : component.dependencies) {
+ if (!(dependency instanceof ResolvedDependencyResult)) {
Review Comment:
Fixed in 187e710. `collectResolvedVersions` now collects the
`UnresolvedDependencyResult`s and throws a `GradleException` naming each of
them, with the resolution failure as its cause. With the configuration cache
the provider is evaluated when the entry is stored, so the failure fails
storing it and nothing is cached; the next build configures again. New
functional test: "a dependency that cannot be resolved fails storing the
configuration cache entry, so a later build resolves it again".
On the second effect: following your comment on line 825, the versions are
now only resolved when a dependency has no version. Without the configuration
cache, a broken classpath whose versions are not needed therefore no longer
stops the publish; when they are needed, it fails as before. With the
configuration cache they are resolved when the entry is stored, so any
unresolved dependency fails the build.
##########
plugin/src/functionalTest/resources/publish-projects/other-artifacts/gradle-plugin-project/build.gradle:
##########
@@ -79,4 +79,13 @@ if (System.getenv('PUBLISH_SHARED_ARTIFACTS')) {
publicationName = 'maven'
addComponents = true
}
+
+ // Both publications write the same coordinates, each with its own pom.
With the configuration cache, tasks of a
+ // project run in parallel, so publish one after the other, keeping the
files of one publication from being mixed
+ // with the signatures of the other
+ tasks.withType(AbstractPublishToMaven).configureEach { task ->
Review Comment:
Fixed in 187e710. The plugin now orders the publish tasks of publications
with the same coordinates (groupId, artifactId and version) that publish to the
same repository, or both to Maven local, by task name
(`orderPublishTasksSharingCoordinates`). The fixture's workaround is removed.
The "publications sharing artifacts" case of `ReleaseSigningSpec` now asserts
the order for both targets, and a unit test covers which tasks get ordered.
##########
plugin/src/functionalTest/groovy/org/apache/grails/gradle/publish/examples/ContainerizedReleaseSpec.groovy:
##########
@@ -106,7 +106,10 @@ class ContainerizedReleaseSpec extends
ExampleProjectSpecification {
ExecResult build = container.execInContainer(ExecConfig.builder()
.workDir('/project')
.envVars(environment)
- .command((['gradle', '--no-daemon', '--stacktrace',
'--init-script', '/init/local-plugin.init.gradle']
+ // the container has its own Gradle user home, so the
configuration cache is enabled here, as it is for
+ // the builds run by the test kit (see GradleSpecification)
+ .command((['gradle', '--no-daemon', '--stacktrace',
'--configuration-cache', '--configuration-cache-problems=fail',
Review Comment:
Right, it cannot store an entry. I checked, and even without the close
task's marking, the Nexus publish tasks make Gradle run this build without the
configuration cache, because the Nexus plugin configures its repository with
explicit credentials. In 187e710 the spec asserts that outcome ("Configuration
cache disabled because incompatible task was found." and no entry stored), the
comment says so, and `--configuration-cache-problems=fail` is dropped. The
release path's task state (the signing and publishing tasks) is serialized by
the MAVEN_PUBLISH release feature of `PublishTargetsSpec`, which now asserts
its entry is stored, and the separate close build now asserts it runs without
storing one.
##########
plugin/src/main/groovy/org/apache/grails/gradle/publish/GrailsPublishGradlePlugin.groovy:
##########
@@ -351,6 +357,14 @@ Note: properties are read as Gradle properties (the root
project's gradle.proper
}
}
+ // The close and release tasks of the Nexus publish plugin (2.0.0)
cannot be stored in the configuration
+ // cache when they run without its publishing tasks, as in a
release closing the staging repository in a
+ // separate build: their transition check options reach the Nexus
extension, and through it the project.
+ // Gradle then runs such a build without storing a configuration
cache entry instead of failing it.
+
project.rootProject.tasks.withType(AbstractTransitionNexusStagingRepositoryTask).configureEach
{ AbstractTransitionNexusStagingRepositoryTask task ->
Review Comment:
Fixed in 187e710: the marking is registered once per build (a flag on the
build's extra properties), and still also when the root project applied the
Nexus plugin itself (unit test). On combined builds: without the marking,
`publishToSonatype closeAndReleaseSonatypeStagingRepository` still runs without
storing an entry, as does a Nexus snapshot publish with no transition task in
it, because the Nexus plugin configures its repository with explicit
credentials. So today the marking costs those builds nothing. The comment
claiming they could be stored was wrong and is corrected. The root project's
tasks are configured in the same place that applies the Nexus plugin to the
root project, which already reaches across projects.
##########
plugin/src/main/groovy/org/apache/grails/gradle/publish/GrailsPublishGradlePlugin.groovy:
##########
@@ -801,7 +791,24 @@ Note: properties are read as Gradle properties (the root
project's gradle.proper
}
}
+ /**
+ * Sets the versions of the pom dependencies declared without one from the
versions resolved by the named
+ * configurations of the project.
+ *
+ * @deprecated reads the project when the pom is generated, which the
configuration cache does not allow; the
+ * plugin itself uses {@link #setDependencyVersions(Node,
Provider)}
+ */
+ @Deprecated
protected void setDependencyVersions(Node pomNode, Project project,
List<String> configurationNames) {
Review Comment:
Fixed in 187e710. When a subclass overrides `setDependencyVersions(Node,
Project, List)`, the plugin calls the override when the pom is generated, as
before, and marks that publication's pom task as not compatible with the
configuration cache, since the override receives the project. A unit test
generates the pom with an overriding subclass.
##########
plugin/src/main/groovy/org/apache/grails/gradle/publish/GrailsPublishGradlePlugin.groovy:
##########
@@ -1233,5 +1254,114 @@ Note: properties are read as Gradle properties (the
root project's gradle.proper
}
}
}
-}
+ /**
+ * Completes the generated pom: removes the dependencyManagement section
(unless the project is a platform),
+ * applies the pom customization, and fills in the versions of
dependencies declared without one.
+ *
+ * The action is part of the state of the pom generation task, so it only
holds values and providers, which keeps
+ * it compatible with the configuration cache. The versions are read from
a provider, which the configuration
+ * cache evaluates when it stores the task.
+ */
+ @PackageScope
+ static class PomXmlAction implements Action<XmlProvider> {
+
+ private final MavenPom pom
+ private final boolean javaPlatform
+ private final Provider<Closure> pomCustomization
+ private final Provider<Boolean> transitiveDependencies
+ private final Provider<Map<String, String>> resolvedVersions
+
+ PomXmlAction(MavenPom pom, boolean javaPlatform, Provider<Closure>
pomCustomization,
+ Provider<Boolean> transitiveDependencies,
Provider<Map<String, String>> resolvedVersions) {
+ this.pom = pom
+ this.javaPlatform = javaPlatform
+ this.pomCustomization = pomCustomization
+ this.transitiveDependencies = transitiveDependencies
+ this.resolvedVersions = resolvedVersions
+ }
+
+ @Override
+ void execute(XmlProvider xml) {
+ Node pomNode = xml.asNode()
+
+ if (!javaPlatform) {
+ // Spring boot dependency management plugin will add the
dependencyManagement section,
+ // we do not want to publish this information as we will
determine the specific versions
+ // and set them instead
+ NodeList dependencyManagement = (NodeList)
pomNode.get('dependencyManagement')
+ if (dependencyManagement) {
+ dependencyManagement.replaceNode {}
+ }
+ }
+
+ if (pomCustomization.isPresent()) {
+ Closure customization = pomCustomization.get()
+ customization.delegate = pom
+ customization.resolveStrategy = Closure.DELEGATE_FIRST
+ customization.call(xml)
+ }
+
+ // fix dependencies without a version, this can occur when the
spring dependency management plugin is used
+ // disabling that plugin will cause gradle to fail on any
unresolved, or by disabling the check with:
+ // https://github.com/gradle/gradle/issues/23030
+ //tasks.withType(GenerateModuleMetadata).configureEach {
+ //
suppressedValidationErrors.add('dependencies-without-versions')
+ //}
+ if (transitiveDependencies.get()) {
+ setDependencyVersions(pomNode, resolvedVersions)
+ }
+ }
+ }
+
+ /**
+ * Fills in the versions the Gradle module metadata is missing, see
+ * {@link #configureModuleMetadataVersions(Project,
GrailsPublishExtension, String, List)}.
+ *
+ * The action is part of the state of the module metadata task, so it only
holds values and providers, which keeps
+ * it compatible with the configuration cache. The versions are read from
a provider, which the configuration
+ * cache evaluates when it stores the task.
+ */
+ @PackageScope
+ static class ModuleMetadataVersionsAction implements Action<Task> {
+
+ private final String publicationName
+ private final Provider<Boolean> transitiveDependencies
+ private final Provider<Map<String, String>> resolvedVersions
+
+ ModuleMetadataVersionsAction(String publicationName, Provider<Boolean>
transitiveDependencies,
+ Provider<Map<String, String>>
resolvedVersions) {
+ this.publicationName = publicationName
+ this.transitiveDependencies = transitiveDependencies
+ this.resolvedVersions = resolvedVersions
+ }
+
+ @Override
+ void execute(Task task) {
+ if (!transitiveDependencies.get()) {
+ return
+ }
+ File moduleFile = ((GenerateModuleMetadata)
task).outputFile.get().asFile
+ Map<String, String> versions = resolvedVersions.get()
+ Map module = new JsonSlurper().parse(moduleFile, 'UTF-8') as Map
+ boolean changed = false
+ for (Map variant : (module.variants ?: []) as List<Map>) {
+ for (Map dependency : (variant.dependencies ?: []) as
List<Map>) {
+ Map version = dependency.version as Map
+ if (version?.requires || version?.strictly ||
version?.prefers) {
+ continue
+ }
+ String resolved =
versions["${dependency.group}:${dependency.module}" as String]
+ if (resolved == null) {
+ throw new InvalidUserDataException("No version found
for dependency ${dependency.group}:${dependency.module} of variant
${variant.name} in the module metadata of publication ${publicationName}.")
+ }
+ dependency.version = [requires: resolved]
Review Comment:
Fixed in 187e710: the resolved version is merged into the existing version
object, keeping `rejects` and any other key. Covered by a unit test and by the
platform-managed-versions functional fixture, which now rejects a version of
groovy-json and checks that the reject is published.
##########
README.md:
##########
@@ -270,6 +270,28 @@ capability, so consumers that do not explicitly request
the capability are unaff
tree is also what allows Gradle to resolve a project dependency (including a
self dependency such as
`cliApi project(path)`) on a project with multiple publications.
+### Configuration Cache
+
+The plugin is compatible with the [configuration
cache](https://docs.gradle.org/current/userguide/configuration_cache.html),
+publishing included. The versions written into the pom and the Gradle module
metadata for dependencies declared without
+one (when `transitiveDependencies` is enabled, the default) are resolved when
the configuration cache entry is stored,
+and a build reusing the entry publishes them without resolving the
configurations again.
+
+The `pomCustomization` closure is stored with the task generating the pom, so
it must only use its `XmlProvider`
+argument and the pom it is delegated to, not the project or other state of the
build script. Read any other value it
+needs into a local variable beforehand, as the
[bom](examples/bom/build.gradle) example does.
+
+Some builds still cannot store a configuration cache entry, because of the
plugins this one works with:
+
+- Gradle does not store publishing tasks of a repository with explicit
credentials (the `MAVEN_PUBLISH_` and
Review Comment:
Done in 187e710, as a fallback rather than the first choice: when neither
MAVEN_PUBLISH_USERNAME/PASSWORD nor the
mavenPublishUsername/mavenPublishPassword properties are given and the
repository is http or https, the plugin uses
`repo.credentials(PasswordCredentials)`, so Gradle reads
`mavenUsername`/`mavenPassword` itself and the build stores an entry. Explicit
credentials still come first, because `mavenUsername` is a common name in
user-home gradle.properties files for other repositories, and preferring it
would change existing builds. The README documents it, and a new
`PublishTargetsSpec` feature publishes with
`ORG_GRADLE_PROJECT_mavenUsername`/`mavenPassword` over https, stores the
entry, and publishes again from it.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]