This is an automated email from the ASF dual-hosted git repository. paulk pushed a commit to branch GROOVY_3_0_X in repository https://gitbox.apache.org/repos/asf/groovy.git
commit a95378bb503f59b5802934ce90cd4227c51cdeeb Author: Paul King <pa...@asert.com.au> AuthorDate: Wed Mar 25 16:38:06 2020 +1000 GROOVY-9478: Groovy Ant task could support Ant resources (closes #1204) --- .../main/java/org/codehaus/groovy/ant/Groovy.java | 160 ++++++++++++++++----- .../groovy-ant/src/spec/doc/groovy-ant-task.adoc | 50 ++++++- .../org/codehaus/groovy/ant/GroovyTest.xml | 32 +++++ .../groovy/org/codehaus/groovy/ant/GroovyTest.java | 40 ++++++ 4 files changed, 239 insertions(+), 43 deletions(-) diff --git a/subprojects/groovy-ant/src/main/java/org/codehaus/groovy/ant/Groovy.java b/subprojects/groovy-ant/src/main/java/org/codehaus/groovy/ant/Groovy.java index 7ec8aef..12c3c96 100644 --- a/subprojects/groovy-ant/src/main/java/org/codehaus/groovy/ant/Groovy.java +++ b/subprojects/groovy-ant/src/main/java/org/codehaus/groovy/ant/Groovy.java @@ -28,11 +28,16 @@ import groovy.util.CharsetToolkit; import org.apache.groovy.io.StringBuilderWriter; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; +import org.apache.tools.ant.filters.util.ChainReaderHelper; import org.apache.tools.ant.taskdefs.Java; import org.apache.tools.ant.types.Commandline; import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterChain; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.resources.FileResource; import org.apache.tools.ant.util.FileUtils; import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilerConfiguration; @@ -42,11 +47,15 @@ import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.runtime.ResourceGroovyMethods; import org.codehaus.groovy.tools.ErrorReporter; +import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Reader; @@ -55,6 +64,7 @@ import java.lang.reflect.Field; import java.nio.charset.Charset; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.List; import java.util.Vector; /** @@ -69,6 +79,11 @@ public class Groovy extends Java { private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; /** + * encoding; set to null or empty means 'default' + */ + private String encoding = null; + + /** * output encoding; set to null or empty means 'default' */ private String outputEncoding = null; @@ -81,9 +96,9 @@ public class Groovy extends Java { private final Vector<FileSet> filesets = new Vector<FileSet>(); /** - * input file + * The input resource */ - private File srcFile = null; + private Resource src = null; /** * input command @@ -91,7 +106,7 @@ public class Groovy extends Java { private String command = ""; /** - * Results Output file. + * Results Output file */ private File output = null; @@ -109,6 +124,8 @@ public class Groovy extends Java { private String scriptBaseClass; private String configscript; + private final List<FilterChain> filterChains = new Vector<>(); + /** * Compiler configuration. * <p> @@ -141,6 +158,18 @@ public class Groovy extends Java { } /** + * Declare the encoding to use when inputting from a resource; + * If not supplied or the empty encoding is supplied, a guess will be made for file resources, + * otherwise the platform's default encoding will be used. + * + * @param encoding the character encoding to use. + * @since 3.0.3 + */ + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + /** * Should a new GroovyShell be used when forking. Special variables won't be available * but you don't need Ant in the classpath. * @@ -171,12 +200,12 @@ public class Groovy extends Java { /** * Set the name of the file to be run. The folder of the file is automatically added to the classpath. - * Required unless statements are enclosed in the build file + * Required unless statements are enclosed in the build file or a nested resource is supplied. * * @param srcFile the file containing the groovy script to execute */ public void setSrc(final File srcFile) { - this.srcFile = srcFile; + addConfigured(new FileResource(srcFile)); } /** @@ -186,14 +215,14 @@ public class Groovy extends Java { * @param txt the inline groovy commands to execute */ public void addText(String txt) { - log("addText('" + txt + "')", Project.MSG_VERBOSE); + log.verbose("addText('" + txt + "')"); this.command += txt; } /** - * Adds a set of files (nested fileset attribute). + * Adds a fileset (nested fileset attribute) which should represent a single source file. * - * @param set the fileset representing source files + * @param set the fileset representing a source file */ public void addFileset(FileSet set) { filesets.addElement(set); @@ -311,14 +340,28 @@ public class Groovy extends Java { command = command.trim(); - if (srcFile == null && command.length() == 0 && filesets.isEmpty()) { - throw new BuildException("Source file does not exist!", getLocation()); + // process filesets + for (FileSet next : filesets) { + for (Resource res : next) { + if (src == null) { + src = res; + } else { + throw new BuildException("A single source resource must be provided!", getLocation()); + } + } + } + + if (src == null && command.length() == 0) { + throw new BuildException("Source does not exist!", getLocation()); } - if (srcFile != null && !srcFile.exists()) { - throw new BuildException("Source file does not exist!", getLocation()); + if (src != null && !src.isExists()) { + throw new BuildException("Source resource does not exist!", getLocation()); } + if (outputEncoding == null || outputEncoding.isEmpty()) { + outputEncoding = Charset.defaultCharset().name(); + } try { PrintStream out = System.out; try { @@ -326,21 +369,55 @@ public class Groovy extends Java { log.verbose("Opening PrintStream to output file " + output); BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(output.getAbsolutePath(), append)); - out = new PrintStream(bos, false, - (outputEncoding == null || outputEncoding.isEmpty()) ? Charset.defaultCharset().name() : outputEncoding); + out = new PrintStream(bos, false, outputEncoding); } // if there are no groovy statements between the enclosing Groovy tags - // then read groovy statements in from a text file using the src attribute + // then read groovy statements in from a resource using the src attribute if (command == null || command.trim().length() == 0) { - if (srcFile == null || !srcFile.exists()) { - throw new BuildException("Source file does not exist!", getLocation()); + Reader reader; + if (src instanceof FileResource) { + File file = ((FileResource) src).getFile(); + createClasspath().add(new Path(getProject(), file.getParentFile().getCanonicalPath())); + if (encoding != null && !encoding.isEmpty()) { + reader = new LineNumberReader(new InputStreamReader(new FileInputStream(file), encoding)); + } else { + reader = new CharsetToolkit(file).getReader(); + } + } else { + if (encoding != null && !encoding.isEmpty()) { + reader = new InputStreamReader(new BufferedInputStream(src.getInputStream()), encoding); + } else { + reader = new InputStreamReader(new BufferedInputStream(src.getInputStream()), Charset.defaultCharset()); + } + } + try { + final long len = src.getSize(); + log.debug("resource size = " + (len != Resource.UNKNOWN_SIZE ? String.valueOf(len) : "unknown")); + if (len == 0) { + log.info("Ignoring empty resource"); + command = null; + } else { + try (ChainReaderHelper.ChainReader chainReader = new ChainReaderHelper(getProject(), reader, filterChains).with(crh -> { + if (len != Resource.UNKNOWN_SIZE && len <= Integer.MAX_VALUE) { + crh.setBufferSize((int) len); + } + }).getAssembledReader()) { + command = chainReader.readFully(); + } + } + } catch (final IOException ioe) { + throw new BuildException("Unable to load resource: ", ioe, getLocation()); + } + } else { + if (src != null) { + log.info("Ignoring supplied resource as direct script text found"); } - createClasspath().add(new Path(getProject(), srcFile.getParentFile().getCanonicalPath())); - command = getText(new CharsetToolkit(srcFile).getReader()); } - execGroovy(command, out); + if (command != null) { + execGroovy(command, out); + } } finally { if (out != null && out != System.out) { @@ -351,20 +428,7 @@ public class Groovy extends Java { throw new BuildException(e, getLocation()); } - log.verbose("statements executed successfully"); - } - - private static String getText(BufferedReader reader) throws IOException { - StringBuilder answer = new StringBuilder(); - // reading the content of the file within a char buffer allow to keep the correct line endings - char[] charBuffer = new char[4096]; - int nbCharRead = 0; - while ((nbCharRead = reader.read(charBuffer)) != -1) { - // appends buffer - answer.append(charBuffer, 0, nbCharRead); - } - reader.close(); - return answer.toString(); + log.verbose("Statements executed successfully"); } public Commandline.Argument createArg() { @@ -372,6 +436,25 @@ public class Groovy extends Java { } /** + * Add the FilterChain element. + * @param filter the filter to add + */ + public final void addFilterChain(FilterChain filter) { + filterChains.add(filter); + } + + /** + * Set the source resource. + * @param a the resource to load as a single element Resource collection. + */ + public void addConfigured(ResourceCollection a) { + if (a.size() != 1) { + throw new BuildException("Only single argument resource collections are supported"); + } + src = a.iterator().next(); + } + + /** * Read in lines and execute them. * * @param reader the reader from which to get the groovy source to exec @@ -630,8 +713,9 @@ public class Groovy extends Java { * @return the name to use when compiling the script */ private String computeScriptName() { - if (srcFile != null) { - return srcFile.getAbsolutePath(); + if (src instanceof FileResource) { + FileResource fr = (FileResource) src; + return fr.getFile().getAbsolutePath(); } else { String name = PREFIX; if (getLocation().getFileName().length() > 0) @@ -663,8 +747,8 @@ public class Groovy extends Java { */ protected void printResults(PrintStream out) { log.debug("printResults()"); - StringBuilder line = new StringBuilder(); - out.println(line); +// StringBuilder line = new StringBuilder(); +// out.println(line); out.println(); } diff --git a/subprojects/groovy-ant/src/spec/doc/groovy-ant-task.adoc b/subprojects/groovy-ant/src/spec/doc/groovy-ant-task.adoc index 9dfcd16..0867dfb 100644 --- a/subprojects/groovy-ant/src/spec/doc/groovy-ant-task.adoc +++ b/subprojects/groovy-ant/src/spec/doc/groovy-ant-task.adoc @@ -22,7 +22,7 @@ = The <groovy> Ant Task Executes a series of Groovy statements from http://ant.apache.org/[Apache Ant]. -Statements can either be read in from a text file using the `src` attribute or from between the enclosing Groovy tags. +Statements can either be read in from a resource or as direct text between the enclosing Groovy tags. == Required taskdef @@ -38,22 +38,62 @@ the `groovy` task being invoked. classpathref="my.classpath"/> ---- -Now use the task like this: +You can simply place statements between the `groovy` tags like this: [source,xml] ---- -<groovy src="..." otherAttributes="..."> +<groovy> +... +</groovy> +---- + +Or you can supply the Groovy source script as a resource. You can specify the pathname using the `src` attribute like this: + +[source,xml] +---- +<groovy src="/some/path/MyGroovyScript.groovy" otherAttributes="..."> ---- -Or this: +Or as a nested `fileset` like this (though the fileset definition is expected to select just one file): [source,xml] ---- <groovy> -... + <fileset file="MyGroovyScript.groovy"/> +</groovy> +---- + +Or as a nested single element https://ant.apache.org/manual/Types/resources.html#collection[resource collection] which could look like any of these: + +[source,xml] +---- +<groovy> + <file file="MyGroovyScript.groovy"/> +</groovy> + +<groovy> + <url url="https://some.domain/some/path/to/MyGroovyScript.groovy"/> +</groovy> + +<groovy> + <javaconstant name="some.packagename.SomeClass.MY_CODE_FRAGMENT"/> </groovy> ---- +You may also supply a https://ant.apache.org/manual/Types/filterchain.html[filter chain] like this: + +[source,xml] +---- +<groovy> + <fileset file="MyGroovyScript.groovy"/> + <!-- take 5 lines after skipping 18 lines, just as an example --> + <filterchain> + <headfilter lines="5" skip="18"/> + </filterchain> +</groovy> +---- + + You might need to use the _contextClassLoader_ attribute (see below) if any of your modules load services via the classpath, e.g. `groovy-json`. == <groovy> attributes diff --git a/subprojects/groovy-ant/src/test-resources/org/codehaus/groovy/ant/GroovyTest.xml b/subprojects/groovy-ant/src/test-resources/org/codehaus/groovy/ant/GroovyTest.xml index 1efbb19..75b098d 100644 --- a/subprojects/groovy-ant/src/test-resources/org/codehaus/groovy/ant/GroovyTest.xml +++ b/subprojects/groovy-ant/src/test-resources/org/codehaus/groovy/ant/GroovyTest.xml @@ -39,6 +39,34 @@ <groovy src="GroovyTest1.groovy"/> </target> + <target name="groovyCodeInExternalFileset" depends="defineTask"> + <groovy> + <fileset file="GroovyTest1.groovy"/> + <!-- skip over copyright header, just as an example --> + <filterchain> + <headfilter skip="18"/> + </filterchain> + </groovy> + </target> + + <target name="groovyCodeInExternalFileResource" depends="defineTask"> + <groovy> + <file file="GroovyTest1.groovy"/> + </groovy> + </target> + + <target name="groovyCodeInExternalJavaConstantResource" depends="defineTask"> + <groovy> + <javaconstant name="org.codehaus.groovy.ant.GroovyTest.CODE_FRAGMENT"/> + </groovy> + </target> + + <target name="groovyCodeInExternalURLResource" depends="defineTask"> + <groovy> + <url url="https://raw.githubusercontent.com/apache/groovy/master/subprojects/groovy-ant/src/test-resources/org/codehaus/groovy/ant/GroovyTest1.groovy"/> + </groovy> + </target> + <target name="groovyCodeInExternalFileWithOtherClass" depends="defineTask"> <groovy src="GroovyTest2.groovy"/> </target> @@ -100,6 +128,10 @@ <groovy src="GroovyTest_errorMessage.groovy"/> </target> + <target name="groovyErrorMsg_NonExistingFile" depends="defineTask"> + <groovy src="Does_not_exist.groovy"/> + </target> + <target name="defineTask"> <taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy"/> </target> diff --git a/subprojects/groovy-ant/src/test/groovy/org/codehaus/groovy/ant/GroovyTest.java b/subprojects/groovy-ant/src/test/groovy/org/codehaus/groovy/ant/GroovyTest.java index 8f9811f..e232373 100644 --- a/subprojects/groovy-ant/src/test/groovy/org/codehaus/groovy/ant/GroovyTest.java +++ b/subprojects/groovy-ant/src/test/groovy/org/codehaus/groovy/ant/GroovyTest.java @@ -40,6 +40,7 @@ import java.util.regex.Pattern; */ public class GroovyTest extends GroovyTestCase { public static String FLAG = null; + public static final String CODE_FRAGMENT = "org.codehaus.groovy.ant.GroovyTest.FLAG = \"from Java constant resource\""; private final File antFile = new File("src/test-resources/org/codehaus/groovy/ant/GroovyTest.xml"); private Project project; @@ -51,6 +52,10 @@ public class GroovyTest extends GroovyTestCase { TestSuite suite = new TestSuite(); suite.addTest(new GroovyTest("testGroovyCodeWithinTag")); suite.addTest(new GroovyTest("testGroovyCodeExternalFile")); + suite.addTest(new GroovyTest("testGroovyCodeExternalFileset")); + suite.addTest(new GroovyTest("testGroovyCodeExternalFileResource")); + suite.addTest(new GroovyTest("testGroovyCodeExternalURLResource")); + suite.addTest(new GroovyTest("testGroovyCodeExternalJavaConstantResource")); suite.addTest(new GroovyTest("testGroovyCodeInExternalFileWithOtherClass")); suite.addTest(new GroovyTest("testPropertiesWithoutFork")); suite.addTest(new GroovyTest("testClasspath_missing")); @@ -59,6 +64,7 @@ public class GroovyTest extends GroovyTestCase { suite.addTest(new GroovyTest("testClasspath_nestedclasspath")); suite.addTest(new GroovyTest("testGroovyArgUsage")); suite.addTest(new GroovyTest("testFileNameInStackTrace")); + suite.addTest(new GroovyTest("testNonExistingFile")); return suite; } @@ -86,6 +92,30 @@ public class GroovyTest extends GroovyTestCase { assertEquals("from groovy file called from ant", FLAG); } + public void testGroovyCodeExternalFileset() { + assertNull(FLAG); + project.executeTarget("groovyCodeInExternalFileset"); + assertEquals("from groovy file called from ant", FLAG); + } + + public void testGroovyCodeExternalFileResource() { + assertNull(FLAG); + project.executeTarget("groovyCodeInExternalFileResource"); + assertEquals("from groovy file called from ant", FLAG); + } + + public void testGroovyCodeExternalURLResource() { + assertNull(FLAG); + project.executeTarget("groovyCodeInExternalURLResource"); + assertEquals("from groovy file called from ant", FLAG); + } + + public void testGroovyCodeExternalJavaConstantResource() { + assertNull(FLAG); + project.executeTarget("groovyCodeInExternalJavaConstantResource"); + assertEquals("from Java constant resource", FLAG); + } + public void testPropertiesWithoutFork() { assertNull(FLAG); project.executeTarget("groovyAntPropertyWithoutFork"); @@ -131,6 +161,16 @@ public class GroovyTest extends GroovyTestCase { assertEquals("from groovytest3.GroovyTest3Class.doSomethingWithArgs() 1 2 3", FLAG); } + public void testNonExistingFile() { + try { + project.executeTarget("groovyErrorMsg_NonExistingFile"); + fail(); + } + catch (final BuildException e) { + assertTrue(e.getMessage().contains("Source resource does not exist!")); + } + } + /** * Test that helpful "file name" appears in the stack trace and not just "Script1" */