Skip to content

Commit 482d2f9

Browse files
committed
Merge branch '7.1.x' into 8.0.x
2 parents 7b424fb + 2a92830 commit 482d2f9

File tree

184 files changed

+2054
-865
lines changed

Some content is hidden

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

184 files changed

+2054
-865
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ jobs:
285285
grails
286286
-x 'grails/.git/*'
287287
-x 'grails/.github/*'
288+
-x 'grails/.mailmap'
288289
- name: "🔐 Set up GPG" # use env var to not expose the key
289290
env:
290291
GPG_KEY: ${{ secrets.GRAILS_GPG_KEY }}

.mailmap

Lines changed: 933 additions & 0 deletions
Large diffs are not rendered by default.

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
}

0 commit comments

Comments
 (0)