@@ -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 | |||
===================================== | |||
@@ -50,7 +50,7 @@ because it tries to read from the standard input.</p> | |||
<tr id="classname"> | |||
<td>classname</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> | |||
<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 | |||
selected. <em>since Ant 1.9.7</em></td> | |||
</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> | |||
<td>args</td> | |||
<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="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> |
@@ -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(); | |||
} | |||
} |
@@ -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, | |||
} | |||
} |
@@ -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 | |||