| @@ -15,6 +15,11 @@ Fixed bugs: | |||||
| root. | root. | ||||
| Bugzilla Report 62502 | 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 | Changes from Ant 1.10.3 TO Ant 1.10.4 | ||||
| ===================================== | ===================================== | ||||
| @@ -50,7 +50,7 @@ because it tries to read from the standard input.</p> | |||||
| <tr id="classname"> | <tr id="classname"> | ||||
| <td>classname</td> | <td>classname</td> | ||||
| <td>the Java class to execute.</td> | <td>the Java class to execute.</td> | ||||
| <td rowspan="3">Exactly one of the three</td> | |||||
| <td rowspan="4">Exactly one of the four</td> | |||||
| </tr> | </tr> | ||||
| <tr> | <tr> | ||||
| <td>jar</td> | <td>jar</td> | ||||
| @@ -65,6 +65,13 @@ because it tries to read from the standard input.</p> | |||||
| entry in the manifest). <var>fork</var> must be set to <q>true</q> if this option is | entry in the manifest). <var>fork</var> must be set to <q>true</q> if this option is | ||||
| selected. <em>since Ant 1.9.7</em></td> | selected. <em>since Ant 1.9.7</em></td> | ||||
| </tr> | </tr> | ||||
| <tr> | |||||
| <td>sourcefile</td> | |||||
| <td class="left">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. | |||||
| <var>fork</var> must be set to <q>true</q> if this option is selected. | |||||
| <em>since Ant 1.10.5</em></td> | |||||
| </tr> | |||||
| <tr> | <tr> | ||||
| <td>args</td> | <td>args</td> | ||||
| <td>the arguments for the class that is executed. <em><u>Deprecated</u>, use | <td>the arguments for the class that is executed. <em><u>Deprecated</u>, use | ||||
| @@ -417,4 +417,68 @@ redirect.err="${redirect.err}" should be empty</fail> | |||||
| <target name="foo"/> | <target name="foo"/> | ||||
| <target name="simpleSourceFile" | |||||
| description="Tests that the single source file programs, introduced in Java 11, works as expected"> | |||||
| <mkdir dir="${output}/javasource"/> | |||||
| <echo file="/tmp/foo.txt" message="${output}"/> | |||||
| <echo file="${output}/javasource/A.java"> | |||||
| 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!"); | |||||
| } | |||||
| } | |||||
| } | |||||
| </echo> | |||||
| <java sourcefile="${output}/javasource/A.java" fork="true" failonerror="true" logerror="true"> | |||||
| <arg value="${output}/javasource/simpleSourceFileOutput.txt"/> | |||||
| </java> | |||||
| <loadfile property="simpleSourceFile.prog.output" srcfile="${output}/javasource/simpleSourceFileOutput.txt"/> | |||||
| <condition property="simpleSourceFile.execution.success"> | |||||
| <equals arg1="${simpleSourceFile.prog.output}" arg2="Hello world!"/> | |||||
| </condition> | |||||
| <fail unless="simpleSourceFile.execution.success">Java source-file execution did not yield the expected | |||||
| result</fail> | |||||
| </target> | |||||
| <target name="generateDummyJavaSource"> | |||||
| <mkdir dir="${output}/javasource"/> | |||||
| <echo file="/tmp/foo.txt" message="${output}"/> | |||||
| <echo file="${output}/javasource/ThrowsException.java"> | |||||
| public class ThrowsException { | |||||
| public static void main(String[] args) throws Exception { | |||||
| throw new RuntimeException("Wasn't expected to be run"); | |||||
| } | |||||
| } | |||||
| </echo> | |||||
| </target> | |||||
| <target name="sourceFileRequiresFork" depends="generateDummyJavaSource"> | |||||
| <java sourcefile="${output}/javasource/ThrowsException.java" failonerror="true" logerror="true"/> | |||||
| <fail>Execution of java task, for sourcefile, was expected to fail since fork wasn't set</fail> | |||||
| </target> | |||||
| <target name="sourceFileCantUseClassname" depends="generateDummyJavaSource"> | |||||
| <java classname="foo.bar" sourcefile="${output}/javasource/ThrowsException.java" | |||||
| fork="true" failonerror="true" logerror="true"/> | |||||
| <fail>Execution of java task, for sourcefile, was expected to fail since classname attribute was set</fail> | |||||
| </target> | |||||
| <target name="sourceFileCantUseJar" depends="generateDummyJavaSource"> | |||||
| <java jar="irrelevant.jar" sourcefile="${output}/javasource/ThrowsException.java" | |||||
| fork="true" failonerror="true" logerror="true"/> | |||||
| <fail>Execution of java task, for sourcefile, was expected to fail since jar attribute was set</fail> | |||||
| </target> | |||||
| <target name="sourceFileCantUseModule" depends="generateDummyJavaSource"> | |||||
| <java module="irrelevant" sourcefile="${output}/javasource/ThrowsException.java" | |||||
| fork="true" failonerror="true" logerror="true"/> | |||||
| <fail>Execution of java task, for sourcefile, was expected to fail since module attribute was set</fail> | |||||
| </target> | |||||
| </project> | </project> | ||||
| @@ -142,7 +142,8 @@ public class Java extends Task { | |||||
| protected void checkConfiguration() throws BuildException { | protected void checkConfiguration() throws BuildException { | ||||
| String classname = getCommandLine().getClassname(); | String classname = getCommandLine().getClassname(); | ||||
| String module = getCommandLine().getModule(); | 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."); | throw new BuildException("Classname must not be null."); | ||||
| } | } | ||||
| if (!fork && getCommandLine().getJar() != null) { | if (!fork && getCommandLine().getJar() != null) { | ||||
| @@ -153,6 +154,9 @@ public class Java extends Task { | |||||
| throw new BuildException( | throw new BuildException( | ||||
| "Cannot execute a module in non-forked mode. Please set fork='true'. "); | "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) { | if (spawn && !fork) { | ||||
| throw new BuildException( | throw new BuildException( | ||||
| "Cannot spawn a java process in non-forked mode. Please set fork='true'. "); | "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. | * @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 { | 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( | 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()); | getCommandLine().setJar(jarfile.getAbsolutePath()); | ||||
| } | } | ||||
| @@ -370,12 +376,12 @@ public class Java extends Task { | |||||
| * | * | ||||
| * @param s the name of the main class. | * @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 { | public void setClassname(String s) throws BuildException { | ||||
| if (getCommandLine().getJar() != null) { | |||||
| if (getCommandLine().getJar() != null || getCommandLine().getSourceFile() != null) { | |||||
| throw new BuildException( | 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); | getCommandLine().setClassname(s); | ||||
| } | } | ||||
| @@ -385,17 +391,37 @@ public class Java extends Task { | |||||
| * | * | ||||
| * @param module the name of the module. | * @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 | * @since 1.9.7 | ||||
| */ | */ | ||||
| public void setModule(String module) throws BuildException { | public void setModule(String module) throws BuildException { | ||||
| if (getCommandLine().getJar() != null) { | |||||
| if (getCommandLine().getJar() != null || getCommandLine().getSourceFile() != null) { | |||||
| throw new BuildException( | 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); | 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. | * Deprecated: use nested arg instead. | ||||
| * Set the command line arguments for the class. | * 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. | * @param command String[] of command-line arguments. | ||||
| */ | */ | ||||
| private int fork(String[] command) throws BuildException { | private int fork(String[] command) throws BuildException { | ||||
| @@ -1018,4 +1044,6 @@ public class Java extends Task { | |||||
| public CommandlineJava.SysProperties getSysProperties() { | public CommandlineJava.SysProperties getSysProperties() { | ||||
| return getCommandLine().getSystemProperties(); | return getCommandLine().getSystemProperties(); | ||||
| } | } | ||||
| } | } | ||||
| @@ -368,6 +368,15 @@ public class CommandlineJava implements Cloneable { | |||||
| return null; | 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. | * Set the module to execute. | ||||
| * @param module the module name. | * @param module the module name. | ||||
| @@ -534,9 +543,11 @@ public class CommandlineJava implements Cloneable { | |||||
| } else if (executableType == ExecutableType.MODULE) { | } else if (executableType == ExecutableType.MODULE) { | ||||
| listIterator.add("-m"); | 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.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.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); | javaCommand.addCommandToList(listIterator); | ||||
| } | } | ||||
| @@ -887,6 +898,11 @@ public class CommandlineJava implements Cloneable { | |||||
| /** | /** | ||||
| * Module execution. | * Module execution. | ||||
| */ | */ | ||||
| MODULE | |||||
| MODULE, | |||||
| /** | |||||
| * Source file (introduced in Java 11) | |||||
| */ | |||||
| SOURCE_FILE, | |||||
| } | } | ||||
| } | } | ||||
| @@ -32,8 +32,10 @@ import java.io.PipedOutputStream; | |||||
| import org.apache.tools.ant.BuildException; | import org.apache.tools.ant.BuildException; | ||||
| import org.apache.tools.ant.BuildFileRule; | import org.apache.tools.ant.BuildFileRule; | ||||
| import org.apache.tools.ant.input.DefaultInputHandler; | 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.FileUtils; | ||||
| import org.apache.tools.ant.util.TeeOutputStream; | import org.apache.tools.ant.util.TeeOutputStream; | ||||
| import org.junit.Assume; | |||||
| import org.junit.AssumptionViolatedException; | import org.junit.AssumptionViolatedException; | ||||
| import org.junit.Before; | import org.junit.Before; | ||||
| import org.junit.Rule; | import org.junit.Rule; | ||||
| @@ -106,28 +108,28 @@ public class JavaTest { | |||||
| @Test | @Test | ||||
| public void testJarAndClassName() { | public void testJarAndClassName() { | ||||
| thrown.expect(BuildException.class); | thrown.expect(BuildException.class); | ||||
| thrown.expectMessage("Cannot use 'jar' and 'classname' attributes in same command"); | |||||
| thrown.expectMessage("Cannot use combination of "); | |||||
| buildRule.executeTarget("testJarAndClassName"); | buildRule.executeTarget("testJarAndClassName"); | ||||
| } | } | ||||
| @Test | @Test | ||||
| public void testClassnameAndJar() { | public void testClassnameAndJar() { | ||||
| thrown.expect(BuildException.class); | 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"); | buildRule.executeTarget("testClassnameAndJar"); | ||||
| } | } | ||||
| @Test | @Test | ||||
| public void testJarAndModule() { | public void testJarAndModule() { | ||||
| thrown.expect(BuildException.class); | thrown.expect(BuildException.class); | ||||
| thrown.expectMessage("Cannot use 'jar' and 'module' attributes in same command"); | |||||
| thrown.expectMessage("Cannot use combination of "); | |||||
| buildRule.executeTarget("testJarAndModule"); | buildRule.executeTarget("testJarAndModule"); | ||||
| } | } | ||||
| @Test | @Test | ||||
| public void testModuleAndJar() { | public void testModuleAndJar() { | ||||
| thrown.expect(BuildException.class); | 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"); | buildRule.executeTarget("testModuleAndJar"); | ||||
| } | } | ||||
| @@ -410,6 +412,78 @@ public class JavaTest { | |||||
| buildRule.executeTarget("flushedInput"); | 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 | * entry point class with no dependencies other | ||||
| * than normal JRE runtime | * than normal JRE runtime | ||||