From 8d4a40364cf17fc2a56bc4fee8e4414798216732 Mon Sep 17 00:00:00 2001 From: Paul King Date: Fri, 31 Aug 2018 22:00:16 +1000 Subject: [PATCH] GROOVY-8771: global AST transformation declarations should move to META-INF/groovy instead of META-INF/services --- .../ASTTransformationCustomizer.groovy | 2 +- .../groovy/control/CompilerConfiguration.java | 4 +- .../transform/ASTTransformationVisitor.java | 176 +++++++++--------- ...odehaus.groovy.transform.ASTTransformation | 0 src/spec/doc/core-metaprogramming.adoc | 6 +- .../metaprogramming/ASTXFormSpecTest.groovy | 2 +- ...lobalLegacyTestTransformClassLoader.groovy | 36 ++++ .../GlobalLegacyTransformTest.groovy | 36 ++++ .../GlobalTestTransformClassLoader.groovy | 2 +- .../transform/GlobalTransformTest.groovy | 27 +-- ...odehaus.groovy.transform.ASTTransformation | 18 ++ .../groovy/transform/TestTransform.groovy | 33 ++-- .../classloading/LegacyTransformsTest.groovy | 94 ++++++++++ ...odehaus.groovy.transform.ASTTransformation | 0 14 files changed, 307 insertions(+), 129 deletions(-) rename src/resources/META-INF/{services => groovy}/org.codehaus.groovy.transform.ASTTransformation (100%) create mode 100644 src/test/org/codehaus/groovy/transform/GlobalLegacyTestTransformClassLoader.groovy create mode 100644 src/test/org/codehaus/groovy/transform/GlobalLegacyTransformTest.groovy create mode 100644 src/test/org/codehaus/groovy/transform/META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation create mode 100644 src/test/org/codehaus/groovy/transform/classloading/LegacyTransformsTest.groovy rename subprojects/groovy-macro/src/main/resources/META-INF/{services => groovy}/org.codehaus.groovy.transform.ASTTransformation (100%) diff --git a/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy b/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy index 7e849681ff3..68e0fc5dac0 100644 --- a/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy +++ b/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy @@ -73,7 +73,7 @@ import java.lang.annotation.Annotation * {@link ASTTransformationCustomizer#ASTTransformationCustomizer(ASTTransformation) AST transformation * constructor}. In that case, the transformation is applied once for the whole source unit. * - * Unlike a global AST transformation declared in the META-INF/services/org.codehaus.groovy.transform.ASTTransformation + * Unlike a global AST transformation declared in the META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation * file, which are applied if the file is in the classpath, using this customizer you'll have the choice to apply * your transformation selectively. It can also be useful to debug global AST transformations without having to * package your annotation in a jar file. diff --git a/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java b/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java index 87402aa0235..df15095342d 100644 --- a/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java +++ b/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java @@ -196,7 +196,7 @@ public class CompilerConfiguration { /** * Sets a list of global AST transformations which should not be loaded even if they are - * defined in META-INF/org.codehaus.groovy.transform.ASTTransformation files. By default, + * defined in META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation files. By default, * none is disabled. */ private Set disabledGlobalASTTransformations; @@ -874,7 +874,7 @@ public Set getDisabledGlobalASTTransformations() { * Disables global AST transformations. In order to avoid class loading side effects, it is not recommended * to use MyASTTransformation.class.getName() by directly use the class name as a string. Disabled AST transformations * only apply to automatically loaded global AST transformations, that is to say transformations defined in a - * META-INF/org.codehaus.groovy.transform.ASTTransformation file. If you explicitly add a global AST transformation + * META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation file. If you explicitly add a global AST transformation * in your compilation process, for example using the {@link org.codehaus.groovy.control.customizers.ASTTransformationCustomizer} or * using a {@link org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation}, then nothing will prevent * the transformation from being loaded. diff --git a/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java b/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java index 1e6de128f55..f7e188652a9 100644 --- a/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java @@ -70,8 +70,6 @@ * transformations. They will only be handled in later phases (and then only * if the type was in the AST prior to any AST transformations being run * against it). - * - * @author Danno Ferrin (shemnon) */ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport { @@ -119,13 +117,12 @@ public void visitClass(ClassNode classNode) { } - // invert the map, is now one to many transforms = new HashMap>(); for (Map.Entry, Set> entry : baseTransforms.entrySet()) { for (ASTNode node : entry.getValue()) { List list = transforms.get(node); - if (list == null) { + if (list == null) { list = new ArrayList(); transforms.put(node, list); } @@ -142,7 +139,7 @@ public void visitClass(ClassNode classNode) { for (ASTNode[] node : targetNodes) { for (ASTTransformation snt : transforms.get(node[0])) { if (snt instanceof CompilationUnitAware) { - ((CompilationUnitAware)snt).setCompilationUnit(context.getCompilationUnit()); + ((CompilationUnitAware) snt).setCompilationUnit(context.getCompilationUnit()); } snt.visit(node, source); } @@ -170,8 +167,8 @@ public static void addPhaseOperations(final CompilationUnit compilationUnit) { compilationUnit.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() { public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException { - ASTTransformationCollectorCodeVisitor collector = - new ASTTransformationCollectorCodeVisitor(source, compilationUnit.getTransformLoader()); + ASTTransformationCollectorCodeVisitor collector = + new ASTTransformationCollectorCodeVisitor(source, compilationUnit.getTransformLoader()); collector.visitClass(classNode); } }, Phases.SEMANTIC_ANALYSIS); @@ -196,11 +193,11 @@ public void call(SourceUnit source, GeneratorContext context, ClassNode classNod } } } - + public static void addGlobalTransformsAfterGrab(ASTTransformationsContext context) { doAddGlobalTransforms(context, false); } - + public static void addGlobalTransforms(ASTTransformationsContext context) { doAddGlobalTransforms(context, true); } @@ -210,90 +207,95 @@ private static void doAddGlobalTransforms(ASTTransformationsContext context, boo GroovyClassLoader transformLoader = compilationUnit.getTransformLoader(); Map transformNames = new LinkedHashMap(); try { - Enumeration globalServices = transformLoader.getResources("META-INF/services/org.codehaus.groovy.transform.ASTTransformation"); - while (globalServices.hasMoreElements()) { - URL service = globalServices.nextElement(); - String className; - try (BufferedReader svcIn = new BufferedReader(new InputStreamReader(URLStreams.openUncachedStream(service), "UTF-8"))) { - try { - className = svcIn.readLine(); - } catch (IOException ioe) { - compilationUnit.getErrorCollector().addError(new SimpleMessage( - "IOException reading the service definition at " - + service.toExternalForm() + " because of exception " + ioe.toString(), null)); - continue; - } - Set disabledGlobalTransforms = compilationUnit.getConfiguration().getDisabledGlobalASTTransformations(); - if (disabledGlobalTransforms == null) disabledGlobalTransforms = Collections.emptySet(); - while (className != null) { - if (!className.startsWith("#") && className.length() > 0) { - if (!disabledGlobalTransforms.contains(className)) { - if (transformNames.containsKey(className)) { - try { - if (!service.toURI().equals(transformNames.get(className).toURI())) { - compilationUnit.getErrorCollector().addWarning( - WarningMessage.POSSIBLE_ERRORS, - "The global transform for class " + className + " is defined in both " - + transformNames.get(className).toExternalForm() - + " and " - + service.toExternalForm() - + " - the former definition will be used and the latter ignored.", - null, - null); - } - } catch (URISyntaxException e) { - compilationUnit.getErrorCollector().addWarning( - WarningMessage.POSSIBLE_ERRORS, - "Failed to parse URL as URI because of exception " + e.toString(), - null, - null); - } - } else { - transformNames.put(className, service); - } - } - } - try { - className = svcIn.readLine(); - } catch (IOException ioe) { - compilationUnit.getErrorCollector().addError(new SimpleMessage( - "IOException reading the service definition at " - + service.toExternalForm() + " because of exception " + ioe.toString(), null)); - //noinspection UnnecessaryContinue - continue; - } - } - } - } + // global transform from different jars might use old or new location, so always process both + processServices(compilationUnit, transformNames, transformLoader.getResources("META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation")); + processServices(compilationUnit, transformNames, transformLoader.getResources("META-INF/services/org.codehaus.groovy.transform.ASTTransformation")); } catch (IOException e) { //FIXME the warning message will NPE with what I have :( compilationUnit.getErrorCollector().addError(new SimpleMessage( - "IO Exception attempting to load global transforms:" + e.getMessage(), - null)); + "IO Exception attempting to load global transforms:" + e.getMessage(), + null)); } // record the transforms found in the first scan, so that in the 2nd scan, phase operations // can be added for only for new transforms that have come in - if(isFirstScan) { + if (isFirstScan) { for (Map.Entry entry : transformNames.entrySet()) { context.getGlobalTransformNames().add(entry.getKey()); } addPhaseOperationsForGlobalTransforms(context.getCompilationUnit(), transformNames, isFirstScan); } else { Iterator> it = transformNames.entrySet().iterator(); - while(it.hasNext()) { + while (it.hasNext()) { Map.Entry entry = it.next(); - if(!context.getGlobalTransformNames().add(entry.getKey())) { + if (!context.getGlobalTransformNames().add(entry.getKey())) { // phase operations for this transform class have already been added before, so remove from current scan cycle - it.remove(); + it.remove(); } } addPhaseOperationsForGlobalTransforms(context.getCompilationUnit(), transformNames, isFirstScan); } } - + + private static void processServices(CompilationUnit compilationUnit, Map transformNames, Enumeration globalServices) throws IOException { + while (globalServices.hasMoreElements()) { + URL service = globalServices.nextElement(); + String className; + try (BufferedReader svcIn = new BufferedReader(new InputStreamReader(URLStreams.openUncachedStream(service), "UTF-8"))) { + try { + className = svcIn.readLine(); + } catch (IOException ioe) { + compilationUnit.getErrorCollector().addError(new SimpleMessage( + "IOException reading the service definition at " + + service.toExternalForm() + " because of exception " + ioe.toString(), null)); + continue; + } + Set disabledGlobalTransforms = compilationUnit.getConfiguration().getDisabledGlobalASTTransformations(); + if (disabledGlobalTransforms == null) disabledGlobalTransforms = Collections.emptySet(); + while (className != null) { + if (!className.startsWith("#") && className.length() > 0) { + if (!disabledGlobalTransforms.contains(className)) { + if (transformNames.containsKey(className)) { + try { + if (!service.toURI().equals(transformNames.get(className).toURI())) { + compilationUnit.getErrorCollector().addWarning( + WarningMessage.POSSIBLE_ERRORS, + "The global transform for class " + className + " is defined in both " + + transformNames.get(className).toExternalForm() + + " and " + + service.toExternalForm() + + " - the former definition will be used and the latter ignored.", + null, + null); + } + } catch (URISyntaxException e) { + compilationUnit.getErrorCollector().addWarning( + WarningMessage.POSSIBLE_ERRORS, + "Failed to parse URL as URI because of exception " + e.toString(), + null, + null); + } + } else { + transformNames.put(className, service); + } + } + } + try { + className = svcIn.readLine(); + } catch (IOException ioe) { + compilationUnit.getErrorCollector().addError(new SimpleMessage( + "IOException reading the service definition at " + + service.toExternalForm() + " because of exception " + ioe.toString(), null)); + //noinspection UnnecessaryContinue + continue; + } + } + } + } + } + private static void addPhaseOperationsForGlobalTransforms(CompilationUnit compilationUnit, - Map transformNames, boolean isFirstScan) { + Map transformNames, boolean isFirstScan) { GroovyClassLoader transformLoader = compilationUnit.getTransformLoader(); for (Map.Entry entry : transformNames.entrySet()) { try { @@ -302,38 +304,38 @@ private static void addPhaseOperationsForGlobalTransforms(CompilationUnit compil GroovyASTTransformation transformAnnotation = (GroovyASTTransformation) gTransClass.getAnnotation(GroovyASTTransformation.class); if (transformAnnotation == null) { compilationUnit.getErrorCollector().addWarning(new WarningMessage( - WarningMessage.POSSIBLE_ERRORS, - "Transform Class " + entry.getKey() + " is specified as a global transform in " + entry.getValue().toExternalForm() - + " but it is not annotated by " + GroovyASTTransformation.class.getName() - + " the global transform associated with it may fail and cause the compilation to fail.", - null, - null)); + WarningMessage.POSSIBLE_ERRORS, + "Transform Class " + entry.getKey() + " is specified as a global transform in " + entry.getValue().toExternalForm() + + " but it is not annotated by " + GroovyASTTransformation.class.getName() + + " the global transform associated with it may fail and cause the compilation to fail.", + null, + null)); continue; } if (ASTTransformation.class.isAssignableFrom(gTransClass)) { - final ASTTransformation instance = (ASTTransformation)gTransClass.newInstance(); + final ASTTransformation instance = (ASTTransformation) gTransClass.newInstance(); if (instance instanceof CompilationUnitAware) { - ((CompilationUnitAware)instance).setCompilationUnit(compilationUnit); + ((CompilationUnitAware) instance).setCompilationUnit(compilationUnit); } CompilationUnit.SourceUnitOperation suOp = new CompilationUnit.SourceUnitOperation() { public void call(SourceUnit source) throws CompilationFailedException { - instance.visit(new ASTNode[] {source.getAST()}, source); + instance.visit(new ASTNode[]{source.getAST()}, source); } - }; - if(isFirstScan) { + }; + if (isFirstScan) { compilationUnit.addPhaseOperation(suOp, transformAnnotation.phase().getPhaseNumber()); } else { compilationUnit.addNewPhaseOperation(suOp, transformAnnotation.phase().getPhaseNumber()); } } else { compilationUnit.getErrorCollector().addError(new SimpleMessage( - "Transform Class " + entry.getKey() + " specified at " - + entry.getValue().toExternalForm() + " is not an ASTTransformation.", null)); + "Transform Class " + entry.getKey() + " specified at " + + entry.getValue().toExternalForm() + " is not an ASTTransformation.", null)); } } catch (Exception e) { compilationUnit.getErrorCollector().addError(new SimpleMessage( - "Could not instantiate global transform class " + entry.getKey() + " specified at " - + entry.getValue().toExternalForm() + " because of exception " + e.toString(), null)); + "Could not instantiate global transform class " + entry.getKey() + " specified at " + + entry.getValue().toExternalForm() + " because of exception " + e.toString(), null)); } } } diff --git a/src/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation b/src/resources/META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation similarity index 100% rename from src/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation rename to src/resources/META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation diff --git a/src/spec/doc/core-metaprogramming.adoc b/src/spec/doc/core-metaprogramming.adoc index da25e577e01..83b9697c25f 100644 --- a/src/spec/doc/core-metaprogramming.adoc +++ b/src/spec/doc/core-metaprogramming.adoc @@ -2854,7 +2854,7 @@ There are two kinds of transformations: global and local transformations. * <> are applied to by the compiler on the code being compiled, wherever the transformation apply. Compiled classes that implement global transformations are in a JAR added to the classpath of the compiler and contain service locator file -`META-INF/services/org.codehaus.groovy.transform.ASTTransformation` with a line with the name of the +`META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation` with a line with the name of the transformation class. The transformation class must have a no-args constructor and implement the `org.codehaus.groovy.transform.ASTTransformation` interface. It will be run against *every source in the compilation*, so be sure to not create transformations which @@ -3029,13 +3029,13 @@ include::{projectdir}/src/spec/test/metaprogramming/ASTXFormSpecTest.groovy[tags To make this work, there are two steps: -. create the `org.codehaus.groovy.transform.ASTTransformation` descriptor inside the `META-INF/services` directory +. create the `org.codehaus.groovy.transform.ASTTransformation` descriptor inside the `META-INF/groovy` directory (`META-INF/services` is supported for backwards compatibility but not recommended) . create the `ASTTransformation` implementation The descriptor file is required and must be found on classpath. It will contain a single line: [source,groovy] -.META-INF/services/org.codehaus.groovy.transform.ASTTransformation +.META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation ---- include::{projectdir}/src/spec/test/metaprogramming/ASTXFormSpecTest.groovy[tags=xform_descriptor_file,indent=0] ---- diff --git a/src/spec/test/metaprogramming/ASTXFormSpecTest.groovy b/src/spec/test/metaprogramming/ASTXFormSpecTest.groovy index d5cc71e9d24..8741a48d327 100644 --- a/src/spec/test/metaprogramming/ASTXFormSpecTest.groovy +++ b/src/spec/test/metaprogramming/ASTXFormSpecTest.groovy @@ -235,7 +235,7 @@ import org.codehaus.groovy.transform.GroovyASTTransformation File dir = builder.baseDir builder { 'META-INF' { - services { + groovy { 'org.codehaus.groovy.transform.ASTTransformation'(Utils.stripAsciidocMarkup(''' // tag::xform_descriptor_file[] gep.WithLoggingASTTransformation diff --git a/src/test/org/codehaus/groovy/transform/GlobalLegacyTestTransformClassLoader.groovy b/src/test/org/codehaus/groovy/transform/GlobalLegacyTestTransformClassLoader.groovy new file mode 100644 index 00000000000..7ff5b2c8eea --- /dev/null +++ b/src/test/org/codehaus/groovy/transform/GlobalLegacyTestTransformClassLoader.groovy @@ -0,0 +1,36 @@ +/* + * 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 + * + * http://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.codehaus.groovy.transform + +class GlobalLegacyTestTransformClassLoader extends GroovyClassLoader { + private final String transformDescriptor + + GlobalLegacyTestTransformClassLoader(ClassLoader parent, Class... transformClasses) { + super(parent) + transformDescriptor = transformClasses*.name.join("\n") + } + + Enumeration getResources(String name) { + if (name == "META-INF/services/org.codehaus.groovy.transform.ASTTransformation") { + return Collections.enumeration(Collections.singleton(new FakeURLFactory().createURL(transformDescriptor))) + } + + super.getResources(name) + } +} \ No newline at end of file diff --git a/src/test/org/codehaus/groovy/transform/GlobalLegacyTransformTest.groovy b/src/test/org/codehaus/groovy/transform/GlobalLegacyTransformTest.groovy new file mode 100644 index 00000000000..e39ac3a7ab6 --- /dev/null +++ b/src/test/org/codehaus/groovy/transform/GlobalLegacyTransformTest.groovy @@ -0,0 +1,36 @@ +/* + * 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 + * + * http://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.codehaus.groovy.transform + +class GlobalLegacyTransformTest extends GroovyTestCase { + def path = "org/codehaus/groovy/transform/META-INF/services/org.codehaus.groovy.transform.ASTTransformation" + URL transformRoot = new File(getClass().classLoader.getResource(path).toURI()).parentFile.parentFile.parentFile.toURI().toURL() + + void testGlobalTransform() { + def shell = new GroovyShell() + shell.classLoader.addURL(transformRoot) + shell.evaluate(""" + import static org.codehaus.groovy.control.CompilePhase.* + + def ph = org.codehaus.groovy.transform.TestTransform.phases + assert ph.TestTransformConversion == [CONVERSION] + assert ph.TestTransformClassGeneration == [CLASS_GENERATION] + """) + } +} diff --git a/src/test/org/codehaus/groovy/transform/GlobalTestTransformClassLoader.groovy b/src/test/org/codehaus/groovy/transform/GlobalTestTransformClassLoader.groovy index ece8ecffe6e..a07f89c7b5b 100644 --- a/src/test/org/codehaus/groovy/transform/GlobalTestTransformClassLoader.groovy +++ b/src/test/org/codehaus/groovy/transform/GlobalTestTransformClassLoader.groovy @@ -27,7 +27,7 @@ class GlobalTestTransformClassLoader extends GroovyClassLoader { } Enumeration getResources(String name) { - if (name == "META-INF/services/org.codehaus.groovy.transform.ASTTransformation") { + if (name == "META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation") { return Collections.enumeration(Collections.singleton(new FakeURLFactory().createURL(transformDescriptor))) } diff --git a/src/test/org/codehaus/groovy/transform/GlobalTransformTest.groovy b/src/test/org/codehaus/groovy/transform/GlobalTransformTest.groovy index aa2e35e2e24..12379ff41dd 100644 --- a/src/test/org/codehaus/groovy/transform/GlobalTransformTest.groovy +++ b/src/test/org/codehaus/groovy/transform/GlobalTransformTest.groovy @@ -18,28 +18,19 @@ */ package org.codehaus.groovy.transform -/** - * @author Danno.Ferrin - * @author Alex Tkachman - */ -class GlobalTransformTest extends GroovyShellTestCase { - - URL transformRoot = new File(getClass().classLoader. - getResource("org/codehaus/groovy/transform/META-INF/services/org.codehaus.groovy.transform.ASTTransformation"). - toURI()).parentFile.parentFile.parentFile.toURL() +class GlobalTransformTest extends GroovyTestCase { + def path = "org/codehaus/groovy/transform/META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation" + URL transformRoot = new File(getClass().classLoader.getResource(path).toURI()).parentFile.parentFile.parentFile.toURI().toURL() void testGlobalTransform() { + def shell = new GroovyShell() shell.classLoader.addURL(transformRoot) shell.evaluate(""" - import org.codehaus.groovy.control.CompilePhase + import static org.codehaus.groovy.control.CompilePhase.* - if (org.codehaus.groovy.transform.TestTransform.phases == [CompilePhase.CONVERSION, CompilePhase.CLASS_GENERATION]) { - println "Phase sync bug fixed" - } else if (org.codehaus.groovy.transform.TestTransform.phases == [CompilePhase.CONVERSION, CompilePhase.INSTRUCTION_SELECTION]) { - println "Phase sync bug still present" - } else { - assert false, "FAIL" - } + def ph = org.codehaus.groovy.transform.TestTransform.phases + assert ph.TestTransformSemanticAnalysis == [SEMANTIC_ANALYSIS] + assert ph.TestTransformClassGeneration == [CLASS_GENERATION] """) } -} \ No newline at end of file +} diff --git a/src/test/org/codehaus/groovy/transform/META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation b/src/test/org/codehaus/groovy/transform/META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation new file mode 100644 index 00000000000..d3d8496598a --- /dev/null +++ b/src/test/org/codehaus/groovy/transform/META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation @@ -0,0 +1,18 @@ +# 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 +# +# http://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. + +# test two transforms in different phases +org.codehaus.groovy.transform.TestTransformSemanticAnalysis +org.codehaus.groovy.transform.TestTransformClassGeneration \ No newline at end of file diff --git a/src/test/org/codehaus/groovy/transform/TestTransform.groovy b/src/test/org/codehaus/groovy/transform/TestTransform.groovy index c0b37521d2e..a20666a8280 100644 --- a/src/test/org/codehaus/groovy/transform/TestTransform.groovy +++ b/src/test/org/codehaus/groovy/transform/TestTransform.groovy @@ -18,33 +18,34 @@ */ package org.codehaus.groovy.transform +import groovy.transform.CompilationUnitAware import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.control.CompilationUnit import org.codehaus.groovy.control.CompilePhase import org.codehaus.groovy.control.SourceUnit -import org.codehaus.groovy.transform.ASTTransformation -import org.codehaus.groovy.transform.GroovyASTTransformation - -/** - * @author Danno.Ferrin - */ -class TestTransform implements ASTTransformation { +class TestTransform implements ASTTransformation, CompilationUnitAware { static List visitedNodes = [] - static List phases = [] + static Map> phases = [:].withDefault{ [] } + CompilationUnit unit = null - public void visit(ASTNode[] nodes, SourceUnit source) { + void visit(ASTNode[] nodes, SourceUnit source) { visitedNodes += nodes - phases += CompilePhase.phases[source.getPhase()] + // TODO work out why source.phase is not equal to unit.phase in all cases + phases[getClass().simpleName] += CompilePhase.phases[unit.phase] } + @Override + void setCompilationUnit(CompilationUnit unit) { + this.unit = unit + } } -@GroovyASTTransformation(phase=CompilePhase.CONVERSION) -class TestTransformConversion extends TestTransform { +@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS) +class TestTransformSemanticAnalysis extends TestTransform { } -} +@GroovyASTTransformation(phase=CompilePhase.CONVERSION) +class TestTransformConversion extends TestTransform { } @GroovyASTTransformation(phase=CompilePhase.CLASS_GENERATION) -class TestTransformClassGeneration extends TestTransform { - -} \ No newline at end of file +class TestTransformClassGeneration extends TestTransform { } diff --git a/src/test/org/codehaus/groovy/transform/classloading/LegacyTransformsTest.groovy b/src/test/org/codehaus/groovy/transform/classloading/LegacyTransformsTest.groovy new file mode 100644 index 00000000000..7f1e0169068 --- /dev/null +++ b/src/test/org/codehaus/groovy/transform/classloading/LegacyTransformsTest.groovy @@ -0,0 +1,94 @@ +/* + * 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 + * + * http://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.codehaus.groovy.transform.classloading + +import junit.framework.TestCase +import org.codehaus.groovy.control.CompilationUnit +import org.codehaus.groovy.transform.GlobalLegacyTestTransformClassLoader +import org.objectweb.asm.ClassVisitor + +/** + * Tests whether global transforms in legacy META-INF location are successfully detected, loaded, + * and run if separate class loaders are used for loading compile dependencies and AST transforms. + */ +class LegacyTransformsTest extends GroovyTestCase { + URL[] urls = collectUrls(getClass().classLoader) + addGroovyUrls() + GroovyClassLoader dependencyLoader = new GroovyClassLoader(new URLClassLoader(urls, (ClassLoader) null)) + GroovyClassLoader transformLoader = new GroovyClassLoader(new URLClassLoader(urls, new GroovyOnlyClassLoader())) + + private static addGroovyUrls() { + [ + GroovyObject.class.protectionDomain.codeSource.location.toURI().toURL(), // load Groovy runtime + ClassVisitor.class.protectionDomain.codeSource.location.toURI().toURL(), // load asm + GroovyTestCase.class.protectionDomain.codeSource.location.toURI().toURL(), // load Groovy test module + TestCase.class.protectionDomain.codeSource.location.toURI().toURL(), // -"- + this.protectionDomain.codeSource.location.toURI().toURL(), // load test as well + ] + } + + private Set collectUrls(ClassLoader classLoader) { + if (classLoader == null) return [] + if (classLoader instanceof URLClassLoader) { + return collectUrls(classLoader.parent) + Arrays.asList(classLoader.URLs) + } + collectUrls(classLoader.parent) + } + + private compileAndLoadClass(String source, GroovyClassLoader dependencyLoader, GroovyClassLoader transformLoader) { + def unit = new CompilationUnit(null, null, dependencyLoader, transformLoader) + unit.addSource("Foo.groovy", source) + unit.compile() + + assert unit.classes.size() == 1 + def classInfo = unit.classes[0] + + def loader = new GroovyClassLoader(getClass().classLoader) + return loader.defineClass(classInfo.name, classInfo.bytes) + } + + void setUp() { + assert dependencyLoader.loadClass(CompilationUnit.class.name) != CompilationUnit + assert dependencyLoader.loadClass(getClass().name) != getClass() + + assert transformLoader.loadClass(CompilationUnit.class.name) == CompilationUnit + // TODO: reversing arguments of != results in VerifyError + assert getClass() != transformLoader.loadClass(getClass().name) + } + + void testGlobalTransform() { + transformLoader = new GlobalLegacyTestTransformClassLoader(transformLoader, ToUpperCaseGlobalTransform) + def clazz = compileAndLoadClass("class Foo {}", dependencyLoader, transformLoader) + assert clazz + assert clazz.name == "FOO" + } + + static class GroovyOnlyClassLoader extends ClassLoader { + synchronized Class loadClass(String name, boolean resolve) { + // treat this package as not belonging to Groovy + if (name.startsWith(getClass().getPackage().name)) { + throw new ClassNotFoundException(name) + } + if (name.startsWith("java.") || name.startsWith("groovy.") || name.startsWith("org.codehaus.groovy.")) { + return getClass().classLoader.loadClass(name, resolve) + } + throw new ClassNotFoundException(name) + } + } + +} diff --git a/subprojects/groovy-macro/src/main/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation b/subprojects/groovy-macro/src/main/resources/META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation similarity index 100% rename from subprojects/groovy-macro/src/main/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation rename to subprojects/groovy-macro/src/main/resources/META-INF/groovy/org.codehaus.groovy.transform.ASTTransformation