codeconsole commented on code in PR #43:
URL: 
https://github.com/apache/grails-gradle-publish/pull/43#discussion_r4189817686


##########
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:
   **Unresolved dependencies are now skipped without any error, and with the 
configuration cache a temporary failure can get stored in the entry**
   
   The old code used `resolvedConfiguration.resolvedArtifacts`, which threw a 
`ResolveException` when any edge failed. `incoming.resolutionResult` does not 
throw on failed edges. It reports them as `UnresolvedDependencyResult`, and 
this loop skips them.
   
   Example: the repository is down (or returns 401) when the configuration 
cache entry for `publish` is stored. A platform managed `testFixturesApi 
'org.apache.groovy:groovy-json'` is unresolved and gets skipped, and the stored 
map has no entry for it. The pom task then fails with `No version found for 
dependency org.apache.groovy:groovy-json.`, which hides the real resolution 
error. The entry is stored before execution, and unresolved edges do not appear 
to be tracked as a cache input. So later builds can reuse the same incomplete 
map and keep failing after the repository is back. With the old strict 
resolution, storing the entry would itself have failed and nothing would have 
been cached.
   
   A second effect: a resolution failure on any of the four classpaths used to 
stop the publish. It now passes silently whenever every pom dependency already 
has a version.
   
   Possible fix: collect the `UnresolvedDependencyResult` failures and rethrow 
them (or at least report them) when a version lookup misses.
   



##########
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:
   **This fixes the parallel publish race only in the test fixture, not in the 
plugin**
   
   With the configuration cache, tasks of the same project run in parallel. Two 
publications that write the same coordinates can then interleave their files 
and signatures. The plugin itself says it supports this setup 
(`GrailsPublishGradlePlugin` lines 523-529: "a build can still add one to 
several publications") and already orders publish tasks after `Sign` for it. 
But the ordering between the publish tasks is only added here, in the test 
build script.
   
   Example: a user build shaped like `PUBLISH_SHARED_ARTIFACTS` turns on the 
configuration cache, as the new README section invites. 
`publishPluginMavenPublicationTo*` and `publishMavenPublicationTo*` run at the 
same time. The published `.pom`/`.module` can end up next to the `.asc` of the 
other publication, and signature verification fails in the staging repository.
   
   Possible fix: add the ordering in the plugin, for publications that share 
coordinates or for all `AbstractPublishToMaven` tasks of the project that 
target the same repository.
   



##########
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:
   **This build can never store a configuration cache entry, so the 
configuration cache flags here test nothing**
   
   This invocation runs `closeSonatypeStagingRepository`, which this PR marks 
`notCompatibleWithConfigurationCache`. It also publishes with explicit Nexus 
credentials, which Gradle degrades on ("Explicit credentials are unsupported 
with the Configuration Cache"). So the build always runs without storing an 
entry, and none of the release path task state (Sign, 
InitializeNexusStagingRepository, the Nexus publish tasks) is ever serialized.
   
   Example: a later change makes one of those tasks hold state that cannot be 
serialized, such as a closure capturing the project. This test still passes, 
which contradicts the comment above and the PR description ("fails on any 
problem, including the containerized release").
   
   Possible fix: assert on the configuration cache outcome you expect (for 
example, that the transition task is the only reported degradation reason), or 
drop the claim.
   



##########
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:
   **This marking is added again by every subproject, unconditionally, and it 
configures another project's tasks**
   
   `project.rootProject.tasks.withType(...).configureEach` runs once for each 
subproject that applies the plugin with Nexus publishing. In a build shaped 
like grails-core, every transition task gets N identical 
`notCompatibleWithConfigurationCache` actions. The marking also applies to 
combined `publishToSonatype close...` builds, which the comment above says can 
be stored. It also adds another cross-project configuration of the root 
project. Isolated Projects reports that as a violation, and the plugin 
otherwise guards against it (`findProjectProperty`, line 219).
   
   Possible fix: register the marking once, for example only where 
`rootProjectPluginManager.apply(NexusPublishPlugin)` runs, or behind a flag on 
the root project.
   



##########
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:
   **Subclasses that override this method are now silently bypassed**
   
   The plugin no longer calls `setDependencyVersions(Node, Project, List)`. 
`PomXmlAction` calls the new `static setDependencyVersions(Node, Provider)` 
directly, and a static method cannot be overridden. This class is meant to be 
extended (protected hooks, and the injected getter is there "so plugins 
extending this one keep working").
   
   Example: a subclass overrides this method to pick versions differently or to 
tolerate missing ones. After this change its override never runs, the published 
pom changes (or the build fails with `No version found`), and the subclass gets 
no compile error. The only warning is the `@Deprecated` marker, and that only 
shows up when the method is called, which no longer happens.
   



##########
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:
   **Filling in a missing version replaces the whole `version` object, so 
`rejects` is lost**
   
   The skip check only looks at `requires`, `strictly` and `prefers`. A 
dependency whose version object has only `rejects` is therefore treated as 
missing a version, and `dependency.version = [requires: resolved]` overwrites 
the whole object.
   
   Example: `testFixturesApi('org.example:lib') { version { reject '1.0.0' } 
}`, managed by a platform. Gradle writes `"version": {"rejects": ["1.0.0"]}`. 
This action replaces it with `{"requires": "1.1.0"}`, and consumers lose the 
reject.
   
   Possible fix: merge into the existing map (`(version ?: [:]) + [requires: 
resolved]`).
   



##########
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:
   **Credentialed publishing, the common case, still never stores an entry, 
partly because of how the plugin sets credentials**
   
   The plugin sets the `MAVEN_PUBLISH_*` credentials explicitly 
(`repo.credentials { credentials.username = ...; credentials.password = ... }`, 
lines 409-412). That is exactly what Gradle degrades on 
(`MavenPublishPlugin.usingExplicitCredentials`: "Explicit credentials are 
unsupported with the Configuration Cache"). So every real snapshot or release 
publish to an authenticated repository still runs without storing an entry. 
Only repositories without credentials store one, as in the new tests.
   
   Possible fix at the root cause, at least for the plugin's own `maven` 
repository: use Gradle's identity based 
`repo.credentials(PasswordCredentials)`, which reads the 
`mavenUsername`/`mavenPassword` Gradle properties (for example 
`ORG_GRADLE_PROJECT_mavenUsername`), and keep the explicit environment variable 
path as a fallback.
   



##########
plugin/src/main/groovy/org/apache/grails/gradle/publish/GrailsPublishGradlePlugin.groovy:
##########
@@ -1035,36 +1032,15 @@ Note: properties are read as Gradle properties (the 
root project's gradle.proper
      * resolved classpaths the pom is completed from.
      */
     protected void configureModuleMetadataVersions(Project project, 
GrailsPublishExtension gpe, String publicationName, List<String> 
configurationNames) {
+        // the action runs when the module metadata is generated, so it is 
given values and providers only: with the
+        // configuration cache, neither the project nor this extension is 
available then
+        Provider<Boolean> transitiveDependencies = gpe.transitiveDependencies
+        Provider<Map<String, String>> resolvedVersions = 
resolvedVersionsProvider(project, transitiveDependencies, configurationNames)

Review Comment:
   **Each publication gets two separate resolved-version providers**
   
   `configurePom` (line 675) and this method each call 
`resolvedVersionsProvider` for the same configurations. With the configuration 
cache, both providers are evaluated when the entry is stored. That walks up to 
four resolution graphs twice per publication and stores two copies of the full 
transitive version map in the entry.
   
   Possible fix: create one provider per publication in the `afterEvaluate` 
block and pass it to both `configurePom` and `configureModuleMetadataVersions`.
   



##########
plugin/src/main/groovy/org/apache/grails/gradle/publish/GrailsPublishGradlePlugin.groovy:
##########
@@ -815,26 +822,16 @@ Note: properties are read as Gradle properties (the root 
project's gradle.proper
         }
         NodeList dependencyNodes = (nodes.get(0) as 
Node).getAt(dependencyQName) as NodeList
 
-        LinkedHashSet<ResolvedArtifact> resolvedArtifacts = []
-        for (String configurationName : configurationNames) {
-            def configuration = 
project.configurations.findByName(configurationName)
-            if (configuration != null) {
-                
resolvedArtifacts.addAll(configuration.resolvedConfiguration.resolvedArtifacts)
-            }
-        }
-
-        dependencyNodes.findAll { dependencyNode ->
+        Map<String, String> versions = resolvedVersions.get()

Review Comment:
   **`resolvedVersions.get()` runs before the code checks whether any 
dependency is missing a version**
   
   The same happens at line 1345 for the module metadata. Without the 
configuration cache (the default for most builds), every pom and module 
generation resolves up to four classpaths, even when version mapping already 
wrote every version, which is the usual case. The javadoc says "only queried 
when the pom has dependencies", which promises more laziness than the code 
delivers.
   
   Possible fix: call `get()` only once `dependenciesWithoutVersion` is 
non-empty, and in the module action, only on the first dependency without a 
version.
   



##########
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) {

Review Comment:
   **The pom and module paths handle an empty resolved version differently**
   
   The pom path fails on `!managedVersion` (null or empty, line 835). This 
check only fails on `null`, so an empty version is written into the module 
metadata as `"requires": ""`.
   
   Example: a project dependency on a sibling whose version resolves to `''`. 
Pom generation fails with `No version found`, while the module metadata 
publishes an empty version without any error. Use the same check (`if 
(!resolved)`) in both places.
   



##########
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 {

Review Comment:
   **Commented-out code moved into the new action**
   
   The commented `tasks.withType(GenerateModuleMetadata)...` snippet came along 
into `PomXmlAction`. There is no `tasks` in scope here, and reaching project 
state from this action is exactly what the class exists to avoid. Consider 
deleting it, together with the trailing "or by disabling the check with:", or 
moving the note to `configureModuleMetadataVersions`.
   



-- 
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]

Reply via email to