This is an automated email from the ASF dual-hosted git repository. jdaugherty pushed a commit to branch grails-geb in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit da2d80f0be360eaf64c50c2c07e1df50fc84b479 Author: James Daugherty <[email protected]> AuthorDate: Sun Apr 20 01:15:23 2025 -0400 Convert ExtractDependencies into a defined class --- dependencies.gradle | 6 + grails-bom/build.gradle | 222 +----------------- grails-gradle/docs-core/build.gradle | 1 + .../gradle/tasks/bom/CoordinateHolder.groovy | 17 ++ .../tasks/bom/CoordinateVersionHolder.groovy | 25 ++ .../tasks/bom/ExtractDependenciesTask.groovy | 259 +++++++++++++++++++++ .../tasks/bom/ExtractedDependencyConstraint.groovy | 25 ++ .../gradle/tasks/bom/PropertyNameCalculator.groovy | 71 ++++++ 8 files changed, 415 insertions(+), 211 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 79f8a5b3e2..f7007d77f4 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -119,4 +119,10 @@ ext { 'rxjava2' : "io.reactivex.rxjava2:rxjava:${bomDependencyVersions['rxjava2.version']}", 'rxjava3' : "io.reactivex.rxjava3:rxjava:${bomDependencyVersions['rxjava3.version']}", ] + + // Because pom exclusions aren't properly supported by gradle, we can't inherit the grails-gradle-bom + // this requires copying the platforms selectively to the grails-bom. Due this via these combinations + combinedPlatforms = ['spring-boot-bom': gradleBomPlatformDependencies['spring-boot-bom']] + bomPlatformDependencies + combinedDependencies = gradleBomDependencies + bomDependencies + combinedVersions = gradleBomDependencyVersions + bomDependencyVersions } diff --git a/grails-bom/build.gradle b/grails-bom/build.gradle index 6ff1bdbc5b..9f3523d25a 100644 --- a/grails-bom/build.gradle +++ b/grails-bom/build.gradle @@ -1,4 +1,5 @@ -import io.spring.gradle.dependencymanagement.org.apache.maven.model.io.xpp3.MavenXpp3Reader +import org.apache.grails.gradle.tasks.bom.ExtractDependenciesTask +import org.apache.grails.gradle.tasks.bom.PropertyNameCalculator // Copyright 2024 the original author or authors. buildscript { @@ -63,217 +64,15 @@ configurations.register('bomDependencies').configure { it.extendsFrom(configurations.named('api').get()) } -String resolveMavenProperty(String errorDescription, String dynamicVersion, Map properties, int maxIterations = 10) { - def dynamicPattern = ~/\$\{([^}]+)\}/ - String expandedVersion = dynamicVersion - - int iterations = 0 - while ((expandedVersion =~ dynamicPattern).find() && iterations < maxIterations) { - expandedVersion = expandedVersion.replaceAll(dynamicPattern) { String fullMatch, String propName -> - String replacement = properties[propName] as String - return replacement ? replacement : fullMatch - } - iterations++ - } - - if ((expandedVersion =~ dynamicPattern).find()) { - logger.warn("Reached max iterations for ${errorDescription} while resolving properties in: ${dynamicVersion}") - } - - expandedVersion -} - - -Closure determinePossibleKey = { Map.Entry<String, String> found, Map versions -> - String possibleKey = found.key.toString() - while (possibleKey != null) { - String propertyName = "${possibleKey}.version" as String - if (versions.containsKey(propertyName)) { - return propertyName - } - - int lastIndex = possibleKey.lastIndexOf('-') - possibleKey = lastIndex > 0 ? possibleKey.substring(0, lastIndex) : null - } -} - -Closure determinePropertyName = { String groupId, String artifactId, String version, boolean isBom -> - Map<String, String> toSearch = isBom ? bomPlatformDependencies : bomDependencies - Map<String, String> toSearchGradle = isBom ? gradleBomPlatformDependencies : gradleBomDependencies - - Map.Entry<String, String> found = toSearch.entrySet().find { "$groupId:$artifactId:$version" as String == it.value as String } - if (!found) { - found = toSearchGradle.entrySet().find { "$groupId:$artifactId:$version" as String == it.value as String } - if (!found) { - return null - } - } - - Map<String, String> versions = bomDependencyVersions - - String propertyName = determinePossibleKey(found, versions) - if (!propertyName) { - propertyName = determinePossibleKey(found, gradleBomDependencyVersions) - } - - if (propertyName) { - return propertyName - } - - throw new GradleException("Could not determine artifact property key for $groupId:$artifactId:$version") -} - -//TODO: This really should be a gradle task that we export so others can use. -tasks.register('extractConstraints').configure { Task it -> - it.inputs.files(configurations.named('bomDependencies')) - it.outputs.file(project.layout.buildDirectory.file('grails-bom-constraints.adoc')) +tasks.register('extractConstraints', ExtractDependenciesTask).configure { ExtractDependenciesTask it -> + it.configuration =configurations.named('bomDependencies') + it.configurationName = 'bomDependencies' + it.destination = project.layout.buildDirectory.file('grails-bom-constraints.adoc') + it.platformDefinitions = combinedPlatforms + it.definitions = combinedDependencies + it.versions = combinedVersions it.dependsOn(project.tasks.named('generateMetadataFileForMavenPublication'), project.tasks.named('generatePomFileForMavenPublication')) - it.doLast { - def conf = configurations.detachedConfiguration() - conf.transitive = true - - Map<String, Tuple5<String, String, String, String, String>> bomConstraints = [:] - Configuration apiConfiguration = configurations.named('bomDependencies').get() - - // Explicit constraints - apiConfiguration.getAllDependencyConstraints().all { constraint -> - - def groupId = constraint.module.group as String - def artifactId = constraint.module.name as String - def artifactVersion = constraint.version as String - def propertyName = determinePropertyName(groupId, artifactId, artifactVersion, false) - bomConstraints.put("${module.group}:${module.name}" as String, - new Tuple5<>( - groupId, - artifactId, - artifactVersion, - propertyName ? "\${${propertyName}}" : '' as String, - 'grails-bom' - ) - ) - } - - // Build an exclusion list so parsing the pom we can exclude any dependencies as they are matched - Map<String, List<Tuple2<String, String>>> allExclusions = [:].withDefault { [] } - apiConfiguration.allDependencies.each { dep -> - if (dep instanceof org.gradle.api.artifacts.ModuleDependency) { - String coordinates = "${dep.group}:${dep.name}" as String - - dep.excludeRules.each { exclusion -> - Tuple2<String, String> exclude = new Tuple2<>(exclusion.group, exclusion.module) - allExclusions.get(coordinates).add(exclude) - } - } - } - - // Find inherited libraries via platform() usage - apiConfiguration.incoming.resolutionResult.allDependencies.each { ResolvedDependencyResult dep -> - // Any non-constraint via api dependency should *always* be a platform dependency, so expand each of those - String bomGroup = dep.requested.group - String bomName = dep.requested.module - String bomVersion = dep.requested.version - - String bomCoordinates = "${bomGroup}:${bomName}" as String - List<Tuple2<String, String>> excluded = allExclusions.get(bomCoordinates) - - // fetch the BOM as a pom file so it can be expanded - def propertyName = determinePropertyName(bomGroup, bomName, bomVersion, true) - bomConstraints.put(bomCoordinates, new Tuple5<>(bomGroup, bomName, bomVersion, propertyName ? "\${${propertyName}}" : '' as String, bomName)) - populatePlatformDependencies(bomGroup, bomName, bomVersion, bomConstraints, excluded) - } - - List lines = [] - bomConstraints.values().sort { a, b -> a.v1 <=> b.v1 ?: a.v2 <=> b.v2 }.withIndex().each { - int position = it.v2 + 1 - String groupId = it.v1.v1 - String artifactId = it.v1.v2 - String artifactVersion = it.v1.v3 - String propertyName = it.v1.v4 - String bomName = it.v1.v5 - lines << "| ${position} | ${groupId} | ${artifactId} | ${artifactVersion} | ${propertyName ? propertyName == '${project.version}' ? '' : propertyName : ''} | ${bomName} " - } - - project.layout.buildDirectory.file('grails-bom-constraints.adoc').get().asFile.withWriter { writer -> - writer.writeLine '[cols="1,1,1,1,1,1", options="header"]' - writer.writeLine '|===' - writer.writeLine '| Index | Group | Artifact | Version | Property Name | Source' - lines.each { line -> - writer.writeLine(line) - } - writer.writeLine '|===' - } - } -} - -private Properties populatePlatformDependencies(String bomGroup, String bomName, String bomVersion, - Map<String, Tuple5<String, String, String, String, String>> bomConstraints, - List<Tuple2<String, String>> excluded, boolean error = true, int level = 0) { - def bomDependency = dependencies.create("${bomGroup}:${bomName}:${bomVersion}@pom") - def bomConf = configurations.detachedConfiguration(bomDependency) - File bomPomFile = bomConf.singleFile - - def reader = new MavenXpp3Reader() - def model = reader.read(new FileReader(bomPomFile)) - - Properties versionProperties = new Properties() - if (model.parent) { - // Need to populate the parent bom if it's present first - populatePlatformDependencies(model.parent.groupId, model.parent.artifactId, model.parent.version, bomConstraints, excluded, false, level + 1)?.entrySet()?.each { - versionProperties.put(it.key, it.value) - } - } - model.properties.entrySet().each { - versionProperties.put(it.key, it.value) - } - versionProperties.put('project.groupId', bomGroup) - versionProperties.put('project.version', bomVersion) - - if (model.dependencyManagement && model.dependencyManagement.dependencies) { - model.dependencyManagement.dependencies.each { depItem -> - String baseCoordinates = "${depItem.groupId}:${depItem.artifactId}" as String - String artifactId = resolveMavenProperty(baseCoordinates, depItem.artifactId, versionProperties) - String groupId = resolveMavenProperty(baseCoordinates, depItem.groupId, versionProperties) - - String resolvedCoordinates = "${groupId}:${artifactId}" as String - if (!bomConstraints.containsKey(resolvedCoordinates)) { - boolean isExcluded = excluded.any { - if (it.v1 && it.v2) { - return resolvedCoordinates == "${it.v1}:${it.v2}" as String - } - - if (it.v1 && !it.v2) { - return depItem.groupId == it.v1 - } - - if (!it.v1 && it.v2) { - return depItem.artifactId == it.v2 - } - - false - } - if (!isExcluded) { - - String resolvedVersion = resolveMavenProperty(resolvedCoordinates, depItem.version, versionProperties) - String propertyName = depItem.version.contains('$') ? depItem.version : null - if (depItem.scope == 'import') { - bomConstraints.put(resolvedCoordinates, new Tuple5<>(groupId, artifactId, resolvedVersion, propertyName, bomName)) - populatePlatformDependencies(groupId, artifactId, resolvedVersion, bomConstraints, excluded, error, level + 1) - } else { - bomConstraints.put(resolvedCoordinates, new Tuple5<>(groupId, artifactId, resolvedVersion, propertyName, bomName)) - } - } - } - } - } else { - if (error) { - // only the boms we directly include need to error since we expect a dependency management; - // parent boms are sometimes use to share properties so we need to not error on these cases - throw new GradleException("BOM ${bomGroup}:${bomName}:${bomVersion} has no dependencyManagement section.") - } - } - - return versionProperties } tasks.register('validateNoSnapshotDependencies').configure { Task it -> @@ -318,8 +117,9 @@ ext { inlineVersion = null } + PropertyNameCalculator propertyNameCalculator = new PropertyNameCalculator(combinedPlatforms, combinedDependencies, combinedVersions) if (inlineVersion) { - String propertyName = determinePropertyName(groupId, artifactId, inlineVersion, isBom) + String propertyName = propertyNameCalculator.calculate(groupId, artifactId, inlineVersion, isBom) if (propertyName) { // Replace the version in the pom with a property reference String propertyReference = "\${${propertyName}}" diff --git a/grails-gradle/docs-core/build.gradle b/grails-gradle/docs-core/build.gradle index 7c436ea5ac..ecc4d27e89 100644 --- a/grails-gradle/docs-core/build.gradle +++ b/grails-gradle/docs-core/build.gradle @@ -30,6 +30,7 @@ dependencies { api 'org.asciidoctor:asciidoctorj' implementation 'org.xhtmlrenderer:flying-saucer-pdf-openpdf' + implementation 'org.springframework.boot:spring-boot-gradle-plugin' runtimeOnly 'org.slf4j:slf4j-api' diff --git a/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/CoordinateHolder.groovy b/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/CoordinateHolder.groovy new file mode 100644 index 0000000000..b6299a14f2 --- /dev/null +++ b/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/CoordinateHolder.groovy @@ -0,0 +1,17 @@ +package org.apache.grails.gradle.tasks.bom + +import groovy.transform.CompileStatic +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +@EqualsAndHashCode(includes = ['groupId', 'artifactId']) +@CompileStatic +@ToString +class CoordinateHolder { + String groupId + String artifactId + + String getCoordinatesWithoutVersion() { + "${groupId}:${artifactId}" as String + } +} \ No newline at end of file diff --git a/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/CoordinateVersionHolder.groovy b/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/CoordinateVersionHolder.groovy new file mode 100644 index 0000000000..8cc7d1d922 --- /dev/null +++ b/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/CoordinateVersionHolder.groovy @@ -0,0 +1,25 @@ +package org.apache.grails.gradle.tasks.bom + +import groovy.transform.CompileStatic +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString +import org.gradle.api.GradleException + +@EqualsAndHashCode(includes = ['version'], callSuper = true) +@CompileStatic +@ToString +class CoordinateVersionHolder extends CoordinateHolder { + String version + + CoordinateHolder toCoordinateHolder() { + new CoordinateHolder(groupId: groupId, artifactId: artifactId) + } + + String getCoordinates() { + if (!version) { + throw new GradleException("Constraint does not have a version: ${this}") + } + + "${groupId}:${artifactId}:${version}" as String + } +} \ No newline at end of file diff --git a/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/ExtractDependenciesTask.groovy b/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/ExtractDependenciesTask.groovy new file mode 100644 index 0000000000..a44d9c2614 --- /dev/null +++ b/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/ExtractDependenciesTask.groovy @@ -0,0 +1,259 @@ +package org.apache.grails.gradle.tasks.bom + +import io.spring.gradle.dependencymanagement.org.apache.maven.model.Model +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.NamedDomainObjectProvider +import org.gradle.api.artifacts.* +import org.gradle.api.artifacts.component.ModuleComponentSelector +import org.gradle.api.artifacts.result.DependencyResult +import org.gradle.api.artifacts.result.ResolvedDependencyResult +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import io.spring.gradle.dependencymanagement.org.apache.maven.model.io.xpp3.MavenXpp3Reader + +/** + * Grails Bom files define their dependencies in a series of maps, this task takes those maps and generates an + * asciidoc file containing all of the resolve dependencies and their versions in the bom. + */ +abstract class ExtractDependenciesTask extends DefaultTask { + @InputFiles + @Classpath + abstract ConfigurableFileCollection getDependencyArtifacts() + + @OutputFile + abstract RegularFileProperty getDestination() + + @Input + abstract MapProperty<String,String> getVersions() + + @Input + abstract Property<String> getConfigurationName() + + @Input + abstract MapProperty<String, String> getPlatformDefinitions() + + @Input + abstract MapProperty<String, String> getDefinitions() + + void setConfiguration(NamedDomainObjectProvider<Configuration> config) { + dependencyArtifacts.from(config) + configurationName.set(config.name) + } + + ExtractDependenciesTask() { + doFirst { + if(!project.pluginManager.hasPlugin('java-platform')) { + throw new GradleException("The 'java-platform' plugin must be applied to the project to use this task.") + } + } + } + + @TaskAction + void generate() { + File outputFile = destination.get().asFile + outputFile.parentFile.mkdirs() + + Map<CoordinateHolder, ExtractedDependencyConstraint> constraints = [:] + PropertyNameCalculator propertyNameCalculator = new PropertyNameCalculator( + getPlatformDefinitions().get(), + getDefinitions().get(), + getVersions().get() + ) + + Configuration configuration = project.configurations.named(configurationName.get()).get() + if(!configuration.canBeResolved) { + throw new GradleException("The configuration ${configuration.name} must be resolvable to use this task.") + } + + populateExplicitConstraints(configuration, constraints, propertyNameCalculator) + + Map<CoordinateHolder, List<CoordinateHolder>> exclusions = determineExclusions(configuration) + populateInheritedConstraints(configuration, exclusions, constraints, propertyNameCalculator) + + List<String> lines = generateAsciiDoc(constraints) + destination.get().asFile.withWriter { writer -> + writer.writeLine '[cols="1,1,1,1,1,1", options="header"]' + writer.writeLine '|===' + writer.writeLine '| Index | Group | Artifact | Version | Property Name | Source' + lines.each { line -> + writer.writeLine(line) + } + writer.writeLine '|===' + } + } + + private List<String> generateAsciiDoc(Map<CoordinateHolder, ExtractedDependencyConstraint> constraints) { + List lines = [] + constraints.values().sort { ExtractedDependencyConstraint a, ExtractedDependencyConstraint b -> a.groupId <=> b.groupId ?: a.artifactId <=> b.artifactId }.withIndex().each { + int position = it.v2 + 1 + lines << "| ${position} | ${it.v1.groupId} | ${it.v1.artifactId} | ${it.v1.version} | ${it.v1.versionProperty ?: ''} | ${it.v1.source} " + } + lines + } + + private populateExplicitConstraints(Configuration configuration, + Map<CoordinateHolder, ExtractedDependencyConstraint> constraints, + PropertyNameCalculator propertyNameCalculator) { + configuration.getAllDependencyConstraints().all { DependencyConstraint constraint -> + String groupId = constraint.module.group as String + String artifactId = constraint.module.name as String + String artifactVersion = constraint.version as String + + ExtractedDependencyConstraint extractConstraint = propertyNameCalculator.calculate(groupId, artifactId, artifactVersion, false) ?: new ExtractedDependencyConstraint(groupId: groupId, artifactId: artifactId, version: artifactVersion) + extractConstraint.source = project.name + constraints.put(new CoordinateHolder(groupId: extractConstraint.groupId, artifactId: extractConstraint.artifactId), extractConstraint) + } + } + + private Map<CoordinateHolder, List<CoordinateHolder>> determineExclusions(Configuration configuration) { + Map<CoordinateHolder, List<CoordinateHolder>> exclusions = [:].withDefault { [] } + for (Dependency dep : configuration.allDependencies) { + if (dep instanceof ModuleDependency) { + CoordinateHolder foundCoordinate = new CoordinateHolder(groupId: dep.group, artifactId: dep.name) + dep.excludeRules.each { ExcludeRule exclusionRule -> + CoordinateHolder exclusion = new CoordinateHolder(groupId: exclusionRule.group, artifactId: exclusionRule.module) + exclusions.get(foundCoordinate).add(exclusion) + } + } + } + exclusions + } + + private void populateInheritedConstraints(Configuration configuration, Map<CoordinateHolder, List<CoordinateHolder>> exclusions, Map<CoordinateHolder, ExtractedDependencyConstraint> constraints, PropertyNameCalculator propertyNameCalculator) { + for (DependencyResult result : configuration.incoming.resolutionResult.allDependencies) { + if(!(result instanceof ResolvedDependencyResult)) { + throw new GradleException("Dependencies should be resolved prior to running this task.") + } + + ResolvedDependencyResult dep = (ResolvedDependencyResult) result + ModuleComponentSelector moduleComponentSelector = dep.requested as ModuleComponentSelector + + // Any non-constraint via api dependency should *always* be a platform dependency, so expand each of those + CoordinateVersionHolder bomCoordinate = new CoordinateVersionHolder( + groupId: moduleComponentSelector.group, + artifactId: moduleComponentSelector.module, + version: moduleComponentSelector.version + ) + + // fetch the BOM as a pom file so it can be expanded + ExtractedDependencyConstraint constraint = propertyNameCalculator.calculate(bomCoordinate.groupId, bomCoordinate.artifactId, bomCoordinate.version, true) + constraint.source = bomCoordinate.artifactId + constraints.put(bomCoordinate.toCoordinateHolder(), constraint) + + List<CoordinateHolder> exclusionRules = exclusions.get(bomCoordinate.toCoordinateHolder()) + populatePlatformDependencies(bomCoordinate, exclusionRules, constraints) + } + } + + Properties populatePlatformDependencies(CoordinateVersionHolder bomCoordinates, List<CoordinateHolder> exclusionRules, Map<CoordinateHolder, ExtractedDependencyConstraint> constraints, boolean error = true, int level = 0) { + Dependency bomDependency = project.dependencies.create("${bomCoordinates.coordinates}@pom") + Configuration dependencyConfiguration = project.configurations.detachedConfiguration(bomDependency) + File bomPomFile = dependencyConfiguration.singleFile + + MavenXpp3Reader reader = new MavenXpp3Reader() + Model model = reader.read(new FileReader(bomPomFile)) + + Properties versionProperties = new Properties() + if(model.parent) { + // Need to populate the parent bom if it's present first + CoordinateVersionHolder parentBom = new CoordinateVersionHolder( + groupId: model.parent.groupId, + artifactId: model.parent.artifactId, + version: model.parent.version + ) + populatePlatformDependencies(parentBom, exclusionRules, constraints,false, level + 1)?.entrySet()?.each { Map.Entry<Object, Object> entry -> + versionProperties.put(entry.key, entry.value) + } + } + model.properties.entrySet().each { Map.Entry<Object, Object> entry -> + versionProperties.put(entry.key, entry.value) + } + versionProperties.put('project.groupId', bomCoordinates.groupId) + versionProperties.put('project.version', bomCoordinates.version) + + if (model.dependencyManagement && model.dependencyManagement.dependencies) { + for (io.spring.gradle.dependencymanagement.org.apache.maven.model.Dependency depItem : model.dependencyManagement.dependencies) { + CoordinateHolder baseCoordinates = new CoordinateHolder( + groupId: depItem.groupId, + artifactId: depItem.artifactId + ) + + CoordinateHolder resolvedCoordinates = new CoordinateHolder( + groupId: resolveMavenProperty(baseCoordinates.coordinatesWithoutVersion, depItem.groupId, versionProperties), + artifactId: resolveMavenProperty(baseCoordinates.coordinatesWithoutVersion, depItem.artifactId, versionProperties) + ) + + if (!constraints.containsKey(resolvedCoordinates)) { + boolean isExcluded = exclusionRules.any { CoordinateHolder excludedCoordinate -> + if (excludedCoordinate.groupId && excludedCoordinate.artifactId) { + return resolvedCoordinates == excludedCoordinate + } + + if (excludedCoordinate.groupId && !excludedCoordinate.artifactId) { + return depItem.groupId == excludedCoordinate.groupId + } + + if (!excludedCoordinate.groupId && excludedCoordinate.artifactId) { + return depItem.artifactId == excludedCoordinate.artifactId + } + + false + } + + if (!isExcluded) { + String resolvedVersion = resolveMavenProperty(resolvedCoordinates.coordinatesWithoutVersion, depItem.version, versionProperties) + String propertyName = depItem.version.contains('$') ? depItem.version : null + ExtractedDependencyConstraint constraint = new ExtractedDependencyConstraint( + groupId: resolvedCoordinates.groupId, artifactId: resolvedCoordinates.artifactId, + version: resolvedVersion, versionProperty: propertyName, source: bomCoordinates.artifactId + ) + if (depItem.scope == 'import') { + constraints.put(resolvedCoordinates, constraint) + + CoordinateVersionHolder resolvedBomCoordinates = new CoordinateVersionHolder( + groupId: resolvedCoordinates.groupId, + artifactId: resolvedCoordinates.artifactId, + version: resolvedVersion + ) + populatePlatformDependencies(resolvedBomCoordinates, exclusionRules, constraints, error, level + 1) + } else { + constraints.put(resolvedCoordinates,constraint) + } + } + } + } + } else { + if (error) { + // only the boms we directly include need to error since we expect a dependency management; + // parent boms are sometimes use to share properties so we need to not error on these cases + throw new GradleException("BOM ${bomCoordinates.coordinates} has no dependencyManagement section.") + } + } + + versionProperties + } + + private String resolveMavenProperty(String errorDescription, String dynamicVersion, Map properties, int maxIterations = 10) { + def dynamicPattern = ~/\$\{([^}]+)\}/ + String expandedVersion = dynamicVersion + + int iterations = 0 + while ((expandedVersion =~ dynamicPattern).find() && iterations < maxIterations) { + expandedVersion = expandedVersion.replaceAll(dynamicPattern) { String fullMatch, String propName -> + String replacement = properties[propName] as String + return replacement ? replacement : fullMatch + } + iterations++ + } + + if ((expandedVersion =~ dynamicPattern).find()) { + project.logger.warn("Reached max iterations for ${errorDescription} while resolving properties in: ${dynamicVersion}") + } + + expandedVersion + } +} diff --git a/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/ExtractedDependencyConstraint.groovy b/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/ExtractedDependencyConstraint.groovy new file mode 100644 index 0000000000..672a211974 --- /dev/null +++ b/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/ExtractedDependencyConstraint.groovy @@ -0,0 +1,25 @@ +package org.apache.grails.gradle.tasks.bom + +import groovy.transform.CompileStatic +import groovy.transform.MapConstructor +import groovy.transform.ToString + +@CompileStatic +@MapConstructor(includes = ['groupId', 'artifactId', 'version', 'versionProperty', 'source'], includeSuperProperties = true) +@ToString(includes = ['groupId', 'artifactId', 'version', 'versionProperty', 'source'], includeSuperProperties = true) +class ExtractedDependencyConstraint extends CoordinateVersionHolder { + String versionProperty + String source + + ExtractedDependencyConstraint(String coordinates) { + coordinates.split(':').with { String[] parts -> + groupId = parts[0] + artifactId = parts[1] + version = parts[2] + } + } + + String getVersionProperty() { + versionProperty == '${project.version}' ? '' : versionProperty + } +} \ No newline at end of file diff --git a/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/PropertyNameCalculator.groovy b/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/PropertyNameCalculator.groovy new file mode 100644 index 0000000000..56808ff902 --- /dev/null +++ b/grails-gradle/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/PropertyNameCalculator.groovy @@ -0,0 +1,71 @@ +package org.apache.grails.gradle.tasks.bom + +import org.gradle.api.GradleException + +/** + * Decides on the property name for a dependency + */ +class PropertyNameCalculator { + final Map<String, String> keysToPlatformCoordinates = [:] + final Map<String, ExtractedDependencyConstraint> platformDefinitions = [:] + + final Map<String, String> keysToCoordinates = [:] + final Map<String, ExtractedDependencyConstraint> definitions = [:] + final Map<String, String> versions = [:] + + PropertyNameCalculator(Map<String, String> platformDefinitions, Map<String, String> definitions, Map<String, String> definitionVersions) { + this.platformDefinitions.putAll(populate(platformDefinitions, keysToPlatformCoordinates)) + this.definitions.putAll(populate(definitions, keysToCoordinates)) + this.versions.putAll(definitionVersions) + } + + private static Map<String, ExtractedDependencyConstraint> populate(Map<String, String> definitions, Map<String, String> keyMappings) { + definitions.collectEntries { Map.Entry<String, String> entry -> + ExtractedDependencyConstraint constraint = new ExtractedDependencyConstraint(entry.value) + if (!constraint.version) { + throw new GradleException("Version is required for dependency: ${entry.value}") + } + + keyMappings.put(constraint.coordinates, entry.key) + + [constraint.coordinates, constraint] + } + } + + ExtractedDependencyConstraint calculate(String groupId, String artifactId, String version, boolean isPlatform) { + Map<String, ExtractedDependencyConstraint> toSearch = isPlatform ? platformDefinitions : definitions as Map<String, ExtractedDependencyConstraint> + Map<String, String> coordinateMapping = isPlatform ? keysToPlatformCoordinates : keysToCoordinates + + ExtractedDependencyConstraint found = toSearch.get("$groupId:$artifactId:$version" as String) + if (!found) { + return null + } + + if(found.versionProperty) { + return found + } + + String propertyName = determinePossibleKey(found, coordinateMapping) + if (propertyName) { + found.versionProperty = propertyName ? "\${${propertyName}}" : '' as String + return found + } + + throw new GradleException("Could not determine artifact property key for ${found.coordinates}") + } + + String determinePossibleKey(ExtractedDependencyConstraint found, Map<String, String> keyMappings) { + String possibleKey = keyMappings[found.coordinates] + while (possibleKey) { + String propertyName = "${possibleKey}.version" as String + if (versions.containsKey(propertyName)) { + return propertyName + } + + int lastIndex = possibleKey.lastIndexOf('-') + possibleKey = lastIndex > 0 ? possibleKey.substring(0, lastIndex) : null + } + + null + } +} \ No newline at end of file
