| /* |
| * 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.ant; |
| |
| import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; |
| import groovy.lang.GroovyClassLoader; |
| import org.antlr.v4.runtime.tree.ParseTreeVisitor; |
| import org.apache.groovy.io.StringBuilderWriter; |
| import org.apache.tools.ant.AntClassLoader; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.apache.tools.ant.RuntimeConfigurable; |
| import org.apache.tools.ant.taskdefs.Execute; |
| import org.apache.tools.ant.taskdefs.Javac; |
| import org.apache.tools.ant.taskdefs.MatchingTask; |
| import org.apache.tools.ant.types.Path; |
| import org.apache.tools.ant.types.Reference; |
| import org.apache.tools.ant.util.GlobPatternMapper; |
| import org.apache.tools.ant.util.SourceFileScanner; |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.control.CompilationUnit; |
| import org.codehaus.groovy.control.CompilerConfiguration; |
| import org.codehaus.groovy.control.SourceExtensionHandler; |
| import org.codehaus.groovy.runtime.DefaultGroovyMethods; |
| import org.codehaus.groovy.runtime.DefaultGroovyStaticMethods; |
| import org.codehaus.groovy.tools.ErrorReporter; |
| import org.codehaus.groovy.tools.FileSystemCompiler; |
| import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit; |
| import org.objectweb.asm.ClassVisitor; |
| import picocli.CommandLine; |
| |
| import java.io.File; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.Writer; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.nio.charset.Charset; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| |
| /** |
| * Compiles Groovy source files using Ant. |
| * <p> |
| * Typically involves using Ant from the command-line and an Ant build file such as: |
| * <pre> |
| * <?xml version="1.0"?> |
| * <project name="MyGroovyBuild" default="compile"> |
| * <property name="groovy.home" location="/Path/To/Groovy"/> |
| * <property name="groovy.version" value="X.Y.Z"/> |
| * |
| * <taskdef name="groovyc" classname="org.codehaus.groovy.ant.Groovyc"> |
| * <classpath> |
| * <fileset file="${groovy.home}/lib/groovy-${groovy.version}.jar"/> |
| * <fileset file="${groovy.home}/lib/groovy-ant-${groovy.version}.jar"/> |
| * </classpath> |
| * </taskdef> |
| * |
| * <target name="compile" description="compile groovy sources"> |
| * <groovyc srcdir="src" destdir="bin" fork="true" listfiles="true" includeantruntime="false"> |
| * <classpath> |
| * <fileset dir="${groovy.home}/lib" includes="groovy-*${groovy.version}.jar" excludes="groovy-ant-${groovy.version}.jar"/> |
| * </classpath> |
| * </groovyc> |
| * </target> |
| * </project> |
| * </pre> |
| * <p> |
| * This task can take the following arguments: |
| * <ul> |
| * <li>srcdir</li> |
| * <li>destdir</li> |
| * <li>sourcepath</li> |
| * <li>sourcepathRef</li> |
| * <li>classpath</li> |
| * <li>classpathRef</li> |
| * <li>scriptExtension</li> |
| * <li>targetBytecode</li> |
| * <li>listfiles</li> |
| * <li>failonerror</li> |
| * <li>proceed</li> |
| * <li>memoryInitialSize</li> |
| * <li>memoryMaximumSize</li> |
| * <li>encoding</li> |
| * <li>verbose</li> |
| * <li>includeantruntime</li> |
| * <li>includejavaruntime</li> |
| * <li>fork</li> |
| * <li>javaHome</li> |
| * <li>executable</li> |
| * <li>updatedProperty</li> |
| * <li>errorProperty</li> |
| * <li>includeDestClasses</li> |
| * <li>jointCompilationOptions</li> |
| * <li>stacktrace</li> |
| * <li>scriptBaseClass</li> |
| * <li>stubdir</li> |
| * <li>keepStubs</li> |
| * <li>forceLookupUnnamedFiles</li> |
| * <li>configscript</li> |
| * <li>parameters</li> |
| * </ul> |
| * And these nested tasks: |
| * <ul> |
| * <li>javac</li> |
| * </ul> |
| * Of these arguments, the <b>srcdir</b> and <b>destdir</b> are required. |
| * <p> |
| * When this task executes, it will recursively scan srcdir and destdir looking |
| * for Groovy source files to compile. This task makes its compile decision based |
| * on timestamp. |
| * <p> |
| * A more elaborate build file showing joint compilation: |
| * <pre> |
| * <?xml version="1.0"?> |
| * <project name="MyJointBuild" default="compile"> |
| * <property name="groovy.home" location="/Path/To/Groovy"/> |
| * <property name="groovy.version" value="X.Y.Z"/> |
| * |
| * <path id="classpath.main"> |
| * <fileset dir="${groovy.home}/lib"> |
| * <include name="groovy-*${groovy.version}.jar"/> |
| * <exclude name="groovy-ant-${groovy.version}.jar"/> |
| * </fileset> |
| * </path> |
| * |
| * <taskdef name="groovyc" classname="org.codehaus.groovy.ant.Groovyc"> |
| * <classpath> |
| * <fileset file="${groovy.home}/lib/groovy-${groovy.version}.jar"/> |
| * <fileset file="${groovy.home}/lib/groovy-ant-${groovy.version}.jar"/> |
| * </classpath> |
| * </taskdef> |
| * |
| * <target name="clean"> |
| * <delete dir="bin" failonerror="false"/> |
| * </target> |
| * |
| * <target name="compile" depends="clean" description="compile java and groovy sources"> |
| * <mkdir dir="bin"/> |
| * |
| * <groovyc srcdir="src" destdir="bin" stubdir="stubs" keepStubs="true" |
| * fork="true" includeantruntime="false" classpathref="classpath.main"> |
| * <javac debug="true" source="1.8" target="1.8"/> |
| * </groovyc> |
| * </target> |
| * </project> |
| * </pre> |
| * <p> |
| * Based on the implementation of the Javac task in Apache Ant. |
| * <p> |
| * Can also be used from {@link groovy.ant.AntBuilder} to allow the build file to be scripted in Groovy. |
| */ |
| public class Groovyc extends MatchingTask { |
| |
| private static final File[] EMPTY_FILE_ARRAY = new File[0]; |
| private static final String[] EMPTY_STRING_ARRAY = new String[0]; |
| |
| private final LoggingHelper log = new LoggingHelper(this); |
| |
| private Path src; |
| private File destDir; |
| private Path compileClasspath; |
| private Path compileSourcepath; |
| private String encoding; |
| private boolean stacktrace; |
| private boolean verbose; |
| private boolean includeAntRuntime = true; |
| private boolean includeJavaRuntime; |
| private boolean fork; |
| private File forkJavaHome; |
| private String forkedExecutable; |
| private String memoryInitialSize; |
| private String memoryMaximumSize; |
| private String scriptExtension = "*.groovy"; |
| private String targetBytecode; |
| |
| protected boolean failOnError = true; |
| protected boolean listFiles; |
| protected File[] compileList = EMPTY_FILE_ARRAY; |
| |
| private String updatedProperty; |
| private String errorProperty; |
| private boolean taskSuccess = true; |
| private boolean includeDestClasses = true; |
| |
| protected CompilerConfiguration configuration; |
| private Javac javac; |
| private boolean jointCompilation; |
| |
| private final List<File> temporaryFiles = new ArrayList<>(2); |
| private File stubDir; |
| private boolean keepStubs; |
| private boolean forceLookupUnnamedFiles; |
| private String scriptBaseClass; |
| private String configscript; |
| |
| private Set<String> scriptExtensions = new LinkedHashSet<>(); |
| |
| /** |
| * If true, generates metadata for reflection on method parameter names (jdk8+ only). Defaults to false. |
| */ |
| private boolean parameters; |
| |
| /** |
| * If true, enable preview Java features (JEP 12) (jdk12+ only). Defaults to false. |
| */ |
| private boolean previewFeatures; |
| |
| /** |
| * Adds a path for source compilation. |
| * |
| * @return a nested src element. |
| */ |
| public Path createSrc() { |
| if (src == null) { |
| src = new Path(getProject()); |
| } |
| return src.createPath(); |
| } |
| |
| /** |
| * Recreate src. |
| * |
| * @return a nested src element. |
| */ |
| protected Path recreateSrc() { |
| src = null; |
| return createSrc(); |
| } |
| |
| /** |
| * Set the source directories to find the source Java files. |
| * |
| * @param srcDir the source directories as a path |
| */ |
| public void setSrcdir(Path srcDir) { |
| if (src == null) { |
| src = srcDir; |
| } else { |
| src.append(srcDir); |
| } |
| } |
| |
| /** |
| * Gets the source dirs to find the source java files. |
| * |
| * @return the source directories as a path |
| */ |
| public Path getSrcdir() { |
| return src; |
| } |
| |
| /** |
| * Set the extension to use when searching for Groovy source files. |
| * Accepts extensions in the form *.groovy, .groovy or groovy |
| * |
| * @param scriptExtension the extension of Groovy source files |
| */ |
| public void setScriptExtension(String scriptExtension) { |
| if (scriptExtension.startsWith("*.")) { |
| this.scriptExtension = scriptExtension; |
| } else if (scriptExtension.startsWith(".")) { |
| this.scriptExtension = "*" + scriptExtension; |
| } else { |
| this.scriptExtension = "*." + scriptExtension; |
| } |
| } |
| |
| /** |
| * Get the extension to use when searching for Groovy source files. |
| * |
| * @return the extension of Groovy source files |
| */ |
| public String getScriptExtension() { |
| return scriptExtension; |
| } |
| |
| /** |
| * Sets the bytecode compatibility level. |
| * The parameter can take one of the values in {@link CompilerConfiguration#ALLOWED_JDKS}. |
| * |
| * @param version the bytecode compatibility level |
| */ |
| public void setTargetBytecode(final String version) { |
| this.targetBytecode = version; |
| } |
| |
| /** |
| * Retrieves the compiler bytecode compatibility level. |
| * |
| * @return bytecode compatibility level. Can be one of the values in {@link CompilerConfiguration#ALLOWED_JDKS}. |
| */ |
| public String getTargetBytecode() { |
| return this.targetBytecode; |
| } |
| |
| /** |
| * Set the destination directory into which the Java source |
| * files should be compiled. |
| * |
| * @param destDir the destination director |
| */ |
| public void setDestdir(File destDir) { |
| this.destDir = destDir; |
| } |
| |
| /** |
| * Gets the destination directory into which the java source files |
| * should be compiled. |
| * |
| * @return the destination directory |
| */ |
| public File getDestdir() { |
| return destDir; |
| } |
| |
| /** |
| * Set the sourcepath to be used for this compilation. |
| * |
| * @param sourcepath the source path |
| */ |
| public void setSourcepath(Path sourcepath) { |
| if (compileSourcepath == null) { |
| compileSourcepath = sourcepath; |
| } else { |
| compileSourcepath.append(sourcepath); |
| } |
| } |
| |
| /** |
| * Gets the sourcepath to be used for this compilation. |
| * |
| * @return the source path |
| */ |
| public Path getSourcepath() { |
| return compileSourcepath; |
| } |
| |
| /** |
| * Adds a path to sourcepath. |
| * |
| * @return a sourcepath to be configured |
| */ |
| public Path createSourcepath() { |
| if (compileSourcepath == null) { |
| compileSourcepath = new Path(getProject()); |
| } |
| return compileSourcepath.createPath(); |
| } |
| |
| /** |
| * Adds a reference to a source path defined elsewhere. |
| * |
| * @param r a reference to a source path |
| */ |
| public void setSourcepathRef(Reference r) { |
| createSourcepath().setRefid(r); |
| } |
| |
| /** |
| * Set the classpath to be used for this compilation. |
| * |
| * @param classpath an Ant Path object containing the compilation classpath. |
| */ |
| public void setClasspath(Path classpath) { |
| if (compileClasspath == null) { |
| compileClasspath = classpath; |
| } else { |
| compileClasspath.append(classpath); |
| } |
| } |
| |
| /** |
| * Gets the classpath to be used for this compilation. |
| * |
| * @return the class path |
| */ |
| public Path getClasspath() { |
| return compileClasspath; |
| } |
| |
| /** |
| * Adds a path to the classpath. |
| * |
| * @return a class path to be configured |
| */ |
| public Path createClasspath() { |
| if (compileClasspath == null) { |
| compileClasspath = new Path(getProject()); |
| } |
| return compileClasspath.createPath(); |
| } |
| |
| /** |
| * Adds a reference to a classpath defined elsewhere. |
| * |
| * @param r a reference to a classpath |
| */ |
| public void setClasspathRef(Reference r) { |
| createClasspath().setRefid(r); |
| } |
| |
| /** |
| * If true, list the source files being handed off to the compiler. |
| * Default is false. |
| * |
| * @param list if true list the source files |
| */ |
| public void setListfiles(boolean list) { |
| listFiles = list; |
| } |
| |
| /** |
| * Get the listfiles flag. |
| * |
| * @return the listfiles flag |
| */ |
| public boolean getListfiles() { |
| return listFiles; |
| } |
| |
| /** |
| * Indicates whether the build will continue |
| * even if there are compilation errors; defaults to true. |
| * |
| * @param fail if true halt the build on failure |
| */ |
| public void setFailonerror(boolean fail) { |
| failOnError = fail; |
| } |
| |
| /** |
| * @param proceed inverse of failonerror |
| */ |
| public void setProceed(boolean proceed) { |
| failOnError = !proceed; |
| } |
| |
| /** |
| * Gets the failonerror flag. |
| * |
| * @return the failonerror flag |
| */ |
| public boolean getFailonerror() { |
| return failOnError; |
| } |
| |
| /** |
| * The initial size of the memory for the underlying VM |
| * if javac is run externally; ignored otherwise. |
| * Defaults to the standard VM memory setting. |
| * (Examples: 83886080, 81920k, or 80m) |
| * |
| * @param memoryInitialSize string to pass to VM |
| */ |
| public void setMemoryInitialSize(String memoryInitialSize) { |
| this.memoryInitialSize = memoryInitialSize; |
| } |
| |
| /** |
| * Gets the memoryInitialSize flag. |
| * |
| * @return the memoryInitialSize flag |
| */ |
| public String getMemoryInitialSize() { |
| return memoryInitialSize; |
| } |
| |
| /** |
| * The maximum size of the memory for the underlying VM |
| * if javac is run externally; ignored otherwise. |
| * Defaults to the standard VM memory setting. |
| * (Examples: 83886080, 81920k, or 80m) |
| * |
| * @param memoryMaximumSize string to pass to VM |
| */ |
| public void setMemoryMaximumSize(String memoryMaximumSize) { |
| this.memoryMaximumSize = memoryMaximumSize; |
| } |
| |
| /** |
| * Gets the memoryMaximumSize flag. |
| * |
| * @return the memoryMaximumSize flag |
| */ |
| public String getMemoryMaximumSize() { |
| return memoryMaximumSize; |
| } |
| |
| /** |
| * Sets the file encoding for generated files. |
| * |
| * @param encoding the file encoding to be used |
| */ |
| public void setEncoding(String encoding) { |
| this.encoding = encoding; |
| } |
| |
| /** |
| * Returns the encoding to be used when creating files. |
| * |
| * @return the file encoding to use |
| */ |
| public String getEncoding() { |
| return encoding; |
| } |
| |
| /** |
| * Enable verbose compiling which will display which files |
| * are being compiled. Default is false. |
| */ |
| public void setVerbose(boolean verbose) { |
| this.verbose = verbose; |
| } |
| |
| /** |
| * Gets the verbose flag. |
| * |
| * @return the verbose flag |
| */ |
| public boolean getVerbose() { |
| return verbose; |
| } |
| |
| /** |
| * If true, includes Ant's own classpath in the classpath. Default is true. |
| * If setting to false and using groovyc in conjunction with AntBuilder |
| * you might need to explicitly add the Groovy jar(s) to the groovyc |
| * classpath using a nested classpath task. |
| * |
| * @param include if true, includes Ant's own classpath in the classpath |
| */ |
| public void setIncludeantruntime(boolean include) { |
| includeAntRuntime = include; |
| } |
| |
| /** |
| * Gets whether the ant classpath is to be included in the classpath. |
| * |
| * @return whether the ant classpath is to be included in the classpath |
| */ |
| public boolean getIncludeantruntime() { |
| return includeAntRuntime; |
| } |
| |
| /** |
| * If true, includes the Java runtime libraries in the classpath. Default is false. |
| * |
| * @param include if true, includes the Java runtime libraries in the classpath |
| */ |
| public void setIncludejavaruntime(boolean include) { |
| includeJavaRuntime = include; |
| } |
| |
| /** |
| * Gets whether the java runtime should be included in this |
| * task's classpath. |
| * |
| * @return the includejavaruntime attribute |
| */ |
| public boolean getIncludejavaruntime() { |
| return includeJavaRuntime; |
| } |
| |
| /** |
| * If true forks the Groovy compiler. Default is false. |
| * |
| * @param f "true|false|on|off|yes|no" |
| */ |
| public void setFork(boolean f) { |
| fork = f; |
| } |
| |
| /** |
| * The JDK Home to use when forked. |
| * Ignored if "executable" is specified. |
| * |
| * @param home the java.home value to use, default is the current JDK's home |
| */ |
| public void setJavaHome(File home) { |
| forkJavaHome = home; |
| } |
| |
| /** |
| * Sets the name of the java executable to use when |
| * invoking the compiler in forked mode, ignored otherwise. |
| * |
| * @param forkExecPath the name of the executable |
| * @since Groovy 1.8.7 |
| */ |
| public void setExecutable(String forkExecPath) { |
| forkedExecutable = forkExecPath; |
| } |
| |
| /** |
| * The value of the executable attribute, if any. |
| * |
| * @return the name of the java executable |
| * @since Groovy 1.8.7 |
| */ |
| public String getExecutable() { |
| return forkedExecutable; |
| } |
| |
| /** |
| * The property to set on compilation success. |
| * This property will not be set if the compilation |
| * fails, or if there are no files to compile. |
| * |
| * @param updatedProperty the property name to use. |
| */ |
| public void setUpdatedProperty(String updatedProperty) { |
| this.updatedProperty = updatedProperty; |
| } |
| |
| /** |
| * The property to set on compilation failure. |
| * This property will be set if the compilation |
| * fails. |
| * |
| * @param errorProperty the property name to use. |
| */ |
| public void setErrorProperty(String errorProperty) { |
| this.errorProperty = errorProperty; |
| } |
| |
| /** |
| * This property controls whether to include the |
| * destination classes directory in the classpath |
| * given to the compiler. |
| * The default value is "true". |
| * |
| * @param includeDestClasses the value to use. |
| */ |
| public void setIncludeDestClasses(boolean includeDestClasses) { |
| this.includeDestClasses = includeDestClasses; |
| } |
| |
| /** |
| * Get the value of the includeDestClasses property. |
| * |
| * @return the value. |
| */ |
| public boolean isIncludeDestClasses() { |
| return includeDestClasses; |
| } |
| |
| /** |
| * Get the result of the groovyc task (success or failure). |
| * |
| * @return true if compilation succeeded, or |
| * was not necessary, false if the compilation failed. |
| */ |
| public boolean getTaskSuccess() { |
| return taskSuccess; |
| } |
| |
| /** |
| * Add the configured nested javac task if present to initiate joint compilation. |
| */ |
| public void addConfiguredJavac(final Javac javac) { |
| this.javac = javac; |
| jointCompilation = true; |
| } |
| |
| /** |
| * Enable compiler to report stack trace information if a problem occurs |
| * during compilation. Default is false. |
| */ |
| public void setStacktrace(boolean stacktrace) { |
| this.stacktrace = stacktrace; |
| } |
| |
| /** |
| * Legacy method to set the indy flag (only true is allowed) |
| * |
| * @param indy true means invokedynamic support is active |
| */ |
| @Deprecated |
| public void setIndy(final boolean indy) { |
| if (!indy) { |
| throw new BuildException("Disabling indy is no longer supported!", getLocation()); |
| } |
| } |
| |
| /** |
| * Get the value of the indy flag (always true). |
| */ |
| @Deprecated |
| public boolean getIndy() { |
| return true; |
| } |
| |
| /** |
| * Set the base script class name for the scripts (must derive from Script) |
| * |
| * @param scriptBaseClass Base class name for scripts (must derive from Script) |
| */ |
| public void setScriptBaseClass(String scriptBaseClass) { |
| this.scriptBaseClass = scriptBaseClass; |
| } |
| |
| /** |
| * Get the base script class name for the scripts (must derive from Script) |
| * |
| * @return Base class name for scripts (must derive from Script) |
| */ |
| public String getScriptBaseClass() { |
| return this.scriptBaseClass; |
| } |
| |
| /** |
| * Get the configuration file used to customize the compilation configuration. |
| * |
| * @return a path to a configuration script |
| */ |
| public String getConfigscript() { |
| return configscript; |
| } |
| |
| /** |
| * Set the configuration file used to customize the compilation configuration. |
| * |
| * @param configscript a path to a configuration script |
| */ |
| public void setConfigscript(final String configscript) { |
| this.configscript = configscript; |
| } |
| |
| /** |
| * Set the stub directory into which the Java source stub |
| * files should be generated. The directory need not exist |
| * and will not be deleted automatically - though its contents |
| * will be cleared unless 'keepStubs' is true. Ignored when forked. |
| * |
| * @param stubDir the stub directory |
| */ |
| public void setStubdir(File stubDir) { |
| jointCompilation = true; |
| this.stubDir = stubDir; |
| } |
| |
| /** |
| * Gets the stub directory into which the Java source stub |
| * files should be generated |
| * |
| * @return the stub directory |
| */ |
| public File getStubdir() { |
| return stubDir; |
| } |
| |
| /** |
| * Set the keepStubs flag. Defaults to false. Set to true for debugging. |
| * Ignored when forked. |
| * |
| * @param keepStubs should stubs be retained |
| */ |
| public void setKeepStubs(boolean keepStubs) { |
| this.keepStubs = keepStubs; |
| } |
| |
| /** |
| * Gets the keepStubs flag. |
| * |
| * @return the keepStubs flag |
| */ |
| public boolean getKeepStubs() { |
| return keepStubs; |
| } |
| |
| /** |
| * Set the forceLookupUnnamedFiles flag. Defaults to false. |
| * <p> |
| * The Groovyc Ant task is frequently used in the context of a build system |
| * that knows the complete list of source files to be compiled. In such a |
| * context, it is wasteful for the Groovy compiler to go searching the |
| * classpath when looking for source files and hence by default the |
| * Groovyc Ant task calls the compiler in a special mode with such searching |
| * turned off. If you wish the compiler to search for source files then |
| * you need to set this flag to {@code true}. |
| * |
| * @param forceLookupUnnamedFiles should unnamed source files be searched for on the classpath |
| */ |
| public void setForceLookupUnnamedFiles(boolean forceLookupUnnamedFiles) { |
| this.forceLookupUnnamedFiles = forceLookupUnnamedFiles; |
| } |
| |
| /** |
| * Gets the forceLookupUnnamedFiles flag. |
| * |
| * @return the forceLookupUnnamedFiles flag |
| */ |
| public boolean getForceLookupUnnamedFiles() { |
| return forceLookupUnnamedFiles; |
| } |
| |
| /** |
| * If true, generates metadata for reflection on method parameter names (jdk8+ only). Defaults to false. |
| * |
| * @param parameters set to true to generate metadata. |
| */ |
| public void setParameters(boolean parameters) { |
| this.parameters = parameters; |
| } |
| |
| /** |
| * Returns true if parameter metadata generation has been enabled. |
| */ |
| public boolean getParameters() { |
| return this.parameters; |
| } |
| |
| /** |
| * If true, enable preview Java features (JEP 12) (jdk12+ only). |
| * |
| * @param previewFeatures set to true to enable preview features |
| */ |
| public void setPreviewFeatures(boolean previewFeatures) { |
| this.previewFeatures = previewFeatures; |
| } |
| |
| /** |
| * Returns true if preview features has been enabled. |
| */ |
| public boolean getPreviewFeatures() { |
| return previewFeatures; |
| } |
| |
| /** |
| * Executes the task. |
| * |
| * @throws BuildException if an error occurs |
| */ |
| @Override |
| public void execute() throws BuildException { |
| checkParameters(); |
| resetFileLists(); |
| loadRegisteredScriptExtensions(); |
| |
| if (javac != null) jointCompilation = true; |
| |
| // scan source directories and dest directory to build up |
| // compile lists |
| String[] list = src.list(); |
| for (String filename : list) { |
| File file = getProject().resolveFile(filename); |
| if (!file.exists()) { |
| throw new BuildException("srcdir \"" + file.getPath() + "\" does not exist!", getLocation()); |
| } |
| DirectoryScanner ds = this.getDirectoryScanner(file); |
| String[] files = ds.getIncludedFiles(); |
| scanDir(file, destDir != null ? destDir : file, files); |
| } |
| |
| compile(); |
| if (updatedProperty != null |
| && taskSuccess |
| && compileList.length != 0) { |
| getProject().setNewProperty(updatedProperty, "true"); |
| } |
| } |
| |
| /** |
| * Clear the list of files to be compiled and copied. |
| */ |
| protected void resetFileLists() { |
| compileList = EMPTY_FILE_ARRAY; |
| scriptExtensions = new LinkedHashSet<>(); |
| } |
| |
| /** |
| * Scans the directory looking for source files to be compiled. |
| * The results are returned in the class variable compileList |
| * |
| * @param srcDir The source directory |
| * @param destDir The destination directory |
| * @param files An array of filenames |
| */ |
| protected void scanDir(File srcDir, File destDir, String[] files) { |
| GlobPatternMapper m = new GlobPatternMapper(); |
| SourceFileScanner sfs = new SourceFileScanner(this); |
| File[] newFiles; |
| for (String extension : getScriptExtensions()) { |
| m.setFrom("*." + extension); |
| m.setTo("*.class"); |
| newFiles = sfs.restrictAsFiles(files, srcDir, destDir, m); |
| addToCompileList(newFiles); |
| } |
| |
| if (jointCompilation) { |
| m.setFrom("*.java"); |
| m.setTo("*.class"); |
| newFiles = sfs.restrictAsFiles(files, srcDir, destDir, m); |
| addToCompileList(newFiles); |
| } |
| } |
| |
| protected void addToCompileList(File[] newFiles) { |
| if (newFiles.length > 0) { |
| File[] newCompileList = new File[compileList.length + newFiles.length]; |
| System.arraycopy(compileList, 0, newCompileList, 0, compileList.length); |
| System.arraycopy(newFiles, 0, newCompileList, compileList.length, newFiles.length); |
| compileList = newCompileList; |
| } |
| } |
| |
| /** |
| * Gets the list of files to be compiled. |
| * |
| * @return the list of files as an array |
| */ |
| public File[] getFileList() { |
| return Arrays.copyOf(compileList, compileList.length); |
| } |
| |
| protected void checkParameters() throws BuildException { |
| if (src == null) { |
| throw new BuildException("srcdir attribute must be set!", getLocation()); |
| } |
| if (src.size() == 0) { |
| throw new BuildException("srcdir attribute must be set!", getLocation()); |
| } |
| |
| if (destDir != null && !destDir.isDirectory()) { |
| throw new BuildException("destination directory \"" |
| + destDir |
| + "\" does not exist or is not a directory", |
| getLocation()); |
| } |
| |
| if (encoding != null && !Charset.isSupported(encoding)) { |
| throw new BuildException("encoding \"" + encoding + "\" not supported."); |
| } |
| } |
| |
| private void listFiles() { |
| if (listFiles) { |
| for (File srcFile : compileList) { |
| log.info(srcFile.getAbsolutePath()); |
| } |
| } |
| } |
| |
| /** |
| * If {@code groovyc} task includes a nested {@code javac} task, check for |
| * shareable configuration. {@code FileSystemCompiler} supports several |
| * command-line arguments for configuring joint compilation: |
| * <ul> |
| * <li><tt>-j</tt> enables joint compile |
| * <li><tt>-F</tt> is used to pass flags |
| * <li><tt>-J</tt> is used to pass name=value pairs |
| * </ul> |
| * Joint compilation options are transferred from {@link FileSystemCompiler} |
| * to {@link CompilerConfiguration}'s jointCompileOptions property. Flags |
| * are saved to key "flags" (with the inclusion of "parameters" if enabled |
| * on groovyc), pairs are saved to key "namedValues" and the key "memStub" |
| * may also be set to {@link Boolean#TRUE} to influence joint compilation. |
| * |
| * @see org.codehaus.groovy.tools.javac.JavacJavaCompiler |
| * @see javax.tools.JavaCompiler |
| */ |
| private List<String> extractJointOptions(Path classpath) { |
| List<String> jointOptions = new ArrayList<>(); |
| if (!jointCompilation) return jointOptions; |
| |
| // map "debug" and "debuglevel" to "-Fg" |
| if (javac.getDebug()) { |
| jointOptions.add("-Fg" + Optional.ofNullable(javac.getDebugLevel()).map(level -> ":" + level).orElse("")); |
| } else { |
| jointOptions.add("-Fg:none"); |
| } |
| |
| // map "deprecation" to "-Fdeprecation" |
| if (javac.getDeprecation()) { |
| jointOptions.add("-Fdeprecation"); |
| } |
| |
| // map "nowarn" to "-Fnowarn" |
| if (javac.getNowarn()) { |
| jointOptions.add("-Fnowarn"); |
| } |
| |
| // map "verbose" to "-Fverbose" |
| if (javac.getVerbose()) { |
| jointOptions.add("-Fverbose"); |
| } |
| |
| RuntimeConfigurable rc = javac.getRuntimeConfigurableWrapper(); |
| |
| for (Map.Entry<String, Object> e : rc.getAttributeMap().entrySet()) { |
| String key = e.getKey(); |
| if (key.equals("depend") |
| || key.equals("encoding") |
| || key.equals("extdirs") |
| || key.equals("nativeheaderdir") |
| || key.equals("release") |
| || key.equals("source") |
| || key.equals("target")) { |
| switch (key) { |
| case "nativeheaderdir": |
| key = "h"; |
| break; |
| case "release": |
| key = "-" + key; // to get "--" when passed to javac |
| break; |
| default: |
| } |
| // map "depend", "encoding", etc. to "-Jkey=val" |
| jointOptions.add("-J" + key + "=" + getProject().replaceProperties(e.getValue().toString())); |
| |
| } else if (key.contains("classpath")) { |
| if (key.startsWith("boot")) { |
| // map "bootclasspath" or "bootclasspathref" to "-Jbootclasspath=" |
| jointOptions.add("-Jbootclasspath=" + javac.getBootclasspath()); |
| } else { |
| // map "classpath" or "classpathref" to "--classpath" |
| classpath.add(javac.getClasspath()); |
| } |
| } else if (key.contains("module") && key.contains("path")) { |
| if (key.startsWith("upgrade")) { |
| // map "upgrademodulepath" or "upgrademodulepathref" to "-J-upgrade-module-path=" |
| jointOptions.add("-J-upgrade-module-path=" + javac.getUpgrademodulepath()); |
| } else if (key.contains("source")) { |
| // map "modulesourcepath" or "modulesourcepathref" to "-J-module-source-path=" |
| jointOptions.add("-J-module-source-path=" + javac.getModulesourcepath()); |
| } else { |
| // map "modulepath" or "modulepathref" to "-J-module-path=" |
| jointOptions.add("-J-module-path=" + javac.getModulepath()); |
| } |
| } else if (!key.contains("debug") && !key.equals("deprecation") && !key.equals("nowarn") && !key.equals("verbose")) { |
| log.warn("The option " + key + " cannot be set on the contained <javac> element. The option will be ignored."); |
| } |
| // TODO: defaultexcludes, excludes(file)?, includes(file)?, includeDestClasses, tempdir |
| } |
| |
| // Ant's <javac> supports nested <compilerarg value=""> elements (there |
| // can be multiple of them) for additional options to be passed to javac. |
| for (RuntimeConfigurable childrc : Collections.list(rc.getChildren())) { |
| if (childrc.getElementTag().equals("compilerarg")) { |
| for (Map.Entry<String, Object> e : childrc.getAttributeMap().entrySet()) { |
| String key = e.getKey(); |
| if (key.equals("value")) { |
| String value = getProject().replaceProperties(e.getValue().toString()); |
| StringTokenizer st = new StringTokenizer(value, " "); |
| while (st.hasMoreTokens()) { |
| String option = st.nextToken(); |
| // GROOVY-5063: map "-Werror", etc. to "-FWerror" |
| jointOptions.add(option.replaceFirst("^-(W|X|proc:)", "-F$1")); |
| } |
| } |
| } |
| } |
| } |
| |
| return jointOptions; |
| } |
| |
| private void doForkCommandLineList(List<String> commandLineList, Path classpath, String separator) { |
| if (forkedExecutable != null && !forkedExecutable.isEmpty()) { |
| commandLineList.add(forkedExecutable); |
| } else { |
| String javaHome; |
| if (forkJavaHome != null) { |
| javaHome = forkJavaHome.getPath(); |
| } else { |
| javaHome = System.getProperty("java.home"); |
| } |
| commandLineList.add(javaHome + separator + "bin" + separator + "java"); |
| } |
| |
| String[] bootstrapClasspath; |
| ClassLoader loader = getClass().getClassLoader(); |
| if (loader instanceof AntClassLoader) { |
| bootstrapClasspath = ((AntClassLoader) loader).getClasspath().split(File.pathSeparator); |
| } else { |
| Class<?>[] bootstrapClasses = { |
| FileSystemCompilerFacade.class, |
| FileSystemCompiler.class, |
| ParseTreeVisitor.class, |
| ClassVisitor.class, |
| CommandLine.class, |
| }; |
| bootstrapClasspath = Arrays.stream(bootstrapClasses).map(Groovyc::getLocation) |
| .map(uri -> new File(uri).getAbsolutePath()).distinct().toArray(String[]::new); |
| } |
| if (bootstrapClasspath.length > 0) { |
| commandLineList.add("-classpath"); |
| commandLineList.add(getClasspathRelative(bootstrapClasspath)); |
| } |
| |
| if (memoryInitialSize != null && !memoryInitialSize.isEmpty()) { |
| commandLineList.add("-Xms" + memoryInitialSize); |
| } |
| if (memoryMaximumSize != null && !memoryMaximumSize.isEmpty()) { |
| commandLineList.add("-Xmx" + memoryMaximumSize); |
| } |
| if (targetBytecode != null) { |
| CompilerConfiguration cc = new CompilerConfiguration(); |
| cc.setTargetBytecode(targetBytecode); // GROOVY-10278: nearest valid value |
| commandLineList.add("-Dgroovy.target.bytecode=" + cc.getTargetBytecode()); |
| } |
| if (!"*.groovy".equals(getScriptExtension())) { |
| String tmpExtension = getScriptExtension(); |
| if (tmpExtension.startsWith("*.")) |
| tmpExtension = tmpExtension.substring(1); |
| commandLineList.add("-Dgroovy.default.scriptExtension=" + tmpExtension); |
| } |
| |
| commandLineList.add(FileSystemCompilerFacade.class.getName()); |
| commandLineList.add("--classpath"); |
| if (includeAntRuntime) { |
| classpath.addExisting(new Path(getProject()).concatSystemClasspath("last")); |
| } |
| if (includeJavaRuntime) { |
| classpath.addJavaRuntime(); |
| } |
| commandLineList.add(getClasspathRelative(classpath.list())); |
| if (forceLookupUnnamedFiles) { |
| commandLineList.add("--forceLookupUnnamedFiles"); |
| } |
| } |
| |
| private String getClasspathRelative(String[] classpath) { |
| String baseDir = getProject().getBaseDir().getAbsolutePath(); |
| StringBuilder sb = new StringBuilder(); |
| for (String next : classpath) { |
| if (sb.length() > 0) { |
| sb.append(File.pathSeparatorChar); |
| } |
| if (next.startsWith(baseDir)) { |
| sb.append(".").append(next, baseDir.length(), next.length()); |
| } else { |
| sb.append(next); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| private static URI getLocation(Class<?> clazz) { |
| try { |
| return clazz.getProtectionDomain().getCodeSource().getLocation().toURI(); |
| } catch (URISyntaxException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Add "groovyc" parameters to the commandLineList, based on the ant configuration. |
| * |
| * @param commandLineList |
| * @param jointOptions |
| * @param classpath |
| */ |
| private void doNormalCommandLineList(List<String> commandLineList, List<String> jointOptions, Path classpath) { |
| if (!fork) { |
| commandLineList.add("--classpath"); |
| commandLineList.add(classpath.toString()); |
| } |
| if (jointCompilation) { |
| commandLineList.add("-j"); |
| commandLineList.addAll(jointOptions); |
| } |
| if (destDir != null) { |
| commandLineList.add("-d"); |
| commandLineList.add(destDir.getPath()); |
| } |
| if (encoding != null) { |
| commandLineList.add("--encoding"); |
| commandLineList.add(encoding); |
| } |
| if (stacktrace) { |
| commandLineList.add("-e"); |
| } |
| if (parameters) { |
| commandLineList.add("--parameters"); |
| } |
| if (previewFeatures) { |
| commandLineList.add("--enable-preview"); |
| } |
| if (scriptBaseClass != null) { |
| commandLineList.add("-b"); |
| commandLineList.add(scriptBaseClass); |
| } |
| if (configscript != null) { |
| commandLineList.add("--configscript"); |
| commandLineList.add(configscript); |
| } |
| } |
| |
| private void addSourceFiles(List<String> commandLineList) { |
| // check to see if an external file is needed |
| int count = 0; |
| if (fork) { |
| for (File srcFile : compileList) { |
| count += srcFile.getPath().length(); |
| } |
| for (Object commandLineArg : commandLineList) { |
| count += commandLineArg.toString().length(); |
| } |
| count += compileList.length; |
| count += commandLineList.size(); |
| } |
| // 32767 is the command line length limit on Windows |
| if (fork && (count > 32767)) { |
| try { |
| File tempFile = File.createTempFile("groovyc-files-", ".txt"); |
| temporaryFiles.add(tempFile); |
| PrintWriter pw = printWriter(tempFile); |
| for (File srcFile : compileList) { |
| pw.println(srcFile.getPath()); |
| } |
| pw.close(); |
| commandLineList.add("@" + tempFile.getPath()); |
| } catch (IOException e) { |
| log.error("Error creating file list", e); |
| } |
| } else { |
| for (File srcFile : compileList) { |
| commandLineList.add(srcFile.getPath()); |
| } |
| } |
| } |
| |
| @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "This is only used to store filenames when exceeding a particular length limit when in fork mode") |
| private PrintWriter printWriter(File tempFile) throws IOException { |
| // we could make a forkFileListEncoding but seems like a rare scenario |
| return new PrintWriter(new FileWriter(tempFile)); |
| } |
| |
| private String[] makeCommandLine(List<String> commandLineList) { |
| log.info("Compilation arguments:\n" + DefaultGroovyMethods.join((Iterable<String>) commandLineList, "\n")); |
| return commandLineList.toArray(EMPTY_STRING_ARRAY); |
| } |
| |
| private void runForked(String[] commandLine) { |
| Execute executor = new Execute(); |
| executor.setAntRun(getProject()); |
| executor.setWorkingDirectory(getProject().getBaseDir()); |
| executor.setCommandline(commandLine); |
| try { |
| executor.execute(); |
| } catch (final IOException ioe) { |
| throw new BuildException("Error running forked groovyc.", ioe); |
| } |
| int returnCode = executor.getExitValue(); |
| if (returnCode != 0) { |
| taskSuccess = false; |
| if (errorProperty != null) { |
| getProject().setNewProperty(errorProperty, "true"); |
| } |
| if (failOnError) { |
| throw new BuildException("Forked groovyc returned error code: " + returnCode); |
| } else { |
| log.error("Forked groovyc returned error code: " + returnCode); |
| } |
| } |
| } |
| |
| private void runCompiler(String[] commandLine) { |
| // hand crank it so we can add our own compiler configuration |
| try { |
| FileSystemCompiler.CompilationOptions options = new FileSystemCompiler.CompilationOptions(); |
| CommandLine parser = FileSystemCompiler.configureParser(options); |
| parser.parseArgs(commandLine); |
| configuration = options.toCompilerConfiguration(); |
| configuration.setScriptExtensions(getScriptExtensions()); |
| String tmpExtension = getScriptExtension(); |
| if (tmpExtension.startsWith("*.")) |
| tmpExtension = tmpExtension.substring(1); |
| configuration.setDefaultScriptExtension(tmpExtension); |
| if (targetBytecode != null) { |
| configuration.setTargetBytecode(targetBytecode); |
| } |
| |
| // Load the file name list |
| String[] fileNames = options.generateFileNames(); |
| boolean fileNameErrors = (fileNames == null || !FileSystemCompiler.validateFiles(fileNames)); |
| if (!fileNameErrors) { |
| try (GroovyClassLoader loader = buildClassLoaderFor()) { |
| FileSystemCompiler.doCompilation(configuration, makeCompileUnit(loader), fileNames, forceLookupUnnamedFiles); |
| } |
| } |
| } catch (Exception e) { |
| Throwable t = e; |
| if (e.getClass() == RuntimeException.class && e.getCause() != null) { |
| // unwrap to the real exception |
| t = e.getCause(); |
| } |
| Writer writer = new StringBuilderWriter(); |
| new ErrorReporter(t, false).write(new PrintWriter(writer)); |
| String message = writer.toString(); |
| |
| taskSuccess = false; |
| if (errorProperty != null) { |
| getProject().setNewProperty(errorProperty, "true"); |
| } |
| |
| if (failOnError) { |
| log.error(message); |
| throw new BuildException("Compilation Failed", t, getLocation()); |
| } else { |
| log.error(message); |
| } |
| } |
| } |
| |
| protected void compile() { |
| if (compileList.length == 0) return; |
| |
| try { |
| log.info("Compiling " + compileList.length + " source file" |
| + (compileList.length == 1 ? "" : "s") |
| + (destDir != null ? " to " + destDir : "")); |
| |
| listFiles(); |
| |
| Path classpath = Optional.ofNullable(getClasspath()).orElse(new Path(getProject())); |
| List<String> jointOptions = extractJointOptions(classpath); |
| List<String> commandLineList = new ArrayList<>(); |
| |
| if (fork) doForkCommandLineList(commandLineList, classpath, File.separator); |
| doNormalCommandLineList(commandLineList, jointOptions, classpath); |
| addSourceFiles(commandLineList); |
| |
| String[] commandLine = makeCommandLine(commandLineList); |
| |
| if (fork) { |
| runForked(commandLine); |
| } else { |
| runCompiler(commandLine); |
| } |
| } finally { |
| for (File temporaryFile : temporaryFiles) { |
| try { |
| FileSystemCompiler.deleteRecursive(temporaryFile); |
| } catch (Throwable t) { |
| System.err.println("error: could not delete temp files - " + temporaryFile.getPath()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @deprecated This method is not in use anymore. Use {@link Groovyc#makeCompileUnit(GroovyClassLoader)} instead. |
| */ |
| @Deprecated |
| protected CompilationUnit makeCompileUnit() { |
| return makeCompileUnit(buildClassLoaderFor()); |
| } |
| |
| protected CompilationUnit makeCompileUnit(GroovyClassLoader loader) { |
| Map<String, Object> options = configuration.getJointCompilationOptions(); |
| if (options != null) { |
| if (keepStubs) { |
| options.put("keepStubs", Boolean.TRUE); |
| } |
| if (stubDir != null) { |
| options.put("stubDir", stubDir); |
| } else { |
| try { |
| File tempStubDir = DefaultGroovyStaticMethods.createTempDir(null, "groovy-generated-", "-java-source"); |
| temporaryFiles.add(tempStubDir); |
| options.put("stubDir", tempStubDir); |
| } catch (IOException ioe) { |
| throw new BuildException(ioe); |
| } |
| } |
| return new JavaAwareCompilationUnit(configuration, loader); |
| } else { |
| return new CompilationUnit(configuration, null, loader); |
| } |
| } |
| |
| protected GroovyClassLoader buildClassLoaderFor() { |
| if (fork) { |
| throw new GroovyBugError("Cannot use Groovyc#buildClassLoaderFor() for forked compilation"); |
| } |
| // GROOVY-5044 |
| if (!getIncludeantruntime()) { |
| throw new IllegalArgumentException("The includeAntRuntime=false option is not compatible with fork=false"); |
| } |
| |
| ClassLoader loader = getClass().getClassLoader(); |
| if (loader instanceof AntClassLoader) { |
| AntClassLoader antLoader = (AntClassLoader) loader; |
| String[] pathElm = antLoader.getClasspath().split(File.pathSeparator, -1); |
| List<String> classpath = configuration.getClasspath(); |
| /* |
| * Iterate over the classpath provided to groovyc, and add any missing path |
| * entries to the AntClassLoader. This is a workaround, since for some reason |
| * 'directory' classpath entries were not added to the 'AntClassLoader' classpath. |
| */ |
| for (String cpEntry : classpath) { |
| boolean found = false; |
| for (String path : pathElm) { |
| if (cpEntry.equals(path)) { |
| found = true; |
| break; |
| } |
| } |
| /* |
| * fix for GROOVY-2284 |
| * seems like AntClassLoader doesn't check if the file |
| * may not exist in the classpath yet |
| */ |
| if (!found && new File(cpEntry).exists()) { |
| try { |
| antLoader.addPathElement(cpEntry); |
| } catch (BuildException e) { |
| log.warn("The classpath entry " + cpEntry + " is not a valid Java resource"); |
| } |
| } |
| } |
| } |
| |
| @SuppressWarnings("removal") // TODO a future Groovy version should perform the operation not as a privileged action |
| GroovyClassLoader groovyLoader = java.security.AccessController.doPrivileged((PrivilegedAction<GroovyClassLoader>) () -> |
| new GroovyClassLoader(loader, configuration)); |
| |
| if (!forceLookupUnnamedFiles) { |
| // in normal case we don't need to do script lookups |
| groovyLoader.setResourceLoader(filename -> null); |
| } |
| return groovyLoader; |
| } |
| |
| private Set<String> getScriptExtensions() { |
| return scriptExtensions; |
| } |
| |
| private void loadRegisteredScriptExtensions() { |
| if (scriptExtensions.isEmpty()) { |
| scriptExtensions.add(getScriptExtension().substring(2)); // first extension will be the one set explicitly on <groovyc> |
| |
| Path classpath = Optional.ofNullable(getClasspath()).orElse(new Path(getProject())); |
| try (GroovyClassLoader loader = new GroovyClassLoader(getClass().getClassLoader())) { |
| for (String element : classpath.list()) { |
| loader.addClasspath(element); |
| } |
| scriptExtensions.addAll(SourceExtensionHandler.getRegisteredExtensions(loader)); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| } |