Skip to content

Commit 712a1c0

Browse files
authored
Merge pull request #15269 from apache/gradle-centralization
Centralizing Gradle Logic - java-config
2 parents 796cd2f + e121dee commit 712a1c0

File tree

177 files changed

+719
-572
lines changed

Some content is hidden

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

177 files changed

+719
-572
lines changed

RELEASE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -489,12 +489,12 @@ Some common gotchas with Java build reproducibility problems:
489489
not ship property files with dynamic timestamps.
490490
4. Gradle builds are not reproducible by default because Gradle does not guarantee file ordering. Grails configures all
491491
tasks that create archive files to ensure file ordering is reproducible. This configuration can be found in
492-
`gradle/java-config.gradle`
492+
the `CompilePlugin` in the `build-logic` project.
493493
5. Grails makes heavy use of Groovy AST transforms. These transforms often lookup methods on a class, and the methods
494494
returned by Java will vary by operating system. Any code using reflection to lookup methods or fields must sort the
495495
results to ensure reproducibility.
496496
6. Javadoc / Groovydoc will by default write out dates in it's documentation headers. Grails configures these settings
497-
in `gradle/java-config.gradle`.
497+
in the `CompilePlugin` in the `build-logic` project.
498498
499499
After a build is made reproducible on the same machine, the next step is to ensure it's reproducible in GitHub actions.
500500
OS differences will exist and even exist between different Docker Runtimes. To help test reproducible builds, we found

build-logic/plugins/build.gradle

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,21 @@ dependencies {
4242

4343
gradlePlugin {
4444
plugins {
45-
publishPlugin {
45+
register('compilePlugin') {
46+
id = 'org.apache.grails.buildsrc.compile'
47+
implementationClass = 'org.apache.grails.buildsrc.CompilePlugin'
48+
}
49+
register('publishPlugin') {
4650
id = 'org.apache.grails.buildsrc.publish'
4751
implementationClass = 'org.apache.grails.buildsrc.PublishPlugin'
4852
}
49-
sbomPlugin {
53+
register('sbomPlugin') {
5054
id = 'org.apache.grails.buildsrc.sbom'
5155
implementationClass = 'org.apache.grails.buildsrc.SbomPlugin'
5256
}
57+
register('sharedPropertyPlugin') {
58+
id = 'org.apache.grails.buildsrc.properties'
59+
implementationClass = 'org.apache.grails.buildsrc.SharedPropertyPlugin'
60+
}
5361
}
5462
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.grails.buildsrc
21+
22+
import java.nio.charset.StandardCharsets
23+
import java.util.concurrent.atomic.AtomicBoolean
24+
25+
import groovy.transform.CompileStatic
26+
27+
import org.gradle.api.Plugin
28+
import org.gradle.api.Project
29+
import org.gradle.api.file.DuplicatesStrategy
30+
import org.gradle.api.plugins.JavaPluginExtension
31+
import org.gradle.api.tasks.bundling.AbstractArchiveTask
32+
import org.gradle.api.tasks.bundling.Jar
33+
import org.gradle.api.tasks.compile.GroovyCompile
34+
import org.gradle.api.tasks.compile.JavaCompile
35+
import org.gradle.api.tasks.javadoc.Javadoc
36+
import org.gradle.external.javadoc.StandardJavadocDocletOptions
37+
38+
import static org.apache.grails.buildsrc.GradleUtils.lookupPropertyByType
39+
40+
@CompileStatic
41+
class CompilePlugin implements Plugin<Project> {
42+
43+
@Override
44+
void apply(Project project) {
45+
def initialized = new AtomicBoolean(false)
46+
project.plugins.withId('java') { // java (applied when groovy is applied) or java-library
47+
if (initialized.compareAndSet(false, true)) {
48+
configureCompile(project)
49+
}
50+
}
51+
}
52+
53+
private static void configureCompile(Project project) {
54+
configureJavaVersion(project)
55+
configureJars(project)
56+
configureCompiler(project)
57+
configureReproducible(project)
58+
}
59+
60+
private static void configureJavaVersion(Project project) {
61+
project.tasks.withType(JavaCompile).configureEach {
62+
it.options.release.set(lookupPropertyByType(project, 'javaVersion', Integer))
63+
}
64+
}
65+
66+
private static void configureJars(Project project) {
67+
project.extensions.configure(JavaPluginExtension) {
68+
it.withJavadocJar()
69+
it.withSourcesJar()
70+
}
71+
72+
// Grails determines the grails version via the META-INF/MANIFEST.MF file
73+
// Note: we exclude attributes such as Built-By, Build-Jdk, Created-By to ensure the build is reproducible.
74+
project.tasks.withType(Jar).configureEach { Jar jar ->
75+
if (lookupPropertyByType(project, 'skipJavaComponent', Boolean)) {
76+
jar.enabled = false
77+
return
78+
}
79+
80+
jar.manifest.attributes(
81+
'Implementation-Title': 'Apache Grails',
82+
'Implementation-Version': lookupPropertyByType(project, 'grailsVersion', String),
83+
'Implementation-Vendor': 'grails.apache.org'
84+
)
85+
// Explicitly fail since duplicates indicate a double configuration that needs fixed
86+
jar.duplicatesStrategy = DuplicatesStrategy.FAIL
87+
}
88+
}
89+
90+
private static void configureCompiler(Project project) {
91+
project.tasks.withType(JavaCompile).configureEach {
92+
// Preserve method parameter names in Groovy/Java classes for IDE parameter hints & bean reflection metadata.
93+
it.options.compilerArgs.add('-parameters')
94+
// encoding needs to be the same since it's different across platforms
95+
it.options.encoding = StandardCharsets.UTF_8.name()
96+
it.options.fork = true
97+
it.options.forkOptions.jvmArgs = ['-Xms128M', '-Xmx2G']
98+
}
99+
100+
project.plugins.withId('groovy') {
101+
project.tasks.withType(GroovyCompile).configureEach {
102+
// encoding needs to be the same since it's different across platforms
103+
it.groovyOptions.encoding = StandardCharsets.UTF_8.name()
104+
// Preserve method parameter names in Groovy/Java classes for IDE parameter hints & bean reflection metadata.
105+
it.groovyOptions.parameters = true
106+
// encoding needs to be the same since it's different across platforms
107+
it.options.encoding = StandardCharsets.UTF_8.name()
108+
it.options.fork = true
109+
it.options.forkOptions.jvmArgs = ['-Xms128M', '-Xmx2G']
110+
}
111+
}
112+
}
113+
114+
private static void configureReproducible(Project project) {
115+
project.tasks.withType(Javadoc).configureEach { Javadoc it ->
116+
def options = it.options as StandardJavadocDocletOptions
117+
options.noTimestamp = true
118+
options.bottom = "Generated ${lookupPropertyByType(project, 'formattedBuildDate', String)} (UTC)"
119+
}
120+
121+
// Any jar, zip, or archive should be reproducible
122+
// No longer needed after https://github.com/gradle/gradle/issues/30871
123+
project.tasks.withType(AbstractArchiveTask).configureEach {
124+
it.preserveFileTimestamps = false // to prevent timestamp mismatches
125+
it.reproducibleFileOrder = true // to keep the same ordering
126+
// to avoid platform specific defaults, set the permissions consistently
127+
it.filePermissions { permissions ->
128+
permissions.unix(0644)
129+
}
130+
it.dirPermissions { permissions ->
131+
permissions.unix(0755)
132+
}
133+
}
134+
}
135+
}

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,42 @@ import org.gradle.api.file.Directory
2727
class GradleUtils {
2828

2929
static Directory findRootGrailsCoreDir(Project project) {
30-
def rootLayout = project.rootProject.layout
3130
// .github / .git related directories are purged from source releases, so use the .asf.yaml as an indicator of
3231
// the parent directory
33-
if (rootLayout.projectDirectory.file('.asf.yaml').asFile.exists()) {
34-
return rootLayout.projectDirectory
35-
}
32+
findAsfRootDir(project.layout.projectDirectory)
33+
}
3634

37-
// we currently only nest 1 project level deep
38-
rootLayout.projectDirectory.dir('../')
35+
static Directory findAsfRootDir(Directory currentDirectory) {
36+
def asfFile = currentDirectory.file('.asf.yaml').asFile
37+
asfFile.exists() ? currentDirectory : findAsfRootDir(currentDirectory.dir('../'))
3938
}
4039

4140
static <T> T lookupProperty(Project project, String name, T defaultValue = null) {
42-
project.findProperty(name) as T ?: defaultValue
41+
T v = lookupPropertyByType(project, name, defaultValue?.class) as T
42+
return v == null ? defaultValue : v
43+
}
44+
45+
static <T> T lookupPropertyByType(Project project, String name, Class<T> type) {
46+
// a cast exception will occur without this
47+
if (type && (type == Integer || type == int.class)) {
48+
def v = findProperty(project, name)
49+
return v == null ? null : Integer.valueOf(v as String) as T
50+
}
51+
52+
findProperty(project, name) as T
53+
}
54+
55+
static Object findProperty(Project project, String name) {
56+
def property = project.findProperty(name)
57+
if (property != null) {
58+
return property
59+
}
60+
61+
def ext = project.extensions.extraProperties
62+
if (ext.has(name)) {
63+
return ext.get(name)
64+
}
65+
66+
null
4367
}
4468
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.grails.buildsrc
21+
22+
import groovy.transform.CompileStatic
23+
24+
import org.gradle.api.Plugin
25+
import org.gradle.api.Project
26+
import org.gradle.api.file.Directory
27+
import org.gradle.api.plugins.ExtraPropertiesExtension
28+
29+
import static org.apache.grails.buildsrc.GradleUtils.findRootGrailsCoreDir
30+
31+
/**
32+
* Gradle can't share properties across buildSrc or composite projects. This plugin ensures that properties not defined
33+
* in this project, but are in the root grails-core project, are accessible in this project. This plugin must be applied
34+
* prior to the access of any property for it to work properly
35+
*/
36+
@CompileStatic
37+
class SharedPropertyPlugin implements Plugin<Project> {
38+
39+
@Override
40+
void apply(Project project) {
41+
populateParentProperties(
42+
project.layout.projectDirectory,
43+
findRootGrailsCoreDir(project),
44+
project.extensions.extraProperties,
45+
project
46+
)
47+
}
48+
49+
void populateParentProperties(Directory projectDirectory, Directory rootDirectory, ExtraPropertiesExtension ext, Project project) {
50+
if (!rootDirectory) {
51+
throw new IllegalStateException('Could not locate the root directory to populate up to')
52+
}
53+
54+
if (projectDirectory.file('gradle.properties').asFile.exists()) {
55+
def propertyPath = rootDirectory.asFile.relativePath(projectDirectory.asFile)
56+
project.logger.info('Using properties from grails-core/{}gradle.properties', propertyPath ? "${propertyPath}/" : '')
57+
projectDirectory.file('gradle.properties').asFile.withInputStream {
58+
Properties rootProperties = new Properties()
59+
rootProperties.load(it)
60+
61+
for (String key : rootProperties.stringPropertyNames()) {
62+
if (!ext.has(key)) {
63+
ext.set(key, rootProperties.getProperty(key))
64+
}
65+
}
66+
67+
if (rootProperties.containsKey('projectVersion')) {
68+
ext.set('grailsVersion', rootProperties.getProperty('projectVersion'))
69+
}
70+
}
71+
}
72+
73+
if (projectDirectory.asFile.absolutePath != rootDirectory.asFile.absolutePath) {
74+
populateParentProperties(projectDirectory.dir('..'), rootDirectory, ext, project)
75+
}
76+
}
77+
}

build.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ ext {
3131
DateTimeFormatter.ISO_INSTANT.format(buildInstant) :
3232
DateTimeFormatter.ISO_DATE.format(LocalDate.ofInstant(buildInstant as Instant, ZoneOffset.UTC))
3333
buildDate = (buildInstant as Instant).atZone(ZoneOffset.UTC) // for reproducible builds
34-
35-
grailsVersion = projectVersion
3634
isCiBuild = System.getenv().get('CI') as Boolean
3735
configuredTestParallel = findProperty('maxTestParallel') as Integer ?: (isCiBuild ? 3 : Runtime.runtime.availableProcessors() * 3 / 4 as int ?: 1)
3836
excludeUnusedTransDeps = findProperty('excludeUnusedTransDeps')

buildSrc/build.gradle

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,10 @@
1919

2020
plugins {
2121
id 'groovy-gradle-plugin'
22+
id 'org.apache.grails.buildsrc.properties'
2223
}
2324

24-
file('../gradle.properties').withInputStream {
25-
Properties props = new Properties()
26-
props.load(it)
27-
project.ext.gradleProperties = props
28-
}
29-
30-
compileJava.options.release = gradleProperties.javaVersion.toString().toInteger()
25+
compileJava.options.release = javaVersion.toString().toInteger()
3126

3227
repositories {
3328
// mavenLocal()
@@ -61,14 +56,13 @@ repositories {
6156
}
6257

6358
dependencies {
64-
implementation platform("org.apache.grails:grails-gradle-bom:${gradleProperties.projectVersion}")
59+
implementation platform("org.apache.grails:grails-gradle-bom:$projectVersion")
6560
implementation 'org.apache.grails.gradle:grails-publish'
6661
implementation 'cloud.wondrify:asset-pipeline-gradle'
6762
implementation 'org.apache.grails:grails-docs-core'
6863
implementation 'org.apache.grails:grails-gradle-plugins'
6964
implementation 'org.asciidoctor:asciidoctor-gradle-jvm'
7065
implementation 'org.springframework.boot:spring-boot-gradle-plugin'
71-
implementation "org.nosphere.apache.rat:org.nosphere.apache.rat.gradle.plugin:${gradleProperties.apacheRatVersion}"
72-
implementation "org.cyclonedx.bom:org.cyclonedx.bom.gradle.plugin:${gradleProperties.gradleCycloneDxPluginVersion}"
73-
implementation "org.gradle.crypto.checksum:org.gradle.crypto.checksum.gradle.plugin:${gradleProperties.gradleChecksumPluginVersion}"
66+
implementation "org.nosphere.apache.rat:org.nosphere.apache.rat.gradle.plugin:$apacheRatVersion"
67+
implementation "org.cyclonedx.bom:org.cyclonedx.bom.gradle.plugin:$gradleCycloneDxPluginVersion"
7468
}

buildSrc/src/main/groovy/grails/doc/macros/HiddenMacro.groovy renamed to buildSrc/settings.gradle

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,12 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
20-
package grails.doc.macros
21-
22-
import org.radeox.macro.BaseMacro
23-
import org.radeox.macro.parameter.MacroParameter
24-
25-
class HiddenMacro extends BaseMacro {
26-
String getName() { "hidden" }
27-
28-
void execute(Writer out, MacroParameter params) {
29-
out << '<div class="hidden-block">' << params.content << '</div>'
19+
pluginManagement {
20+
includeBuild('../build-logic') {
21+
name = 'build-logic-root'
3022
}
3123
}
24+
25+
includeBuild('../build-logic') {
26+
name = 'build-logic-root'
27+
}

0 commit comments

Comments
 (0)