This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch rmannibucau/java-scripting-draft in repository https://gitbox.apache.org/repos/asf/maven-scripting-plugin.git
commit 0346ed96d069c6a4edcd6bf980fea7409169e84d Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Fri Feb 26 09:41:24 2021 +0100 basic java scripting engine support --- pom.xml | 38 ++ src/it/java/pom.xml | 68 ++++ src/it/java/verify.bsh | 44 +++ .../plugins/scripting/AbstractScriptEvaluator.java | 22 +- .../apache/maven/plugins/scripting/EvalMojo.java | 70 +++- .../plugins/scripting/FileScriptEvaluator.java | 5 +- .../plugins/scripting/StringScriptEvaluator.java | 4 +- .../plugins/scripting/engine/JavaScriptEngine.java | 422 +++++++++++++++++++++ .../scripting/engine/JavaScriptEngineFactory.java | 133 +++++++ .../scripting/engine/JavaScriptEngineTest.java | 99 +++++ 10 files changed, 897 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 13fb174..5b20d0d 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,7 @@ under the License. <mavenVersion>3.0</mavenVersion> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> + <plexus.compiler.version>2.8.8</plexus.compiler.version> <project.build.outputTimestamp>2021-02-24T19:52:25Z</project.build.outputTimestamp> </properties> @@ -96,6 +97,42 @@ under the License. <scope>provided</scope> </dependency> + <!-- java script engine --> + <dependency> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-compiler-api</artifactId> + <version>${plexus.compiler.version}</version> + <exclusions> + <exclusion> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-component-api</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-compiler-manager</artifactId> + <version>${plexus.compiler.version}</version> + <exclusions> + <exclusion> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-component-api</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-compiler-javac</artifactId> + <version>${plexus.compiler.version}</version> + <scope>runtime</scope> + <exclusions> + <exclusion> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-component-api</artifactId> + </exclusion> + </exclusions> + </dependency> + <!-- Test --> <dependency> <groupId>junit</groupId> @@ -133,6 +170,7 @@ under the License. <postBuildHookScript>verify</postBuildHookScript> <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath> <settingsFile>src/it/settings.xml</settingsFile> + <postBuildHookScript>verify.bsh</postBuildHookScript> <goals> <goal>scripting:eval</goal> </goals> diff --git a/src/it/java/pom.xml b/src/it/java/pom.xml new file mode 100644 index 0000000..0db926d --- /dev/null +++ b/src/it/java/pom.xml @@ -0,0 +1,68 @@ +<?xml version='1.0' encoding='UTF-8'?> + +<!-- +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. +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.plugins.scripting.its</groupId> + <artifactId>java</artifactId> + <version>1.0.0-SNAPSHOT</version> + <packaging>pom</packaging> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-scripting-plugin</artifactId> + <version>@project.version@</version> + <configuration> + <engineName>java</engineName> + <scriptEngineConfiguration> + <main>MyMain</main> + <arg.00>1</arg.00> + <arg.01>2</arg.01> + <arg.02>3</arg.02> + <arg.03>4</arg.03> + <arg.10>5</arg.10> + <arg.110>6</arg.110> + </scriptEngineConfiguration> + <script> + <![CDATA[ + import java.nio.file.Files; + import java.nio.file.Path; + import java.nio.file.Paths; + import java.nio.file.StandardOpenOption; + + public class MyMain { + public static void main(String... args) throws Exception { + Path out = Paths.get("target/out"); + Files.createDirectories(out.getParent()); + Files.write(out, String.join(",", args).getBytes(), + StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + } + ]]> + </script> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/src/it/java/verify.bsh b/src/it/java/verify.bsh new file mode 100644 index 0000000..a0f3f98 --- /dev/null +++ b/src/it/java/verify.bsh @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// beanshell does not like much java.nio.file so let's use java.io which is more than enough for us +import java.util.stream.Collectors; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.File; + +File file = new File( basedir, "target/out" ); +if ( !file.exists() ) +{ + throw new FileNotFoundException( "Could not find generated JAR: " + file ); +} +String content; +BufferedReader reader = new BufferedReader( new FileReader( file ) ); +try +{ + content = reader.lines().collect(Collectors.joining( "\n" ) ); +} +finally +{ + reader.close(); +} +if ( !"1,2,3,4,5,6".equals( content ) ) +{ + throw new IllegalArgumentException( "Wrong content: '" + content + "'" ); +} \ No newline at end of file diff --git a/src/main/java/org/apache/maven/plugins/scripting/AbstractScriptEvaluator.java b/src/main/java/org/apache/maven/plugins/scripting/AbstractScriptEvaluator.java index 7605680..cfd0866 100644 --- a/src/main/java/org/apache/maven/plugins/scripting/AbstractScriptEvaluator.java +++ b/src/main/java/org/apache/maven/plugins/scripting/AbstractScriptEvaluator.java @@ -19,6 +19,8 @@ package org.apache.maven.plugins.scripting; * under the License. */ +import org.apache.maven.plugins.scripting.engine.JavaScriptEngineFactory; + import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.ScriptEngine; @@ -31,6 +33,12 @@ import javax.script.ScriptException; */ abstract class AbstractScriptEvaluator { + protected final EvalMojo mojo; + + protected AbstractScriptEvaluator( final EvalMojo mojo ) + { + this.mojo = mojo; + } /** * @param bindings not null bindings to provide to the script to execute @@ -40,7 +48,7 @@ abstract class AbstractScriptEvaluator */ public final Object eval( Bindings bindings ) throws ScriptException, UnsupportedScriptEngineException { - ScriptEngineManager manager = new ScriptEngineManager(); + ScriptEngineManager manager = newScriptEngineManager(); ScriptEngine engine = getEngine( manager ); ScriptContext context = engine.getContext(); @@ -65,4 +73,16 @@ abstract class AbstractScriptEvaluator */ protected abstract ScriptEngine getEngine( ScriptEngineManager manager ) throws UnsupportedScriptEngineException; + + private ScriptEngineManager newScriptEngineManager() + { + // don't go through the spi to have the mojo + JavaScriptEngineFactory javaScriptEngineFactory = new JavaScriptEngineFactory( mojo ); + ScriptEngineManager manager = new ScriptEngineManager(); + javaScriptEngineFactory.getExtensions().forEach( ext -> manager.registerEngineExtension( + ext, javaScriptEngineFactory ) ); + javaScriptEngineFactory.getNames().forEach( name -> manager.registerEngineName( + name, javaScriptEngineFactory ) ); + return manager; + } } diff --git a/src/main/java/org/apache/maven/plugins/scripting/EvalMojo.java b/src/main/java/org/apache/maven/plugins/scripting/EvalMojo.java index 4a6dbbc..27b1391 100644 --- a/src/main/java/org/apache/maven/plugins/scripting/EvalMojo.java +++ b/src/main/java/org/apache/maven/plugins/scripting/EvalMojo.java @@ -20,17 +20,24 @@ package org.apache.maven.plugins.scripting; */ import java.io.File; +import java.util.Map; import javax.script.Bindings; import javax.script.ScriptException; import javax.script.SimpleBindings; +import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; +import org.apache.maven.toolchain.ToolchainManager; +import org.codehaus.plexus.compiler.manager.CompilerManager; + +import static org.apache.maven.plugins.annotations.ResolutionScope.COMPILE_PLUS_RUNTIME; /** * Evaluate the specified script or scriptFile @@ -38,7 +45,7 @@ import org.apache.maven.project.MavenProject; * @author Robert Scholte * @since 3.0.0 */ -@Mojo( name = "eval" ) +@Mojo( name = "eval", requiresDependencyResolution = COMPILE_PLUS_RUNTIME ) public class EvalMojo extends AbstractMojo { @@ -60,9 +67,39 @@ public class EvalMojo @Parameter private File scriptFile; + /** + * Script Engine specific configuration. + * For now it configured maven java script engine. + * Available keys today: + * <ul> + * <li>javaVersion: java version for the compiler (source/target)</li> + * <li>output: where to put generated files by the compilation</li> + * <li>encoding: encoding for the compiler</li> + * <li>outputFileName: folder name to compile into</li> + * <li>main: main fqn</li> + * <li>method: method to execute if not a main, can have optionally a String array as parameter</li> + * <li>arg.{i}: i-th argument of the main</li> + * <li>usePluginLoader: should the parent classloader of the mojo be the plugin one or not</li> + * </ul> + */ + @Parameter + protected Map<String, String> scriptEngineConfiguration; + + @Component + protected ToolchainManager toolchainManager; + + @Component + protected CompilerManager compilerManager; + // script variables @Parameter( defaultValue = "${project}", readonly = true ) - private MavenProject project; + protected MavenProject project; + + /** + * Build session. + */ + @Parameter( defaultValue = "${session}", readonly = true, required = true ) + protected MavenSession session; @Override public void execute() @@ -100,12 +137,12 @@ public class EvalMojo if ( scriptFile != null ) { - execute = new FileScriptEvaluator( engineName, scriptFile ); + execute = new FileScriptEvaluator( engineName, scriptFile, this ); } else if ( script != null ) { - execute = new StringScriptEvaluator( engineName, script ); + execute = new StringScriptEvaluator( engineName, script, this ); } else @@ -114,4 +151,29 @@ public class EvalMojo } return execute; } + + public CompilerManager getCompilerManager() + { + return compilerManager; + } + + public MavenProject getProject() + { + return project; + } + + public Map<String, String> getScriptEngineConfiguration() + { + return scriptEngineConfiguration; + } + + public MavenSession getSession() + { + return session; + } + + public ToolchainManager getToolchainManager() + { + return toolchainManager; + } } \ No newline at end of file diff --git a/src/main/java/org/apache/maven/plugins/scripting/FileScriptEvaluator.java b/src/main/java/org/apache/maven/plugins/scripting/FileScriptEvaluator.java index 9f7abd7..af056bf 100644 --- a/src/main/java/org/apache/maven/plugins/scripting/FileScriptEvaluator.java +++ b/src/main/java/org/apache/maven/plugins/scripting/FileScriptEvaluator.java @@ -51,10 +51,10 @@ public class FileScriptEvaluator extends AbstractScriptEvaluator * @param engineName optional engine name, used to override the engine selection from the file extension * @param scriptFile not null */ - public FileScriptEvaluator( String engineName, File scriptFile ) + public FileScriptEvaluator( String engineName, File scriptFile, EvalMojo mojo ) { + super( mojo ); this.scriptFile = scriptFile; - this.engineName = engineName; } @@ -69,6 +69,7 @@ public class FileScriptEvaluator extends AbstractScriptEvaluator { try ( FileReader reader = new FileReader( scriptFile ) ) { + context.setAttribute( "maven.filename", scriptFile.getName(), ScriptContext.ENGINE_SCOPE ); return engine.eval( reader, context ); } catch ( IOException ex ) diff --git a/src/main/java/org/apache/maven/plugins/scripting/StringScriptEvaluator.java b/src/main/java/org/apache/maven/plugins/scripting/StringScriptEvaluator.java index 5a31c85..f2ddd06 100644 --- a/src/main/java/org/apache/maven/plugins/scripting/StringScriptEvaluator.java +++ b/src/main/java/org/apache/maven/plugins/scripting/StringScriptEvaluator.java @@ -46,8 +46,9 @@ public class StringScriptEvaluator extends AbstractScriptEvaluator * @param script the script * @throws IllegalArgumentException if either engineName or script is null */ - public StringScriptEvaluator( String engineName, String script ) + public StringScriptEvaluator( String engineName, String script, EvalMojo mojo ) { + super( mojo ); if ( engineName == null || engineName.isEmpty() ) { throw new IllegalArgumentException( "Expected a non-empty engine name provided" ); @@ -85,6 +86,7 @@ public class StringScriptEvaluator extends AbstractScriptEvaluator */ protected Object eval( ScriptEngine engine, ScriptContext context ) throws ScriptException { + context.setAttribute( "maven.filename", "mem_" + script.hashCode() + ".java", ScriptContext.ENGINE_SCOPE ); return engine.eval( script, context ); } } \ No newline at end of file diff --git a/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngine.java b/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngine.java new file mode 100644 index 0000000..a503f24 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngine.java @@ -0,0 +1,422 @@ +package org.apache.maven.plugins.scripting.engine; + +/* + * 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. + */ + +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugins.scripting.EvalMojo; +import org.apache.maven.toolchain.Toolchain; +import org.codehaus.plexus.compiler.CompilerConfiguration; +import org.codehaus.plexus.compiler.CompilerMessage; +import org.codehaus.plexus.compiler.CompilerResult; + +import javax.script.AbstractScriptEngine; +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.ScriptEngineFactory; +import javax.script.SimpleBindings; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import static java.lang.ClassLoader.getSystemClassLoader; +import static java.util.Collections.singletonList; +import static java.util.Comparator.comparing; +import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; + +/** + * Maven-java implementation of a script engine. + */ +public class JavaScriptEngine extends AbstractScriptEngine +{ + private final JavaScriptEngineFactory factory; + + private final List<Path> toDelete = new ArrayList<>(); + + public JavaScriptEngine( final JavaScriptEngineFactory factory ) + { + this.factory = factory; + } + + @Override + public Object eval( final String script, final ScriptContext context ) + { + return eval( new StringReader( script ), context ); + } + + @Override + public Bindings createBindings() + { + return new SimpleBindings(); + } + + @Override + public ScriptEngineFactory getFactory() + { + return factory; + } + + @Override + public Object eval( final Reader reader, final ScriptContext context ) + { + final EvalMojo mojo = factory.getMojo(); + final String mainClassName = requireNonNull( + mojo.getScriptEngineConfiguration().get( "main" ), "missing main" ); + + final CompilerConfiguration conf = createConf( reader, mojo, mainClassName ); + toDelete.add( Paths.get( conf.getOutputLocation() ) ); + try + { + CompilerResult result; + try + { + result = mojo.getCompilerManager().getCompiler( "javac" ).performCompile( conf ); + } + catch ( Exception e ) + { + throw new IllegalStateException( "Fatal error compiling", e ); + } + + log( mojo, result ); + + final String[] args = ofNullable( mojo.getScriptEngineConfiguration() ) + .map( c -> c.keySet().stream() + .filter( it -> it.startsWith( "arg." ) ) + .sorted( comparing( i -> Integer.parseInt( i.substring( "arg.".length() ) ) ) ) + .map( c::get ) + .toArray( String[]::new ) ) + .orElseGet( () -> new String[0] ); + + return doExecute( mainClassName, conf, args, mojo.getScriptEngineConfiguration() ); + } + finally + { + toDelete.forEach( path -> + { + if ( !Files.isDirectory( path ) ) + { + try + { + Files.delete( path ); + } + catch ( final IOException e ) + { + throw new IllegalArgumentException( e ); + } + } + else + { + try + { + Files.walkFileTree( path, new SimpleFileVisitor<Path>() + { + @Override + public FileVisitResult visitFile( final Path file, final BasicFileAttributes attrs ) + throws IOException + { + Files.delete( file ); + return super.visitFile( file, attrs ); + } + + @Override + public FileVisitResult postVisitDirectory( final Path dir, final IOException exc ) + throws IOException + { + Files.delete( dir ); + return super.postVisitDirectory( dir, exc ); + } + } ); + } + catch ( final IOException e ) + { + throw new IllegalArgumentException( e ); + } + } + } ); + toDelete.clear(); + } + } + + private Object doExecute( final String mainClassName, final CompilerConfiguration conf, + final String[] args, final Map<String, String> mojoConf ) + { + try + { + try ( final URLClassLoader classLoader = new URLClassLoader( + Stream.concat( + conf.getClasspathEntries().stream() + .map( Paths::get ) + .map( p -> + { + try + { + return p.toUri().toURL(); + } + catch ( final MalformedURLException e ) + { + throw new IllegalArgumentException( e ); + } + } ), + Stream.of( Paths.get( conf.getOutputLocation() ) + .toUri() + .toURL() ) ) + .toArray( URL[]::new ), + Boolean.parseBoolean( mojoConf.get( "usePluginLoader" ) ) + ? Thread.currentThread().getContextClassLoader() + : getSystemClassLoader() ) ) + { + try + { + final Class<?> mainClass = classLoader.loadClass( mainClassName.trim() ); + final String mtd = mojoConf.get( "method" ); + final Method main; + if ( mtd == null ) + { + main = mainClass.getDeclaredMethod( "main", String[].class ); + } + else + { + main = Stream.of( mainClass.getDeclaredMethods() ) + .filter( m -> Objects.equals( mtd, m.getName() ) ) + .sorted( comparing( Method::getParameterCount ) ) + .findAny() + .orElseThrow( () -> new IllegalArgumentException( + "No method '" + mtd + "' in " + mainClass ) ); + } + if ( !main.isAccessible() ) + { + main.setAccessible( true ); + } + main.invoke( null, new Object[]{args} ); + return null; + } + catch ( final Exception e ) + { + throw new IllegalStateException( e ); + } + } + } + catch ( final IOException e ) + { + throw new IllegalArgumentException( e ); + } + } + + private CompilerConfiguration createConf( final Reader reader, final EvalMojo mojo, + final String mainClassName ) + { + final Toolchain tc = mojo.getToolchainManager().getToolchainFromBuildContext( "jdk", mojo.getSession() ); + final String defaultJavaVersion = ofNullable( mojo.getScriptEngineConfiguration() ) + .map( map -> map.get( "javaVersion" ) ) + .orElseGet( () -> ofNullable( mojo.getProject().getProperties().getProperty( "maven.compiler.source" ) ) + .orElse( "8" ) ); + final String output = ofNullable( mojo.getScriptEngineConfiguration() ) + .map( map -> map.get( "output" ) ) + .orElseGet( () -> Paths.get( mojo.getProject().getBuild().getDirectory() ) + .resolve( "maven-scripting-plugin/classes_" + context.getAttribute( "maven.filename" ) ) + .toString() ); + final String encoding = ofNullable( mojo.getScriptEngineConfiguration() ) + .map( map -> map.get( "encoding" ) ) + .orElseGet( () -> ofNullable( mojo.getProject() + .getProperties().getProperty( "project.build.sourceEncoding" ) ) + .orElse( "UTF-8" ) ); + final String outputFileName = ofNullable( mojo.getScriptEngineConfiguration() ) + .map( map -> map.get( "outputFileName" ) ) + .orElse( "script" ); + final String compileSourceRoot = ofNullable( mojo.getScriptEngineConfiguration() ) + .map( map -> map.get( "compileSourceRoot" ) ) + .filter( s -> !s.isEmpty() ) + .orElseGet( () -> + { + final File file = new File( mojo.getProject().getBasedir(), "src/main/scripting" ); + if ( !file.exists() ) + { + return null; + } + return file.getAbsolutePath(); + } ); + final List<String> classpath = ofNullable( mojo.getScriptEngineConfiguration() ) + .map( c -> c.get( "classpath" ) ) + .map( c -> Stream.of( c.split( ":" ) ).collect( toList() ) ) + .orElseGet( () -> mojo.getProject().getArtifacts().stream() + .map( Artifact::getFile ) + .filter( Objects::nonNull ) + .map( File::getAbsolutePath ) + .collect( toList() ) ); + final File tempSources = new File( mojo.getProject().getBuild().getDirectory(), + "maven-scripting-plugin/sources" ); + toDelete.add( tempSources.toPath() ); + try + { + final int lastDot = mainClassName.lastIndexOf( '.' ) + 1; + final String className = lastDot == 0 ? mainClassName : mainClassName.substring( 0, lastDot ); + final File target = new File( tempSources, className + ".java" ); + if ( !target.getParentFile().exists() && !target.getParentFile().mkdirs() ) + { + throw new IllegalArgumentException( "Can't create " + target.getParentFile() ); + } + Files.copy( new ReaderInputStream( reader, StandardCharsets.UTF_8 ), target.toPath(), + StandardCopyOption.REPLACE_EXISTING ); + } + catch ( final IOException e ) + { + throw new IllegalArgumentException( e ); + } + + final CompilerConfiguration conf = new CompilerConfiguration(); + conf.setOutputLocation( output ); + conf.setParameters( true ); + conf.setShowWarnings( true ); + conf.setShowDeprecation( true ); + conf.setSourceVersion( defaultJavaVersion ); + conf.setTargetVersion( defaultJavaVersion ); + if ( !"8".equals( defaultJavaVersion ) && !"1.8".equals( defaultJavaVersion ) ) + { + conf.setReleaseVersion( defaultJavaVersion ); + } + conf.setSourceLocations( compileSourceRoot == null + ? singletonList( tempSources.getAbsolutePath() ) + : Stream.concat( + Stream.of( tempSources.getAbsolutePath() ), + Stream.of( compileSourceRoot.split( ":" ) ) ) + .distinct() + .collect( toList() ) ); + conf.setSourceEncoding( encoding ); + conf.setFork( true ); + conf.setWorkingDirectory( mojo.getProject().getBasedir() ); + conf.setBuildDirectory( new File( mojo.getProject().getBuild().getDirectory() ) ); + conf.setOutputFileName( outputFileName ); + conf.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew ); + conf.setForceJavacCompilerUse( true ); + conf.setClasspathEntries( classpath ); + if ( tc != null ) + { + conf.setExecutable( tc.findTool( "javac" ) ); + } + try + { + conf.setSourceFiles( compileSourceRoot == null ? null : Files.list( Paths.get( compileSourceRoot ) ) + .filter( it -> it.getFileName().toString().endsWith( ".java" ) ) + .map( Path::toFile ) + .collect( toSet() ) ); + } + catch ( final IOException e ) + { + throw new IllegalStateException( e ); + } + return conf; + } + + private void log( final EvalMojo mojo, final CompilerResult result ) + { + List<CompilerMessage> warnings = new ArrayList<>(); + List<CompilerMessage> errors = new ArrayList<>(); + List<CompilerMessage> others = new ArrayList<>(); + for ( final CompilerMessage message : result.getCompilerMessages() ) + { + if ( message.getKind() == CompilerMessage.Kind.ERROR ) + { + errors.add( message ); + } + else if ( message.getKind() == CompilerMessage.Kind.WARNING + || message.getKind() == CompilerMessage.Kind.MANDATORY_WARNING ) + { + warnings.add( message ); + } + else + { + others.add( message ); + } + } + + if ( !result.isSuccess() ) + { + others.forEach( message -> mojo.getLog().info( message.toString() ) ); + if ( !warnings.isEmpty() ) + { + mojo.getLog().info( "-------------------------------------------------------------" ); + mojo.getLog().warn( "COMPILATION WARNING : " ); + mojo.getLog().info( "-------------------------------------------------------------" ); + others.forEach( warning -> mojo.getLog().warn( warning.toString() ) ); + mojo.getLog().info( warnings.size() + ( ( warnings.size() > 1 ) ? " warnings " : " warning" ) ); + mojo.getLog().info( "-------------------------------------------------------------" ); + } + + if ( !errors.isEmpty() ) + { + mojo.getLog().info( "-------------------------------------------------------------" ); + mojo.getLog().error( "COMPILATION ERROR : " ); + mojo.getLog().info( "-------------------------------------------------------------" ); + others.forEach( error -> mojo.getLog().error( error.toString() ) ); + mojo.getLog().info( errors.size() + ( ( errors.size() > 1 ) ? " errors " : " error" ) ); + mojo.getLog().info( "-------------------------------------------------------------" ); + } + + throw new IllegalStateException( ( !errors.isEmpty() ? errors : warnings ).stream() + .map( CompilerMessage::toString ) + .collect( joining( "\n" ) ) ); + } + else + { + result.getCompilerMessages().forEach( message -> + { + switch ( message.getKind() ) + { + case NOTE: + case OTHER: + mojo.getLog().info( message.toString() ); + break; + + case ERROR: + mojo.getLog().error( message.toString() ); + break; + + case MANDATORY_WARNING: + case WARNING: + default: + mojo.getLog().warn( message.toString() ); + break; + } + } ); + } + } +} diff --git a/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineFactory.java b/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineFactory.java new file mode 100644 index 0000000..8a28443 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineFactory.java @@ -0,0 +1,133 @@ +package org.apache.maven.plugins.scripting.engine; + +/* + * 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. + */ + +import org.apache.maven.plugins.scripting.EvalMojo; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * Maven-java script engine factory. + */ +public class JavaScriptEngineFactory implements ScriptEngineFactory +{ + private final EvalMojo mojo; + + public JavaScriptEngineFactory( final EvalMojo mojo ) + { + this.mojo = mojo; + } + + EvalMojo getMojo() + { + return mojo; + } + + @Override + public String getEngineName() + { + return "maven"; + } + + @Override + public String getEngineVersion() + { + return "1.0.0"; + } + + @Override + public List<String> getExtensions() + { + return asList( "java", "maven" ); + } + + @Override + public List<String> getMimeTypes() + { + return asList( "text/java", "application/java" ); + } + + @Override + public List<String> getNames() + { + return asList( getClass().getSimpleName(), "MavenJava", "Java", "java", "maven" ); + } + + @Override + public String getLanguageName() + { + return "Java"; + } + + @Override + public String getLanguageVersion() + { + return System.getProperty( "java.version" ); + } + + @Override + public Object getParameter( final String key ) + { + switch ( key ) + { + case "javax.script.engine_version": + return getEngineVersion(); + case "javax.script.engine": + return getEngineName(); + case "javax.script.language": + case "javax.script.name": + return getLanguageName(); + case "javax.script.language_version": + return getLanguageVersion(); + case "THREADING": + default: + return null; + } + } + + @Override + public String getMethodCallSyntax( final String obj, final String method, + final String... args ) + { + return obj + '.' + method + '(' + String.join( ", ", args ) + ");"; + } + + @Override + public String getOutputStatement( final String toDisplay ) + { + return "System.out.println(" + toDisplay + ");"; + } + + @Override + public String getProgram( final String... statements ) + { + return String.join( ";\n", statements ); + } + + @Override + public ScriptEngine getScriptEngine() + { + return new JavaScriptEngine( this ); + } +} diff --git a/src/test/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineTest.java b/src/test/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineTest.java new file mode 100644 index 0000000..1389fa5 --- /dev/null +++ b/src/test/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineTest.java @@ -0,0 +1,99 @@ +package org.apache.maven.plugins.scripting.engine; + +/* + * 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. + */ + +import org.apache.maven.execution.DefaultMavenExecutionRequest; +import org.apache.maven.execution.MavenExecutionResult; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Build; +import org.apache.maven.plugins.scripting.EvalMojo; +import org.apache.maven.project.MavenProject; +import org.apache.maven.toolchain.DefaultToolchainManager; +import org.codehaus.plexus.compiler.Compiler; +import org.codehaus.plexus.compiler.javac.JavacCompiler; +import org.codehaus.plexus.compiler.manager.DefaultCompilerManager; +import org.codehaus.plexus.compiler.manager.NoSuchCompilerException; +import org.junit.Test; + +import javax.script.SimpleScriptContext; +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class JavaScriptEngineTest { + @Test + public void execute() { + System.clearProperty("JavaScriptEngineTest.execute"); + + final Map<String, String> config = new HashMap<>(); + config.put("compileSourceRoot", ""); + config.put("main", "Run"); + config.put("arg.01", "JavaScriptEngineTest.execute"); + + new JavaScriptEngine(new JavaScriptEngineFactory(new EvalMojo() + { + { + compilerManager = new DefaultCompilerManager() + { + @Override + public Compiler getCompiler(final String compilerId) + { + return new JavacCompiler(); + } + }; + toolchainManager = new DefaultToolchainManager(); + project = new MavenProject() + { + { + setBuild(new Build() + { + { + setDirectory("target/test-work/JavaScriptEngineTest-execute"); + } + }); + } + + @Override + public File getBasedir() + { + return new File("target/test-work"); + } + }; + session = new MavenSession(null, null, new DefaultMavenExecutionRequest(), (MavenExecutionResult) null); + } + + @Override + public Map<String, String> getScriptEngineConfiguration() + { + return config; + } + })).eval("" + + "public class Run {\n" + + " public static void main(String... args) {\n" + + " System.setProperty(args[0], \"true\");\n" + + " }\n" + + "}\n" + + "\n", new SimpleScriptContext()); + assertEquals("true", System.getProperty("JavaScriptEngineTest.execute")); + System.clearProperty("JavaScriptEngineTest.execute"); + } +}