This is an automated email from the ASF dual-hosted git repository.

davydotcom 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 3e8010fd5a reproducible builds - prevent property files from writing 
an arbitrary date if SOURCE_DATE_EPOCH is defined (#14675)
3e8010fd5a is described below

commit 3e8010fd5ab0f0dc9b516b52158581248f687428
Author: James Daugherty <[email protected]>
AuthorDate: Mon Apr 28 09:30:00 2025 -0400

    reproducible builds - prevent property files from writing an arbitrary date 
if SOURCE_DATE_EPOCH is defined (#14675)
---
 .../GlobalGrailsClassInjectorTransformation.groovy | 120 +++++++++++++--------
 1 file changed, 73 insertions(+), 47 deletions(-)

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 ef6cb5d085..f186987f6b 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
@@ -28,12 +28,17 @@ import grails.util.GrailsNameUtils
 import groovy.transform.CompilationUnitAware
 import groovy.transform.CompileDynamic
 import groovy.transform.CompileStatic
-import groovy.xml.slurpersupport.GPathResult
-import groovy.xml.XmlSlurper
 import groovy.xml.MarkupBuilder
 import groovy.xml.StreamingMarkupBuilder
+import groovy.xml.XmlSlurper
+import groovy.xml.slurpersupport.GPathResult
 import org.apache.grails.common.compiler.GroovyTransformOrder
-import org.codehaus.groovy.ast.*
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.ast.AnnotationNode
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.ModuleNode
+import org.codehaus.groovy.ast.PropertyNode
 import org.codehaus.groovy.ast.expr.ConstantExpression
 import org.codehaus.groovy.control.CompilationUnit
 import org.codehaus.groovy.control.CompilePhase
@@ -47,6 +52,8 @@ import org.grails.io.support.GrailsResourceUtils
 import org.grails.io.support.UrlResource
 
 import java.lang.reflect.Modifier
+import java.nio.charset.StandardCharsets
+import java.time.Instant
 
 /**
  * A global transformation that applies Grails' transformations to classes 
within a Grails project
@@ -54,7 +61,7 @@ import java.lang.reflect.Modifier
  * @author Graeme Rocher
  * @since 3.0
  */
-@GroovyASTTransformation(phase= CompilePhase.CANONICALIZATION)
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
 @CompileStatic
 class GlobalGrailsClassInjectorTransformation implements ASTTransformation, 
CompilationUnitAware, TransformWithPriority {
 
@@ -75,8 +82,8 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
 
         URL url = GrailsASTUtils.getSourceUrl(source);
 
-        if(url == null ) return
-        if(!GrailsResourceUtils.isProjectSource(new UrlResource(url))) return;
+        if (url == null) return
+        if (!GrailsResourceUtils.isProjectSource(new UrlResource(url))) return;
 
         List<ArtefactHandler> artefactHandlers = 
GrailsFactoriesLoader.loadFactories(ArtefactHandler)
         ClassInjector[] classInjectors = 
GrailsAwareInjectionOperation.getClassInjectors()
@@ -94,7 +101,7 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
         for (ClassNode classNode : classes) {
             def projectName = classNode.getNodeMetaData("projectName")
             def projectVersion = classNode.getNodeMetaData("projectVersion")
-            if(projectVersion == null) {
+            if (projectVersion == null) {
                 projectVersion = 
getClass().getPackage().getImplementationVersion()
             }
 
@@ -102,46 +109,46 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
 
             def classNodeName = classNode.name
 
-            if(classNodeName.endsWith("GrailsPlugin") && 
!classNode.isAbstract()) {
+            if (classNodeName.endsWith("GrailsPlugin") && 
!classNode.isAbstract()) {
                 pluginClassNode = classNode
 
-                if(!classNode.getProperty('version')) {
-                    classNode.addProperty(new PropertyNode('version', 
Modifier.PUBLIC, ClassHelper.make(Object), classNode, new 
ConstantExpression(projectVersion.toString()) , null, null))
+                if (!classNode.getProperty('version')) {
+                    classNode.addProperty(new PropertyNode('version', 
Modifier.PUBLIC, ClassHelper.make(Object), classNode, new 
ConstantExpression(projectVersion.toString()), null, null))
                 }
 
                 continue
             }
 
-            if(updateGrailsFactoriesWithType(classNode, 
ARTEFACT_HANDLER_CLASS, compilationTargetDirectory)) {
+            if (updateGrailsFactoriesWithType(classNode, 
ARTEFACT_HANDLER_CLASS, compilationTargetDirectory)) {
                 continue
             }
-            if(updateGrailsFactoriesWithType(classNode, 
APPLICATION_CONTEXT_COMMAND_CLASS, compilationTargetDirectory)) {
+            if (updateGrailsFactoriesWithType(classNode, 
APPLICATION_CONTEXT_COMMAND_CLASS, compilationTargetDirectory)) {
                 continue
             }
-            if(updateGrailsFactoriesWithType(classNode, TRAIT_INJECTOR_CLASS, 
compilationTargetDirectory)) {
+            if (updateGrailsFactoriesWithType(classNode, TRAIT_INJECTOR_CLASS, 
compilationTargetDirectory)) {
                 continue
             }
 
-            if(!GrailsResourceUtils.isGrailsResource(new UrlResource(url))) 
continue;
+            if (!GrailsResourceUtils.isGrailsResource(new UrlResource(url))) 
continue;
 
-            if(projectName && projectVersion) {
-                GrailsASTUtils.addAnnotationOrGetExisting(classNode, 
GrailsPlugin, [name: 
GrailsNameUtils.getPropertyNameForLowerCaseHyphenSeparatedName(projectName.toString()),
 version:projectVersion])
+            if (projectName && projectVersion) {
+                GrailsASTUtils.addAnnotationOrGetExisting(classNode, 
GrailsPlugin, [name: 
GrailsNameUtils.getPropertyNameForLowerCaseHyphenSeparatedName(projectName.toString()),
 version: projectVersion])
             }
 
             classNode.getModule().addImport("Autowired", 
ClassHelper.make("org.springframework.beans.factory.annotation.Autowired"))
 
-            for(ArtefactHandler handler in artefactHandlers) {
-                if(handler.isArtefact(classNode)) {
-                    if(!classNode.getAnnotations(ARTEFACT_CLASS_NODE)) {
+            for (ArtefactHandler handler in artefactHandlers) {
+                if (handler.isArtefact(classNode)) {
+                    if (!classNode.getAnnotations(ARTEFACT_CLASS_NODE)) {
                         transformedClasses.add classNodeName
                         def annotationNode = new AnnotationNode(new 
ClassNode(Artefact.class))
                         annotationNode.addMember("value", new 
ConstantExpression(handler.getType()))
                         classNode.addAnnotation(annotationNode)
 
                         List<ClassInjector> injectors = cache[handler.type]
-                        for (ClassInjector injector: injectors) {
+                        for (ClassInjector injector : injectors) {
                             if (injector instanceof CompilationUnitAware) {
-                                
((CompilationUnitAware)injector).compilationUnit = compilationUnit
+                                ((CompilationUnitAware) 
injector).compilationUnit = compilationUnit
                             }
                         }
                         ArtefactTypeAstTransformation.performInjection(source, 
classNode, injectors)
@@ -150,10 +157,10 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
                 }
             }
 
-            if(!transformedClasses.contains(classNodeName)) {
+            if (!transformedClasses.contains(classNodeName)) {
                 def globalClassInjectors = 
GrailsAwareInjectionOperation.globalClassInjectors
 
-                for(ClassInjector injector in globalClassInjectors) {
+                for (ClassInjector injector in globalClassInjectors) {
                     injector.performInjection(source, classNode)
                 }
             }
@@ -168,12 +175,12 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
 
     static File resolveCompilationTargetDirectory(SourceUnit source) {
         File targetDirectory = null
-        if(source.getClass().name == 
'org.codehaus.jdt.groovy.control.EclipseSourceUnit') {
+        if (source.getClass().name == 
'org.codehaus.jdt.groovy.control.EclipseSourceUnit') {
             targetDirectory = 
GroovyEclipseCompilationHelper.resolveEclipseCompilationTargetDirectory(source)
         } else {
-                       targetDirectory = source.configuration.targetDirectory
-               }
-        if(targetDirectory == null) {
+            targetDirectory = source.configuration.targetDirectory
+        }
+        if (targetDirectory == null) {
             targetDirectory = new File('build/classes/main')
         }
         return targetDirectory
@@ -181,7 +188,7 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
 
     static boolean updateGrailsFactoriesWithType(ClassNode classNode, 
ClassNode superType, File compilationTargetDirectory) {
         if (GrailsASTUtils.isSubclassOfOrImplementsInterface(classNode, 
superType)) {
-            if(Modifier.isAbstract(classNode.getModifiers())) return false
+            if (Modifier.isAbstract(classNode.getModifiers())) return false
 
             def classNodeName = classNode.name
             def props = new Properties()
@@ -200,20 +207,39 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
 
             addToProps(props, superTypeName, classNodeName)
 
-            factoriesFile.withWriter {  Writer writer ->
+            factoriesFile.withWriter { Writer writer ->
                 props.store(writer, "Grails Factories File")
             }
+
+            makeFactoriesFileReproducible(factoriesFile)
+
             return true
         }
         return false
     }
 
+    private static void makeFactoriesFileReproducible(File factoriesFile) {
+        String sourceDateEpoch = System.getenv('SOURCE_DATE_EPOCH')
+        if (!sourceDateEpoch) {
+            return
+        }
+
+        Instant buildInstant = Instant.ofEpochSecond(sourceDateEpoch as Long)
+        List<String> lines = 
factoriesFile.readLines(StandardCharsets.ISO_8859_1.name())
+        lines[1] = "# ${Date.from(buildInstant).toString()}" as String
+        factoriesFile.withWriter { BufferedWriter writer ->
+            lines.each { String line ->
+                writer.writeLine(line)
+            }
+        }
+    }
+
     private static void loadFromFile(Properties props, File factoriesFile) {
         if (factoriesFile.exists()) {
             Properties fileProps = new Properties()
             factoriesFile.withInputStream { InputStream input ->
                 fileProps.load(input)
-                fileProps.each { Map.Entry prop->
+                fileProps.each { Map.Entry prop ->
                     addToProps(props, (String) prop.key, (String) prop.value)
                 }
             }
@@ -260,7 +286,7 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
             }
         } else if (pluginXmlExists) {
             // if the class isn't the *GrailsPlugin class then only update the 
plugin.xml if it already exists
-            updatePluginXml(null, pluginVersion, pluginXmlFile,  pluginClasses)
+            updatePluginXml(null, pluginVersion, pluginXmlFile, pluginClasses)
         } else {
             // otherwise add it to a list of pending classes to populated when 
the plugin.xml is created
             pendingPluginClasses.addAll(transformedClasses)
@@ -269,35 +295,35 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
 
     @CompileDynamic
     static void writePluginXml(ClassNode pluginClassNode, String 
pluginVersion, File pluginXml, Collection<String> artefactClasses) {
-        if(pluginClassNode) {
+        if (pluginClassNode) {
             PluginAstReader pluginAstReader = new PluginAstReader()
             def info = pluginAstReader.readPluginInfo(pluginClassNode)
 
-            pluginXml.withWriter( "UTF-8") { Writer writer ->
+            pluginXml.withWriter("UTF-8") { Writer writer ->
                 def mkp = new MarkupBuilder(writer)
                 def pluginName = 
GrailsNameUtils.getLogicalPropertyName(pluginClassNode.name, "GrailsPlugin")
 
                 def pluginProperties = info.getProperties()
                 def excludes = pluginProperties.get('pluginExcludes')
-                if(excludes instanceof List) {
+                if (excludes instanceof List) {
                     pluginExcludes.clear()
                     pluginExcludes.addAll(excludes)
                 }
 
                 def grailsVersion = pluginProperties['grailsVersion'] ?: 
getClass().getPackage().getImplementationVersion() + " > *"
-                mkp.plugin(name:pluginName, version: pluginVersion, 
grailsVersion: grailsVersion) {
+                mkp.plugin(name: pluginName, version: pluginVersion, 
grailsVersion: grailsVersion) {
                     type(pluginClassNode.name)
 
-                    for(entry in pluginProperties) {
+                    for (entry in pluginProperties) {
                         delegate."$entry.key"(entry.value)
                     }
 
                     // if there are pending classes to add to the plugin.xml 
add those
-                    if(artefactClasses) {
+                    if (artefactClasses) {
                         def antPathMatcher = new AntPathMatcher()
                         resources {
-                            for(String cn in artefactClasses) {
-                                if (!pluginExcludes.any() { String exc -> 
antPathMatcher.match(exc, cn.replace('.','/')) }) {
+                            for (String cn in artefactClasses) {
+                                if (!pluginExcludes.any() { String exc -> 
antPathMatcher.match(exc, cn.replace('.', '/')) }) {
                                     resource cn
                                 }
                             }
@@ -312,13 +338,13 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
 
     @CompileDynamic
     static void updatePluginXml(ClassNode pluginClassNode, String 
pluginVersion, File pluginXmlFile, Collection<String> artefactClasses) {
-        if(!artefactClasses) return
+        if (!artefactClasses) return
 
         try {
             XmlSlurper xmlSlurper = IOUtils.createXmlSlurper()
 
             def pluginXml = xmlSlurper.parse(pluginXmlFile)
-            if(pluginClassNode) {
+            if (pluginClassNode) {
                 def pluginName = 
GrailsNameUtils.getLogicalPropertyName(pluginClassNode.name, "GrailsPlugin")
                 pluginXml.@name = pluginName
                 pluginXml.@version = pluginVersion
@@ -330,12 +356,12 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
                 def pluginProperties = info.getProperties()
                 def grailsVersion = pluginProperties['grailsVersion'] ?: 
getClass().getPackage().getImplementationVersion() + " > *"
                 pluginXml.@grailsVersion = grailsVersion
-                for(entry in pluginProperties) {
+                for (entry in pluginProperties) {
                     pluginXml."$entry.key" = entry.value
                 }
 
                 def excludes = pluginProperties.get('pluginExcludes')
-                if(excludes instanceof List) {
+                if (excludes instanceof List) {
                     pluginExcludes.clear()
                     pluginExcludes.addAll(excludes)
                 }
@@ -343,8 +369,8 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
 
             def resources = pluginXml.resources
 
-            for(String cn in artefactClasses) {
-                if ( !resources.resource.find { it.text() == cn } ) {
+            for (String cn in artefactClasses) {
+                if (!resources.resource.find { it.text() == cn }) {
                     resources.appendNode {
                         resource(cn)
                     }
@@ -365,7 +391,7 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
 
         } catch (e) {
             // corrupt, recreate
-            writePluginXml(pluginClassNode,pluginVersion, pluginXmlFile, 
artefactClasses)
+            writePluginXml(pluginClassNode, pluginVersion, pluginXmlFile, 
artefactClasses)
         }
     }
 
@@ -375,7 +401,7 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
 
             def antPathMatcher = new AntPathMatcher()
             pluginXml.resources.resource.each { res ->
-                if (pluginExcludes.any() { String exc -> 
antPathMatcher.match(exc, res.text().replace('.','/')) }) {
+                if (pluginExcludes.any() { String exc -> 
antPathMatcher.match(exc, res.text().replace('.', '/')) }) {
                     res.replaceNode {}
                 }
             }

Reply via email to