Skip to content

Commit 5b0ef66

Browse files
authored
Merge pull request #15608 from apache/8.0.x-splitbom
8.0.x splitbom
2 parents 11e2d4b + b5d8922 commit 5b0ef66

53 files changed

Lines changed: 1242 additions & 236 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/gradle.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
- name: "🐘 Setup Gradle"
4040
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
4141
with:
42-
develocity-access-key: ${{ secrets.GRAILS_DEVELOCITY_ACCESS_KEY }}
42+
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
4343
- name: "🔍 Validate grails-core dependency versions"
4444
run: >
4545
./gradlew validateDependencyVersions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
.gradle
1313
.gradle/
1414
.idea
15+
.mailmap
1516
.idea/
1617
!.idea/codeStyles/
1718
!.idea/codeStyles/Project.xml
@@ -52,6 +53,9 @@ prodDb.mv.db
5253
testWatchedFile.properties
5354
_alternativeTable.gsp
5455
**/src/en/ref/Versions/Grails BOM.adoc
56+
**/src/en/ref/Versions/Grails BOM Hibernate5.adoc
57+
**/src/en/ref/Versions/Grails BOM Micronaut.adoc
58+
**/src/en/ref/Configuration/Application Properties.adoc
5559
stacktrace.log
5660
target
5761
tmp/

build-logic/docs-core/src/main/groovy/org/apache/grails/gradle/tasks/bom/ExtractDependenciesTask.groovy

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import org.gradle.api.artifacts.ExcludeRule
3434
import org.gradle.api.artifacts.ModuleDependency
3535
import org.gradle.api.artifacts.component.ModuleComponentSelector
3636
import org.gradle.api.artifacts.dsl.DependencyHandler
37+
import org.gradle.api.artifacts.component.ProjectComponentSelector
3738
import org.gradle.api.artifacts.result.DependencyResult
3839
import org.gradle.api.artifacts.result.ResolvedDependencyResult
3940
import org.gradle.api.file.ConfigurableFileCollection
@@ -94,11 +95,30 @@ abstract class ExtractDependenciesTask extends DefaultTask {
9495
@Internal
9596
ConfigurationContainer configurationContainer
9697

98+
/**
99+
* When {@code true}, transitive platform dependencies that are not explicitly
100+
* registered in {@code combinedPlatforms} / {@code dependencies.gradle} will be
101+
* auto-registered in {@link PropertyNameCalculator} instead of causing a build
102+
* failure. This is required for BOMs that import an external platform via the
103+
* gradle module format (e.g. {@code micronaut-platform}) which itself imports
104+
* many sub-BOMs that Grails does not manage directly.
105+
*
106+
* <p>Defaults to {@code false} so that existing BOMs (grails-bom, grails-base-bom)
107+
* retain the strict validation that every platform dependency has a known property
108+
* name in {@code dependencies.gradle}.</p>
109+
*/
110+
@Input
111+
abstract Property<Boolean> getAutoRegisterTransitivePlatforms()
112+
97113
void setConfiguration(NamedDomainObjectProvider<Configuration> config) {
98114
dependencyArtifacts.from(config)
99115
configurationName.set(config.name)
100116
}
101117

118+
ExtractDependenciesTask() {
119+
autoRegisterTransitivePlatforms.convention(false)
120+
}
121+
102122
/**
103123
* Captures project-scoped services at configuration time so they can be used
104124
* at execution time without accessing the deprecated Task.project property.
@@ -200,6 +220,13 @@ abstract class ExtractDependenciesTask extends DefaultTask {
200220
}
201221

202222
ResolvedDependencyResult dep = (ResolvedDependencyResult) result
223+
224+
// Skip project dependencies (e.g. platform(project(':grails-bom'))) since their
225+
// constraints are already captured through the explicit constraints population
226+
if (dep.requested instanceof ProjectComponentSelector) {
227+
continue
228+
}
229+
203230
ModuleComponentSelector moduleComponentSelector = dep.requested as ModuleComponentSelector
204231

205232
// Any non-constraint via api dependency should *always* be a platform dependency, so expand each of those
@@ -211,6 +238,16 @@ abstract class ExtractDependenciesTask extends DefaultTask {
211238

212239
// fetch the BOM as a pom file so it can be expanded
213240
ExtractedDependencyConstraint constraint = propertyNameCalculator.calculate(bomCoordinate.groupId, bomCoordinate.artifactId, bomCoordinate.version, true)
241+
if (!constraint) {
242+
if (autoRegisterTransitivePlatforms.get()) {
243+
// Auto-register the transitive platform so it can be documented and expanded,
244+
// but without a version property reference since the version is managed by the
245+
// parent platform (e.g. micronaut-platform), not by a Grails property.
246+
constraint = new ExtractedDependencyConstraint(bomCoordinate.coordinates)
247+
} else {
248+
throw new GradleException("Failed to find a property name for BOM dependency: ${bomCoordinate.coordinates}. All platform dependencies must have a property name defined meeting the naming requirements.")
249+
}
250+
}
214251
constraint.source = bomCoordinate.artifactId
215252
constraints.put(bomCoordinate.toCoordinateHolder(), constraint)
216253

build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GradleUtils.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ class GradleUtils {
4848
def v = findProperty(project, name)
4949
return v == null ? null : Integer.valueOf(v as String) as T
5050
}
51+
if (type && (type == Boolean || type == boolean.class)) {
52+
def v = findProperty(project, name)
53+
return v == null ? null : Boolean.parseBoolean(v as String) as T
54+
}
5155

5256
findProperty(project, name) as T
5357
}

build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsDependencyValidatorPlugin.groovy

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,47 @@ import org.gradle.api.artifacts.result.ResolvedComponentResult
4747
class GrailsDependencyValidatorPlugin implements Plugin<Project> {
4848

4949
static final String VALIDATE_TASK_NAME = 'validateDependencyVersions'
50-
private static final Set<String> BOM_PROJECT_NAMES = ['grails-bom', 'grails-gradle-bom'].toSet()
50+
/**
51+
* Project ext property name that holds a collection of {@code "group:name"} keys
52+
* to exempt from version validation. Use this when a deliberate override is applied
53+
* (e.g. via {@code resolutionStrategy.useVersion}) and the divergence from the BOM
54+
* is intentional.
55+
*/
56+
static final String ALLOWED_OVERRIDES_EXT = 'allowedBomOverrides'
57+
58+
private static final Set<String> BOM_PROJECT_NAMES = ['grails-bom', 'grails-gradle-bom', 'grails-base-bom', 'grails-hibernate5-bom', 'grails-micronaut-bom'].toSet()
5159

5260
@Override
5361
void apply(Project project) {
5462
project.plugins.withId('java') {
5563
project.tasks.register(VALIDATE_TASK_NAME) { Task task ->
5664
task.group = 'verification'
5765
task.description = 'Validates that no transitive dependency upgrades a version beyond what the BOM manages.'
58-
task.onlyIf { Task t -> !t.project.hasProperty('skipDependencyValidation') }
66+
task.onlyIf { Task t -> !shouldSkip(t.project) }
5967
task.doLast { Task t -> validateDependencies(project) }
6068
}
6169
}
6270
}
6371

72+
/**
73+
* Returns true when the project should skip dependency validation. Honors a
74+
* {@code skipDependencyValidation} project property (any non-null, non-false value)
75+
* and an {@code ext.skipDependencyValidation} extra property. This lets specific
76+
* projects opt out via {@code ext.skipDependencyValidation = true} when they have
77+
* unresolvable BOM conflicts (e.g. Micronaut platform overrides).
78+
*/
79+
private static boolean shouldSkip(Project project) {
80+
if (!project.hasProperty('skipDependencyValidation')) {
81+
return false
82+
}
83+
Object value = project.findProperty('skipDependencyValidation')
84+
if (value == null) {
85+
// -PskipDependencyValidation with no value is treated as truthy
86+
return true
87+
}
88+
return Boolean.parseBoolean(value.toString())
89+
}
90+
6491
private static void validateDependencies(Project project) {
6592
String bomPath = detectBomPath(project)
6693
if (bomPath == null) {
@@ -79,10 +106,14 @@ class GrailsDependencyValidatorPlugin implements Plugin<Project> {
79106
return
80107
}
81108

109+
Set<String> allowedOverrides = resolveAllowedOverrides(project)
82110
Map<String, String> resolvedVersions = collectResolvedVersions(bomConfigurations)
83111
List<String> violations = new ArrayList<>()
84112

85113
for (Map.Entry<String, String> entry : resolvedVersions.entrySet()) {
114+
if (allowedOverrides.contains(entry.key)) {
115+
continue
116+
}
86117
String bomVersion = bomVersions.get(entry.key)
87118
if (bomVersion != null && bomVersion != entry.value) {
88119
violations.add(" ${entry.key} - resolved ${entry.value}, expected ${bomVersion}" as String)
@@ -94,11 +125,38 @@ class GrailsDependencyValidatorPlugin implements Plugin<Project> {
94125
"The following dependencies resolved to versions different from the BOM (${bomPath}):\n\n" +
95126
violations.join('\n') + '\n\n' +
96127
'A transitive dependency is upgrading these versions.\n' +
97-
'To fix, update the dependency version in dependencies.gradle or add an exclusion in the build file.'
128+
'To fix, update the dependency version in dependencies.gradle or add an exclusion in the build file.\n' +
129+
"For intentional overrides, add the coordinate to project.ext.${ALLOWED_OVERRIDES_EXT} (e.g. as a Set<String> of 'group:name' keys)."
98130
throw new GradleException(message)
99131
}
100132
}
101133

134+
/**
135+
* Resolves the set of {@code "group:name"} coordinates the project has marked as
136+
* intentional version overrides, via the {@link #ALLOWED_OVERRIDES_EXT} ext property.
137+
* Accepts a {@link Collection} or a single {@link CharSequence}. Unknown types are
138+
* silently ignored.
139+
*/
140+
private static Set<String> resolveAllowedOverrides(Project project) {
141+
if (!project.extensions.extraProperties.has(ALLOWED_OVERRIDES_EXT)) {
142+
return Collections.emptySet()
143+
}
144+
Object raw = project.extensions.extraProperties.get(ALLOWED_OVERRIDES_EXT)
145+
if (raw instanceof CharSequence) {
146+
return Collections.singleton(raw.toString())
147+
}
148+
if (raw instanceof Collection) {
149+
Set<String> result = new LinkedHashSet<>()
150+
for (Object item : (Collection<?>) raw) {
151+
if (item != null) {
152+
result.add(item.toString())
153+
}
154+
}
155+
return result
156+
}
157+
return Collections.emptySet()
158+
}
159+
102160
/**
103161
* Scans the project's configurations to find which BOM project is in use.
104162
*/

build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/PublishPlugin.groovy

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,26 @@ class PublishPlugin implements Plugin<Project> {
7979
disableSigningWhenTesting(project)
8080

8181
project.plugins.withId('maven-publish') {
82+
// Ensure Gradle module metadata includes resolved versions for all dependencies.
83+
// Without this, dependencies declared without an explicit version (relying on a
84+
// platform/BOM) are published with no version in the .module file, causing
85+
// resolution failures for consumers since Gradle prefers .module over .pom.
86+
// Only apply to non-platform projects since java-platform has no runtimeClasspath.
87+
if (!project.pluginManager.hasPlugin('java-platform')) {
88+
project.extensions.configure(PublishingExtension) { PublishingExtension pe ->
89+
pe.publications.withType(MavenPublication).configureEach { MavenPublication pub ->
90+
pub.versionMapping { strategy ->
91+
strategy.usage('java-api') { variant ->
92+
variant.fromResolutionOf('runtimeClasspath')
93+
}
94+
strategy.usage('java-runtime') { variant ->
95+
variant.fromResolutionResult()
96+
}
97+
}
98+
}
99+
}
100+
}
101+
82102
def artifactsTask = configurePublishedArtifacts(project)
83103

84104
configureChecksums(project, artifactsTask)

0 commit comments

Comments
 (0)