This is an automated email from the ASF dual-hosted git repository.
jdaugherty pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
The following commit(s) were added to refs/heads/7.0.x by this push:
new de0545c8a0 reproducible builds
de0545c8a0 is described below
commit de0545c8a06bdc943155261dfa21f4b8ace52674
Author: James Daugherty <[email protected]>
AuthorDate: Thu Apr 24 09:59:53 2025 -0400
reproducible builds
---
.github/workflows/gradle.yml | 16 ++++++++++++++
.github/workflows/release.yml | 6 ++++++
build.gradle | 21 +++++++++---------
etc/bin/reproducible-build.sh | 38 +++++++++++++++++++++++++++++++++
gradle/java-config.gradle | 25 ++++++++++++++++++----
grails-gradle/gradle/java-config.gradle | 27 +++++++++++++++++------
6 files changed, 112 insertions(+), 21 deletions(-)
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index d1868e0bfb..c2c47e7976 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -39,6 +39,8 @@ jobs:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout repository"
uses: actions/checkout@v4
- name: "☕️ Setup JDK"
@@ -64,6 +66,8 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout repository"
uses: actions/checkout@v4
- name: "☕️ Setup JDK"
@@ -94,6 +98,8 @@ jobs:
java: [ '17', '21', '23' ]
runs-on: ubuntu-latest
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout repository"
uses: actions/checkout@v4
- name: "☕️ Setup JDK"
@@ -120,6 +126,8 @@ jobs:
java: [ 17, 21 ]
mongodb-version: [ '5.0', '6.0', '7.0', '8.0' ]
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout the repository"
uses: actions/checkout@v4
- name: "☕️ Setup JDK"
@@ -149,6 +157,8 @@ jobs:
matrix:
java: [ 17, 21 ]
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout the repository"
uses: actions/checkout@v4
- name: "☕️ Setup JDK"
@@ -172,6 +182,8 @@ jobs:
contents: read # limit to read access
runs-on: ubuntu-latest
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout repository"
uses: actions/checkout@v4
- name: "☕️ Setup JDK"
@@ -200,6 +212,8 @@ jobs:
contents: read # limit to read access
runs-on: ubuntu-latest
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout repository"
uses: actions/checkout@v4
- name: "☕️ Setup JDK"
@@ -227,6 +241,8 @@ jobs:
permissions:
contents: write
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout the repository"
uses: actions/checkout@v4
- name: "🔀 Store current branch name"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 445c4e9338..8a5c058a3d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -32,6 +32,8 @@ jobs:
release_version: ${{ steps.release_version.outputs.value }}
target_branch: ${{ steps.extract_branch.outputs.value }}
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout repository"
uses: actions/checkout@v4
- name: "☕️ Setup JDK"
@@ -114,6 +116,8 @@ jobs:
permissions:
contents: read # limit to read access
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout repository"
uses: actions/checkout@v4
with:
@@ -159,6 +163,8 @@ jobs:
needs: publish
runs-on: ubuntu-24.04
steps:
+ - name: 'Ensure Common Build Date' # to ensure a reproducible build
+ run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >>
"$GITHUB_ENV"
- name: "📥 Checkout repository"
uses: actions/checkout@v4
with:
diff --git a/build.gradle b/build.gradle
index 84e134cd51..e01425fdb1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,3 +1,7 @@
+import java.time.Instant
+import java.time.ZoneOffset
+import java.time.format.DateTimeFormatter
+
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -18,6 +22,13 @@
*/
ext {
+ buildInstant =
java.util.Optional.ofNullable(System.getenv("SOURCE_DATE_EPOCH"))
+ .map(Long::parseLong)
+ .map(Instant::ofEpochSecond)
+ .orElseGet(Instant::now)
+ formattedBuildDate = DateTimeFormatter.ISO_INSTANT.format(buildInstant)
+ buildDate = (buildInstant as Instant).atZone(ZoneOffset.UTC) // for
reproducible builds
+
grailsVersion = projectVersion
isCiBuild = System.getenv().get('CI') as Boolean
configuredTestParallel = findProperty('maxTestParallel') as Integer ?:
(isCiBuild ? 3 : Runtime.runtime.availableProcessors() * 3 / 4 as int ?: 1)
@@ -74,16 +85,6 @@ subprojects {
}
}
- // This is added to prevent a remote cache misses, because the project JAR
include a manifest
- // file with Built-By and Created-By properties which might be different
for CI vs Local.
- normalization {
- runtimeClasspath {
- metaInf {
- ignoreAttribute('Built-By')
- ignoreAttribute('Created-By')
- }
- }
- }
apply from:
rootProject.layout.projectDirectory.file('gradle/dependency-licenses.gradle')
}
diff --git a/etc/bin/reproducible-build.sh b/etc/bin/reproducible-build.sh
new file mode 100755
index 0000000000..23e6ba3385
--- /dev/null
+++ b/etc/bin/reproducible-build.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+
+cd "$SCRIPT_DIR/../.."
+
+git clean -xdf
+./gradlew build --rerun-tasks -PskipTests
+FIRST_BUILD=$(sha256sum build/libs/*)
+
+git clean -xdf
+./gradlew build --rerun-tasks -PskipTests
+SECOND_BUILD=$(sha256sum build/libs/*)
+
+cd $SCRIPT_DIR
+echo $FIRST_BUILD > first.txt
+echo $SECOND_BUILD > second.txt
+
+diff -u first.txt second.txt
\ No newline at end of file
diff --git a/gradle/java-config.gradle b/gradle/java-config.gradle
index d0aafc7fb2..b18be0d0d7 100644
--- a/gradle/java-config.gradle
+++ b/gradle/java-config.gradle
@@ -25,22 +25,39 @@ extensions.configure(JavaPluginExtension) {
it.withJavadocJar()
}
+tasks.withType(Javadoc).configureEach { Javadoc it ->
+ it.options.noTimestamp true // prevent the file header with the date
+ it.options.bottom "Generated ${formattedBuildDate} (UTC)"
+}
+
+// JavaCompile is not configured because we put java files inside of the
groovy source sets
+
tasks.withType(GroovyCompile).configureEach {
- groovyOptions.encoding = 'UTF-8'
+ groovyOptions.encoding = 'UTF-8' // encoding needs to be the same since
it's different across platforms
// Preserve method parameter names in Groovy classes for IDE parameter
hints.
groovyOptions.parameters = true
- options.encoding = 'UTF-8'
+ options.encoding = 'UTF-8' // encoding needs to be the same since it's
different across platforms
options.fork = true
options.forkOptions.jvmArgs = ['-Xms128M', '-Xmx1G']
}
+// Grails determines the grails version via the META-INF/MANIFEST.MF file
+// Note: we exclude attributes such as Built-By, Build-Jdk, Created-By to
ensure the build is reproducible.
tasks.withType(Jar).configureEach {
manifest.attributes(
- 'Built-By': System.properties['user.name'],
- 'Created-By': System.properties['java.vm.version'] + ' (' +
System.properties['java.vm.vendor'] + ')',
'Implementation-Title': 'Grails',
'Implementation-Version': grailsVersion,
'Implementation-Vendor': 'grails.org'
)
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
+
+// Any jar, zip, or archive should be reproducible
+// No longer needed after https://github.com/gradle/gradle/issues/30871
+tasks.withType(AbstractArchiveTask).configureEach {
+ preserveFileTimestamps = false // to prevent timestamp mismatches
+ reproducibleFileOrder = true // to keep the same ordering
+ dirMode = 0755 // To avoid platform specific defaults
+ fileMode = 0644 // to avoid platform specific defaults
+}
+
diff --git a/grails-gradle/gradle/java-config.gradle
b/grails-gradle/gradle/java-config.gradle
index 7996788a18..b762684f70 100644
--- a/grails-gradle/gradle/java-config.gradle
+++ b/grails-gradle/gradle/java-config.gradle
@@ -25,25 +25,38 @@ extensions.configure(JavaPluginExtension) {
it.withJavadocJar()
}
-tasks.withType(JavaCompile).configureEach {
- options.deprecation = true
- options.debug = true
+tasks.withType(Javadoc).configureEach { Javadoc it ->
+ it.options.noTimestamp true // prevent the file header with the date
+ it.options.bottom "Generated ${formattedBuildDate} (UTC)"
}
+// JavaCompile is not configured because we put java files inside of the
groovy source sets
+
tasks.withType(GroovyCompile).configureEach {
- groovyOptions.encoding = 'UTF-8'
- options.encoding = 'UTF-8'
+ groovyOptions.encoding = 'UTF-8' // encoding needs to be the same since
it's different across platforms
+ // Preserve method parameter names in Groovy classes for IDE parameter
hints.
+ groovyOptions.parameters = true
+ options.encoding = 'UTF-8' // encoding needs to be the same since it's
different across platforms
options.fork = true
options.forkOptions.jvmArgs = ['-Xms128M', '-Xmx1G']
}
+// Grails determines the grails version via the META-INF/MANIFEST.MF file
+// Note: we exclude attributes such as Built-By, Build-Jdk, Created-By to
ensure the build is reproducible.
tasks.withType(Jar).configureEach {
manifest.attributes(
- 'Built-By': System.properties['user.name'],
- 'Created-By': System.properties['java.vm.version'] + ' (' +
System.properties['java.vm.vendor'] + ')',
'Implementation-Title': 'Grails',
'Implementation-Version': projectVersion,
'Implementation-Vendor': 'grails.org'
)
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
+
+// Any jar, zip, or archive should be reproducible
+// No longer needed after https://github.com/gradle/gradle/issues/30871
+tasks.withType(AbstractArchiveTask).configureEach {
+ preserveFileTimestamps = false // to prevent timestamp mismatches
+ reproducibleFileOrder = true // to keep the same ordering
+ dirMode = 0755 // To avoid platform specific defaults
+ fileMode = 0644 // to avoid platform specific defaults
+}