@@ -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 | ||||