jdaugherty commented on code in PR #15568: URL: https://github.com/apache/grails-core/pull/15568#discussion_r3196289258
########## grails-bom/hibernate5-micronaut/build.gradle: ########## @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.grails.gradle.tasks.bom.ExtractDependenciesTask +import org.apache.grails.gradle.tasks.bom.ExtractedDependencyConstraint +import org.apache.grails.gradle.tasks.bom.PropertyNameCalculator + +buildscript { + apply from: rootProject.layout.projectDirectory.file('dependencies.gradle') +} + +plugins { + id 'java-platform' + id 'org.apache.grails.buildsrc.publish' + id 'org.apache.grails.buildsrc.sbom' +} + +version = projectVersion +group = 'org.apache.grails' + +javaPlatform { + allowDependencies() +} + +ext { + isReleaseBuild = System.getenv('GRAILS_PUBLISH_RELEASE') == 'true' + isPublishedExternal = System.getenv().containsKey('NEXUS_PUBLISH_STAGING_PROFILE_ID') + // TODO: It should be possible to pull these build names using includedBuild, but I haven't found a way to do so + gradleBuildProjects = [ + 'grails-gradle-plugins':'org.apache.grails', + 'grails-gradle-model':'org.apache.grails.gradle', + 'grails-gradle-common':'org.apache.grails.gradle', + 'grails-gradle-tasks':'org.apache.grails', + ] +} + +// Register the Micronaut platform in combinedPlatforms/combinedVersions so +// PropertyNameCalculator (used by extractConstraints and pomCustomization) can +// resolve a property name for the micronaut-platform constraint. +project.ext.combinedPlatforms = combinedPlatforms + ['micronaut-platform': "io.micronaut.platform:micronaut-platform:$micronautPlatformVersion".toString()] +project.ext.combinedVersions = combinedVersions + ['micronaut-platform.version': micronautPlatformVersion as String] + +// Coordinates we override via customBomDependencies — these must be excluded from the +// inherited platform chain so they don't conflict with our strictly-versioned overrides +// when consumers apply this BOM via enforcedPlatform. +def overriddenModules = customBomDependencies.values().collect { String coord -> + def parts = coord.split(':') + [group: parts[0], module: parts[1]] +} +Set<String> overriddenCoords = overriddenModules.collect { "${it.group}:${it.module}".toString() } as Set + +dependencies { + api(platform(project(':grails-base-bom'))) { + overriddenModules.each { ovr -> + exclude group: ovr.group, module: ovr.module + } + } + + // Re-export the Micronaut platform so consumers inherit Micronaut's managed versions + // transitively. Exclude Groovy since we declare the required version explicitly via + // customBomDependencies. Exclude Spock since we manage that version ourselves. + api(platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")) { + exclude group: 'org.apache.groovy' + exclude group: 'org.spockframework' + } + + constraints { + // Re-declare base BOM constraints directly so enforcedPlatform() consumers + // get forced versions. Constraints inherited via platform() are not enforced + // by enforcedPlatform — only direct constraints are. + // Skip entries we override below in customBomDependencies to avoid conflicting + // strictly constraints under enforcedPlatform. + gradleBomDependencies.values().each { String coord -> + def parts = coord.split(':') + String key = parts[0] + ':' + parts[1] + if (key in overriddenCoords) { + return + } + api coord + } + bomDependencies.values().each { String coord -> + def parts = coord.split(':') + String key = parts[0] + ':' + parts[1] + if (key in overriddenCoords) return + api coord + } + for (def entry : bomPlatformDependencies.entrySet()) { + api entry.value + } + // Re-declare the Micronaut platform as a constraint for enforcedPlatform support + api "io.micronaut.platform:micronaut-platform:$micronautPlatformVersion" + for (def entry : customBomDependencies.entrySet()) { + def parts = entry.value.split(':') + if (parts.length == 3) { + api("${parts[0]}:${parts[1]}") { + version { + strictly parts[2] + } + } + } else { + api entry.value + } + } + } +} + +configurations.register('bomDependencies').configure { + it.canBeResolved = true + it.transitive = true + it.extendsFrom(configurations.named('api').get()) +} + +tasks.register('extractConstraints', ExtractDependenciesTask).configure { ExtractDependenciesTask it -> + it.captureProjectServices(project.dependencies, project.configurations) + it.configuration = configurations.named('bomDependencies') + it.configurationName = 'bomDependencies' + it.destination = project.layout.buildDirectory.file('grails-hibernate5-micronaut-bom-constraints.adoc') + it.platformDefinitions = combinedPlatforms + it.definitions = combinedDependencies + it.projectName = project.name + it.versions = combinedVersions + // Micronaut's platform imports many sub-BOMs (micronaut-*-bom, netty-bom, etc.) that are + // not explicitly registered in dependencies.gradle. Auto-register them so extractConstraints + // can document their versions without requiring manual entries for every transitive platform. + // this is required because the micronaut bom format uses gradle modules instead of a pom like spring boot + it.autoRegisterTransitivePlatforms = true + rootProject.subprojects.each { p -> + evaluationDependsOn(p.path) + } + it.projectArtifactIds.set(project.provider { + Map<String, String> artifactIdMappings = [:] + + rootProject.subprojects.each { p -> + artifactIdMappings[p.name] = p.findProperty('pomArtifactId') ?: p.name + } + + for (Map.Entry<String, String> dependency : project.ext.gradleBuildProjects.entrySet()) { + artifactIdMappings[dependency.key] = dependency.key + } + + artifactIdMappings + }) + it.forcedGroupPrefixes.set(['org.apache.grails.profiles': 'grails-profile']) + it.projectCoordinateProperties.set(project.provider { + Map<String, String> projectCoordinates = [:] + + rootProject.subprojects.each { p -> + String artifactId = p.findProperty('pomArtifactId') as String ?: p.name + String baseVersionName = artifactId.replaceAll('[.]', '-') + projectCoordinates["${p.group}:${ artifactId}:${p.version}" as String] = baseVersionName + } + + for (Map.Entry<String, String> dependency : project.ext.gradleBuildProjects.entrySet()) { + projectCoordinates["${dependency.value}:${dependency.key}:${project.version}" as String] = dependency.key + } + + projectCoordinates + }) + + it.dependsOn(project.tasks.named('generateMetadataFileForMavenPublication'), project.tasks.named('generatePomFileForMavenPublication')) +} + +def validateNoSnapshotDependencies = tasks.register('validateNoSnapshotDependencies') +validateNoSnapshotDependencies.configure { Task it -> + it.group = 'publishing' + it.description = 'Validates that no snapshot dependencies are present in the project when performing a release.' + + it.doLast { + configurations.each { config -> + config.allDependencies.each { dep -> + if (dep.version && dep.version.contains('-SNAPSHOT')) { + throw new GradleException("Releases cannot have a snapshot dependency: ${dep.group}:${dep.name} (${dep.version})") + } + } + } + } +} + +if (ext.isReleaseBuild && ext.isPublishedExternal) { + project.afterEvaluate { + tasks.named('generateMetadataFileForMavenPublication').configure { + dependsOn(validateNoSnapshotDependencies) + } + tasks.named('generatePomFileForMavenPublication').configure { + dependsOn(validateNoSnapshotDependencies) + } + } +} + +ext { + pomDescription = 'Grails Hibernate 5 Micronaut BOM (Bill of Materials) for Grails projects integrating with Micronaut and Hibernate 5. Layers Hibernate 5 dependency management on top of grails-micronaut-bom; consume as enforcedPlatform.' + pomCustomization = { xml -> + def root = xml.asNode() + + def propertiesNode = root.properties ? root.properties[0] : root.appendNode('properties') + + def depMgmt = root.dependencyManagement?.getAt(0) + def deps = depMgmt?.dependencies?.getAt(0) + if (deps) { + PropertyNameCalculator propertyNameCalculator = new PropertyNameCalculator(combinedPlatforms, combinedDependencies, combinedVersions) + propertyNameCalculator.addForcedGroupPrefix('org.apache.grails.profiles', 'grails-profile') + propertyNameCalculator.addProjects(rootProject.subprojects) + for (String gradleArtifactId : project.ext.gradleBuildProjects) { + propertyNameCalculator.addProject('org.apache.grails.gradle', gradleArtifactId, project.version as String, gradleArtifactId) + } + + Map<String, String> pomProperties = [:] + deps.dependency.each { dep -> + String groupId = dep.groupId.text().trim() + String artifactId = dep.artifactId.text().trim() + boolean isBom = dep.scope.text().trim() == 'import' + + String inlineVersion = dep.version.text().trim() + if (inlineVersion == 'null') { + inlineVersion = null + } + + if (inlineVersion) { + ExtractedDependencyConstraint extractedConstraint = propertyNameCalculator.calculate(groupId, artifactId, inlineVersion, isBom) + if (extractedConstraint?.versionPropertyReference) { + // use the property reference instead of the hard coded version so that it can be + // overriden by the spring boot dependency management plugin Review Comment: Typo: `overriden` → `overridden` (same typo appears in `hibernate7-micronaut/build.gradle` line 238). ########## grails-bom/hibernate5-micronaut/build.gradle: ########## @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.grails.gradle.tasks.bom.ExtractDependenciesTask +import org.apache.grails.gradle.tasks.bom.ExtractedDependencyConstraint +import org.apache.grails.gradle.tasks.bom.PropertyNameCalculator + +buildscript { + apply from: rootProject.layout.projectDirectory.file('dependencies.gradle') +} + +plugins { + id 'java-platform' + id 'org.apache.grails.buildsrc.publish' + id 'org.apache.grails.buildsrc.sbom' +} + +version = projectVersion +group = 'org.apache.grails' + +javaPlatform { + allowDependencies() +} + +ext { + isReleaseBuild = System.getenv('GRAILS_PUBLISH_RELEASE') == 'true' + isPublishedExternal = System.getenv().containsKey('NEXUS_PUBLISH_STAGING_PROFILE_ID') + // TODO: It should be possible to pull these build names using includedBuild, but I haven't found a way to do so + gradleBuildProjects = [ + 'grails-gradle-plugins':'org.apache.grails', + 'grails-gradle-model':'org.apache.grails.gradle', + 'grails-gradle-common':'org.apache.grails.gradle', + 'grails-gradle-tasks':'org.apache.grails', + ] +} + +// Register the Micronaut platform in combinedPlatforms/combinedVersions so +// PropertyNameCalculator (used by extractConstraints and pomCustomization) can +// resolve a property name for the micronaut-platform constraint. +project.ext.combinedPlatforms = combinedPlatforms + ['micronaut-platform': "io.micronaut.platform:micronaut-platform:$micronautPlatformVersion".toString()] +project.ext.combinedVersions = combinedVersions + ['micronaut-platform.version': micronautPlatformVersion as String] + +// Coordinates we override via customBomDependencies — these must be excluded from the +// inherited platform chain so they don't conflict with our strictly-versioned overrides +// when consumers apply this BOM via enforcedPlatform. +def overriddenModules = customBomDependencies.values().collect { String coord -> + def parts = coord.split(':') + [group: parts[0], module: parts[1]] +} +Set<String> overriddenCoords = overriddenModules.collect { "${it.group}:${it.module}".toString() } as Set + +dependencies { + api(platform(project(':grails-base-bom'))) { + overriddenModules.each { ovr -> + exclude group: ovr.group, module: ovr.module + } + } + + // Re-export the Micronaut platform so consumers inherit Micronaut's managed versions + // transitively. Exclude Groovy since we declare the required version explicitly via + // customBomDependencies. Exclude Spock since we manage that version ourselves. + api(platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")) { + exclude group: 'org.apache.groovy' + exclude group: 'org.spockframework' + } + + constraints { + // Re-declare base BOM constraints directly so enforcedPlatform() consumers + // get forced versions. Constraints inherited via platform() are not enforced + // by enforcedPlatform — only direct constraints are. + // Skip entries we override below in customBomDependencies to avoid conflicting + // strictly constraints under enforcedPlatform. + gradleBomDependencies.values().each { String coord -> + def parts = coord.split(':') + String key = parts[0] + ':' + parts[1] + if (key in overriddenCoords) { + return + } + api coord + } + bomDependencies.values().each { String coord -> + def parts = coord.split(':') + String key = parts[0] + ':' + parts[1] + if (key in overriddenCoords) return + api coord + } + for (def entry : bomPlatformDependencies.entrySet()) { + api entry.value + } + // Re-declare the Micronaut platform as a constraint for enforcedPlatform support + api "io.micronaut.platform:micronaut-platform:$micronautPlatformVersion" + for (def entry : customBomDependencies.entrySet()) { + def parts = entry.value.split(':') + if (parts.length == 3) { + api("${parts[0]}:${parts[1]}") { + version { + strictly parts[2] + } + } + } else { + api entry.value + } + } + } +} + +configurations.register('bomDependencies').configure { + it.canBeResolved = true + it.transitive = true + it.extendsFrom(configurations.named('api').get()) +} + +tasks.register('extractConstraints', ExtractDependenciesTask).configure { ExtractDependenciesTask it -> + it.captureProjectServices(project.dependencies, project.configurations) + it.configuration = configurations.named('bomDependencies') + it.configurationName = 'bomDependencies' + it.destination = project.layout.buildDirectory.file('grails-hibernate5-micronaut-bom-constraints.adoc') + it.platformDefinitions = combinedPlatforms + it.definitions = combinedDependencies + it.projectName = project.name + it.versions = combinedVersions + // Micronaut's platform imports many sub-BOMs (micronaut-*-bom, netty-bom, etc.) that are + // not explicitly registered in dependencies.gradle. Auto-register them so extractConstraints + // can document their versions without requiring manual entries for every transitive platform. + // this is required because the micronaut bom format uses gradle modules instead of a pom like spring boot + it.autoRegisterTransitivePlatforms = true + rootProject.subprojects.each { p -> + evaluationDependsOn(p.path) + } + it.projectArtifactIds.set(project.provider { + Map<String, String> artifactIdMappings = [:] + + rootProject.subprojects.each { p -> + artifactIdMappings[p.name] = p.findProperty('pomArtifactId') ?: p.name + } + + for (Map.Entry<String, String> dependency : project.ext.gradleBuildProjects.entrySet()) { + artifactIdMappings[dependency.key] = dependency.key + } + + artifactIdMappings + }) + it.forcedGroupPrefixes.set(['org.apache.grails.profiles': 'grails-profile']) + it.projectCoordinateProperties.set(project.provider { + Map<String, String> projectCoordinates = [:] + + rootProject.subprojects.each { p -> + String artifactId = p.findProperty('pomArtifactId') as String ?: p.name + String baseVersionName = artifactId.replaceAll('[.]', '-') + projectCoordinates["${p.group}:${ artifactId}:${p.version}" as String] = baseVersionName Review Comment: Stray space in GString: `"${p.group}:${ artifactId}:${p.version}"` — `${ artifactId}` has a leading space inside the braces. Should be `${artifactId}`. Same issue on line 166 of `hibernate7-micronaut/build.gradle`. ########## grails-bom/hibernate5-micronaut/build.gradle: ########## @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.grails.gradle.tasks.bom.ExtractDependenciesTask +import org.apache.grails.gradle.tasks.bom.ExtractedDependencyConstraint +import org.apache.grails.gradle.tasks.bom.PropertyNameCalculator + +buildscript { + apply from: rootProject.layout.projectDirectory.file('dependencies.gradle') +} + +plugins { + id 'java-platform' + id 'org.apache.grails.buildsrc.publish' + id 'org.apache.grails.buildsrc.sbom' +} + +version = projectVersion +group = 'org.apache.grails' + +javaPlatform { + allowDependencies() +} + +ext { + isReleaseBuild = System.getenv('GRAILS_PUBLISH_RELEASE') == 'true' + isPublishedExternal = System.getenv().containsKey('NEXUS_PUBLISH_STAGING_PROFILE_ID') + // TODO: It should be possible to pull these build names using includedBuild, but I haven't found a way to do so + gradleBuildProjects = [ + 'grails-gradle-plugins':'org.apache.grails', + 'grails-gradle-model':'org.apache.grails.gradle', + 'grails-gradle-common':'org.apache.grails.gradle', + 'grails-gradle-tasks':'org.apache.grails', + ] +} + +// Register the Micronaut platform in combinedPlatforms/combinedVersions so +// PropertyNameCalculator (used by extractConstraints and pomCustomization) can +// resolve a property name for the micronaut-platform constraint. +project.ext.combinedPlatforms = combinedPlatforms + ['micronaut-platform': "io.micronaut.platform:micronaut-platform:$micronautPlatformVersion".toString()] +project.ext.combinedVersions = combinedVersions + ['micronaut-platform.version': micronautPlatformVersion as String] + +// Coordinates we override via customBomDependencies — these must be excluded from the +// inherited platform chain so they don't conflict with our strictly-versioned overrides +// when consumers apply this BOM via enforcedPlatform. +def overriddenModules = customBomDependencies.values().collect { String coord -> + def parts = coord.split(':') + [group: parts[0], module: parts[1]] +} +Set<String> overriddenCoords = overriddenModules.collect { "${it.group}:${it.module}".toString() } as Set + +dependencies { + api(platform(project(':grails-base-bom'))) { + overriddenModules.each { ovr -> + exclude group: ovr.group, module: ovr.module + } + } + + // Re-export the Micronaut platform so consumers inherit Micronaut's managed versions + // transitively. Exclude Groovy since we declare the required version explicitly via + // customBomDependencies. Exclude Spock since we manage that version ourselves. + api(platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")) { + exclude group: 'org.apache.groovy' + exclude group: 'org.spockframework' + } + + constraints { + // Re-declare base BOM constraints directly so enforcedPlatform() consumers + // get forced versions. Constraints inherited via platform() are not enforced + // by enforcedPlatform — only direct constraints are. + // Skip entries we override below in customBomDependencies to avoid conflicting + // strictly constraints under enforcedPlatform. + gradleBomDependencies.values().each { String coord -> + def parts = coord.split(':') + String key = parts[0] + ':' + parts[1] + if (key in overriddenCoords) { + return + } + api coord + } + bomDependencies.values().each { String coord -> + def parts = coord.split(':') + String key = parts[0] + ':' + parts[1] + if (key in overriddenCoords) return + api coord + } + for (def entry : bomPlatformDependencies.entrySet()) { + api entry.value + } + // Re-declare the Micronaut platform as a constraint for enforcedPlatform support + api "io.micronaut.platform:micronaut-platform:$micronautPlatformVersion" + for (def entry : customBomDependencies.entrySet()) { + def parts = entry.value.split(':') + if (parts.length == 3) { + api("${parts[0]}:${parts[1]}") { + version { + strictly parts[2] + } + } + } else { + api entry.value + } + } + } +} + +configurations.register('bomDependencies').configure { + it.canBeResolved = true + it.transitive = true + it.extendsFrom(configurations.named('api').get()) +} + +tasks.register('extractConstraints', ExtractDependenciesTask).configure { ExtractDependenciesTask it -> + it.captureProjectServices(project.dependencies, project.configurations) + it.configuration = configurations.named('bomDependencies') + it.configurationName = 'bomDependencies' + it.destination = project.layout.buildDirectory.file('grails-hibernate5-micronaut-bom-constraints.adoc') + it.platformDefinitions = combinedPlatforms + it.definitions = combinedDependencies + it.projectName = project.name + it.versions = combinedVersions + // Micronaut's platform imports many sub-BOMs (micronaut-*-bom, netty-bom, etc.) that are + // not explicitly registered in dependencies.gradle. Auto-register them so extractConstraints + // can document their versions without requiring manual entries for every transitive platform. + // this is required because the micronaut bom format uses gradle modules instead of a pom like spring boot + it.autoRegisterTransitivePlatforms = true + rootProject.subprojects.each { p -> + evaluationDependsOn(p.path) + } + it.projectArtifactIds.set(project.provider { + Map<String, String> artifactIdMappings = [:] + + rootProject.subprojects.each { p -> + artifactIdMappings[p.name] = p.findProperty('pomArtifactId') ?: p.name + } + + for (Map.Entry<String, String> dependency : project.ext.gradleBuildProjects.entrySet()) { + artifactIdMappings[dependency.key] = dependency.key + } + + artifactIdMappings + }) + it.forcedGroupPrefixes.set(['org.apache.grails.profiles': 'grails-profile']) + it.projectCoordinateProperties.set(project.provider { + Map<String, String> projectCoordinates = [:] + + rootProject.subprojects.each { p -> + String artifactId = p.findProperty('pomArtifactId') as String ?: p.name + String baseVersionName = artifactId.replaceAll('[.]', '-') + projectCoordinates["${p.group}:${ artifactId}:${p.version}" as String] = baseVersionName + } + + for (Map.Entry<String, String> dependency : project.ext.gradleBuildProjects.entrySet()) { + projectCoordinates["${dependency.value}:${dependency.key}:${project.version}" as String] = dependency.key + } + + projectCoordinates + }) + + it.dependsOn(project.tasks.named('generateMetadataFileForMavenPublication'), project.tasks.named('generatePomFileForMavenPublication')) +} + +def validateNoSnapshotDependencies = tasks.register('validateNoSnapshotDependencies') +validateNoSnapshotDependencies.configure { Task it -> + it.group = 'publishing' + it.description = 'Validates that no snapshot dependencies are present in the project when performing a release.' + + it.doLast { + configurations.each { config -> + config.allDependencies.each { dep -> + if (dep.version && dep.version.contains('-SNAPSHOT')) { + throw new GradleException("Releases cannot have a snapshot dependency: ${dep.group}:${dep.name} (${dep.version})") + } + } + } + } +} + +if (ext.isReleaseBuild && ext.isPublishedExternal) { + project.afterEvaluate { + tasks.named('generateMetadataFileForMavenPublication').configure { + dependsOn(validateNoSnapshotDependencies) + } + tasks.named('generatePomFileForMavenPublication').configure { + dependsOn(validateNoSnapshotDependencies) + } + } +} + +ext { + pomDescription = 'Grails Hibernate 5 Micronaut BOM (Bill of Materials) for Grails projects integrating with Micronaut and Hibernate 5. Layers Hibernate 5 dependency management on top of grails-micronaut-bom; consume as enforcedPlatform.' + pomCustomization = { xml -> + def root = xml.asNode() + + def propertiesNode = root.properties ? root.properties[0] : root.appendNode('properties') + + def depMgmt = root.dependencyManagement?.getAt(0) + def deps = depMgmt?.dependencies?.getAt(0) + if (deps) { + PropertyNameCalculator propertyNameCalculator = new PropertyNameCalculator(combinedPlatforms, combinedDependencies, combinedVersions) + propertyNameCalculator.addForcedGroupPrefix('org.apache.grails.profiles', 'grails-profile') + propertyNameCalculator.addProjects(rootProject.subprojects) + for (String gradleArtifactId : project.ext.gradleBuildProjects) { + propertyNameCalculator.addProject('org.apache.grails.gradle', gradleArtifactId, project.version as String, gradleArtifactId) + } + + Map<String, String> pomProperties = [:] + deps.dependency.each { dep -> + String groupId = dep.groupId.text().trim() + String artifactId = dep.artifactId.text().trim() + boolean isBom = dep.scope.text().trim() == 'import' + + String inlineVersion = dep.version.text().trim() + if (inlineVersion == 'null') { + inlineVersion = null + } + + if (inlineVersion) { + ExtractedDependencyConstraint extractedConstraint = propertyNameCalculator.calculate(groupId, artifactId, inlineVersion, isBom) + if (extractedConstraint?.versionPropertyReference) { + // use the property reference instead of the hard coded version so that it can be + // overriden by the spring boot dependency management plugin + dep.version[0].value = extractedConstraint.versionPropertyReference + + // Add an entry in the <properties> node with the actual version number + pomProperties.put(extractedConstraint.versionPropertyName, inlineVersion) + } + } else if (!inlineVersion) { + throw new GradleException("Dependency $groupId:$artifactId does not have a version.") + } + } + + for (Map.Entry<String, String> property : pomProperties.entrySet()) { + propertiesNode.appendNode(property.key, property.value) + } + } + } +} Review Comment: `hibernate5-micronaut/build.gradle` and `hibernate7-micronaut/build.gradle` are 254-line near-identical copies of each other (and closely mirror `grails-bom/micronaut/build.gradle`). The only real differences are the `pomDescription` string and the BOM project referenced. Consider extracting the shared boilerplate into a convention plugin or shared Gradle script to reduce 3× duplication and make future maintenance easier. ########## grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.java: ########## @@ -0,0 +1,421 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.orm.hibernate.cfg; + +import java.io.IOException; +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import javax.sql.DataSource; + +import jakarta.annotation.Nullable; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.MappedSuperclass; + +import org.hibernate.HibernateException; +import org.hibernate.MappingException; +import org.hibernate.SessionFactory; +import org.hibernate.boot.registry.BootstrapServiceRegistry; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.AdditionalMappingContributor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.BytecodeSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.cfg.JdbcSettings; +import org.hibernate.context.spi.CurrentSessionContext; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.service.ServiceRegistry; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.util.ClassUtils; + +import org.grails.datastore.gorm.GormEntity; +import org.grails.datastore.gorm.jdbc.connections.DataSourceSettings; +import org.grails.datastore.mapping.core.connections.ConnectionSource; +import org.grails.datastore.mapping.model.PersistentEntity; +import org.grails.orm.hibernate.EventListenerIntegrator; +import org.grails.orm.hibernate.GrailsSessionContext; +import org.grails.orm.hibernate.HibernateEventListeners; +import org.grails.orm.hibernate.MetadataIntegrator; +import org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsDomainBinder; +import org.grails.orm.hibernate.cfg.domainbinding.util.NamingStrategyProvider; +import org.grails.orm.hibernate.proxy.GrailsBytecodeProvider; + +/** + * A Configuration that uses a MappingContext to configure Hibernate + * + * @since 5.0 + */ +@SuppressWarnings({"rawtypes", "PMD.UseProperClassLoader", "PMD.DataflowAnomalyAnalysis", "PMD.CloseResource"}) +public class HibernateMappingContextConfiguration extends Configuration + implements ApplicationContextAware, Serializable { + + @Serial + private static final long serialVersionUID = -7115087342689305517L; + + private static final String RESOURCE_PATTERN = "/**/*.class"; + + private static final TypeFilter[] ENTITY_TYPE_FILTERS = new TypeFilter[] { + new AnnotationTypeFilter(Entity.class, false), + new AnnotationTypeFilter(Embeddable.class, false), + new AnnotationTypeFilter(MappedSuperclass.class, false) + }; + private static final String FALSE_LITERAL = "false"; + private final Class<? extends CurrentSessionContext> currentSessionContext = GrailsSessionContext.class; + // private MetadataContributor metadataContributor; + private final Set<Class> additionalClasses = new HashSet<>(); + protected String sessionFactoryBeanName = "sessionFactory"; + protected String dataSourceName = ConnectionSource.DEFAULT; + protected transient HibernateMappingContext hibernateMappingContext; + private transient HibernateEventListeners hibernateEventListeners; + private Map<String, Object> eventListeners; + private transient ServiceRegistry serviceRegistry; + private transient ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + private transient NamingStrategyProvider namingStrategyProvider = new NamingStrategyProvider(); + protected GrailsBytecodeProvider bytecodeProvider; + + public void setBytecodeProvider(GrailsBytecodeProvider bytecodeProvider) { + this.bytecodeProvider = bytecodeProvider; + } + + public NamingStrategyProvider getNamingStrategyProvider() { + return namingStrategyProvider; + } + + public void setNamingStrategyProvider(NamingStrategyProvider namingStrategyProvider) { + this.namingStrategyProvider = namingStrategyProvider; + } + + public MappingCacheHolder getMappingCacheHolder() { + return hibernateMappingContext != null ? hibernateMappingContext.getMappingCacheHolder() : null; + } + + public void setHibernateMappingContext(HibernateMappingContext hibernateMappingContext) { + this.hibernateMappingContext = hibernateMappingContext; + } + + @Override + public void setApplicationContext(@Nullable ApplicationContext applicationContext) throws BeansException { + resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(applicationContext); + String dsName = ConnectionSource.DEFAULT.equals(dataSourceName) ? "dataSource" : "dataSource_" + dataSourceName; + Properties properties = getProperties(); + + if (applicationContext != null) { + if (!properties.containsKey(JdbcSettings.JAKARTA_NON_JTA_DATASOURCE) && applicationContext.containsBean(dsName)) { + properties.put(JdbcSettings.JAKARTA_NON_JTA_DATASOURCE, applicationContext.getBean(dsName)); + } + properties.put(Environment.CURRENT_SESSION_CONTEXT_CLASS, currentSessionContext.getName()); + properties.put( + "hibernate.enhancer.bytecodeprovider.instance", + getGrailsBytecodeProvider()); + properties.put("hibernate.bytecode.allow_enhancement_as_proxy", FALSE_LITERAL); + properties.put("hibernate.bytecode.enhancement_metadata_cache", FALSE_LITERAL); + properties.put("hibernate.enhancer.enableLazyInitialization", FALSE_LITERAL); + properties.put("hibernate.enhancer.enableDirtyTracking", FALSE_LITERAL); + properties.put("hibernate.enhancer.enableAssociationManagement", FALSE_LITERAL); + ClassLoader classLoader = applicationContext.getClassLoader(); + if (classLoader != null) { + properties.put(AvailableSettings.CLASSLOADERS, classLoader); + } + } + } + + protected GrailsBytecodeProvider getGrailsBytecodeProvider() { + return bytecodeProvider != null ? bytecodeProvider : new GrailsBytecodeProvider(); + } + + /** + * Set the target SQL {@link DataSource} + * + * @param connectionSource The data source to use + */ + public void setDataSourceConnectionSource(ConnectionSource<DataSource, DataSourceSettings> connectionSource) { + this.dataSourceName = connectionSource.getName(); + DataSource source = connectionSource.getSource(); + getProperties().put(JdbcSettings.JAKARTA_NON_JTA_DATASOURCE, source); + getProperties().put(Environment.CURRENT_SESSION_CONTEXT_CLASS, GrailsSessionContext.class.getName()); + setBytecodeProvider(getGrailsBytecodeProvider()); + final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader != null && + contextClassLoader.getClass().getSimpleName().equalsIgnoreCase("RestartClassLoader")) { + getProperties().put(AvailableSettings.CLASSLOADERS, contextClassLoader); + } else { + getProperties() + .put( + AvailableSettings.CLASSLOADERS, + connectionSource.getClass().getClassLoader()); + } + } + + /** + * Add the given annotated classes in a batch. + * + * @return Configuration + * @see #addAnnotatedClass + * @see #scanPackages + */ + @Override + public Configuration addAnnotatedClasses(Class... annotatedClasses) { + for (Class<?> annotatedClass : annotatedClasses) { + addAnnotatedClass(annotatedClass); + } + return this; + } + + @Override + public Configuration addAnnotatedClass(Class annotatedClass) { + additionalClasses.add(annotatedClass); + return super.addAnnotatedClass(annotatedClass); + } + + @Override + public HibernateMappingContextConfiguration addPackages(String... annotatedPackages) { + for (String annotatedPackage : annotatedPackages) { + addPackage(annotatedPackage); + } + return this; + } + + /** + * Perform Spring-based scanning for entity classes, registering them as annotated classes with + * this {@code Configuration}. + * + * @param packagesToScan one or more Java package names + * @throws HibernateException if scanning fails for any reason + */ + public void scanPackages(String... packagesToScan) throws HibernateException { + try { + MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver); + for (String pkg : packagesToScan) { + String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(pkg) + + RESOURCE_PATTERN; + Resource[] resources = resourcePatternResolver.getResources(pattern); + for (Resource resource : resources) { + if (resource.isReadable()) { + MetadataReader reader = readerFactory.getMetadataReader(resource); + String className = reader.getClassMetadata().getClassName(); + if (matchesFilter(reader, readerFactory)) { + ClassLoader classLoader = resourcePatternResolver.getClassLoader(); + Class<?> loadedClass = classLoader != null ? + classLoader.loadClass(className) : + ClassUtils.forName(className, null); + addAnnotatedClasses(loadedClass); + } + } + } + } + } catch (IOException ex) { + throw new MappingException("Failed to scan classpath for unlisted classes", ex); + } catch (ClassNotFoundException ex) { + throw new MappingException("Failed to load annotated classes from classpath", ex); + } + } + + /** + * Check whether any of the configured entity type filters matches the current class descriptor + * contained in the metadata reader. + */ + protected boolean matchesFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { + for (TypeFilter filter : ENTITY_TYPE_FILTERS) { + if (filter.match(reader, readerFactory)) { + return true; + } + } + return false; + } + + public void setSessionFactoryBeanName(String name) { + sessionFactoryBeanName = name; + } + + public void setDataSourceName(String name) { + dataSourceName = name; + } + + /* (non-Javadoc) + * @see org.hibernate.cfg.Configuration#buildSessionFactory() + */ + @Override + public SessionFactory buildSessionFactory() throws HibernateException { + // 1. FORCE the custom bytecode provider instance right before bootstrap + // This bypasses the ServiceLoader and ensures your GrailsBytecodeProvider is used. + GrailsBytecodeProvider bytecodeProvider = getGrailsBytecodeProvider(); + getProperties() + .put( + BytecodeSettings.BYTECODE_PROVIDER_INSTANCE, + bytecodeProvider); + + // set the class loader to load Groovy classes + + // work around for HHH-2624 + SessionFactory sessionFactory; + + Object classLoaderObject = getProperties().get(AvailableSettings.CLASSLOADERS); + ClassLoader appClassLoader; + + if (classLoaderObject instanceof ClassLoader) { + appClassLoader = (ClassLoader) classLoaderObject; + } else { + appClassLoader = getClass().getClassLoader(); + } + + ConfigurationHelper.resolvePlaceHolders(getProperties()); + + final GrailsDomainBinder domainBinder = new GrailsDomainBinder( + dataSourceName, + sessionFactoryBeanName, + hibernateMappingContext, + namingStrategyProvider, + hibernateMappingContext.getMappingCacheHolder()); + + List<Class> annotatedClasses = new ArrayList<>(); + for (PersistentEntity persistentEntity : hibernateMappingContext.getPersistentEntities()) { + Class<?> javaClass = persistentEntity.getJavaClass(); + if (javaClass.isAnnotationPresent(Entity.class)) { + annotatedClasses.add(javaClass); + } + } + + if (!additionalClasses.isEmpty()) { + for (Class additionalClass : additionalClasses) { + if (GormEntity.class.isAssignableFrom(additionalClass)) { + hibernateMappingContext.addPersistentEntity(additionalClass); + } + } + } + + addAnnotatedClasses(annotatedClasses.toArray(new Class[0])); + + ClassLoaderService classLoaderService = new ClassLoaderServiceImpl(appClassLoader) { + @Override + public <S> Collection<S> loadJavaServices(Class<S> serviceContract) { + // Ensure Grails contributes mappings for GORM entities even if they lack JPA @Entity + if (AdditionalMappingContributor.class.isAssignableFrom(serviceContract)) { + // Include the GrailsDomainBinder first, then any other contributors + // discovered by the parent classloader (e.g., Envers AdditionalMappingContributorImpl). + // Without this, Envers' AdditionalMappingContributor would be excluded, + // preventing EnversService from being initialized before EnversIntegrator runs. + Collection<S> parentContributors = super.loadJavaServices(serviceContract); + @SuppressWarnings("unchecked") + S grailsBinder = (S) domainBinder; + List<S> allContributors = new ArrayList<>(parentContributors.size() + 1); + allContributors.add(grailsBinder); + allContributors.addAll(parentContributors); + return allContributors; Review Comment: Good fix. Previously `Collections.singletonList(domainBinder)` silently excluded all other `AdditionalMappingContributor` implementations (including Envers' `AdditionalMappingContributorImpl`), causing `EnversService` to be uninitialized before `EnversIntegrator` ran. Prepending the Grails binder before the parent contributors is the correct ordering. ########## grails-doc/src/en/guide/upgrading/upgrading80x.adoc: ########## @@ -267,6 +267,36 @@ This affects any code path that goes through Micronaut's HTTP client, filters, o NOTE: The Grails Forge generator enforces this requirement: selecting any Micronaut feature with a JDK version below 25 will fail with `IllegalArgumentException` at generation time. +===== 7.4 Hibernate-Specific Micronaut BOMs Review Comment: Section is numbered `7.4` — please verify that sections `7.1`–`7.3` exist earlier in the document, otherwise the numbering skips. If this is the only or first sub-section of section 7, it should be numbered `7.1`. ########## grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy: ########## @@ -482,10 +488,12 @@ ${importStatements} } throw new GradleException( - "Project '${project.name}' uses Micronaut but does not apply grails-micronaut-bom as an enforcedPlatform. " + + "Project '${project.name}' uses Micronaut but does not apply a Micronaut BOM as an enforcedPlatform. " + Review Comment: The error message uses single-quoted strings, so `$grailsVersion` is printed literally. That's intentional as example code, but it may confuse users who try to copy-paste it. Consider using `<grailsVersion>` as the placeholder instead, which is unambiguous as a fill-in token. -- 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]
