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

commit 0baef55ba86eb1e38bc84e615ffb5aef20e93c9a
Author: James Daugherty <[email protected]>
AuthorDate: Thu Apr 24 10:41:12 2025 -0400

    Revert "Revert "reproducible builds""
    
    This reverts commit e5d40dedbc209bf1d7e1f32449b9cfe07a66d3b6.
---
 .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
+}

Reply via email to