All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.grails.cli.profile.tasks.Groovy Maven / Gradle / Ivy

There is a newer version: 2023.1.0-RC1
Show newest version
/*
 * Copyright 2022-2024 the original author or authors.
 *
 * Licensed 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
 *
 *      https://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.grails.cli.profile.tasks;

import groovy.ant.AntBuilder;
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyShell;
import groovy.lang.MissingMethodException;
import groovy.lang.Script;
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.ant.AntProjectPropertiesDelegate;
import org.codehaus.groovy.ant.LoggingHelper;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.codehaus.groovy.reflection.ReflectionUtils;
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;
import java.io.Writer;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.security.PrivilegedAction;
import java.util.List;
import java.util.Vector;

import org.grails.build.logging.GrailsConsoleAntProject;

/**
 * Groovy Ant task to execute a series of Groovy statements.
 * 

*

Statements can either be read in from a text file using * the src attribute or from between the enclosing groovy tags. *

* This class is copied from {@link org.codehaus.groovy.ant.Groovy}, * allow to customize {@link groovy.lang.Binding} and {@link groovy.ant.AntBuilder}. * * @since 2023.0 * @see org.codehaus.groovy.ant.Groovy */ public class Groovy extends Java { private static final String PREFIX = "embedded_script_in_"; private static final String SUFFIX = "groovy_Ant_task"; 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; private final LoggingHelper log = new LoggingHelper(this); /** * files to load */ private final Vector filesets = new Vector<>(); /** * The input resource */ private Resource src = null; /** * input command */ private String command = ""; /** * Results Output file */ private File output = null; /** * Append to an existing file or overwrite it? */ private boolean append = false; private Path classpath; private boolean fork = false; private boolean includeAntRuntime = true; private boolean useGroovyShell = false; private String scriptBaseClass; private String configscript; private final List filterChains = new Vector<>(); /** * Compiler configuration. *

* Used to specify the debug output to print stacktraces in case something fails. */ private final CompilerConfiguration configuration = new CompilerConfiguration(); private final Commandline cmdline = new Commandline(); private boolean contextClassLoader; private AntBuilder antBuilder; private Binding binding = new Binding(); /** * Should the script be executed using a forked process. Defaults to false. * * @param fork true if the script should be executed in a forked process */ @Override public void setFork(boolean fork) { this.fork = fork; } /** * Declare the encoding to use when outputting to a file; * Leave unspecified or use "" for the platform's default encoding. * * @param encoding the character encoding to use. * @since 3.0.3 */ public void setOutputEncoding(String encoding) { this.outputEncoding = encoding; } /** * 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. * * @param useGroovyShell true if GroovyShell should be used to run the script directly */ public void setUseGroovyShell(boolean useGroovyShell) { this.useGroovyShell = useGroovyShell; } /** * Should the system classpath be included on the classpath when forking. Defaults to true. * * @param includeAntRuntime true if the system classpath should be on the classpath */ public void setIncludeAntRuntime(boolean includeAntRuntime) { this.includeAntRuntime = includeAntRuntime; } /** * Enable compiler to report stack trace information if a problem occurs * during compilation. * * @param stacktrace set to true to enable stacktrace reporting */ public void setStacktrace(boolean stacktrace) { configuration.setDebug(stacktrace); } /** * 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 or a nested resource is supplied. * * @param srcFile the file containing the groovy script to execute */ public void setSrc(final File srcFile) { addConfigured(new FileResource(srcFile)); } /** * Set an inline command to execute. * NB: Properties are not expanded in this text. * * @param txt the inline groovy commands to execute */ public void addText(String txt) { log.verbose("addText('" + txt + "')"); this.command += txt; } /** * Adds a fileset (nested fileset attribute) which should represent a single source file. * * @param set the fileset representing a source file */ public void addFileset(FileSet set) { filesets.addElement(set); } /** * Set the output file; * optional, defaults to the Ant log. * * @param output the output file */ @Override public void setOutput(File output) { this.output = output; } /** * Whether output should be appended to or overwrite * an existing file. Defaults to false. * * @param append set to true to append */ @Override public void setAppend(boolean append) { this.append = append; } /** * Sets the classpath for loading. * * @param classpath The classpath to set */ @Override public void setClasspath(final Path classpath) { this.classpath = classpath; } public void setAntBuilder(AntBuilder antBuilder) { this.antBuilder = antBuilder; } public void setBinding(Binding binding) { this.binding = binding; } /** * Returns a new path element that can be configured. * Gets called for instance by Ant when it encounters a nested <classpath> element. * * @return the resulting created path */ @Override public Path createClasspath() { if (this.classpath == null) { this.classpath = new Path(getProject()); } return this.classpath.createPath(); } /** * Set the classpath for loading * using the classpath reference. * * @param ref the refid to use */ @Override public void setClasspathRef(final Reference ref) { createClasspath().setRefid(ref); } /** * Gets the classpath. * * @return Returns a Path */ public Path getClasspath() { return classpath; } /** * Sets the configuration script for the groovy compiler configuration. * * @param configscript path to the configuration script */ public void setConfigscript(final String configscript) { this.configscript = configscript; } /** * 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()); } } /** * Set the script base class name * * @param scriptBaseClass the name of the base class for scripts */ public void setScriptBaseClass(final String scriptBaseClass) { this.scriptBaseClass = scriptBaseClass; } /** * 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) { configuration.setParameters(parameters); } /** * Returns true if parameter metadata generation has been enabled. */ public boolean getParameters() { return configuration.getParameters(); } /** * Load the file and then execute it */ @Override public void execute() throws BuildException { log.info("execute script: " + this.src); command = command.trim(); // 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 (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 { if (output != null) { log.verbose("Opening PrintStream to output file " + output); BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(output.getAbsolutePath(), append)); out = new PrintStream(bos, false, outputEncoding); } // if there are no groovy statements between the enclosing Groovy tags // then read groovy statements in from a resource using the src attribute if (command == null || command.trim().length() == 0) { 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()); } } readCommandFromReader(reader); } else { if (src != null) { log.info("Ignoring supplied resource as direct script text found"); } } if (command != null) { execGroovy(command, out); } } finally { if (out != null && out != System.out) { out.close(); } } } catch (IOException e) { throw new BuildException(e, getLocation()); } log.verbose("Statements executed successfully"); } private void readCommandFromReader(Reader reader) { 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()); } } @Override public Commandline.Argument createArg() { return cmdline.createArgument(); } /** * 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 * @param out the outputstream to use * @throws java.io.IOException if something goes wrong */ protected void runStatements(Reader reader, PrintStream out) throws IOException { log.debug("runStatements()"); StringBuilder txt = new StringBuilder(); String line = ""; BufferedReader in = new BufferedReader(reader); while ((line = in.readLine()) != null) { line = getProject().replaceProperties(line); if (line.contains("--")) { txt.append("\n"); } } // Catch any statements not followed by ; if (!txt.toString().isEmpty()) { execGroovy(txt.toString(), out); } } /** * Exec the statement. * * @param txt the groovy source to exec * @param out not used? */ protected void execGroovy(final String txt, final PrintStream out) { log.debug("execGroovy()"); // Check and ignore empty statements if (txt.trim().isEmpty()) { return; } log.verbose("Script: " + txt); if (classpath != null) { log.debug("Explicit Classpath: " + classpath.toString()); } if (fork) { log.debug("Using fork mode"); try { createClasspathParts(); createNewArgs(txt); super.setFork(fork); super.setClassname(useGroovyShell ? "groovy.lang.GroovyShell" : "org.codehaus.groovy.ant.Groovy"); configureCompiler(); super.execute(); } catch (Exception e) { Writer writer = new StringBuilderWriter(); new ErrorReporter(e, false).write(new PrintWriter(writer)); String message = writer.toString(); throw new BuildException("Script Failed: " + message, e, getLocation()); } return; } Object mavenPom = null; final Project project = getProject(); final ClassLoader baseClassLoader; ClassLoader savedLoader = null; final Thread thread = Thread.currentThread(); boolean maven = "org.apache.commons.grant.GrantProject".equals(project.getClass().getName()); // treat the case Ant is run through Maven, and if (maven) { if (contextClassLoader) { throw new BuildException("Using setContextClassLoader not permitted when using Maven.", getLocation()); } try { final Object propsHandler = project.getClass().getMethod("getPropsHandler").invoke(project); final Field contextField = propsHandler.getClass().getDeclaredField("context"); ReflectionUtils.trySetAccessible(contextField); final Object context = contextField.get(propsHandler); mavenPom = InvokerHelper.invokeMethod(context, "getProject", EMPTY_OBJECT_ARRAY); } catch (Exception e) { throw new BuildException("Impossible to retrieve Maven's Ant project: " + e.getMessage(), getLocation()); } // load groovy into "root.maven" classloader instead of "root" so that // groovy script can access Maven classes baseClassLoader = mavenPom.getClass().getClassLoader(); } else { baseClassLoader = GroovyShell.class.getClassLoader(); } if (contextClassLoader || maven) { savedLoader = thread.getContextClassLoader(); thread.setContextClassLoader(GroovyShell.class.getClassLoader()); } final String scriptName = computeScriptName(); @SuppressWarnings("removal") // TODO a future Groovy version should perform the operation not as a privileged action final GroovyClassLoader classLoader = java.security.AccessController.doPrivileged((PrivilegedAction) () -> new GroovyClassLoader(baseClassLoader)); addClassPathes(classLoader); configureCompiler(); final GroovyShell groovy = new GroovyShell(classLoader, binding, configuration); try { if (this.antBuilder == null) { this.antBuilder = new AntBuilder(this); } parseAndRunScript(groovy, txt, mavenPom, scriptName, null, antBuilder); } finally { groovy.resetLoadedClasses(); groovy.getClassLoader().clearCache(); if (contextClassLoader || maven) thread.setContextClassLoader(savedLoader); } } private void configureCompiler() { if (scriptBaseClass != null) { configuration.setScriptBaseClass(scriptBaseClass); } if (configscript != null) { Binding binding = new Binding(); binding.setVariable("configuration", configuration); CompilerConfiguration configuratorConfig = new CompilerConfiguration(); ImportCustomizer customizer = new ImportCustomizer(); customizer.addStaticStars("org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder"); configuratorConfig.addCompilationCustomizers(customizer); GroovyShell shell = new GroovyShell(binding, configuratorConfig); File confSrc = new File(configscript); try { shell.evaluate(confSrc); } catch (IOException e) { throw new BuildException("Unable to configure compiler using configuration file: " + confSrc, e); } } } private void parseAndRunScript(GroovyShell shell, String txt, Object mavenPom, String scriptName, File scriptFile, AntBuilder builder) { try { final Script script; if (scriptFile != null) { script = shell.parse(scriptFile); } else { script = shell.parse(txt, scriptName); } final Project project = getProject(); script.setProperty("ant", builder); script.setProperty("project", project); script.setProperty("properties", new AntProjectPropertiesDelegate(project)); script.setProperty("target", getOwningTarget()); script.setProperty("task", this); if (project instanceof GrailsConsoleAntProject) { script.setProperty("options", ((GrailsConsoleAntProject) project).getOptions()); } script.setProperty("args", cmdline.getCommandline()); if (mavenPom != null) { script.setProperty("pom", mavenPom); } script.run(); } catch (final MissingMethodException mme) { // not a script, try running through run method but properties will not be available if (scriptFile != null) { try { shell.run(scriptFile, cmdline.getCommandline()); } catch (IOException e) { processError(e); } } else { shell.run(txt, scriptName, cmdline.getCommandline()); } } catch (final CompilationFailedException | IOException e) { processError(e); } } private void processError(Exception e) { Writer writer = new StringBuilderWriter(); new ErrorReporter(e, false).write(new PrintWriter(writer)); String message = writer.toString(); throw new BuildException("Script Failed: " + message, e, getLocation()); } public static void main(String[] args) { final GroovyShell shell = new GroovyShell(new Binding()); final Groovy groovy = new Groovy(); for (int i = 1; i < args.length; i++) { final Commandline.Argument argument = groovy.createArg(); argument.setValue(args[i]); } final AntBuilder builder = new AntBuilder(); groovy.setProject(builder.getProject()); groovy.parseAndRunScript(shell, null, null, null, new File(args[0]), builder); } private void createClasspathParts() { Path path; if (classpath != null) { path = super.createClasspath(); path.setPath(classpath.toString()); } if (includeAntRuntime) { path = super.createClasspath(); path.setPath(System.getProperty("java.class.path")); } String groovyHome = null; final String[] strings = getSysProperties().getVariables(); if (strings != null) { for (String prop : strings) { if (prop.startsWith("-Dgroovy.home=")) { groovyHome = prop.substring("-Dgroovy.home=".length()); } } } if (groovyHome == null) { groovyHome = System.getProperty("groovy.home"); } if (groovyHome == null) { groovyHome = System.getenv("GROOVY_HOME"); } if (groovyHome == null) { throw new IllegalStateException("Neither ${groovy.home} nor GROOVY_HOME defined."); } File jarDir = new File(groovyHome, "lib"); if (!jarDir.exists()) { throw new IllegalStateException("GROOVY_HOME incorrectly defined. No lib directory found in: " + groovyHome); } final File[] files = jarDir.listFiles(); if (files != null) { for (File file : files) { try { log.debug("Adding jar to classpath: " + file.getCanonicalPath()); } catch (IOException e) { // ignore } path = super.createClasspath(); path.setLocation(file); } } } private void createNewArgs(String txt) throws IOException { final String[] args = cmdline.getCommandline(); // Temporary file - delete on exit, create (assured unique name). final File tempFile = FileUtils.getFileUtils().createTempFile(null, PREFIX, SUFFIX, null, true, true); final String[] commandline = new String[args.length + 1]; ResourceGroovyMethods.write(tempFile, txt); commandline[0] = tempFile.getCanonicalPath(); System.arraycopy(args, 0, commandline, 1, args.length); super.clearArgs(); for (String arg : commandline) { final Commandline.Argument argument = super.createArg(); argument.setValue(arg); } } /** * Try to build a script name for the script of the groovy task to have a helpful value in stack traces in case of exception * * @return the name to use when compiling the script */ private String computeScriptName() { if (src instanceof FileResource) { FileResource fr = (FileResource) src; return fr.getFile().getAbsolutePath(); } else { String name = PREFIX; if (getLocation().getFileName().length() > 0) name += getLocation().getFileName().replaceAll("[^\\w_\\.]", "_").replaceAll("[\\.]", "_dot_"); else name += SUFFIX; return name; } } /** * Adds the class paths (if any) * * @param classLoader the classloader to configure */ protected void addClassPathes(final GroovyClassLoader classLoader) { if (classpath != null) { for (int i = 0; i < classpath.list().length; i++) { classLoader.addClasspath(classpath.list()[i]); } } } /** * print any results in the statement. * * @param out the output PrintStream to print to */ protected void printResults(PrintStream out) { log.debug("printResults()"); out.println(); } /** * Setting to true will cause the contextClassLoader to be set with * the classLoader of the shell used to run the script. Not used if * fork is true. Not allowed when running from Maven but in that * case the context classLoader is set appropriately for Maven. * * @param contextClassLoader set to true to set the context classloader */ public void setContextClassLoader(boolean contextClassLoader) { this.contextClassLoader = contextClassLoader; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy