diff --git a/WHATSNEW b/WHATSNEW index 5233a0a09..1523a8c40 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -15,6 +15,11 @@ Fixed bugs: root. Bugzilla Report 62502 +Other changes: +-------------- + * Java task now accepts a "sourcefile" attribute to allow single file + source program execution, a feature that is introduced in Java 11. + Changes from Ant 1.10.3 TO Ant 1.10.4 ===================================== diff --git a/manual/Tasks/java.html b/manual/Tasks/java.html index f6449886b..849f594ec 100644 --- a/manual/Tasks/java.html +++ b/manual/Tasks/java.html @@ -50,7 +50,7 @@ because it tries to read from the standard input.

classname the Java class to execute. - Exactly one of the three + Exactly one of the four jar @@ -65,6 +65,13 @@ because it tries to read from the standard input.

entry in the manifest). fork must be set to true if this option is selected. since Ant 1.9.7 + + sourcefile + The location of a ".java" file or a file containing shebang with Java source code. + Set this attribute to run Java single file source programs, a feature introduced in Java 11. + fork must be set to true if this option is selected. + since Ant 1.10.5 + args the arguments for the class that is executed. Deprecated, use diff --git a/src/etc/testcases/taskdefs/java.xml b/src/etc/testcases/taskdefs/java.xml index 022a10f33..720959a38 100644 --- a/src/etc/testcases/taskdefs/java.xml +++ b/src/etc/testcases/taskdefs/java.xml @@ -417,4 +417,68 @@ redirect.err="${redirect.err}" should be empty + + + + + import java.nio.file.Files; + import java.nio.file.Paths; + import java.io.BufferedWriter; + public class A { + public static void main(String[] args) throws Exception { + final String outFile = args[0]; + try(BufferedWriter bw = Files.newBufferedWriter(Paths.get(outFile));) { + bw.write("Hello world!"); + } + } + } + + + + + + + + + Java source-file execution did not yield the expected + result + + + + + + + + public class ThrowsException { + public static void main(String[] args) throws Exception { + throw new RuntimeException("Wasn't expected to be run"); + } + } + + + + + + Execution of java task, for sourcefile, was expected to fail since fork wasn't set + + + + + Execution of java task, for sourcefile, was expected to fail since classname attribute was set + + + + + Execution of java task, for sourcefile, was expected to fail since jar attribute was set + + + + + Execution of java task, for sourcefile, was expected to fail since module attribute was set + + diff --git a/src/main/org/apache/tools/ant/taskdefs/Java.java b/src/main/org/apache/tools/ant/taskdefs/Java.java index 5d5a99d87..6e3e54d74 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Java.java +++ b/src/main/org/apache/tools/ant/taskdefs/Java.java @@ -142,7 +142,8 @@ public class Java extends Task { protected void checkConfiguration() throws BuildException { String classname = getCommandLine().getClassname(); String module = getCommandLine().getModule(); - if (classname == null && getCommandLine().getJar() == null && module == null) { + final String sourceFile = getCommandLine().getSourceFile(); + if (classname == null && getCommandLine().getJar() == null && module == null && sourceFile == null) { throw new BuildException("Classname must not be null."); } if (!fork && getCommandLine().getJar() != null) { @@ -153,6 +154,9 @@ public class Java extends Task { throw new BuildException( "Cannot execute a module in non-forked mode. Please set fork='true'. "); } + if (!fork && sourceFile != null) { + throw new BuildException("Cannot execute sourcefile in non-forked mode. Please set fork='true'"); + } if (spawn && !fork) { throw new BuildException( "Cannot spawn a java process in non-forked mode. Please set fork='true'. "); @@ -355,12 +359,14 @@ public class Java extends Task { * * @param jarfile the jarfile to execute. * - * @throws BuildException if there is also a main class specified. + * @throws BuildException if there is also a {@code classname}, {@code module} + * or {@code sourcefile} attribute specified */ public void setJar(File jarfile) throws BuildException { - if (getCommandLine().getClassname() != null || getCommandLine().getModule() != null) { + if (getCommandLine().getClassname() != null || getCommandLine().getModule() != null + || getCommandLine().getSourceFile() != null) { throw new BuildException( - "Cannot use 'jar' with 'classname' or 'module' attributes in same command."); + "Cannot use combination of 'jar', 'sourcefile', 'classname', 'module' attributes in same command"); } getCommandLine().setJar(jarfile.getAbsolutePath()); } @@ -370,12 +376,12 @@ public class Java extends Task { * * @param s the name of the main class. * - * @throws BuildException if the jar attribute has been set. + * @throws BuildException if there is also a {@code jar} or {@code sourcefile} attribute specified */ public void setClassname(String s) throws BuildException { - if (getCommandLine().getJar() != null) { + if (getCommandLine().getJar() != null || getCommandLine().getSourceFile() != null) { throw new BuildException( - "Cannot use 'jar' and 'classname' attributes in same command"); + "Cannot use combination of 'jar', 'classname', sourcefile attributes in same command"); } getCommandLine().setClassname(s); } @@ -385,17 +391,37 @@ public class Java extends Task { * * @param module the name of the module. * - * @throws BuildException if the jar attribute has been set. + * @throws BuildException if there is also a {@code jar} or {@code sourcefile} attribute specified * @since 1.9.7 */ public void setModule(String module) throws BuildException { - if (getCommandLine().getJar() != null) { + if (getCommandLine().getJar() != null || getCommandLine().getSourceFile() != null) { throw new BuildException( - "Cannot use 'jar' and 'module' attributes in same command"); + "Cannot use combination of 'jar', 'module', sourcefile attributes in same command"); } getCommandLine().setModule(module); } + /** + * Set the Java source-file to execute. Support for single file source program + * execution, in Java, is only available since Java 11. + * + * @param sourceFile The path to the source file + * @throws BuildException if there is also a {@code jar}, {@code classname} + * or {@code module} attribute specified + * @since Ant 1.10.5 + */ + public void setSourceFile(final String sourceFile) throws BuildException { + final String jar = getCommandLine().getJar(); + final String className = getCommandLine().getClassname(); + final String module = getCommandLine().getModule(); + if (jar != null || className != null || module != null) { + throw new BuildException("Cannot use 'sourcefile' in combination with 'jar' or " + + "'module' or 'classname'"); + } + getCommandLine().setSourceFile(sourceFile); + } + /** * Deprecated: use nested arg instead. * Set the command line arguments for the class. @@ -843,7 +869,7 @@ public class Java extends Task { } /** - * Executes the given classname with the given arguments in a separate VM. + * Executes the given source-file or classname with the given arguments in a separate VM. * @param command String[] of command-line arguments. */ private int fork(String[] command) throws BuildException { @@ -1018,4 +1044,6 @@ public class Java extends Task { public CommandlineJava.SysProperties getSysProperties() { return getCommandLine().getSystemProperties(); } + + } diff --git a/src/main/org/apache/tools/ant/types/CommandlineJava.java b/src/main/org/apache/tools/ant/types/CommandlineJava.java index fd6588000..1ee821aca 100644 --- a/src/main/org/apache/tools/ant/types/CommandlineJava.java +++ b/src/main/org/apache/tools/ant/types/CommandlineJava.java @@ -368,6 +368,15 @@ public class CommandlineJava implements Cloneable { return null; } + public void setSourceFile(final String sourceFile) { + this.executableType = ExecutableType.SOURCE_FILE; + javaCommand.setExecutable(sourceFile); + } + + public String getSourceFile() { + return this.executableType == ExecutableType.SOURCE_FILE ? this.javaCommand.getExecutable() : null; + } + /** * Set the module to execute. * @param module the module name. @@ -534,9 +543,11 @@ public class CommandlineJava implements Cloneable { } else if (executableType == ExecutableType.MODULE) { listIterator.add("-m"); } - // this is the classname to run as well as its arguments. + // this is the classname/source-file to run as well as its arguments. // in case of ExecutableType.JAR, the executable is a jar file, // in case of ExecutableType.MODULE, the executable is a module name, potentially including a class name. + // in case of ExecutableType.SOURCE_FILE, the executable is a Java source file (ending in .java) or a shebang + // file containing Java source javaCommand.addCommandToList(listIterator); } @@ -887,6 +898,11 @@ public class CommandlineJava implements Cloneable { /** * Module execution. */ - MODULE + MODULE, + + /** + * Source file (introduced in Java 11) + */ + SOURCE_FILE, } } diff --git a/src/tests/junit/org/apache/tools/ant/taskdefs/JavaTest.java b/src/tests/junit/org/apache/tools/ant/taskdefs/JavaTest.java index 06d6b904c..2630a81a2 100644 --- a/src/tests/junit/org/apache/tools/ant/taskdefs/JavaTest.java +++ b/src/tests/junit/org/apache/tools/ant/taskdefs/JavaTest.java @@ -32,8 +32,10 @@ import java.io.PipedOutputStream; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.BuildFileRule; import org.apache.tools.ant.input.DefaultInputHandler; +import org.apache.tools.ant.taskdefs.condition.JavaVersion; import org.apache.tools.ant.util.FileUtils; import org.apache.tools.ant.util.TeeOutputStream; +import org.junit.Assume; import org.junit.AssumptionViolatedException; import org.junit.Before; import org.junit.Rule; @@ -106,28 +108,28 @@ public class JavaTest { @Test public void testJarAndClassName() { thrown.expect(BuildException.class); - thrown.expectMessage("Cannot use 'jar' and 'classname' attributes in same command"); + thrown.expectMessage("Cannot use combination of "); buildRule.executeTarget("testJarAndClassName"); } @Test public void testClassnameAndJar() { thrown.expect(BuildException.class); - thrown.expectMessage("Cannot use 'jar' with 'classname' or 'module' attributes in same command."); + thrown.expectMessage("Cannot use combination of "); buildRule.executeTarget("testClassnameAndJar"); } @Test public void testJarAndModule() { thrown.expect(BuildException.class); - thrown.expectMessage("Cannot use 'jar' and 'module' attributes in same command"); + thrown.expectMessage("Cannot use combination of "); buildRule.executeTarget("testJarAndModule"); } @Test public void testModuleAndJar() { thrown.expect(BuildException.class); - thrown.expectMessage("Cannot use 'jar' with 'classname' or 'module' attributes in same command."); + thrown.expectMessage("Cannot use combination of "); buildRule.executeTarget("testModuleAndJar"); } @@ -410,6 +412,78 @@ public class JavaTest { buildRule.executeTarget("flushedInput"); } + /** + * Test that the Java single file source program feature introduced in Java 11 works fine + * + * @throws Exception + */ + @Test + public void testSimpleSourceFile() throws Exception { + requireJava11(); + buildRule.executeTarget("simpleSourceFile"); + } + + /** + * Test that the sourcefile option of the Java task can only be run when fork attribute is set + * + * @throws Exception + */ + @Test + public void testSourceFileRequiresFork() throws Exception { + requireJava11(); + thrown.expect(BuildException.class); + thrown.expectMessage("Cannot execute sourcefile in non-forked mode. Please set fork='true'"); + buildRule.executeTarget("sourceFileRequiresFork"); + } + + /** + * Tests that the sourcefile attribute and the classname attribute of the Java task cannot be used + * together + * + * @throws Exception + */ + @Test + public void testSourceFileCantUseClassname() throws Exception { + requireJava11(); + thrown.expect(BuildException.class); + thrown.expectMessage("Cannot use 'sourcefile' in combination with"); + buildRule.executeTarget("sourceFileCantUseClassname"); + } + + /** + * Tests that the sourcefile attribute and the jar attribute of the Java task cannot be used + * together + * + * @throws Exception + */ + @Test + public void testSourceFileCantUseJar() throws Exception { + requireJava11(); + thrown.expect(BuildException.class); + thrown.expectMessage("Cannot use 'sourcefile' in combination with"); + buildRule.executeTarget("sourceFileCantUseJar"); + } + + /** + * Tests that the sourcefile attribute and the module attribute of the Java task cannot be used + * together + * + * @throws Exception + */ + @Test + public void testSourceFileCantUseModule() throws Exception { + requireJava11(); + thrown.expect(BuildException.class); + thrown.expectMessage("Cannot use 'sourcefile' in combination with"); + buildRule.executeTarget("sourceFileCantUseModule"); + } + + private static void requireJava11() { + final JavaVersion javaVersion = new JavaVersion(); + javaVersion.setAtLeast("11"); + Assume.assumeTrue("Skipping test which requires a minimum of Java 11 runtime", javaVersion.eval()); + } + /** * entry point class with no dependencies other * than normal JRE runtime