Copilot commented on code in PR #16042:
URL: https://github.com/apache/grails-core/pull/16042#discussion_r3673660022
##########
grails-core/src/main/groovy/org/grails/compiler/injection/GlobalGrailsClassInjectorTransformation.groovy:
##########
@@ -56,140 +58,305 @@ import org.grails.io.support.GrailsResourceUtils
import org.grails.io.support.UrlResource
/**
- * A global transformation that applies Grails' transformations to classes
within a Grails project
+ * Global AST transformation that applies Grails compiler injection to Grails
project sources,
+ * including applications and plugins.
+ *
+ * <p>It identifies Grails artefacts, applies the relevant {@link
ClassInjector} and
+ * {@link grails.compiler.traits.TraitInjector} implementations, and registers
artefact handlers
+ * and injector implementations. When compiling a plugin descriptor, it also
creates or updates
+ * the {@code META-INF/grails-plugin.xml} descriptor and records transformed
plugin resources.</p>
*
- * @author Graeme Rocher
* @since 3.0
*/
-@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+@Slf4j
@CompileStatic
+@GroovyASTTransformation
class GlobalGrailsClassInjectorTransformation implements ASTTransformation,
CompilationUnitAware, TransformWithPriority {
+ /**
+ * The system property signalling that a multi-project build compiles each
project into its own
+ * isolated output directory. When set, the transform must never fall back
to a shared or guessed
+ * location, which could leak one module's generated metadata into another.
+ */
+ public static final String ISOLATED_BUILD_PROPERTY =
'grails.isolated.build'
+
+ public static final ClassNode ARTEFACT_CLASS_NODE = new ClassNode(Artefact)
public static final ClassNode ARTEFACT_HANDLER_CLASS =
ClassHelper.make('grails.core.ArtefactHandler')
public static final ClassNode TRAIT_INJECTOR_CLASS =
ClassHelper.make('grails.compiler.traits.TraitInjector')
+ private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher()
+
+ private final LinkedHashSet<String> pendingPluginClassNames = []
+ private final Collection<String> pluginExcludePatterns = []
+
+ CompilationUnit compilationUnit
+
+ /**
+ * Returns the ordering position used to run this transformation relative
to other global
+ * transformations.
+ *
+ * @return the global Grails transformation order
+ */
@Override
int priority() {
- return GroovyTransformOrder.GLOBAL_GRAILS_TRANSFORM_ORDER
+ GroovyTransformOrder.GLOBAL_GRAILS_TRANSFORM_ORDER
}
+ /**
+ * Applies Grails artefact and class injection to a project source and
updates the generated
+ * plugin metadata for the compiled classes.
+ *
+ * @param nodes AST nodes supplied by Groovy
+ * @param source the source unit being compiled
+ */
@Override
void visit(ASTNode[] nodes, SourceUnit source) {
-
- ModuleNode ast = source.getAST()
- List<ClassNode> classes = new ArrayList<>(ast.getClasses())
-
- URL url = GrailsASTUtils.getSourceUrl(source)
-
- if (url == null) return
- if (!GrailsResourceUtils.isProjectSource(new UrlResource(url))) return
-
- List<ArtefactHandler> artefactHandlers =
GrailsFactoriesLoader.loadFactories(ArtefactHandler)
- ClassInjector[] classInjectors =
GrailsAwareInjectionOperation.getClassInjectors()
-
- Map<String, List<ClassInjector>> cache = new LinkedHashMap<String,
List<ClassInjector>>().withDefault { String key ->
- ArtefactTypeAstTransformation.findInjectors(key, classInjectors)
+ def url = GrailsASTUtils.getSourceUrl(source)
+ if (!shouldVisit(url)) {
+ return
}
- LinkedHashSet<String> transformedClasses = []
- String pluginVersion = null
ClassNode pluginClassNode = null
+ String pluginVersion = null
+ def transformedClassNames = new LinkedHashSet<String>()
def compilationTargetDirectory =
resolveCompilationTargetDirectory(source)
def pluginXmlFile = new File(compilationTargetDirectory,
'META-INF/grails-plugin.xml')
+ def artefactHandlers =
GrailsFactoriesLoader.loadFactories(ArtefactHandler)
+ def classInjectorCache = new LinkedHashMap<String,
List<ClassInjector>>().withDefault { String key ->
+ ArtefactTypeAstTransformation.findInjectors(
+ key,
+ GrailsAwareInjectionOperation.classInjectors
+ )
+ }
- for (ClassNode classNode : classes) {
- def projectName = classNode.getNodeMetaData('projectName')
- def projectVersion = classNode.getNodeMetaData('projectVersion')
- if (projectVersion == null) {
- projectVersion =
getClass().getPackage().getImplementationVersion()
- }
-
- pluginVersion = projectVersion
-
- def classNodeName = classNode.name
-
- if (classNodeName.endsWith('GrailsPlugin') &&
!classNode.isAbstract()) {
+ for (def classNode : source.AST.classes.toList()) { // toList() to
avoid concurrent modification exception
+ def projectName = resolveProjectName(classNode)
+ def projectVersion = resolveProjectVersion(classNode)
+ if (isGrailsPluginDescriptorClass(classNode)) {
pluginClassNode = classNode
-
- if (!classNode.getProperty('version')) {
- classNode.addProperty(new PropertyNode('version',
Modifier.PUBLIC, ClassHelper.make(Object), classNode, new
ConstantExpression(projectVersion.toString()), null, null))
- }
-
+ pluginVersion = resolvePluginVersion(classNode,
projectVersion?.toString())
+ addPluginVersionProperty(classNode, pluginVersion)
continue
}
-
- if (updateGrailsFactoriesWithType(classNode,
ARTEFACT_HANDLER_CLASS, compilationTargetDirectory)) {
+ if (updateGrailsFactoriesWithTypes(classNode,
[ARTEFACT_HANDLER_CLASS, TRAIT_INJECTOR_CLASS], compilationTargetDirectory)) {
continue
}
- if (updateGrailsFactoriesWithType(classNode, TRAIT_INJECTOR_CLASS,
compilationTargetDirectory)) {
+ 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])
+ addPluginAnnotation(classNode, projectName, projectVersion)
}
- classNode.getModule().addImport('Autowired',
ClassHelper.make('org.springframework.beans.factory.annotation.Autowired'))
+ addImport(classNode,
'org.springframework.beans.factory.annotation.Autowired')
- for (ArtefactHandler handler in artefactHandlers) {
+ for (def handler : artefactHandlers) {
if (handler.isArtefact(classNode)) {
if (!classNode.getAnnotations(ARTEFACT_CLASS_NODE)) {
- transformedClasses.add(classNodeName)
- def annotationNode = new AnnotationNode(new
ClassNode(Artefact))
- annotationNode.addMember('value', new
ConstantExpression(handler.getType()))
- classNode.addAnnotation(annotationNode)
-
- List<ClassInjector> injectors = cache[handler.type]
- for (ClassInjector injector : injectors) {
- if (injector instanceof CompilationUnitAware) {
- ((CompilationUnitAware)
injector).compilationUnit = compilationUnit
+ transformedClassNames.add(classNode.name)
+ addArtefactAnnotation(classNode, handler.type)
+ def classInjectors = classInjectorCache[handler.type]
+ for (def classInjector : classInjectors) {
+ if (classInjector instanceof CompilationUnitAware)
{
+ ((CompilationUnitAware)
classInjector).compilationUnit = compilationUnit
}
}
- ArtefactTypeAstTransformation.performInjection(source,
classNode, injectors)
- TraitInjectionUtils.processTraitsForNode(source,
classNode, handler.getType(), compilationUnit)
+ ArtefactTypeAstTransformation.performInjection(source,
classNode, classInjectors)
+ TraitInjectionUtils.processTraitsForNode(source,
classNode, handler.type, compilationUnit)
}
}
}
- if (!transformedClasses.contains(classNodeName)) {
+ if (!transformedClassNames.contains(classNode.name)) {
def globalClassInjectors =
GrailsAwareInjectionOperation.globalClassInjectors
-
- for (ClassInjector injector in globalClassInjectors) {
- injector.performInjection(source, classNode)
+ for (def classInjector : globalClassInjectors) {
+ classInjector.performInjection(source, classNode)
}
}
}
- // now create or update grails-plugin.xml
- // first check if plugin.xml exists
- pluginXmlFile.parentFile.mkdirs()
+ validatePluginVersionDefined(pluginClassNode, pluginVersion,
pluginXmlFile, source)
- generatePluginXml(pluginClassNode, pluginVersion, transformedClasses,
pluginXmlFile)
+ // create or update grails-plugin.xml
+ generatePluginXml(pluginClassNode, pluginVersion,
transformedClassNames, pluginXmlFile)
Review Comment:
If `pluginClassNode` is present but `pluginVersion` is null,
`validatePluginVersionDefined(...)` reports an AST error but
`generatePluginXml(...)` is still invoked with a null version. That can lead to
a runtime failure (e.g., writing/updating XML with a null `pluginVersion`) and
mask the intended compilation error. Make the validation short-circuit the
visit (e.g., return immediately after reporting the error), or make
`generatePluginXml` a no-op when `pluginClassNode != null && pluginVersion ==
null`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]