You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by jd...@apache.org on 2025/08/04 19:23:26 UTC
(grails-core) 01/01: fix: #14951 - make groovy extension merge for shadowJar reproducible
This is an automated email from the ASF dual-hosted git repository.
jdaugherty pushed a commit to branch reproducibleGroovyExtension
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 815a39be30c1a3326f200bb69b9c1a2f58d37df9
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Mon Aug 4 15:23:16 2025 -0400
fix: #14951 - make groovy extension merge for shadowJar reproducible
---
grails-common/build.gradle | 2 +
.../common/properties/PropertyFileUtils.groovy | 57 --------
.../GlobalGrailsClassInjectorTransformation.groovy | 2 +-
grails-forge/buildSrc/build.gradle | 1 +
.../GrailsGroovyExtensionTransformer.groovy | 96 ++++++++++++++
grails-forge/grails-cli/build.gradle | 8 +-
.../common}/build.gradle | 37 +++---
.../grails/gradle/common/PropertyFileUtils.groovy | 80 ++++++++++++
.../gradle/common/PropertyFileUtilsSpec.groovy | 144 +++++++++++++++++++++
grails-gradle/gradle/publish-root-config.gradle | 3 +-
grails-gradle/plugins/build.gradle | 1 +
.../gradle/plugin/core/GrailsGradlePlugin.groovy | 31 +----
grails-gradle/settings.gradle | 3 +
.../grails/gsp/compiler/GroovyPageCompiler.groovy | 2 +-
14 files changed, 358 insertions(+), 109 deletions(-)
diff --git a/grails-common/build.gradle b/grails-common/build.gradle
index 3dadf85c54..1823888a0b 100644
--- a/grails-common/build.gradle
+++ b/grails-common/build.gradle
@@ -27,6 +27,8 @@ group = 'org.apache.grails.common'
dependencies {
implementation platform(project(':grails-bom'))
+ api 'org.apache.grails.gradle.common:grails-gradle-common'
+
// WARNING: this file is not to depend on grails-core; it is meant to be a shared library that can be used
// independently of Grails. Mainly to share data between Grails & GORM
diff --git a/grails-common/src/main/groovy/org/apache/grails/common/properties/PropertyFileUtils.groovy b/grails-common/src/main/groovy/org/apache/grails/common/properties/PropertyFileUtils.groovy
deleted file mode 100644
index 018add755c..0000000000
--- a/grails-common/src/main/groovy/org/apache/grails/common/properties/PropertyFileUtils.groovy
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.apache.grails.common.properties
-
-import java.nio.charset.StandardCharsets
-import java.time.LocalDate
-import java.time.ZoneOffset
-import java.util.regex.Pattern
-
-final class PropertyFileUtils {
-
- private PropertyFileUtils() {
- // prevent instantiation
- }
-
- // TODO: this is copied from grails-common, but we should consider moving grails-common up to grails-gradle once
- // the publish plugin is moved out of grails-core
- static void makePropertiesFileReproducible(File factoriesFile) {
- String sourceDateEpoch = System.getenv('SOURCE_DATE_EPOCH')
- if (!sourceDateEpoch) {
- sourceDateEpoch = LocalDate.now(ZoneOffset.UTC)
- .atStartOfDay(ZoneOffset.UTC)
- .toEpochSecond().toString()
- }
-
- Pattern timeRegex = Pattern.compile('^#(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)(?:,|\\s).*$')
-
- List<String> lines = factoriesFile.readLines(StandardCharsets.ISO_8859_1.name())
-
- boolean dateReplaced = false
- factoriesFile.withWriter { BufferedWriter writer ->
- lines.each { String line ->
- if (!dateReplaced && timeRegex.matcher(line).matches()) {
- dateReplaced = true
- writer.writeLine("# SOURCE_DATE_EPOCH = ${sourceDateEpoch}" as String)
- return
- }
-
- writer.writeLine(line)
- }
- }
- }
-}
diff --git a/grails-core/src/main/groovy/org/grails/compiler/injection/GlobalGrailsClassInjectorTransformation.groovy b/grails-core/src/main/groovy/org/grails/compiler/injection/GlobalGrailsClassInjectorTransformation.groovy
index db424c91f2..4b71658b2c 100644
--- a/grails-core/src/main/groovy/org/grails/compiler/injection/GlobalGrailsClassInjectorTransformation.groovy
+++ b/grails-core/src/main/groovy/org/grails/compiler/injection/GlobalGrailsClassInjectorTransformation.groovy
@@ -34,7 +34,7 @@ import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlSlurper
import groovy.xml.slurpersupport.GPathResult
import org.apache.grails.common.compiler.GroovyTransformOrder
-import org.apache.grails.common.properties.PropertyFileUtils
+import org.apache.grails.gradle.common.PropertyFileUtils
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.ClassHelper
diff --git a/grails-forge/buildSrc/build.gradle b/grails-forge/buildSrc/build.gradle
index d43981885c..9446ec85f3 100644
--- a/grails-forge/buildSrc/build.gradle
+++ b/grails-forge/buildSrc/build.gradle
@@ -95,6 +95,7 @@ dependencies {
implementation "org.antlr:antlr4-runtime:$antlr4Version"
implementation "org.gradle.crypto.checksum:org.gradle.crypto.checksum.gradle.plugin:$gradleChecksumPluginVersion"
implementation "org.apache.ant:ant:$antVersion"
+ implementation "org.apache.grails.gradle.common:grails-gradle-common:$projectVersion"
}
gradlePlugin {
diff --git a/grails-forge/buildSrc/src/main/groovy/org/apache/grails/forge/buildlogic/shadowjar/GrailsGroovyExtensionTransformer.groovy b/grails-forge/buildSrc/src/main/groovy/org/apache/grails/forge/buildlogic/shadowjar/GrailsGroovyExtensionTransformer.groovy
new file mode 100644
index 0000000000..3622fa483c
--- /dev/null
+++ b/grails-forge/buildSrc/src/main/groovy/org/apache/grails/forge/buildlogic/shadowjar/GrailsGroovyExtensionTransformer.groovy
@@ -0,0 +1,96 @@
+package org.apache.grails.forge.buildlogic.shadowjar
+
+import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
+import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
+import groovy.transform.CompileStatic
+import org.apache.grails.gradle.common.PropertyFileUtils
+import org.apache.tools.zip.ZipEntry
+import org.apache.tools.zip.ZipOutputStream
+import org.gradle.api.file.FileTreeElement
+
+/**
+ * Based on com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionModuleTransformer but with
+ * reproducible property file support
+ */
+@CompileStatic
+class GrailsGroovyExtensionTransformer implements Transformer {
+
+ private static final String GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATH = "META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule"
+
+ private static final String MODULE_NAME_KEY = 'moduleName'
+ private static final String MODULE_VERSION_KEY = 'moduleVersion'
+ private static final String EXTENSION_CLASSES_KEY = 'extensionClasses'
+ private static final String STATIC_EXTENSION_CLASSES_KEY = 'staticExtensionClasses'
+
+ private static final String MERGED_MODULE_NAME = 'MergedByGrailsShadowJar'
+ private static final String MERGED_MODULE_VERSION = '1.0.0'
+
+ private final Properties module = new Properties()
+
+ @Override
+ boolean canTransformResource(FileTreeElement element) {
+ def path = element.relativePath.pathString
+ path == GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATH
+ }
+
+ @Override
+ void transform(TransformerContext context) {
+ def props = new Properties()
+ props.load(context.is)
+ props.entrySet().each { Map.Entry<Object, Object> entry ->
+ String key = entry.key as String
+ String value = entry.value as String
+ switch (key) {
+ case MODULE_NAME_KEY:
+ handle(key, value) {
+ module.setProperty(key, MERGED_MODULE_NAME)
+ }
+ break
+ case MODULE_VERSION_KEY:
+ handle(key, value) {
+ module.setProperty(key, MERGED_MODULE_VERSION)
+ }
+ break
+ case [EXTENSION_CLASSES_KEY, STATIC_EXTENSION_CLASSES_KEY]:
+ handle(key, value) { String existingValue ->
+ def newValue = "${existingValue},${value}"
+ module.setProperty(key, newValue)
+ }
+ break
+ }
+ }
+ }
+
+ private handle(String key, String value, Closure mergeValue) {
+ def existingValue = module.getProperty(key)
+ if (existingValue) {
+ mergeValue(existingValue)
+ } else {
+ module.setProperty(key, value)
+ }
+ }
+
+ @Override
+ boolean hasTransformedResource() {
+ return module.size() > 0
+ }
+
+ @Override
+ void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) {
+ ZipEntry entry = new ZipEntry(GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATH)
+ entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time)
+ os.putNextEntry(entry)
+
+ os.write()
+ try (InputStream inputStream = toInputStream(module)) {
+ inputStream.transferTo(os)
+ }
+ os.closeEntry()
+ }
+
+ private static InputStream toInputStream(Properties props) {
+ def baos = new ByteArrayOutputStream()
+ props.store(baos, null)
+ return PropertyFileUtils.makePropertiesOutputReproducible(baos)
+ }
+}
diff --git a/grails-forge/grails-cli/build.gradle b/grails-forge/grails-cli/build.gradle
index 3d6e707de6..a48763bb71 100644
--- a/grails-forge/grails-cli/build.gradle
+++ b/grails-forge/grails-cli/build.gradle
@@ -15,6 +15,7 @@
* limitations under the License.
*/
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import org.apache.grails.forge.buildlogic.shadowjar.GrailsGroovyExtensionTransformer
import org.apache.grails.forge.buildlogic.shadowjar.GrailsShadowLicenseTransform
import org.apache.grails.forge.buildlogic.shadowjar.GrailsShadowNoticeTransform
@@ -100,8 +101,9 @@ shadowJarTask.configure { ShadowJar it ->
it.mergeServiceFiles()
it.mergeServiceFiles('META-INF/grails.factories')
- // groovy extensions are shipped with grails-core
- it.mergeGroovyExtensionModules()
+ // groovy extensions are shipped with grails-core, use our own transformer instead of mergeGroovyExtensionModules()
+ // for property file reproducibility
+ it.transform(GrailsGroovyExtensionTransformer)
// combines all NOTICE files
it.transform(GrailsShadowNoticeTransform) {
@@ -117,6 +119,8 @@ shadowJarTask.configure { ShadowJar it ->
it.exclude(
'DISCLAIMER', // because we include our own jars, we need to exclude any files that we include
'META-INF/DEPENDENCIES', // until we publish our own SBOM, this won't be correct so exclude
+ 'META-INF/grails-plugin.xml', // we do not start or compile a grails application so these files are not needed (grails-core, url mappings, etc plugins)
+ 'META-INF/grails-plugin.xml.asc', // avoid signing artifacts
'about.html' // restatement of the Eclipse Distribution License - Version 1.0 for jakarta
)
}
diff --git a/grails-common/build.gradle b/grails-gradle/common/build.gradle
similarity index 66%
copy from grails-common/build.gradle
copy to grails-gradle/common/build.gradle
index 3dadf85c54..a306925d4f 100644
--- a/grails-common/build.gradle
+++ b/grails-gradle/common/build.gradle
@@ -18,36 +18,37 @@
plugins {
id 'groovy'
id 'java-library'
- id 'project-report'
}
version = projectVersion
-group = 'org.apache.grails.common'
+group = 'org.apache.grails.gradle.common'
-dependencies {
- implementation platform(project(':grails-bom'))
-
- // WARNING: this file is not to depend on grails-core; it is meant to be a shared library that can be used
- // independently of Grails. Mainly to share data between Grails & GORM
+ext {
+ pomTitle = 'Grails Gradle Common'
+ pomDescription = 'Various common utilities that are shared between Grails Gradle code and the Grails Framework'
+ pomDevelopers = ['jdaugherty': 'James Daugherty']
+}
- // the annotation metadata readers are used by both GORM & grails-core so it can be placed in a shared build
- implementation 'org.springframework:spring-core'
+dependencies {
+ compileOnly platform(project(':grails-gradle-bom'))
- api 'org.apache.groovy:groovy'
+ // compile with the Groovy version provided by Gradle
+ // to ensure build compatibility with Gradle, currently Groovy 3.0.x
+ // see: https://docs.gradle.org/current/userguide/compatibility.html#groovy
+ compileOnly 'org.codehaus.groovy:groovy'
- testImplementation 'org.apache.groovy:groovy-test-junit5'
+ // Testing
+ testImplementation platform(project(':grails-gradle-bom'))
+ testImplementation 'org.slf4j:slf4j-simple'
+ testImplementation('org.spockframework:spock-core') { transitive = false }
+ testImplementation 'org.codehaus.groovy:groovy-test-junit5'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testImplementation 'org.junit.platform:junit-platform-runner'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
- // Logging
- api 'org.slf4j:slf4j-api'
- api 'org.slf4j:jcl-over-slf4j'
-
- // Testing
- testImplementation 'org.slf4j:slf4j-simple'
- testImplementation('org.spockframework:spock-core') { transitive = false }
+ // for easier setting of environment variables in tests
+ testImplementation 'uk.org.webcompere:system-stubs-core:2.1.8'
}
apply {
diff --git a/grails-gradle/common/src/main/groovy/org/apache/grails/gradle/common/PropertyFileUtils.groovy b/grails-gradle/common/src/main/groovy/org/apache/grails/gradle/common/PropertyFileUtils.groovy
new file mode 100644
index 0000000000..1ca3b9b76f
--- /dev/null
+++ b/grails-gradle/common/src/main/groovy/org/apache/grails/gradle/common/PropertyFileUtils.groovy
@@ -0,0 +1,80 @@
+/*
+ * 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.apache.grails.gradle.common
+
+import groovy.transform.CompileStatic
+import org.codehaus.groovy.runtime.IOGroovyMethods
+
+import java.nio.charset.StandardCharsets
+import java.time.LocalDate
+import java.time.ZoneOffset
+import java.util.regex.Pattern
+
+@CompileStatic
+final class PropertyFileUtils {
+ private final static Pattern TIME_REGEX = Pattern.compile('^#(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)(?:,|\\s).*$')
+
+ private PropertyFileUtils() {
+ // prevent instantiation
+ }
+
+ static ByteArrayInputStream makePropertiesOutputReproducible(ByteArrayOutputStream propertyOutputStream) {
+ List<String> lines = propertyOutputStream.toString(StandardCharsets.ISO_8859_1.name()).readLines()
+
+ ByteArrayOutputStream toWrite = new ByteArrayOutputStream()
+ toWrite.withWriter(StandardCharsets.ISO_8859_1.name()) { Writer writer ->
+ BufferedWriter bufferedWriter = new BufferedWriter(writer)
+ makePropertyLinesReproducible(lines, bufferedWriter)
+ bufferedWriter.flush()
+ }
+
+ new ByteArrayInputStream(toWrite.toByteArray())
+ }
+
+ static void makePropertiesFileReproducible(File factoriesFile) {
+ List<String> lines = factoriesFile.readLines(StandardCharsets.ISO_8859_1.name())
+
+ factoriesFile.withWriter(StandardCharsets.ISO_8859_1.name()) { BufferedWriter writer ->
+ makePropertyLinesReproducible(lines, writer)
+ }
+ }
+
+ private static void makePropertyLinesReproducible(List<String> lines, BufferedWriter writer) {
+ String sourceDateEpoch = determineSourceDateEpoch()
+
+ boolean dateReplaced = false
+ lines.each { String line ->
+ if (!dateReplaced && TIME_REGEX.matcher(line).matches()) {
+ dateReplaced = true
+ IOGroovyMethods.writeLine(writer, "# SOURCE_DATE_EPOCH = ${sourceDateEpoch}" as String)
+ return
+ }
+
+ writer.writeLine(line)
+ }
+ }
+
+ private static String determineSourceDateEpoch() {
+ String sourceDateEpoch = System.getenv('SOURCE_DATE_EPOCH')
+ if (!sourceDateEpoch) {
+ sourceDateEpoch = LocalDate.now(ZoneOffset.UTC)
+ .atStartOfDay(ZoneOffset.UTC)
+ .toEpochSecond().toString()
+ }
+ sourceDateEpoch
+ }
+}
diff --git a/grails-gradle/common/src/test/groovy/org/apache/grails/gradle/common/PropertyFileUtilsSpec.groovy b/grails-gradle/common/src/test/groovy/org/apache/grails/gradle/common/PropertyFileUtilsSpec.groovy
new file mode 100644
index 0000000000..8020e763b5
--- /dev/null
+++ b/grails-gradle/common/src/test/groovy/org/apache/grails/gradle/common/PropertyFileUtilsSpec.groovy
@@ -0,0 +1,144 @@
+/*
+ * 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.apache.grails.gradle.common
+
+import spock.lang.Specification
+import uk.org.webcompere.systemstubs.SystemStubs
+
+import java.nio.charset.StandardCharsets
+import java.time.LocalDate
+import java.time.ZoneOffset
+
+class PropertyFileUtilsSpec extends Specification {
+
+ def 'file - replaces first timestamp comment with default SOURCE_DATE_EPOCH'() {
+ given:
+ File file = File.createTempFile('props', '.properties')
+ file.write([
+ '# comment before',
+ '#Fri Jan 01 00:00:00 UTC 1970',
+ 'key1=value1',
+ '#Sat Mar 03 15:00:00 UTC 2001'
+ ].join('\n'), StandardCharsets.ISO_8859_1.name())
+
+ when:
+ PropertyFileUtils.makePropertiesFileReproducible(file)
+
+ then:
+ List<String> lines = file.readLines(StandardCharsets.ISO_8859_1.name())
+ String expected = LocalDate.now(ZoneOffset.UTC)
+ .atStartOfDay(ZoneOffset.UTC)
+ .toEpochSecond()
+ .toString()
+ lines[1] == "# SOURCE_DATE_EPOCH = ${expected}"
+ // later timestamp is untouched
+ lines.contains('#Sat Mar 03 15:00:00 UTC 2001')
+
+ cleanup:
+ file?.delete()
+ }
+
+ def 'file - preserves content when no timestamp comment is present'() {
+ given:
+ File file = File.createTempFile('notimestamp', '.properties')
+ file.deleteOnExit()
+ file.write('fuz=buz\nweb=foo', StandardCharsets.ISO_8859_1.name())
+
+ when:
+ PropertyFileUtils.makePropertiesFileReproducible(file)
+
+ then:
+ file.readLines(StandardCharsets.ISO_8859_1.name()) == ['fuz=buz', 'web=foo']
+ }
+
+ def 'file - uses SOURCE_DATE_EPOCH env var when set'() {
+ given:
+ File file = File.createTempFile('envprops', '.properties')
+ file.deleteOnExit()
+ file.write('#Mon Apr 04 04:04:04 UTC 2004\nhello=world', StandardCharsets.ISO_8859_1.name())
+
+ when:
+ SystemStubs.withEnvironmentVariable('SOURCE_DATE_EPOCH', '42424242').execute {
+ PropertyFileUtils.makePropertiesFileReproducible(file)
+ }
+
+ then:
+ file.readLines(StandardCharsets.ISO_8859_1.name())[0] == '# SOURCE_DATE_EPOCH = 42424242'
+ }
+
+ def 'outputstream - replaces first timestamp comment with default SOURCE_DATE_EPOCH'() {
+ given:
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()
+ baos.write([
+ '# comment before',
+ '#Fri Jan 01 00:00:00 UTC 1970',
+ 'key1=value1',
+ '#Sat Mar 03 15:00:00 UTC 2001'
+ ].join('\n').getBytes(StandardCharsets.ISO_8859_1.name()))
+
+ when:
+ ByteArrayInputStream result = PropertyFileUtils.makePropertiesOutputReproducible(baos)
+ String output = new String(result.readAllBytes(), StandardCharsets.ISO_8859_1.name())
+ List<String> lines = output.readLines()
+
+ then:
+ String expected = LocalDate.now(ZoneOffset.UTC)
+ .atStartOfDay(ZoneOffset.UTC)
+ .toEpochSecond()
+ .toString()
+ lines[1] == "# SOURCE_DATE_EPOCH = ${expected}"
+ // later timestamp is untouched
+ lines.contains('#Sat Mar 03 15:00:00 UTC 2001')
+ }
+
+ def 'outputstream - uses SOURCE_DATE_EPOCH env var when set'() {
+ given:
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()
+ baos.write([
+ '#Tue Apr 02 02:02:02 UTC 2002',
+ 'testing=another'
+ ].join('\n').getBytes(StandardCharsets.ISO_8859_1.name()))
+
+ when:
+ ByteArrayInputStream result = null
+ SystemStubs.withEnvironmentVariable('SOURCE_DATE_EPOCH', '42424242').execute {
+ result = PropertyFileUtils.makePropertiesOutputReproducible(baos)
+ }
+ List<String> lines = new String(
+ result.readAllBytes(),
+ StandardCharsets.ISO_8859_1.name()
+ ).readLines()
+
+ then:
+ lines[0] == '# SOURCE_DATE_EPOCH = 42424242'
+ lines[1] == 'testing=another'
+ }
+
+ def 'outputstream - leaves content unchanged when no timestamp comment present'() {
+ given:
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()
+ baos.write('alpha=one\nbeta=two\n'.getBytes(StandardCharsets.ISO_8859_1.name()))
+
+ when:
+ ByteArrayInputStream result = PropertyFileUtils.makePropertiesOutputReproducible(baos)
+ String output = new String(result.readAllBytes(), StandardCharsets.ISO_8859_1.name())
+
+ then:
+ output == 'alpha=one\nbeta=two\n'
+ }
+}
+
diff --git a/grails-gradle/gradle/publish-root-config.gradle b/grails-gradle/gradle/publish-root-config.gradle
index bd6642b6dc..16a23ec5c3 100644
--- a/grails-gradle/gradle/publish-root-config.gradle
+++ b/grails-gradle/gradle/publish-root-config.gradle
@@ -24,8 +24,9 @@ version = projectVersion
group = 'this.will.be.overridden'
def publishedProjects = [
- 'grails-gradle-bom',
'grails-docs-core',
+ 'grails-gradle-bom',
+ 'grails-gradle-common',
'grails-gradle-model',
'grails-gradle-plugins',
'grails-gradle-tasks',
diff --git a/grails-gradle/plugins/build.gradle b/grails-gradle/plugins/build.gradle
index 66a788ee39..db070c9c79 100644
--- a/grails-gradle/plugins/build.gradle
+++ b/grails-gradle/plugins/build.gradle
@@ -40,6 +40,7 @@ dependencies {
// see: https://docs.gradle.org/current/userguide/compatibility.html#groovy
compileOnly "org.codehaus.groovy:groovy"
+ implementation project(':grails-gradle-common')
implementation project(':grails-gradle-tasks')
// spock is leaking from the grails-gradle-bom through grails-gradle-model
diff --git a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy
index 039e95b390..10d292071e 100644
--- a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy
+++ b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy
@@ -26,6 +26,7 @@ import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import io.spring.gradle.dependencymanagement.DependencyManagementPlugin
import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension
+import org.apache.grails.gradle.common.PropertyFileUtils
import org.apache.tools.ant.filters.EscapeUnicode
import org.apache.tools.ant.filters.ReplaceTokens
import org.gradle.api.Action
@@ -170,34 +171,6 @@ class GrailsGradlePlugin extends GroovyPlugin {
}
}
- // TODO: this is copied from grails-common, but we should consider moving grails-common up to grails-gradle once
- // the publish plugin is moved out of grails-core
- static void makePropertiesFileReproducible(File factoriesFile) {
- String sourceDateEpoch = System.getenv('SOURCE_DATE_EPOCH')
- if (!sourceDateEpoch) {
- sourceDateEpoch = LocalDate.now(ZoneOffset.UTC)
- .atStartOfDay(ZoneOffset.UTC)
- .toEpochSecond().toString()
- }
-
- Pattern timeRegex = Pattern.compile('^#(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)(?:,|\\s).*$')
-
- List<String> lines = factoriesFile.readLines(StandardCharsets.ISO_8859_1.name())
-
- boolean dateReplaced = false
- factoriesFile.withWriter { BufferedWriter writer ->
- lines.each { String line ->
- if (!dateReplaced && timeRegex.matcher(line).matches()) {
- dateReplaced = true
- writer.writeLine("# SOURCE_DATE_EPOCH = ${sourceDateEpoch}" as String)
- return
- }
-
- writer.writeLine(line)
- }
- }
- }
-
private static Provider<String> getMainClassProvider(Project project) {
Provider<FindMainClassTask> findMainClassTask = project.tasks.named('findMainClass', FindMainClassTask)
project.provider {
@@ -403,7 +376,7 @@ class GrailsGradlePlugin extends GroovyPlugin {
entry key: me.key, value: me.value
}
}
- makePropertiesFileReproducible(buildInfoFile)
+ PropertyFileUtils.makePropertiesFileReproducible(buildInfoFile)
}
TaskContainer tasks = project.tasks
diff --git a/grails-gradle/settings.gradle b/grails-gradle/settings.gradle
index 20833b8127..387fa65488 100644
--- a/grails-gradle/settings.gradle
+++ b/grails-gradle/settings.gradle
@@ -52,6 +52,9 @@ buildCache {
rootProject.name = 'grails-gradle'
+include 'grails-gradle-common'
+project(':grails-gradle-common').projectDir = file('common')
+
include 'grails-gradle-plugins'
project(':grails-gradle-plugins').projectDir = file('plugins')
diff --git a/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy b/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy
index 47636564bd..45b5122335 100644
--- a/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy
+++ b/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy
@@ -21,7 +21,7 @@ package org.grails.gsp.compiler
import grails.config.ConfigMap
import org.apache.commons.logging.LogFactory
import org.apache.commons.logging.Log
-import org.apache.grails.common.properties.PropertyFileUtils
+import org.apache.grails.gradle.common.PropertyFileUtils
import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.Phases