Browse Source

Added tasks for JDK's jmod and jlink tools.

master
VGR Stefan Bodewig 6 years ago
parent
commit
343dff90f2
17 changed files with 8374 additions and 7 deletions
  1. +6
    -0
      build.xml
  2. +6
    -1
      manual/Tasks/jlink.html
  3. +330
    -0
      manual/Tasks/jmod.html
  4. +579
    -0
      manual/Tasks/link.html
  5. +2
    -0
      manual/tasklist.html
  6. +4
    -4
      src/etc/testcases/taskdefs/jar.xml
  7. +992
    -0
      src/etc/testcases/taskdefs/jmod.xml
  8. +1088
    -0
      src/etc/testcases/taskdefs/link.xml
  9. +2
    -0
      src/main/org/apache/tools/ant/taskdefs/defaults.properties
  10. +1282
    -0
      src/main/org/apache/tools/ant/taskdefs/modules/Jmod.java
  11. +2120
    -0
      src/main/org/apache/tools/ant/taskdefs/modules/Link.java
  12. +23
    -0
      src/main/org/apache/tools/ant/taskdefs/modules/package-info.java
  13. +4
    -2
      src/main/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java
  14. +147
    -0
      src/main/org/apache/tools/ant/types/ModuleVersion.java
  15. +690
    -0
      src/tests/junit/org/apache/tools/ant/taskdefs/modules/JmodTest.java
  16. +984
    -0
      src/tests/junit/org/apache/tools/ant/taskdefs/modules/LinkTest.java
  17. +115
    -0
      src/tests/junit/org/apache/tools/ant/types/ModuleVersionTest.java

+ 6
- 0
build.xml View File

@@ -42,6 +42,7 @@
<property name="ant.package" value="org/apache/tools/ant"/>
<property name="taskdefs.package" value="${ant.package}/taskdefs"/>
<property name="condition.package" value="${taskdefs.package}/condition"/>
<property name="modules.package" value="${taskdefs.package}/modules"/>
<property name="optional.package" value="${taskdefs.package}/optional"/>
<property name="type.package" value="${ant.package}/types"/>
<property name="optional.type.package" value="${type.package}/optional"/>
@@ -178,6 +179,10 @@
===================================================================
-->

<selector id="needs.jdk9+">
<filename name="${modules.package}/"/>
</selector>

<!-- Kaffe has some JDK 1.5 features including java.lang.Readable,
but not all of them -->
<selector id="not.in.kaffe">
@@ -609,6 +614,7 @@
<selector id="conditional-patterns">
<not>
<or>
<selector refid="needs.jdk9+" unless="jdk9+"/>
<selector refid="not.in.kaffe" if="kaffe"/>
<selector refid="needs.apache-resolver" unless="apache.resolver.present"/>
<selector refid="needs.junit" unless="junit.present"/> <!-- TODO should perhaps use -source 1.4? -->


+ 6
- 1
manual/Tasks/jlink.html View File

@@ -26,9 +26,14 @@
<p><em>This task has been <u>deprecated</u>. Use a <a href="../Types/zipfileset.html">zipfileset</a>
or <a href="../Tasks/zip.html#zipgroupfileset">zipgroupfileset</a> with
the <a href="../Tasks/jar.html">Jar task</a> or <a href="../Tasks/zip.html">Zip task</a>
instead.</em></p>
instead. For a task based on the JDK's jlink tool, see
<a href="link.html">Link</a>.</em></p>

<h3>Description</h3>
<p><strong>For a task based on the JDK's jlink tool, see
<a href="link.html">Link</a>. This task is for something else
entirely.</strong></p>

<p>Links entries from sub-builds and libraries.</p>

<p>The <code>jlink</code> task can be used to build jar and zip files, similar to


+ 330
- 0
manual/Tasks/jmod.html View File

@@ -0,0 +1,330 @@
<!--
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.
-->
<html>

<head>
<meta http-equiv="Content-Language" content="en-us">
<link rel="stylesheet" type="text/css" href="../stylesheets/style.css">
<title>Jmod Task</title>
</head>

<body>

<h2 id="jmod">Jmod</h2>
<h3>Description</h3>
<p>Creates a linkable jmod file from a modular jar file, and optionally from
other application files such as native libraries and license documents.
Equivalent to the JDK's
<a href="https://docs.oracle.com/en/java/javase/11/tools/jmod.html">jmod</a>
tool.</p>
<p>Requires Java 9 or later.</p>

<h3>Parameters</h3>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>destFile</td>
<td>jmod file to create.</td>
<td>Yes</td>
</tr>
<tr>
<td>classpath</td>
<td>Files to be placed in the jmod file. Usually a single module.</td>
<td rowspan="2">One of these is required, unless a nested
<code>&lt;classpath&gt;</code> is present.</td>
</tr>
<tr>
<td>classpathref</td>
<td class="left">Files to be placed in the jmod file, given as a
<a href="../using.html#references">reference</a>
to a path defined elsewhere.</td>
</tr>
<tr>
<td>modulepath</td>
<td>Locations of modules on which classpath modules depend.</td>
<td>No</td>
</tr>
<tr>
<td>modulepathref</td>
<td>Locations of modules on which classpath modules depend,
given as a <a href="../using.html#references">reference</a>
to a path defined elsewhere.</td>
<td>No</td>
</tr>
<tr>
<td>commandpath</td>
<td>Directories containing native commands to include in jmod.</td>
<td>No</td>
</tr>
<tr>
<td>commandpathref</td>
<td>Directories containing native commands to include in jmod,
given as a <a href="../using.html#references">reference</a>
to a path defined elsewhere.</td>
<td>No</td>
</tr>
<tr>
<td>headerpath</td>
<td>Directories containing header files to include in jmod.</td>
<td>No</td>
</tr>
<tr>
<td>headerpathref</td>
<td>Directories containing header files to include in jmod,
given as a <a href="../using.html#references">reference</a>
to a path defined elsewhere.</td>
<td>No</td>
</tr>
<tr>
<td>configpath</td>
<td>Directories containing user-editable configuration files
to include in jmod.</td>
<td>No</td>
</tr>
<tr>
<td>configpathref</td>
<td>Directories containing user-editable configuration files
to include in jmod,
given as a <a href="../using.html#references">reference</a>
to a path defined elsewhere.</td>
<td>No</td>
</tr>
<tr>
<td>legalpath</td>
<td>Directories containing legal licenses and notices to include in jmod.</td>
<td>No</td>
</tr>
<tr>
<td>legalpathref</td>
<td>Directories containing legal licenses and notices to include in jmod,
given as a <a href="../using.html#references">reference</a>
to a path defined elsewhere.</td>
<td>No</td>
</tr>
<tr>
<td>nativelibpath</td>
<td>Directories containing native libraries to include in jmod.</td>
<td>No</td>
</tr>
<tr>
<td>nativelibpathref</td>
<td>Directories containing native libraries to include in jmod,
given as a <a href="../using.html#references">reference</a>
to a path defined elsewhere.</td>
<td>No</td>
</tr>
<tr>
<td>manpath</td>
<td>Directories containing man pages to include in jmod.</td>
<td>No</td>
</tr>
<tr>
<td>manpathref</td>
<td>Directories containing man pages to include in jmod,
given as a <a href="../using.html#references">reference</a>
to a path defined elsewhere.</td>
<td>No</td>
</tr>
<tr>
<td>version</td>
<td><a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/module/ModuleDescriptor.Version.html">Module version</a> of jmod.</td>
<td>No</td>
</tr>
<tr>
<td>mainclass</td>
<td>Class that acts as executable entry point of module.</td>
<td>No</td>
</tr>
<tr>
<td>platform</td>
<td>The target platform for the jmod. Typically takes the form
<var>OS</var><code>-</code><var>architecture</var>. A particular JDK's
platform can be seen by running a command like
<samp>jmod describe $JDK_HOME/jmods/java.base.jmod | grep -i platform</samp></td>
<td>No</td>
</tr>
<tr>
<td>hashModulesPattern</td>
<td>Regular expression for names of modules in the module path
which depend on the jmod being created, and which should have
hashes generated for them and included in the new jmod.</td>
<td>No</td>
</tr>
<tr>
<td>resolveByDefault</td>
<td>Boolean indicating whether the jmod should be one of
the default resolved modules when it is in a module path
searched by tools and applications.</td>
<td>No. Default is true.</td>
</tr>
<tr>
<td>moduleWarnings</td>
<td>Whether to emit warnings when resolving modules which are
not recommended for use. Comma-separated list of one of more of
the following:
<dl>
<dt><code>deprecated</code></dt>
<dd>Warn if module is deprecated</dd>
<dt><code>leaving</code></dt>
<dd>Warn if module is deprecated for removal</dd>
<dt><code>incubating</code></dt>
<dd>Warn if module is an incubating (not yet official) module</dd>
</dl>
</td>
<td>No, default is no warnings.</td>
</tr>
</table>

<h3>Parameters specified as nested elements</h3>
<h4>classpath, modulepath, commandpath, headerpath, configpath, legalpath, nativelibpath, manpath</h4>
<p>The
<code>classpath</code>,
<code>modulepath</code>,
<code>commandpath</code>,
<code>headerpath</code>,
<code>configpath</code>,
<code>legalpath</code>,
<code>nativelibpath</code>, and
<code>manpath</code>
attributes are <a href="../using.html#path">path-like structures</a>
and can also be set via nested
<code>&lt;classpath&gt;</code>,
<code>&lt;modulepath&gt;</code>,
<code>&lt;commandpath&gt;</code>,
<code>&lt;headerpath&gt;</code>,
<code>&lt;configpath&gt;</code>,
<code>&lt;legalpath&gt;</code>,
<code>&lt;nativelibpath&gt;</code>, and
<code>&lt;manpath&gt;</code>
elements, respectively.</p>

<h4>version</h4>
<p>Fine-grained alternative to the <code>version</code> attribute. This
nested element has these attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>number</td>
<td>Primary version number. Can be any text, as long as it does not
contain a hyphen (<code>-</code>) or plus (<code>+</code>).</td>
<td>Yes</td>
</tr>
<tr>
<td>preRelease</td>
<td>Pre-release version. Can be any text, as long as it does not
contain a plus (<code>+</code>).</td>
<td>No</td>
</tr>
<tr>
<td>build</td>
<td>Build version. Can be any text.
<td>No</td>
</tr>
</table>

<p>See the <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/module/ModuleDescriptor.Version.html">ModuleDescriptor.Version documentation</a>
for a full description of the meaning of each version component.</p>

<h4>moduleWarning</h4>
<p>Like the <code>moduleWarnings</code> attribute, but only specifies a single
basis for emitting warnings. This child element may appear multiple times,
to specify multiple conditions under which warnings should be emitted by the
jmod tool.</p>

<p>Attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>reason</td>
<td>Condition which will cause jmod tool to emit warnings. One of:
<dl>
<dt><code>deprecated</code></dt>
<dd>Warn if module is deprecated</dd>
<dt><code>leaving</code></dt>
<dd>Warn if module is deprecated for removal</dd>
<dt><code>incubating</code></dt>
<dd>Warn if module is an incubating (not yet official) module</dd>
</dl>
<td>Yes</td>
</tr>
</table>

<h3>Examples</h3>

<h4>Basic jmod</h4>
<p>Create a jmod from a single modular jar file:</p>
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"/&gt;
</pre>

<h4>With dependencies</h4>
<p>Create a jmod from a modular jar file which depends on another module:</p>
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"&gt;
&lt;modulepath&gt;
&lt;pathelement location="libs/thirdpartyutils.jar"/&gt;
&lt;/modulepath&gt;
&lt;/jmod&gt;
</pre>

<h4>With version</h4>
<p>Create a jmod with a module version:</p>
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"
version="1.2.1-ea+29"/&gt;
</pre>

<p>Create a versioned jmod from module version components:</p>
<pre>
&lt;property name="version" value="1.2.1"/&gt;
&lt;buildnumber/&gt;
&lt;loadfile property="buildnum" srcFile="build.number"/&gt;
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"&gt;
&lt;version number="${version}" build="${buildnum}"/&gt;
&lt;/jmod&gt;
</pre>

<h4>Main class</h4>
<p>Create a jmod with a main class:</p>
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"
mainclass="com.example.myapp.MainWindow"/&gt;
</pre>

<h4>Target platform</h4>
<p>Create a jmod for a specific platform, possibly different from the
current platform:</p>

<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"
platform="windows-amd64"/&gt;
</pre>

</body>
</html>

+ 579
- 0
manual/Tasks/link.html View File

@@ -0,0 +1,579 @@
<!--
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.
-->
<html>

<head>
<meta http-equiv="Content-Language" content="en-us">
<link rel="stylesheet" type="text/css" href="../stylesheets/style.css">
<title>Link Task</title>
</head>

<body>

<h2 id="link">Link</h2>
<h3>Description</h3>
<p>Assembles jmod files into an executable image. Equivalent to the JDK's
<a href="https://docs.oracle.com/en/java/javase/11/tools/jlink.html">jlink</a>
tool.
</p>
<p>Requires Java 9 or later.</p>

<h3>Parameters</h3>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>destDir</td>
<td>Root directory of created image.</td>
<td>Yes</td>
</tr>
<tr>
<td>modulepath</td>
<td>Path-like sequence of jmod files to link in order to create image.</td>
<td rowspan="2">One of these is required, unless a nested
<code>&lt;modulepath&gt;</code> is present.</td>
</tr>
<tr>
<td>modulepathref</td>
<td class="left">Path-like sequence of jmod files to link in order to
create image, given as a <a href="../using.html#references">reference</a>
to a path defined elsewhere.</td>
</tr>
<tr>
<td>modules</td>
<td>Comma-separated list of modules to place in the linked image.</td>
<td>Yes, unless one or more nested <code>&lt;module&gt;</code> elements
are present.</td>
</tr>
<tr>
<td>observableModules</td>
<td>Comma-separated list of explicit modules that comprise
"universe" visible to link tool while linking.</td>
<td>No</td>
</tr>
<tr>
<td>launchers</td>
<td>Comma-separated list of commands, each of the form
<var>name</var><code>=</code><var>module</var> or
<var>name</var><code>=</code><var>module</var><code>/</code><var>mainclass</var></td>
<td>No</td>
</tr>
<tr>
<td>locales</td>
<td>Comma-separated list of extra locales, or wildcard patterns matching
multiple locale names, to include.
Requires <code>jdk.localedata</code> module.</td>
<td>No</td>
</tr>
<tr>
<td>excludeResources</td>
<td>Comma-separated list of patterns specifying resources to exclude
from source jmods. Each is either a
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a>
or <code>@</code><var>filename</var>, indicating a text file with
one resource name per line.</td>
<td>No</td>
</tr>
<tr>
<td>excludeFiles</td>
<td>Comma-separated list of patterns specifying files to exclude
from linked image. Each is either a
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a>
or <code>@</code><var>filename</var>, indicating a text file with
one file name per line.</td>
<td>No</td>
</tr>
<tr>
<td>resourceOrder</td>
<td>Comma-separated list of patterns specifying resource search order.
Each is either a
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a>
or <code>@</code><var>filename</var>, indicating a text file with
one resource name per line.</td>
<td>No</td>
</tr>
<tr>
<td>bindServices</td>
<td>Boolean, whether to include in linked image any service providers
found in module path corresponding to service provider interfaces
used by explicitly linked modules.</td>
<td>No, default is false</td>
</tr>
<tr>
<td>ignoreSigning</td>
<td>Boolean, whether to allow signed jar files.
(Note: As of Java 11, this is ignored and is always treated as true.)</td>
<td>No, default is false</td>
</tr>
<tr>
<td>includeHeaders</td>
<td>Boolean, whether to include header files in linked image.</td>
<td>No, default is true</td>
</tr>
<tr>
<td>includeManPages</td>
<td>Boolean, whether to include man pages in linked image.</td>
<td>No, default is true</td>
</tr>
<tr>
<td>includeNativeCommands</td>
<td>Boolean, whether to include native executables in linked image.</td>
<td>No, default is true</td>
</tr>
<tr>
<td>debug</td>
<td>Boolean, whether to include debug information.</td>
<td>No, default is true</td>
</tr>
<tr>
<td>verboseLevel</td>
<td>If set, the linker will produce verbose output, which will be logged at
the specified Ant log level (<code>DEBUG</code>, <code>VERBOSE</code>,
<code>INFO</code>, <code>WARN</code>, or <code>ERR</code>).</td>
<td>No, default is no verbose output</td>
</tr>
<tr>
<td>compress</td>
<td>Compression level of linked image. One of:
<dl>
<dt><code>0</code> or
<code>none</code></dt>
<dd>no compression (default)</dd>
<dt><code>1</code> or
<code>strings</code></dt>
<dd>constant string sharing</dd>
<dt><code>2</code> or
<code>zip</code></dt>
<dd>zip compression</dd>
</dl>
</td>
<td>No, default is no compression</td>
</tr>
<tr>
<td>endianness</td>
<td>Byte order of linked image, must be <code>little</code> or <code>big</code>
<td>No, default is native byte order</td>
</tr>
<tr>
<td>checkDuplicateLegal</td>
<td>Boolean. When merging legal notices from different modules
because they have the same name, verify that their contents
are identical.</td>
<td>No, default is false, which means any license files
with the same name are assumed to have the same content, and no
checking is done.</td>
</tr>
<tr>
<td>vmType</td>
<td>Hotspot VM in image. One of:
<ul>
<li><code>client</code>
<li><code>server</code>
<li><code>minimal</code>
<li><code>all</code>
</ul>
</td>
<td>No, default is <code>all</code></td>
</tr>
</table>

<h3>Parameters specified as nested elements</h3>

<p><code>&lt;link&gt;</code> can have the following nested elements:</p>
<ul>
<li><a href="#nested-modulepath">modulepath</a></li>
<li><a href="#nested-module">module</a></li>
<li><a href="#nested-observableModule">observableModule</a></li>
<li><a href="#nested-launcher">launcher</a></li>
<li><a href="#nested-locale">locale</a></li>
<li><a href="#nested-resourceOrder">resourceOrder</a></li>
<li><a href="#nested-excludeResources">excludeResources</a></li>
<li><a href="#nested-excludeFiles">excludeFiles</a></li>
<li><a href="#nested-compress">compress</a></li>
<li><a href="#nested-releaseInfo">releaseInfo</a></li>
</ul>

<h4 id="nested-modulepath">modulepath</h4>
<p><a href="../using.html#path">Path-like structure</a> pointing to
jmod files to link into image.</p>

<h4 id="nested-module">module</h4>
<p>Names a single module to be placed in the linked image. This may be
specified multiple times.</p>
<p>Attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>name</td>
<td>Name of module to add.</td>
<td>Yes</td>
</tr>
</table>

<h4 id="nested-observableModule">observableModule</h4>
<p>Names a module visible to the linking process, instead of every module
in the module path being considered. This may be specified multiple times.
<p>Attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>name</td>
<td>Name of module to add to list of observable modules.</td>
<td>Yes</td>
</tr>
</table>

<h4 id="nested-launcher">launcher</h4>
<p>Specifies an executable file which will be added to the linked image,
which executes a particular module's main class. Attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>name</td>
<td>Name of launcher. This typically is used for the name of the
executable file.</td>
<td>Yes</td>
</tr>
<tr>
<td>module</td>
<td>Name of module to execute.</td>
<td>Yes</td>
</tr>
<tr>
<td>mainClass</td>
<td>Name of entry point class in module to execute.</td>
<td>Required unless module has its own main class defined.</td>
</tr>
</table>

<h4 id="nested-locale">locale</h4>
<p>Specifies locales to include in linked image. May be specified multiple
times. Requires <code>jdk.localedata</code> module. Attributes:
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>name</td>
<td>Name of locale, or wildcard pattern with <q><code>*</code></q>
that matches multiple locale names.</td>
<td>Yes</td>
</tr>
</table>

<h4 id="nested-resourceOrder">resourceOrder</h4>
<p>Explicit resource search order in linked image. May be specified multiple
times. Attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>pattern</td>
<td>A <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a>
for matching resources</td>
<td rowspan="2">Exactly one of these</td>
</tr>
<tr>
<td>listFile</td>
<td class="left">Text file containing list of resource names (not patterns),
one per line</td>
</tr>
</table>

<p>If the <code>resourceOrder</code> attribute is also present on the task, its
patterns are treated as if they occur before patterns in nested
<code><resourceOrder></code> elements.</p>

<h4 id="nested-excludeResources">excludeResources</h4>
<p>Excludes files from linked image tree. May be specified multiple times.
Attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>pattern</td>
<td>A <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a>
for matching resources</td>
<td rowspan="2">Exactly one of these</td>
</tr>
<tr>
<td>listFile</td>
<td class="left">Text file containing list of resource names (not patterns),
one per line</td>
</tr>
</table>

<h4 id="nested-excludeFiles">excludeFiles</h4>
<p>Excludes files from linked image. May be specified multiple times.
Attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>pattern</td>
<td>A <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a>
for matching files</td>
<td rowspan="2">Exactly one of these</td>
</tr>
<tr>
<td>listFile</td>
<td class="left">Text file containing list of file names (not patterns),
one per line</td>
</tr>
</table>

<h4 id="nested-compress">compress</h4>
<p>Describes how image should be compressed. Attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>level</td>
<td>Compression level of linked image. One of:
<dl>
<dt><code>0</code> or
<code>none</code></dt>
<dd>no compression (default)</dd>
<dt><code>1</code> or
<code>strings</code></dt>
<dd>constant string sharing</dd>
<dt><code>2</code> or
<code>zip</code></dt>
<dd>zip compression</dd>
</dl>
</td>
<td>Yes</td>
</tr>
<tr>
<td>files</td>
<td>Comma-separated list of patterns matching files to compress.
Each pattern either a
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a>
or <code>@</code><var>filename</var>, indicating a text file with
one file name per line.</td>
<td>No</td>
</tr>
</table>

<p><code>&lt;compress&gt;</code> can also have any number of nested
<code>&lt;files&gt;</code> elements, with these attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>pattern</td>
<td>A <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a>
for matching files</td>
<td rowspan="2">Exactly one of these</td>
</tr>
<tr>
<td>listFile</td>
<td class="left">Text file containing list of file names (not patterns),
one per line</td>
</tr>
</table>

<h4 id="nested-releaseInfo">releaseInfo</h4>
<p>Replaces, augments, or trims the image's release info properties.
Can be specified multiple times. Attributes:
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>file</td>
<td>Java properties file containing new release info properties
that will entirely replace the current ones.</td>
<td>No</td>
</tr>
<tr>
<td>delete</td>
<td>Comma-separated property keys to remove from application's
release info
<td>No</td>
</tr>
</table>

<p><code>&lt;releaseInfo&gt;</code> can also have any number of these nested elements:</p>
<h5>add</h5>
<p>Specifies additional release info properties. Attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>key</td>
<td>Key of single property to add.</td>
<td rowspan="2">Yes, unless <code>file</code> is specified</td>
</tr>
<tr>
<td>value</td>
<td class="left">Value of single property to add.</td>
</tr>
<tr>
<td>file</td>
<td>Java property file containing any number of properties to add.</td>
<td>Yes, unless <code>key</code> and <code>value</code> are specified</td>
</tr>
<tr>
<td>charset</td>
<td>Character set of property file.</td>
<td>No, default is <code>ISO_8859_1</code>, in accordance with
java.util.Properties class.</td>
</tr>
</table>

<h5>delete</h5>
<p>Property keys to remove from applicaiton's release info. Attributes:</p>
<table class="attr">
<tr>
<th scope="col">Attribute</th>
<th scope="col">Description</th>
<th scope="col">Required</th>
</tr>
<tr>
<td>key</td>
<td>Key of property to remove.</td>
<td>Yes</td>
</tr>
</table>

<h3>Examples</h3>
<h4>Basic linking</h4>
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"/&gt;
&lt;link destDir="build/image" modulepath="MyApp.jmod"
modules="com.example.myapp"/&gt;
</pre>

<h4>Custom binaries</h4>
<p>This will cause a <samp>bin/MyEditor</samp> script to appear in the
image:
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"/&gt;
&lt;link destDir="build/image" modulepath="MyApp.jmod"
modules="com.example.myapp"
launchers="MyEditor=com.example.myapp/com.example.myapp.editors.EditorMain"/&gt;
</pre>

<p>Same thing, using a nested launcher element:</p>
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"/&gt;
&lt;link destDir="build/image" modulepath="MyApp.jmod"
modules="com.example.myapp"&gt;

&lt;launcher name="MyEditor" module="com.example.myapp"
mainClass="com.example.myapp.editors.EditorMain"/&gt;

&lt;/link&gt;
</pre>

<h4>Limiting locales</h4>
<p>Include just the locales needed by the application from the <a href="https://docs.oracle.com/en/java/javase/11/docs/api/jdk.localedata/module-summary.html">jdk.localedata</a> module:</p>
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"/&gt;
&lt;link destDir="build/image" modulepath="MyApp.jmod"
modules="com.example.myapp,jdk.localedata"
locales="zh,jp-*"/&gt;
</pre>

<h4>Compressed image</h4>
<p>Compress entire image:</p>
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"/&gt;
&lt;link destDir="build/image" modulepath="MyApp.jmod"
modules="com.example.myapp,jdk.localedata"
compress="zip"/&gt;
</pre>

<p>Compress only some files in the image:</p>
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar"/&gt;
&lt;link destDir="build/image" modulepath="MyApp.jmod"
modules="com.example.myapp,jdk.localedata"&gt;

&lt;compress level="zip" files=".*\.xml"/&gt;

&lt;/link&gt;
</pre>

<h4>Cross-compiling</h4>
<p>To create an image for a different platform:

<ul>
<li>Download the JDK for that platform, and expand the archive manually into
a directory of your choice. (Downloading a zip or tar.gz version of a JDK
instead of an installer will make this easier.)</li>
<li>Determine the foreign JDK's platform string. This can be done with
a command that examines the JDK's <samp>jmods/java.base.jmod</samp> file:
<pre>
jmod describe "$FOREIGN_JDK_HOME"/jmods/java.base.jmod | grep '^platform'
</pre>
</li>
<li>Create your jmod using the foreign JDK's platform string:
<pre>
&lt;jmod destfile="MyApp.jmod" classpath="build/myapp.jar" platform="windows-amd64"/&gt;
</pre>
</li>
<li>Link with the foreign JDK's <samp>jmods</samp> directory in the module path:
<pre>
&lt;link destDir="build/image"
modulepath="MyApp.jmod;${foreign-jdk-home}/jmods"
modules="com.example.myapp"/&gt;
</pre>
</li>
</ul>
<ul>

</body>
</html>

+ 2
- 0
manual/tasklist.html View File

@@ -108,11 +108,13 @@
<li><a href="Tasks/jjdoc.html">JJDoc</a></li>
<li><a href="Tasks/jjtree.html">JJTree</a></li>
<li><a href="Tasks/jlink.html"><em>Jlink</em></a></li>
<li><a href="Tasks/jmod.html">Jmod</a></li>
<li><a href="Tasks/jspc.html"><em>JspC</em></a></li>
<li><a href="Tasks/junit.html">JUnit</a> (3 &amp; 4)</li>
<li><a href="Tasks/junitlauncher.html">JUnitLauncher</a> (JUnit 5)</li>
<li><a href="Tasks/junitreport.html">JUnitReport</a></li>
<li><a href="Tasks/length.html">Length</a><br/></li>
<li><a href="Tasks/link.html">Link</a><br/></li>
<li><a href="Tasks/loadfile.html">LoadFile</a></li>
<li><a href="Tasks/loadproperties.html">LoadProperties</a></li>
<li><a href="Tasks/loadresource.html">LoadResource</a></li>


+ 4
- 4
src/etc/testcases/taskdefs/jar.xml View File

@@ -121,7 +121,7 @@
destfile="${tmp.jar}"
basedir="."
includes="j*.xml"
excludes="java.xml"
excludes="java.xml,jmod.xml"
update="true"
/>
</target>
@@ -131,7 +131,7 @@
destfile="${tmp.jar}"
basedir="."
includes="j*.xml"
excludes="java.xml"
excludes="java.xml,jmod.xml"
/>
</target>

@@ -144,14 +144,14 @@
depends="makezip">
<jar destfile="${tmp.jar}"
update="true">
<zipfileset src="${tmp.zip}" excludes="java.xml"/>
<zipfileset src="${tmp.zip}" excludes="java.xml,jmod.xml"/>
</jar>
</target>

<target name="testNoRecreateZipfilesetExcludesWithoutUpdate"
depends="makezip">
<jar destfile="${tmp.jar}">
<zipfileset src="${tmp.zip}" excludes="java.xml"/>
<zipfileset src="${tmp.zip}" excludes="java.xml,jmod.xml"/>
</jar>
</target>



+ 992
- 0
src/etc/testcases/taskdefs/jmod.xml View File

@@ -0,0 +1,992 @@
<?xml version="1.0"?>
<!--
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 name="jmodtest" default="nil" >
<import file="../buildfiletest-base.xml"/>

<target name="nil"/>

<target name="-dirs">
<mkdir dir="${input}"/>
<mkdir dir="${output}"/>
<property name="jmod" value="${output}/test.jmod"/>
<property name="version" value="1.0.1-+99"/>
</target>

<target name="setUp" depends="-dirs">
</target>

<!-- Creates simple modular jar, with only Java SE dependencies. -->
<target name="-hello" depends="-dirs">

<property name="hello.root" value="${input}/hello"/>

<property name="hello.src" value="${hello.root}/src"/>
<property name="hello.classes" value="${hello.root}/classes"/>
<property name="hello.jar.dir" value="${hello.root}/jars"/>
<property name="hello.jar" value="${hello.jar.dir}/hello.jar"/>

<property name="hello.pkg" value= "org.apache.tools.ant.test.hello"/>
<property name="hello.pkg.dir" value="${hello.src}/org/apache/tools/ant/test/hello"/>

<property name="hello.main-class" value="${hello.pkg}.HelloWorld"/>

<mkdir dir="${hello.pkg.dir}"/>
<echo file="${hello.pkg.dir}/HelloWorld.java">
package ${hello.pkg};

import java.util.logging.Logger;

public class HelloWorld {
public void run() {
Logger logger = Logger.getLogger(HelloWorld.class.getName());
logger.info("HELLO WORLD");
}

public static void main(String[] args) {
new HelloWorld().run();
}
}
</echo>
<echo file="${hello.src}/module-info.java">
module ${hello.pkg} {
exports ${hello.pkg};
requires java.logging;
}
</echo>

<mkdir dir="${hello.classes}"/>
<javac srcdir="${hello.src}" destdir="${hello.classes}" includeAntRuntime="false"/>

<mkdir dir="${hello.jar.dir}"/>
<jar destfile="${hello.jar}" basedir="${hello.classes}"/>
</target>

<!-- Creates modular jar with dependency on hello module. -->
<target name="-smile" depends="-dirs,-hello">
<property name="smile.root" value="${input}/smile"/>

<property name="smile.src" value="${smile.root}/src"/>
<property name="smile.classes" value="${smile.root}/classes"/>
<property name="smile.jar.dir" value="${smile.root}/jars"/>
<property name="smile.jar" value="${smile.jar.dir}/smile.jar"/>

<property name="smile.pkg" value= "org.apache.tools.ant.test.smile"/>
<property name="smile.pkg.dir" value="${smile.src}/org/apache/tools/ant/test/smile"/>

<property name="smile.main-class" value="${smile.pkg}.Smile"/>

<mkdir dir="${smile.pkg.dir}"/>
<echo file="${smile.pkg.dir}/Smile.java">
package ${smile.pkg};

import java.util.logging.Logger;
import ${hello.pkg}.HelloWorld;

public class Smile {
public void run() {
Logger logger = Logger.getLogger(Smile.class.getName());
logger.info("\u263a\u263b\u263a\u263b");
}

public static void main(String[] args) {
new Smile().run();
new HelloWorld().run();
}
}
</echo>
<echo file="${smile.src}/module-info.java">
module ${smile.pkg} {
exports ${smile.pkg};
requires java.logging;
requires ${hello.pkg};
}
</echo>

<mkdir dir="${smile.classes}"/>
<javac srcdir="${smile.src}" destdir="${smile.classes}"
includeAntRuntime="false"
modulepath="${hello.jar}"/>

<mkdir dir="${smile.jar.dir}"/>
<jar destfile="${smile.jar}" basedir="${smile.classes}"/>
</target>

<!-- Creates additional modular jar, with only Java SE dependencies. -->
<target name="-foobar" depends="-smile">

<property name="foobar.root" value="${input}/foobar"/>

<property name="foobar.src" value="${foobar.root}/src"/>
<property name="foobar.classes" value="${foobar.root}/classes"/>
<property name="foobar.jar.dir" value="${foobar.root}/jars"/>
<property name="foobar.jar" value="${foobar.jar.dir}/foobar.jar"/>

<property name="foobar.pkg" value= "org.apache.tools.ant.test.foobar"/>
<property name="foobar.pkg.dir" value="${foobar.src}/org/apache/tools/ant/test/foobar"/>

<property name="foobar.main-class" value="${foobar.pkg}.FooBar"/>

<mkdir dir="${foobar.pkg.dir}"/>
<echo file="${foobar.pkg.dir}/FooBar.java">
package ${foobar.pkg};

import ${hello.main-class};
import ${smile.main-class};

public class FooBar {
public void run() {
new HelloWorld().run();
new Smile().run();
}

public static void main(String[] args) {
new FooBar().run();
}
}
</echo>
<echo file="${foobar.src}/module-info.java">
module ${foobar.pkg} {
exports ${foobar.pkg};
requires java.logging;
requires ${hello.pkg};
requires ${smile.pkg};
}
</echo>

<mkdir dir="${foobar.classes}"/>
<javac srcdir="${foobar.src}" destdir="${foobar.classes}"
includeAntRuntime="false" modulepath="${hello.jar};${smile.jar}"/>

<mkdir dir="${foobar.jar.dir}"/>
<jar destfile="${foobar.jar}" basedir="${foobar.classes}"/>
</target>

<target name="destAndClasspathNoJmod" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}"/>
</target>

<target name="destAndClasspathOlderThanJmod" depends="-hello">
<property name="dateformat" value="yyyy-MM-dd HH:mm:ss.SSS"/>
<tstamp>
<format property="future" pattern="${dateformat}" offset="1" unit="hour"/>
</tstamp>
<touch file="${jmod}" datetime="${future}" pattern="${dateformat}"/>

<jmod destfile="${jmod}" classpath="${hello.jar}"/>
</target>

<target name="noDestFile">
<jmod classpath="."/>
</target>

<target name="noClasspath">
<jmod destfile="${jmod}"/>
</target>

<target name="emptyClasspath">
<jmod destfile="${jmod}" classpath=""/>
</target>

<target name="nonexistentClasspath">
<jmod destfile="${jmod}" classpath="dummy:dummy2"/>
</target>

<target name="classpathref" depends="-hello">
<path id="classpathref.testpath">
<pathelement location="${hello.jar}"/>
</path>

<jmod destfile="${jmod}" classpathref="classpathref.testpath"/>
</target>

<target name="classpath-nested" depends="-hello">
<jmod destfile="${jmod}">
<classpath>
<pathelement location="${hello.jar}"/>
</classpath>
</jmod>
</target>

<target name="classpath-both" depends="-smile">
<jmod destfile="${jmod}" classpath="${hello.jar}">
<classpath>
<pathelement location="${smile.classes}"/>
</classpath>
</jmod>
</target>

<!-- modulepath targets -->

<target name="modulepath" depends="-smile">
<jmod destfile="${jmod}" classpath="${smile.jar}"
modulepath="${hello.jar.dir}"/>
</target>

<target name="modulepathref" depends="-smile">
<path id="modulepathref.testpath">
<pathelement location="${hello.jar.dir}"/>
</path>

<jmod destfile="${jmod}" classpath="${smile.jar}"
modulepathref="modulepathref.testpath"/>
</target>

<target name="modulepath-nested" depends="-smile">
<jmod destfile="${jmod}" classpath="${smile.jar}">
<modulepath>
<pathelement location="${hello.jar.dir}"/>
</modulepath>
</jmod>
</target>

<target name="modulepath-both" depends="-smile,-foobar">
<jmod destfile="${jmod}" classpath="${foobar.jar}"
modulepath="${hello.jar.dir}">
<modulepath>
<pathelement location="${smile.jar.dir}"/>
</modulepath>
</jmod>
</target>

<target name="modulepathnondir" depends="-smile">
<jmod destfile="${jmod}" classpath="${smile.jar}"
modulepath="${hello.jar}"/>
</target>

<!-- commandpath targets -->

<target name="commandpath" depends="-hello">
<property name="commands" value="${output}/commands"/>
<mkdir dir="${commands}"/>

<property name="command1" value="${commands}/command1"/>

<echo file="${command1}">
#!/bin/bash
`dirname "$0"`/java -m ${hello.pkg}/${hello.pkg}.HelloWorld
</echo>
<setpermissions mode="777" nonPosixMode="tryDosOrPass">
<file file="${command1}"/>
</setpermissions>

<jmod destfile="${jmod}" classpath="${hello.jar}"
commandpath="${commands}"/>
</target>

<target name="commandpathref" depends="-hello">
<property name="commands" value="${output}/commands"/>
<mkdir dir="${commands}"/>

<property name="command2" value="${commands}/command2"/>

<echo file="${command2}">
#!/bin/bash
`dirname "$0"`/java -m ${hello.pkg}/${hello.pkg}.HelloWorld
</echo>
<setpermissions mode="777" nonPosixMode="tryDosOrPass">
<file file="${command2}"/>
</setpermissions>

<path id="commandpathref.testpath">
<pathelement location="${commands}"/>
</path>

<jmod destfile="${jmod}" classpath="${hello.jar}"
commandpathref="commandpathref.testpath"/>
</target>

<target name="commandpath-nested" depends="-hello">
<property name="commands" value="${output}/commands"/>
<mkdir dir="${commands}"/>

<property name="command3" value="${commands}/command3"/>

<echo file="${command3}">
#!/bin/bash
`dirname "$0"`/java -m ${hello.pkg}/${hello.pkg}.HelloWorld
</echo>
<setpermissions mode="777" nonPosixMode="tryDosOrPass">
<file file="${command3}"/>
</setpermissions>

<jmod destfile="${jmod}" classpath="${hello.jar}">
<commandpath>
<pathelement location="${commands}"/>
</commandpath>
</jmod>
</target>

<target name="commandpath-both" depends="-hello">
<property name="commands1" value="${output}/commands1"/>
<property name="commands2" value="${output}/commands2"/>
<mkdir dir="${commands1}"/>
<mkdir dir="${commands2}"/>

<property name="command4" value="${commands1}/command4"/>
<echo file="${command4}">
#!/bin/bash
`dirname "$0"`/java -m ${hello.pkg}/${hello.pkg}.HelloWorld
</echo>
<setpermissions mode="777" nonPosixMode="tryDosOrPass">
<file file="${command4}"/>
</setpermissions>

<property name="command5" value="${commands2}/command5"/>
<echo file="${command5}">
#!/bin/bash
`dirname "$0"`/java -m ${hello.pkg}/${hello.pkg}.HelloWorld
</echo>
<setpermissions mode="777" nonPosixMode="tryDosOrPass">
<file file="${command5}"/>
</setpermissions>

<jmod destfile="${jmod}" classpath="${hello.jar}"
commandpath="${commands1}">
<commandpath>
<pathelement location="${commands2}"/>
</commandpath>
</jmod>
</target>

<!-- headerpath targets -->

<target name="headerpath" depends="-hello">
<property name="headers" value="${output}/headers"/>
<mkdir dir="${headers}"/>

<property name="header1" value="${headers}/header1.h"/>

<echo file="${header1}">
typedef int index_t;
</echo>

<jmod destfile="${jmod}" classpath="${hello.jar}"
headerpath="${headers}"/>
</target>

<target name="headerpathref" depends="-hello">
<property name="headers" value="${output}/headers"/>
<mkdir dir="${headers}"/>

<property name="header2" value="${headers}/header2.h"/>

<echo file="${header2}">
typedef char * string_t;
</echo>

<path id="headerpathref.testpath">
<pathelement location="${headers}"/>
</path>

<jmod destfile="${jmod}" classpath="${hello.jar}"
headerpathref="headerpathref.testpath"/>
</target>

<target name="headerpath-nested" depends="-hello">
<property name="headers" value="${output}/headers"/>
<mkdir dir="${headers}"/>

<property name="header3" value="${headers}/header3.h"/>

<echo file="${header3}">
typedef char * string_t;
</echo>

<jmod destfile="${jmod}" classpath="${hello.jar}">
<headerpath>
<pathelement location="${headers}"/>
</headerpath>
</jmod>
</target>

<target name="headerpath-both" depends="-hello">
<property name="headers4" value="${output}/headers4"/>
<property name="headers5" value="${output}/headers5"/>
<mkdir dir="${headers4}"/>
<mkdir dir="${headers5}"/>

<property name="header4" value="${headers4}/header4.h"/>
<property name="header5" value="${headers5}/header5.h"/>

<echo file="${header4}">
typedef int index_t;
</echo>
<echo file="${header5}">
typedef char * string_t;
</echo>

<jmod destfile="${jmod}" classpath="${hello.jar}" headerpath="${headers4}">
<headerpath>
<pathelement location="${headers5}"/>
</headerpath>
</jmod>
</target>

<!-- configpath targets -->

<target name="configpath" depends="-hello">
<property name="config" value="${output}/config"/>
<mkdir dir="${config}"/>

<property name="config1" value="${config}/config1.properties"/>

<echo file="${config1}">
timeout=3600
</echo>

<jmod destfile="${jmod}" classpath="${hello.jar}" configpath="${config}"/>
</target>

<target name="configpathref" depends="-hello">
<property name="config" value="${output}/config"/>
<mkdir dir="${config}"/>

<property name="config2" value="${config}/config2.properties"/>

<echo file="${config2}">
timeout=7200
</echo>

<path id="configpathref.testpath">
<pathelement location="${config}"/>
</path>

<jmod destfile="${jmod}" classpath="${hello.jar}"
configpathref="configpathref.testpath"/>
</target>

<target name="configpath-nested" depends="-hello">
<property name="config" value="${output}/config"/>
<mkdir dir="${config}"/>

<property name="config3" value="${config}/config3.properties"/>

<echo file="${config3}">
timeout=7200
</echo>

<jmod destfile="${jmod}" classpath="${hello.jar}">
<configpath>
<pathelement location="${config}"/>
</configpath>
</jmod>
</target>

<target name="configpath-both" depends="-hello">
<property name="config4" value="${output}/config4"/>
<property name="config5" value="${output}/config5"/>
<mkdir dir="${config4}"/>
<mkdir dir="${config5}"/>

<property name="configfile4" value="${config4}/config4.properties"/>
<property name="configfile5" value="${config5}/config5.properties"/>

<echo file="${configfile4}">
timeout=3600
</echo>
<echo file="${configfile5}">
timeout=7200
</echo>

<jmod destfile="${jmod}" classpath="${hello.jar}" configpath="${config4}">
<configpath>
<pathelement location="${config5}"/>
</configpath>
</jmod>
</target>

<!-- legalpath targets -->

<target name="legalpath" depends="-hello">
<property name="legal" value="${output}/legal"/>
<mkdir dir="${legal}"/>

<property name="legal1" value="${legal}/legal1.txt"/>

<echo file="${legal1}">
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.
</echo>

<jmod destfile="${jmod}" classpath="${hello.jar}" legalpath="${legal}"/>
</target>

<target name="legalpathref" depends="-hello">
<property name="legal" value="${output}/legal"/>
<mkdir dir="${legal}"/>

<property name="legal2" value="${legal}/legal2.txt"/>

<echo file="${legal2}">
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.
</echo>

<path id="legalpathref.testpath">
<pathelement location="${legal}"/>
</path>

<jmod destfile="${jmod}" classpath="${hello.jar}"
legalpathref="legalpathref.testpath"/>
</target>

<target name="legalpath-nested" depends="-hello">
<property name="legal" value="${output}/legal"/>
<mkdir dir="${legal}"/>

<property name="legal3" value="${legal}/legal3.txt"/>

<echo file="${legal3}">
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.
</echo>

<jmod destfile="${jmod}" classpath="${hello.jar}">
<legalpath>
<pathelement location="${legal}"/>
</legalpath>
</jmod>
</target>

<target name="legalpath-both" depends="-hello">
<property name="legal4" value="${output}/legal4"/>
<property name="legal5" value="${output}/legal5"/>
<mkdir dir="${legal4}"/>
<mkdir dir="${legal5}"/>

<property name="legalfile4" value="${legal4}/legal4.txt"/>
<property name="legalfile5" value="${legal5}/legal5.txt"/>

<echo file="${legalfile4}">
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.
</echo>
<copy file="${legalfile4}" tofile="${legalfile5}"/>

<jmod destfile="${jmod}" classpath="${hello.jar}" legalpath="${legal4}">
<legalpath>
<pathelement location="${legal5}"/>
</legalpath>
</jmod>
</target>

<!-- manpath targets -->

<target name="manpath" depends="-hello">
<property name="manpages" value="${output}/manpages"/>
<mkdir dir="${manpages}"/>

<property name="man1" value="${manpages}/man1.1"/>

<echo file="${man1}"><!--
--><![CDATA[.TH TRUE "1" "February 2017" "GNU coreutils 8.26" "User Commands"
.SH NAME
true \- do nothing, successfully
.SH SYNOPSIS
.B true
[\fI\,ignored command line arguments\/\fR]
.br
.B true
\fI\,OPTION\/\fR
.SH DESCRIPTION
.\" Add any additional description here
.PP
Exit with a status code indicating success.
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
NOTE: your shell may have its own version of true, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.
.SH AUTHOR
Written by Jim Meyering.
.SH "REPORTING BUGS"
GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
.br
Report true translation bugs to <http://translationproject.org/team/>
.SH COPYRIGHT
Copyright \(co 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
.br
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
.SH "SEE ALSO"
Full documentation at: <http://www.gnu.org/software/coreutils/true>
]]><!--
--></echo>

<jmod destfile="${jmod}" classpath="${hello.jar}" manpath="${manpages}"/>
</target>

<target name="manpathref" depends="-hello">
<property name="manpages" value="${output}/manpages"/>
<mkdir dir="${manpages}"/>

<property name="man2" value="${manpages}/man2.1"/>

<echo file="${man2}"><!--
--><![CDATA[.TH FALSE "1" "February 2017" "GNU coreutils 8.26" "User Commands"
.SH NAME
false \- do nothing, unsuccessfully
.SH SYNOPSIS
.B false
[\fI\,ignored command line arguments\/\fR]
.br
.B false
\fI\,OPTION\/\fR
.SH DESCRIPTION
.\" Add any additional description here
.PP
Exit with a status code indicating failure.
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
NOTE: your shell may have its own version of false, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.
.SH AUTHOR
Written by Jim Meyering.
.SH "REPORTING BUGS"
GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
.br
Report false translation bugs to <http://translationproject.org/team/>
.SH COPYRIGHT
Copyright \(co 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
.br
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
.SH "SEE ALSO"
Full documentation at: <http://www.gnu.org/software/coreutils/false>
]]><!--
--></echo>

<path id="manpathref.testpath">
<pathelement location="${manpages}"/>
</path>

<jmod destfile="${jmod}" classpath="${hello.jar}"
manpathref="manpathref.testpath"/>
</target>

<target name="manpath-nested" depends="-hello">
<property name="manpages" value="${output}/manpages"/>
<mkdir dir="${manpages}"/>

<property name="man3" value="${manpages}/man3.1"/>

<echo file="${man3}"><!--
--><![CDATA[.TH FALSE "1" "February 2017" "GNU coreutils 8.26" "User Commands"
.SH NAME
false \- do nothing, unsuccessfully
.SH SYNOPSIS
.B false
[\fI\,ignored command line arguments\/\fR]
.br
.B false
\fI\,OPTION\/\fR
.SH DESCRIPTION
.\" Add any additional description here
.PP
Exit with a status code indicating failure.
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
NOTE: your shell may have its own version of false, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.
.SH AUTHOR
Written by Jim Meyering.
.SH "REPORTING BUGS"
GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
.br
Report false translation bugs to <http://translationproject.org/team/>
.SH COPYRIGHT
Copyright \(co 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
.br
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
.SH "SEE ALSO"
Full documentation at: <http://www.gnu.org/software/coreutils/false>
]]><!--
--></echo>

<jmod destfile="${jmod}" classpath="${hello.jar}">
<manpath>
<pathelement location="${manpages}"/>
</manpath>
</jmod>
</target>

<target name="manpath-both" depends="-hello">
<property name="manpages4" value="${output}/manpages4"/>
<property name="manpages5" value="${output}/manpages5"/>
<mkdir dir="${manpages4}"/>
<mkdir dir="${manpages5}"/>

<property name="man4" value="${manpages4}/man4.1"/>
<property name="man5" value="${manpages5}/man5.1"/>

<echo file="${man4}"><!--
--><![CDATA[.TH TRUE "1" "February 2017" "GNU coreutils 8.26" "User Commands"
.SH NAME
true \- do nothing, successfully
.SH SYNOPSIS
.B true
[\fI\,ignored command line arguments\/\fR]
.br
.B true
\fI\,OPTION\/\fR
.SH DESCRIPTION
.\" Add any additional description here
.PP
Exit with a status code indicating success.
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
NOTE: your shell may have its own version of true, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.
.SH AUTHOR
Written by Jim Meyering.
.SH "REPORTING BUGS"
GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
.br
Report true translation bugs to <http://translationproject.org/team/>
.SH COPYRIGHT
Copyright \(co 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
.br
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
.SH "SEE ALSO"
Full documentation at: <http://www.gnu.org/software/coreutils/true>
]]><!--
--></echo>
<echo file="${man5}"><!--
--><![CDATA[.TH FALSE "1" "February 2017" "GNU coreutils 8.26" "User Commands"
.SH NAME
false \- do nothing, unsuccessfully
.SH SYNOPSIS
.B false
[\fI\,ignored command line arguments\/\fR]
.br
.B false
\fI\,OPTION\/\fR
.SH DESCRIPTION
.\" Add any additional description here
.PP
Exit with a status code indicating failure.
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
NOTE: your shell may have its own version of false, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.
.SH AUTHOR
Written by Jim Meyering.
.SH "REPORTING BUGS"
GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
.br
Report false translation bugs to <http://translationproject.org/team/>
.SH COPYRIGHT
Copyright \(co 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
.br
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
.SH "SEE ALSO"
Full documentation at: <http://www.gnu.org/software/coreutils/false>
]]><!--
--></echo>

<jmod destfile="${jmod}" classpath="${hello.jar}" manpath="${manpages4}">
<manpath>
<pathelement location="${manpages5}"/>
</manpath>
</jmod>
</target>

<!-- nativelibpath targets -->

<target name="nativelibpath" depends="-hello">
<property name="nativelib" location="${java.home}/lib;${java.home}/bin"/>
<jmod destfile="${jmod}" classpath="${hello.jar}"
nativelibpath="${nativelib}"/>
</target>

<target name="nativelibpathref" depends="-hello">

<path id="nativelibpathref.testpath">
<pathelement location="${java.home}/lib"/>
<pathelement location="${java.home}/bin"/>
</path>

<jmod destfile="${jmod}" classpath="${hello.jar}"
nativelibpathref="nativelibpathref.testpath"/>
</target>

<target name="nativelibpath-nested" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}">
<nativelibpath>
<pathelement location="${java.home}/lib"/>
<pathelement location="${java.home}/bin"/>
</nativelibpath>
</jmod>
</target>

<target name="nativelibpath-both" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}"
nativelibpath="${java.home}/lib;${java.home}/bin">
<nativelibpath>
<dirset dir="${java.home}">
<include name="lib/server"/>
<include name="bin/server"/>
</dirset>
</nativelibpath>
</jmod>
</target>

<!-- non-path targets -->

<target name="version" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}" version="${version}"/>
</target>

<target name="version-nested" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}">
<version number="1.0.1" build="99"/>
</jmod>
</target>

<target name="version-nested-number" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}">
<version number="1.0.1"/>
</jmod>
</target>

<target name="version-nested-no-number" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}">
<version build="99"/>
</jmod>
</target>

<target name="version-nested-invalid-number" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}">
<version number="1-0-1" build="99"/>
</jmod>
</target>

<target name="version-nested-invalid-prerelease" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}">
<version number="1.0.1" build="99" prerelease="unit+testing"/>
</jmod>
</target>

<target name="version-both" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}" version="${version}">
<version number="1.0.1" build="99"/>
</jmod>
</target>

<target name="mainclass" depends="-hello">
<jmod destfile="${jmod}" classpath="${hello.jar}"
mainclass="${hello.main-class}"/>
</target>

<target name="platform" depends="-smile">
<property name="target-platform" value="windows-amd64"/>
<jmod destfile="${jmod}" classpath="${hello.jar}"
platform="${target-platform}"/>
</target>

<target name="hashing" depends="-smile">
<jmod destfile="${jmod}" classpath="${hello.jar}"
modulepath="${smile.jar.dir}"
hashModulesPattern=".*smile.*"/>
</target>
</project>

+ 1088
- 0
src/etc/testcases/taskdefs/link.xml
File diff suppressed because it is too large
View File


+ 2
- 0
src/main/org/apache/tools/ant/taskdefs/defaults.properties View File

@@ -66,6 +66,8 @@ jar=org.apache.tools.ant.taskdefs.Jar
java=org.apache.tools.ant.taskdefs.Java
javac=org.apache.tools.ant.taskdefs.Javac
javadoc=org.apache.tools.ant.taskdefs.Javadoc
jmod=org.apache.tools.ant.taskdefs.modules.Jmod
link=org.apache.tools.ant.taskdefs.modules.Link
length=org.apache.tools.ant.taskdefs.Length
loadfile=org.apache.tools.ant.taskdefs.LoadFile
loadproperties=org.apache.tools.ant.taskdefs.LoadProperties


+ 1282
- 0
src/main/org/apache/tools/ant/taskdefs/modules/Jmod.java
File diff suppressed because it is too large
View File


+ 2120
- 0
src/main/org/apache/tools/ant/taskdefs/modules/Link.java
File diff suppressed because it is too large
View File


+ 23
- 0
src/main/org/apache/tools/ant/taskdefs/modules/package-info.java View File

@@ -0,0 +1,23 @@
/*
* 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.
*
*/

/**
* Tasks for dealing with Java modules, which are supported starting with
* Java 9.
*/
package org.apache.tools.ant.taskdefs.modules;

+ 4
- 2
src/main/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java View File

@@ -25,8 +25,10 @@ import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.Path;

/**
* This class defines objects that can link together various jar and
* zip files.
* This task defines objects that can link together various jar and
* zip files. It is not related to the {@code jlink} tool present in
* Java 9 and later; for that, see
* {@link org.apache.tools.ant.taskdefs.modules.Link}.
*
* <p>It is basically a wrapper for the jlink code written originally
* by <a href="mailto:beard@netscape.com">Patrick Beard</a>. The


+ 147
- 0
src/main/org/apache/tools/ant/types/ModuleVersion.java View File

@@ -0,0 +1,147 @@
/*
* 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.
*
*/

package org.apache.tools.ant.types;

import java.util.Objects;

/**
* Element describing the parts of a Java
* <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/module/ModuleDescriptor.Version.html">module version</a>.
* The version number is required; all other parts are optional.
*/
public class ModuleVersion {
/** Module version's required <em>version number</em>. */
private String number;

/** Module version's optional <em>pre-release version</em>. */
private String preRelease;

/** Module version's optional <em>build version</em>. */
private String build;

/**
* Returns this element's version number.
*
* @return version number
*/
public String getNumber() {
return number;
}

/**
* Sets this element's required version number. This cannot contain
* an ASCII hyphen ({@code -}) or plus ({@code +}), as those characters
* are used as delimiters in a complete module version string.
*
* @throws NullPointerException if argument is {@code null}
* @throws IllegalArgumentException if argument contains {@code '-'}
* or {@code '+'}
*/
public void setNumber(final String number) {
Objects.requireNonNull(number, "Version number cannot be null.");
if (number.indexOf('-') >= 0 || number.indexOf('+') >= 0) {
throw new IllegalArgumentException(
"Version number cannot contain '-' or '+'.");
}
this.number = number;
}

/**
* Returns this element's pre-release version, if set.
*
* @return pre-release value, or {@code null}
*/
public String getPreRelease() {
return preRelease;
}

/**
* Sets this element's pre-release version. This can be any value
* which doesn't contain an ASCII plus ({@code +}).
*
* @param pre pre-release version, or {@code null}
*
* @throws IllegalArgumentException if argument contains "{@code +}"
*/
public void setPreRelease(final String pre) {
if (pre != null && pre.indexOf('+') >= 0) {
throw new IllegalArgumentException(
"Version's pre-release cannot contain '+'.");
}
this.preRelease = pre;
}

/**
* Returns this element's build version, if set.
*
* @return build value, or {@code null}
*/
public String getBuild() {
return build;
}

/**
* Sets this element's build version. This can be any value, including
* {@code null}.
*
* @param build build version, or {@code null}
*/
public void setBuild(final String build) {
this.build = build;
}

/**
* Snapshots this element's state and converts it to a string compliant
* with {@code ModuleDescriptor.Version}.
*
* @return Java module version string built from this object's properties
*
* @throws IllegalStateException if {@linkplain #getNumber() number}
* is {@code null}
*/
public String toModuleVersionString() {
if (number == null) {
throw new IllegalStateException("Version number cannot be null.");
}

StringBuilder version = new StringBuilder(number);
if (preRelease != null || build != null) {
version.append('-').append(Objects.toString(preRelease, ""));
}
if (build != null) {
version.append('+').append(build);
}

return version.toString();
}

/**
* Returns a summary of this object's state, suitable for debugging.
*
* @return string form of this instance
*/
@Override
public String toString() {
return getClass().getName() +
"[number=" + number +
", preRelease=" + preRelease +
", build=" + build +
"]";
}
}

+ 690
- 0
src/tests/junit/org/apache/tools/ant/taskdefs/modules/JmodTest.java View File

@@ -0,0 +1,690 @@
/*
* 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.
*
*/

package org.apache.tools.ant.taskdefs.modules;

import java.io.BufferedReader;
import java.io.StringReader;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import java.io.File;
import java.io.IOException;

import java.nio.file.Files;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

import java.util.function.Predicate;
import java.util.regex.Pattern;

import java.util.spi.ToolProvider;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildFileRule;

/**
* Tests the {@link Jmod} task.
*/
public class JmodTest {
@Rule
public final BuildFileRule buildRule = new BuildFileRule();

@Rule
public final ExpectedException expected = ExpectedException.none();

@Before
public void setUp() {
buildRule.configureProject("src/etc/testcases/taskdefs/jmod.xml");
buildRule.executeTarget("setUp");
}

@Test
public void testDestAndClasspathNoJmod() {
buildRule.executeTarget("destAndClasspathNoJmod");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());
}

@Test
public void testDestAndNestedClasspath() {
buildRule.executeTarget("classpath-nested");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());
}

@Test
public void testDestAndClasspathOlderThanJmod()
throws IOException {
buildRule.executeTarget("destAndClasspathOlderThanJmod");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
File jar = new File(buildRule.getProject().getProperty("hello.jar"));
Assert.assertTrue("Checking that newer jmod was not written "
+ "when source files are older.",
Files.getLastModifiedTime(jmod.toPath()).toInstant().isAfter(
Instant.now().plus(30, ChronoUnit.MINUTES)));
}

@Test
public void testNoDestFile() {
expected.expect(BuildException.class);
buildRule.executeTarget("noDestFile");
}

@Test
public void testNoClasspath() {
expected.expect(BuildException.class);
buildRule.executeTarget("noClasspath");
}

@Test
public void testEmptyClasspath() {
expected.expect(BuildException.class);
buildRule.executeTarget("emptyClasspath");
}

@Test
public void testClasspathEntirelyNonexistent() {
expected.expect(BuildException.class);
buildRule.executeTarget("nonexistentClasspath");
}

@Test
public void testClasspathref() {
buildRule.executeTarget("classpathref");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());
}

@Test
public void testClasspathAttributeAndChildElement() {
buildRule.executeTarget("classpath-both");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());
}

@Test
public void testModulepath() {
buildRule.executeTarget("modulepath");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());
}

@Test
public void testModulepathref() {
buildRule.executeTarget("modulepathref");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());
}

@Test
public void testModulepathNested() {
buildRule.executeTarget("modulepath-nested");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());
}

@Test
public void testModulepathNonDir() {
expected.expect(BuildException.class);
buildRule.executeTarget("modulepathnondir");
}

@Test
public void testModulepathAttributeAndChildElement() {
buildRule.executeTarget("modulepath-both");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());
}

@Test
public void testCommandPath() {
buildRule.executeTarget("commandpath");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains command.",
containsLine(output, l -> l.equals("bin/command1")));
}

@Test
public void testCommandPathref() {
buildRule.executeTarget("commandpathref");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains command.",
containsLine(output, l -> l.equals("bin/command2")));
}

@Test
public void testCommandPathNested() {
buildRule.executeTarget("commandpath-nested");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains command.",
containsLine(output, l -> l.equals("bin/command3")));
}

@Test
public void testCommandPathAttributeAndChildElement() {
buildRule.executeTarget("commandpath-both");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains commands "
+ "from both attribute and child element.",
containsAll(output,
l -> l.equals("bin/command4"),
l -> l.equals("bin/command5")));
}

@Test
public void testHeaderPath() {
buildRule.executeTarget("headerpath");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains header file.",
containsLine(output, l -> l.equals("include/header1.h")));
}

@Test
public void testHeaderPathref() {
buildRule.executeTarget("headerpathref");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains header file.",
containsLine(output, l -> l.equals("include/header2.h")));
}

@Test
public void testHeaderPathNested() {
buildRule.executeTarget("headerpath-nested");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains header file.",
containsLine(output, l -> l.equals("include/header3.h")));
}

@Test
public void testHeaderPathAttributeAndChildElement() {
buildRule.executeTarget("headerpath-both");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains header files "
+ "from both attribute and child element.",
containsAll(output,
l -> l.equals("include/header4.h"),
l -> l.equals("include/header5.h")));
}

@Test
public void testConfigPath() {
buildRule.executeTarget("configpath");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains config file.",
containsLine(output, l -> l.equals("conf/config1.properties")));
}

@Test
public void testConfigPathref() {
buildRule.executeTarget("configpathref");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains config file.",
containsLine(output, l -> l.equals("conf/config2.properties")));
}

@Test
public void testConfigPathNested() {
buildRule.executeTarget("configpath-nested");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains config file.",
containsLine(output, l -> l.equals("conf/config3.properties")));
}

@Test
public void testConfigPathAttributeAndChildElement() {
buildRule.executeTarget("configpath-both");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains config files "
+ "from both attribute and child element.",
containsAll(output,
l -> l.equals("conf/config4.properties"),
l -> l.equals("conf/config5.properties")));
}

@Test
public void testLegalPath() {
buildRule.executeTarget("legalpath");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains license file.",
containsLine(output, l -> l.equals("legal/legal1.txt")));
}

@Test
public void testLegalPathref() {
buildRule.executeTarget("legalpathref");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains license file.",
containsLine(output, l -> l.equals("legal/legal2.txt")));
}

@Test
public void testLegalPathNested() {
buildRule.executeTarget("legalpath-nested");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains license file.",
containsLine(output, l -> l.equals("legal/legal3.txt")));
}

@Test
public void testLegalPathAttributeAndChildElement() {
buildRule.executeTarget("legalpath-both");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains legal files "
+ "from both attribute and child element.",
containsAll(output,
l -> l.equals("legal/legal4.txt"),
l -> l.equals("legal/legal5.txt")));
}

@Test
public void testManPath() {
buildRule.executeTarget("manpath");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains man page.",
containsLine(output, l -> l.equals("man/man1.1")));
}

@Test
public void testManPathref() {
buildRule.executeTarget("manpathref");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains man page.",
containsLine(output, l -> l.equals("man/man2.1")));
}

@Test
public void testManPathNested() {
buildRule.executeTarget("manpath-nested");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains man page.",
containsLine(output, l -> l.equals("man/man3.1")));
}

@Test
public void testManPathAttributeAndChildElement() {
buildRule.executeTarget("manpath-both");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains man pages "
+ "from both attribute and child element.",
containsAll(output,
l -> l.equals("man/man4.1"),
l -> l.equals("man/man5.1")));
}

@Test
public void testNativeLibPath() {
buildRule.executeTarget("nativelibpath");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains native library.",
containsLine(output, l -> l.matches("lib/[^/]+\\.(dll|dylib|so)")));
}

@Test
public void testNativeLibPathref() {
buildRule.executeTarget("nativelibpathref");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains native library.",
containsLine(output, l -> l.matches("lib/[^/]+\\.(dll|dylib|so)")));
}

@Test
public void testNativeLibPathNested() {
buildRule.executeTarget("nativelibpath-nested");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains native library.",
containsLine(output, l -> l.matches("lib/[^/]+\\.(dll|dylib|so)")));
}

@Test
public void testNativeLibPathAttributeAndChildElement() {
buildRule.executeTarget("nativelibpath-both");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("list", jmod.toString());
Assert.assertTrue("Checking that jmod contains native libraries "
+ "from both attribute and child element.",
containsAll(output,
l -> l.matches("lib/(lib)?zip\\.(dll|dylib|so)"),
l -> l.matches("lib/(lib)?jvm\\.(dll|dylib|so)")));
}

@Test
public void testVersion() {
buildRule.executeTarget("version");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String version = buildRule.getProject().getProperty("version");
Assert.assertNotNull("Checking that 'version' property is set",
version);
Assert.assertFalse("Checking that 'version' property is not empty",
version.isEmpty());

String output = runJmod("describe", jmod.toString());
Assert.assertTrue("Checking that jmod has correct version.",
containsLine(output, l -> l.endsWith("@" + version)));
}

@Test
public void testNestedVersion() {
buildRule.executeTarget("version-nested");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("describe", jmod.toString());
Assert.assertTrue("Checking that jmod has correct version.",
containsLine(output, l -> l.matches(".*@1\\.0\\.1[-+]+99")));
}

@Test
public void testNestedVersionNumberOnly() {
buildRule.executeTarget("version-nested-number");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("describe", jmod.toString());
Assert.assertTrue("Checking that jmod has correct version.",
containsLine(output, l -> l.endsWith("@1.0.1")));
}

@Test
public void testNestedVersionNoNumber() {
expected.expect(BuildException.class);
buildRule.executeTarget("version-nested-no-number");
}

@Test
public void testNestedVersionInvalidNumber() {
expected.expect(BuildException.class);
buildRule.executeTarget("version-nested-invalid-number");
}

@Test
public void testNestedVersionInvalidPreRelease() {
expected.expect(BuildException.class);
buildRule.executeTarget("version-nested-invalid-prerelease");
}

@Test
public void testVersionAttributeAndChildElement() {
expected.expect(BuildException.class);
buildRule.executeTarget("version-both");
}

@Test
public void testMainClass() {
buildRule.executeTarget("mainclass");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String mainClass =
buildRule.getProject().getProperty("hello.main-class");
Assert.assertNotNull("Checking that 'main-class' property is set",
mainClass);
Assert.assertFalse("Checking that 'main-class' property is not empty",
mainClass.isEmpty());

String output = runJmod("describe", jmod.toString());

String mainClassPattern = "main-class\\s+" + Pattern.quote(mainClass);
Assert.assertTrue("Checking that jmod has correct main class.",
containsLine(output, l -> l.matches(mainClassPattern)));
}

@Test
public void testPlatform() {
buildRule.executeTarget("platform");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String platform = buildRule.getProject().getProperty("target-platform");
Assert.assertNotNull("Checking that 'target-platform' property is set",
platform);
Assert.assertFalse("Checking that 'target-platform' property "
+ "is not empty", platform.isEmpty());

String output = runJmod("describe", jmod.toString());

String platformPattern = "platform\\s+" + Pattern.quote(platform);
Assert.assertTrue("Checking that jmod has correct main class.",
containsLine(output, l -> l.matches(platformPattern)));
}

@Test
public void testHashing() {
buildRule.executeTarget("hashing");

File jmod = new File(buildRule.getProject().getProperty("jmod"));
Assert.assertTrue("Checking that jmod was successfully created.",
jmod.exists());

String output = runJmod("describe", jmod.toString());

Assert.assertTrue("Checking that jmod has module hashes.",
containsLine(output, l -> l.startsWith("hashes")));
}

private String runJmod(final String... args) {
ToolProvider jmod = ToolProvider.findFirst("jmod").orElseThrow(
() -> new RuntimeException("jmod tool not found in JDK."));

ByteArrayOutputStream stdout = new ByteArrayOutputStream();
ByteArrayOutputStream stderr = new ByteArrayOutputStream();

int exitCode;
try (PrintStream out = new PrintStream(stdout);
PrintStream err = new PrintStream(stderr)) {

exitCode = jmod.run(out, err, args);
}

if (exitCode != 0) {
throw new RuntimeException(
"jmod failed, output is: " + stdout + ", error is: " + stderr);
}

return stdout.toString();
}

private boolean containsLine(final String lines,
final Predicate<? super String> test) {
try (BufferedReader reader =
new BufferedReader(new StringReader(lines))) {

return reader.lines().anyMatch(test);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private boolean containsAll(final String lines,
final Predicate<? super String> test1,
final Predicate<? super String> test2) {

try (BufferedReader reader =
new BufferedReader(new StringReader(lines))) {

boolean test1Matched = false;
boolean test2Matched = false;

String line;
while ((line = reader.readLine()) != null) {
test1Matched |= test1.test(line);
test2Matched |= test2.test(line);
}

return test1Matched && test2Matched;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

+ 984
- 0
src/tests/junit/org/apache/tools/ant/taskdefs/modules/LinkTest.java View File

@@ -0,0 +1,984 @@
/*
* 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.
*
*/

package org.apache.tools.ant.taskdefs.modules;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileReader;
import java.io.StringReader;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;

import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.spi.ToolProvider;

import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildFileRule;

/**
* Tests the {@link Link} task.
*/
public class LinkTest {
/*
* TODO:
* Test --order-resources (how?)
* Test --exclude-files (what does this actually do?)
* Test --endian (how?)
* Test --vm (how?)
*/

@Rule
public final BuildFileRule buildRule = new BuildFileRule();

@Rule
public final ExpectedException expected = ExpectedException.none();

@Before
public void setUp() {
buildRule.configureProject("src/etc/testcases/taskdefs/link.xml");
buildRule.executeTarget("setUp");
}

private static boolean isWindows() {
return System.getProperty("os.name").contains("Windows");
}

private static boolean isEarlierThan(final Instant time,
final Path path) {
try {
return Files.getLastModifiedTime(path).toInstant().isBefore(time);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private static class ImageStructure {
final File root;
final File bin;
final File java;

ImageStructure(final File root) {
this.root = root;

bin = new File(root, "bin");
java = new File(bin, isWindows() ? "java.exe" : "java");
}
}

private ImageStructure verifyImageBuiltNormally() {
ImageStructure image = new ImageStructure(
new File(buildRule.getProject().getProperty("image")));

Assert.assertTrue("Checking that image was successfully created.",
image.root.exists());

Assert.assertTrue("Checking that image has java executable.",
image.java.exists());

return image;
}

@Test
public void testModulepath() {
buildRule.executeTarget("modulepath");
verifyImageBuiltNormally();
}

@Test
public void testImageNotRecreatedFromStaleJmods()
throws IOException {
buildRule.executeTarget("imageNewerThanJmods");
ImageStructure image = verifyImageBuiltNormally();

Instant future = Instant.now().plus(30, ChronoUnit.MINUTES);
try (Stream<Path> imageFiles = Files.walk(image.root.toPath())) {

Assert.assertTrue("Checking that newer image was not written "
+ "when source files are older.",
imageFiles.noneMatch(i -> isEarlierThan(future, i)));
}
}

@Test
public void testNoModulePath() {
expected.expect(BuildException.class);
buildRule.executeTarget("nomodulepath");
}

@Test
public void testNoModules() {
expected.expect(BuildException.class);
buildRule.executeTarget("nomodules");
}

@Test
public void testModulePathRef() {
buildRule.executeTarget("modulepathref");
verifyImageBuiltNormally();
}

@Test
public void testNestedModulePath() {
buildRule.executeTarget("modulepath-nested");
verifyImageBuiltNormally();
}

@Test
public void testModulePathInAttributeAndNested() {
buildRule.executeTarget("modulepath-both");
verifyImageBuiltNormally();
}

@Test
public void testNestedModules()
throws IOException,
InterruptedException {

buildRule.executeTarget("modules-nested");

ImageStructure image = verifyImageBuiltNormally();

ProcessBuilder builder = new ProcessBuilder(
image.java.toString(),
buildRule.getProject().getProperty("hello.main-class"));
builder.inheritIO();
int exitCode = builder.start().waitFor();
Assert.assertEquals(
"Checking that execution of first module succeeded.", 0, exitCode);

builder.command(
image.java.toString(),
buildRule.getProject().getProperty("smile.main-class"));
exitCode = builder.start().waitFor();
Assert.assertEquals(
"Checking that execution of second module succeeded.", 0, exitCode);
}

@Test
public void testNestedModuleMissingName() {
expected.expect(BuildException.class);
buildRule.executeTarget("modules-nested-missing-name");
}

@Test
public void testModulesInAttributeAndNested() {
buildRule.executeTarget("modules-both");
verifyImageBuiltNormally();
}

@Test
public void testObservableModules() {
expected.expect(BuildException.class);
buildRule.executeTarget("observable");
}

@Test
public void testNestedObservableModules() {
expected.expect(BuildException.class);
buildRule.executeTarget("observable-nested");
}

@Test
public void testNestedObservableModuleMissingName() {
expected.expect(BuildException.class);
buildRule.executeTarget("observable-nested-missing-name");
}

@Test
public void testObservableModulesInAttributeAndNested() {
buildRule.executeTarget("observable-both");
verifyImageBuiltNormally();
}

private void verifyLaunchersExist() {
ImageStructure image = verifyImageBuiltNormally();

File launcher1 =
new File(image.bin, isWindows() ? "Hello.bat" : "Hello");
Assert.assertTrue("Checking that image has 'Hello' launcher.",
launcher1.exists());

File launcher2 =
new File(image.bin, isWindows() ? "Smile.bat" : "Smile");
Assert.assertTrue("Checking that image has 'Smile' launcher.",
launcher2.exists());
}

@Test
public void testLaunchers() {
buildRule.executeTarget("launchers");
verifyLaunchersExist();
}

@Test
public void testNestedLaunchers() {
buildRule.executeTarget("launchers-nested");
verifyLaunchersExist();
}

@Test
public void testNestedLauncherMissingName() {
expected.expect(BuildException.class);
buildRule.executeTarget("launchers-nested-missing-name");
}

@Test
public void testNestedLauncherMissingModule() {
expected.expect(BuildException.class);
buildRule.executeTarget("launchers-nested-missing-module");
}

@Test
public void testLaunchersInAttributeAndNested() {
buildRule.executeTarget("launchers-both");
verifyLaunchersExist();
}

private void verifyLocales()
throws IOException,
InterruptedException {

ImageStructure image = verifyImageBuiltNormally();

String mainClass =
buildRule.getProject().getProperty("localefinder.main-class");
Assert.assertNotNull("Checking that main-class property exists",
mainClass);

ProcessBuilder builder =
new ProcessBuilder(image.java.toString(), mainClass, "zh", "in");
builder.inheritIO();
int exitCode = builder.start().waitFor();

Assert.assertEquals("Verifying that image has access to locales "
+ "specified during linking.", 0, exitCode);

builder.command(image.java.toString(), mainClass, "ja");
exitCode = builder.start().waitFor();

Assert.assertNotEquals(
"Verifying that image does not have access to locales "
+ "not specified during linking.", 0, exitCode);
}

@Test
public void testLocales()
throws IOException,
InterruptedException {

buildRule.executeTarget("locales");
verifyLocales();
}

@Test
public void testNestedLocales()
throws IOException,
InterruptedException {

buildRule.executeTarget("locales-nested");
verifyLocales();
}

@Test
public void testNestedLocaleMissingName() {
expected.expect(BuildException.class);
buildRule.executeTarget("locales-nested-missing-name");
}

@Test
public void testLocalesInAttributeAndNested()
throws IOException,
InterruptedException {

buildRule.executeTarget("locales-both");
verifyLocales();
}

@Test
public void testExcludeResources()
throws IOException {
buildRule.executeTarget("excluderesources");
ImageStructure image = verifyImageBuiltNormally();

String mainClass =
buildRule.getProject().getProperty("hello.main-class");
Assert.assertNotNull("Checking that main-class property exists",
mainClass);

ProcessBuilder builder =
new ProcessBuilder(image.java.toString(), mainClass,
"resource1.txt", "resource2.txt");
builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
builder.redirectErrorStream(true);

Collection<String> outputLines;
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {

outputLines = reader.lines().collect(Collectors.toList());
}

Assert.assertTrue(
"Checking that excluded resource is actually excluded.",
outputLines.stream().anyMatch(
l -> l.endsWith("resource1.txt absent")));

Assert.assertTrue(
"Checking that resource not excluded is present.",
outputLines.stream().anyMatch(
l -> l.endsWith("resource2.txt present")));
}

@Test
public void testNestedExcludeResources()
throws IOException {
buildRule.executeTarget("excluderesources-nested");
ImageStructure image = verifyImageBuiltNormally();

String mainClass =
buildRule.getProject().getProperty("hello.main-class");
Assert.assertNotNull("Checking that main-class property exists",
mainClass);

ProcessBuilder builder =
new ProcessBuilder(image.java.toString(), mainClass,
"resource1.txt", "resource2.txt");
builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
builder.redirectErrorStream(true);

Collection<String> outputLines;
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {

outputLines = reader.lines().collect(Collectors.toList());
}

Assert.assertTrue(
"Checking that excluded resource is actually excluded.",
outputLines.stream().anyMatch(
l -> l.endsWith("resource1.txt absent")));

Assert.assertTrue(
"Checking that resource not excluded is present.",
outputLines.stream().anyMatch(
l -> l.endsWith("resource2.txt present")));
}

@Test
public void testNestedExcludeResourcesFile()
throws IOException {
buildRule.executeTarget("excluderesources-nested-file");
ImageStructure image = verifyImageBuiltNormally();

String mainClass =
buildRule.getProject().getProperty("hello.main-class");
Assert.assertNotNull("Checking that main-class property exists",
mainClass);

ProcessBuilder builder =
new ProcessBuilder(image.java.toString(), mainClass,
"resource1.txt", "resource2.txt");
builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
builder.redirectErrorStream(true);

Collection<String> outputLines;
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {

outputLines = reader.lines().collect(Collectors.toList());
}

Assert.assertTrue(
"Checking that excluded resource is actually excluded.",
outputLines.stream().anyMatch(
l -> l.endsWith("resource1.txt absent")));

Assert.assertTrue(
"Checking that resource not excluded is present.",
outputLines.stream().anyMatch(
l -> l.endsWith("resource2.txt present")));
}

@Test
public void testNestedExcludeResourcesNoAttributes() {
expected.expect(BuildException.class);
buildRule.executeTarget("excluderesources-nested-no-attr");
}

@Test
public void testNestedExcludeResourcesFileAndPattern() {
expected.expect(BuildException.class);
buildRule.executeTarget("excluderesources-nested-both");
}

@Test
public void testExcludeResourcesAttributeAndNested()
throws IOException {
buildRule.executeTarget("excluderesources-both");
ImageStructure image = verifyImageBuiltNormally();

String mainClass =
buildRule.getProject().getProperty("hello.main-class");
Assert.assertNotNull("Checking that main-class property exists",
mainClass);

ProcessBuilder builder =
new ProcessBuilder(image.java.toString(), mainClass,
"resource1.txt", "resource2.txt");
builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
builder.redirectErrorStream(true);

Collection<String> outputLines;
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {

outputLines = reader.lines().collect(Collectors.toList());
}

Assert.assertTrue(
"Checking that first excluded resource is actually excluded.",
outputLines.stream().anyMatch(
l -> l.endsWith("resource1.txt absent")));

Assert.assertTrue(
"Checking that second excluded resource is actually excluded.",
outputLines.stream().anyMatch(
l -> l.endsWith("resource2.txt absent")));
}

@Test
public void testExcludeFiles()
throws IOException {
buildRule.executeTarget("excludefiles");
verifyImageBuiltNormally();
// TODO: Test created image (what does --exclude-files actually do?)
}

@Test
public void testNestedExcludeFiles()
throws IOException {
buildRule.executeTarget("excludefiles-nested");
verifyImageBuiltNormally();
// TODO: Test created image (what does --exclude-files actually do?)
}

@Test
public void testNestedExcludeFilesFile()
throws IOException {
buildRule.executeTarget("excludefiles-nested-file");
ImageStructure image = verifyImageBuiltNormally();
// TODO: Test created image (what does --exclude-files actually do?)
}

@Test
public void testNestedExcludeFilesNoAttributes() {
expected.expect(BuildException.class);
buildRule.executeTarget("excludefiles-nested-no-attr");
}

@Test
public void testNestedExcludeFilesFileAndPattern() {
expected.expect(BuildException.class);
buildRule.executeTarget("excludefiles-nested-both");
}

@Test
public void testExcludeFilesAttributeAndNested()
throws IOException {
buildRule.executeTarget("excludefiles-both");
verifyImageBuiltNormally();
// TODO: Test created image (what does --exclude-files actually do?)
}

@Test
public void testOrdering()
throws IOException {
buildRule.executeTarget("ordering");
verifyImageBuiltNormally();
// TODO: Test resource order in created image (how?)
}

@Test
public void testNestedOrdering()
throws IOException {
buildRule.executeTarget("ordering-nested");
verifyImageBuiltNormally();
// TODO: Test resource order in created image (how?)
}

@Test
public void testNestedOrderingListFile()
throws IOException {
buildRule.executeTarget("ordering-nested-file");
ImageStructure image = verifyImageBuiltNormally();
// TODO: Test resource order in created image (how?)
}

@Test
public void testNestedOrderingNoAttributes() {
expected.expect(BuildException.class);
buildRule.executeTarget("ordering-nested-no-attr");
}

@Test
public void testNestedOrderingFileAndPattern() {
expected.expect(BuildException.class);
buildRule.executeTarget("ordering-nested-both");
}

@Test
public void testOrderingAttributeAndNested()
throws IOException {
buildRule.executeTarget("ordering-both");
verifyImageBuiltNormally();
// TODO: Test resource order in created image (how?)
}

@Test
public void testIncludeHeaders() {
buildRule.executeTarget("includeheaders");
ImageStructure image = verifyImageBuiltNormally();

File[] headers = new File(image.root, "include").listFiles();
Assert.assertTrue("Checking that include files were omitted.",
headers == null || headers.length == 0);
}

@Test
public void testIncludeManPages() {
buildRule.executeTarget("includemanpages");
ImageStructure image = verifyImageBuiltNormally();

File[] manPages = new File(image.root, "man").listFiles();
Assert.assertTrue("Checking that man pages were omitted.",
manPages == null || manPages.length == 0);
}

@Test
public void testIncludeNativeCommands() {
buildRule.executeTarget("includenativecommands");
ImageStructure image = new ImageStructure(
new File(buildRule.getProject().getProperty("image")));

Assert.assertTrue("Checking that image was successfully created.",
image.root.exists());

Assert.assertFalse(
"Checking that image was stripped of java executable.",
image.java.exists());
}

private long totalSizeOf(final Path path)
throws IOException {
if (Files.isDirectory(path)) {
long size = 0;
try (DirectoryStream<Path> children = Files.newDirectoryStream(path)) {
for (Path child : children) {
size += totalSizeOf(child);
}
}
return size;
}

if (Files.isRegularFile(path)) {
return Files.size(path);
}

return 0;
}

@Test
public void testCompression()
throws IOException {
buildRule.executeTarget("compression");
ImageStructure image = verifyImageBuiltNormally();

File compressedImageRoot =
new File(buildRule.getProject().getProperty("compressed-image"));

long size = totalSizeOf(image.root.toPath());
long compressedSize = totalSizeOf(compressedImageRoot.toPath());

Assert.assertTrue("Checking that compression resulted in smaller image.",
compressedSize < size);
}

@Test
public void testNestedCompression()
throws IOException {
buildRule.executeTarget("compression-nested");
ImageStructure image = verifyImageBuiltNormally();

File compressedImageRoot =
new File(buildRule.getProject().getProperty("compressed-image"));

long size = totalSizeOf(image.root.toPath());
long compressedSize = totalSizeOf(compressedImageRoot.toPath());

Assert.assertTrue("Checking that compression resulted in smaller image.",
compressedSize < size);
}

@Test
public void testNestedCompressionNoAttributes() {
expected.expect(BuildException.class);
buildRule.executeTarget("compression-nested-no-attr");
}

@Test
public void testNestedCompressionAttributeAndNested() {
expected.expect(BuildException.class);
buildRule.executeTarget("compression-both");
}

@Test
public void testEndian() {
buildRule.executeTarget("endian");
verifyImageBuiltNormally();
// TODO: How can we test the created image? Which files does --endian
// affect?
}

@Test
public void testVMType() {
buildRule.executeTarget("vm");
verifyImageBuiltNormally();
// TODO: How can we test the created image? Which files does --vm
// affect?
}

@Test
public void testReleaseInfoFile()
throws IOException {
buildRule.executeTarget("releaseinfo-file");
ImageStructure image = verifyImageBuiltNormally();

File release = new File(image.root, "release");
try (BufferedReader reader =
Files.newBufferedReader(release.toPath())) {

Assert.assertTrue("Checking for 'test=true' in image release info.",
reader.lines().anyMatch(l -> l.equals("test=true")));
}
}

@Test
public void testReleaseInfoDelete()
throws IOException {
buildRule.executeTarget("releaseinfo-delete");
ImageStructure image = verifyImageBuiltNormally();

File release = new File(image.root, "release");
try (BufferedReader reader =
Files.newBufferedReader(release.toPath())) {

Assert.assertFalse("Checking that 'test' was deleted "
+ "from image release info.",
reader.lines().anyMatch(l -> l.startsWith("test=")));
}
}

@Test
public void testReleaseInfoNestedDelete()
throws IOException {
buildRule.executeTarget("releaseinfo-nested-delete");
ImageStructure image = verifyImageBuiltNormally();

File release = new File(image.root, "release");
try (BufferedReader reader =
Files.newBufferedReader(release.toPath())) {

Assert.assertFalse("Checking that 'test' was deleted "
+ "from image release info.",
reader.lines().anyMatch(l -> l.startsWith("test=")));
}
}

@Test
public void testReleaseInfoNestedDeleteNoKey() {
expected.expect(BuildException.class);
buildRule.executeTarget("releaseinfo-nested-delete-no-key");
}

@Test
public void testReleaseInfoDeleteAttributeAndNested()
throws IOException {
buildRule.executeTarget("releaseinfo-nested-delete-both");
ImageStructure image = verifyImageBuiltNormally();

File release = new File(image.root, "release");
try (BufferedReader reader =
Files.newBufferedReader(release.toPath())) {

Assert.assertTrue(
"Checking that 'test' and 'foo' were deleted "
+ "from image release info.",
reader.lines().noneMatch(l ->
l.startsWith("test=") || l.startsWith("foo=")));
}
}

@Test
public void testReleaseInfoAddFile()
throws IOException {
buildRule.executeTarget("releaseinfo-add-file");
ImageStructure image = verifyImageBuiltNormally();

File release = new File(image.root, "release");
try (BufferedReader reader = new BufferedReader(
new FileReader(release))) {

Assert.assertTrue("Checking that 'test=s\u00ed' was added "
+ "to image release info.",
reader.lines().anyMatch(l -> l.equals("test=s\u00ed")));
}
}

@Test
public void testReleaseInfoAddFileWithCharset()
throws IOException {
buildRule.executeTarget("releaseinfo-add-file-charset");
ImageStructure image = verifyImageBuiltNormally();

File release = new File(image.root, "release");
// Using FileReader here since 'release' file is in platform's charset.
try (BufferedReader reader = new BufferedReader(
new FileReader(release))) {

Assert.assertTrue("Checking that 'test=s\u00ed' was added "
+ "to image release info.",
reader.lines().anyMatch(l -> l.equals("test=s\u00ed")));
}
}

@Test
public void testReleaseInfoAddKeyAndValue()
throws IOException {
buildRule.executeTarget("releaseinfo-add-key");
ImageStructure image = verifyImageBuiltNormally();

File release = new File(image.root, "release");
try (BufferedReader reader =
Files.newBufferedReader(release.toPath())) {

Assert.assertTrue("Checking that 'test=true' was added "
+ "to image release info.",
reader.lines().anyMatch(l -> l.equals("test=true")));
}
}

@Test
public void testReleaseInfoAddNoValue() {
expected.expect(BuildException.class);
buildRule.executeTarget("releaseinfo-add-no-value");
}

@Test
public void testReleaseInfoAddNoKey() {
expected.expect(BuildException.class);
buildRule.executeTarget("releaseinfo-add-no-key");
}

@Test
public void testReleaseInfoAddFileAndKey() {
expected.expect(BuildException.class);
buildRule.executeTarget("releaseinfo-add-file-and-key");
}

@Test
public void testReleaseInfoAddFileAndValue() {
expected.expect(BuildException.class);
buildRule.executeTarget("releaseinfo-add-file-and-value");
}

@Test
public void testDebugStripping()
throws IOException,
InterruptedException {

buildRule.executeTarget("debug");
ImageStructure image = verifyImageBuiltNormally();

ProcessBuilder builder = new ProcessBuilder(
image.java.toString(),
buildRule.getProject().getProperty("thrower.main-class"));
builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
builder.redirectErrorStream(true);

Process process = builder.start();
try (BufferedReader linesReader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {

Assert.assertTrue(
"Checking that stack trace contains no debug information.",
linesReader.lines().noneMatch(
l -> l.matches(".*\\([^)]*:[0-9]+\\)")));
}
process.waitFor();
}

@Test
public void testDeduplicationOfLicenses() {
buildRule.executeTarget("dedup");
ImageStructure image = verifyImageBuiltNormally();

String helloModuleName =
buildRule.getProject().getProperty("hello.mod");
String smileModuleName =
buildRule.getProject().getProperty("smile.mod");

Assert.assertNotNull("Checking that 'hello.mod' property was set.",
helloModuleName);
Assert.assertNotNull("Checking that 'smile.mod' property was set.",
smileModuleName);

Assume.assumeFalse("Checking that this operating system"
+ " supports symbolic links as a means of license de-duplication.",
System.getProperty("os.name").contains("Windows"));

Path legal = image.root.toPath().resolve("legal");

Path[] licenses = {
legal.resolve(helloModuleName).resolve("USELESSLICENSE"),
legal.resolve(smileModuleName).resolve("USELESSLICENSE"),
};

int nonLinkCount = 0;
for (Path license : licenses) {
if (!Files.isSymbolicLink(license)) {
nonLinkCount++;
}
}

Assert.assertEquals(
"Checking that USELESSLICENSE only exists once in image "
+ "and all other instances are links to it.",
1, nonLinkCount);
}

@Test
public void testIgnoreSigning() {
buildRule.executeTarget("ignoresigning");
verifyImageBuiltNormally();
}

/**
* Should fail due to jlink rejecting identically named files whose
* contents are different.
*/
@Test
public void testDeduplicationOfInconsistentLicenses() {
expected.expect(BuildException.class);
buildRule.executeTarget("dedup-identical");
}

@Test
public void testBindingOfServices()
throws IOException,
InterruptedException {
buildRule.executeTarget("bindservices");
ImageStructure image = verifyImageBuiltNormally();

String mainClass = buildRule.getProject().getProperty("inc.main-class");

ProcessBuilder builder = new ProcessBuilder(
image.java.toString(), mainClass);
builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
builder.redirectError(ProcessBuilder.Redirect.INHERIT);

Process process = builder.start();
try (BufferedReader linesReader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {

Assert.assertEquals(
"Checking that bindServices=false results in no providers in image.",
0, linesReader.lines().count());
}
process.waitFor();

image = new ImageStructure(
new File(buildRule.getProject().getProperty("image2")));

Assert.assertTrue("Checking that image2 was successfully created.",
image.root.exists());
Assert.assertTrue("Checking that image2 has java executable.",
image.java.exists());

builder = new ProcessBuilder(image.java.toString(), mainClass);
builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
builder.redirectError(ProcessBuilder.Redirect.INHERIT);

process = builder.start();
try (BufferedReader linesReader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {

Assert.assertEquals(
"Checking that bindServices=true results in image with provider.",
5, linesReader.lines().count());
}
process.waitFor();
}

private String runJlink(final String... args) {
ToolProvider jlink = ToolProvider.findFirst("jlink").orElseThrow(
() -> new RuntimeException("jlink tool not found in JDK."));

ByteArrayOutputStream stdout = new ByteArrayOutputStream();
ByteArrayOutputStream stderr = new ByteArrayOutputStream();

int exitCode;
try (PrintStream out = new PrintStream(stdout);
PrintStream err = new PrintStream(stderr)) {

exitCode = jlink.run(out, err, args);
}

if (exitCode != 0) {
throw new RuntimeException(
"jlink failed, output is: " + stdout + ", error is: " + stderr);
}

return stdout.toString();
}
}

+ 115
- 0
src/tests/junit/org/apache/tools/ant/types/ModuleVersionTest.java View File

@@ -0,0 +1,115 @@
package org.apache.tools.ant.types;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

/**
* Tests {@link ModuleVersion} class.
*/
public class ModuleVersionTest {
@Rule
public final ExpectedException expected = ExpectedException.none();

@Test
public void testModuleVersionStringNumberPreBuild() {
ModuleVersion moduleVersion = new ModuleVersion();

moduleVersion.setNumber("1.1.3");
moduleVersion.setPreRelease("ea");
moduleVersion.setBuild("25");

String versionStr = moduleVersion.toModuleVersionString();

Assert.assertNotNull("Checking for non-null module version string.",
versionStr);
Assert.assertTrue("Checking for correct module version string.",
versionStr.matches("1\\.1\\.3[-+]ea\\+25"));
}

@Test
public void testModuleVersionStringNumberPre() {
ModuleVersion moduleVersion = new ModuleVersion();

moduleVersion.setNumber("1.1.3");
moduleVersion.setPreRelease("ea");

String versionStr = moduleVersion.toModuleVersionString();

Assert.assertNotNull("Checking for non-null module version string.",
versionStr);
Assert.assertTrue("Checking for correct module version string.",
versionStr.matches("1\\.1\\.3[-+]ea"));
}

@Test
public void testModuleVersionStringNumberBuild() {
ModuleVersion moduleVersion = new ModuleVersion();

moduleVersion.setNumber("1.1.3");
moduleVersion.setBuild("25");

String versionStr = moduleVersion.toModuleVersionString();

Assert.assertNotNull("Checking for non-null module version string.",
versionStr);
Assert.assertTrue("Checking for correct module version string.",
versionStr.matches("1\\.1\\.3[-+]\\+25"));
}

@Test
public void testModuleVersionStringNumberOnly() {
ModuleVersion moduleVersion = new ModuleVersion();

moduleVersion.setNumber("1.1.3");

String versionStr = moduleVersion.toModuleVersionString();

Assert.assertNotNull("Checking for non-null module version string.",
versionStr);
Assert.assertEquals("Checking for correct module version string.",
"1.1.3", versionStr);
}

@Test
public void testModuleVersionStringNullNumber() {
expected.expect(IllegalStateException.class);

ModuleVersion moduleVersion = new ModuleVersion();
moduleVersion.toModuleVersionString();
}

@Test
public void testNullNumber() {
expected.expect(NullPointerException.class);

ModuleVersion moduleVersion = new ModuleVersion();
moduleVersion.setNumber(null);
}

@Test
public void testInvalidNumber() {
expected.expect(IllegalArgumentException.class);

ModuleVersion moduleVersion = new ModuleVersion();
moduleVersion.setNumber("1-1-3");
}

@Test
public void testInvalidNumber2() {
expected.expect(IllegalArgumentException.class);

ModuleVersion moduleVersion = new ModuleVersion();
moduleVersion.setNumber("1.1+3");
}

@Test
public void testInvalidPreRelease() {
expected.expect(IllegalArgumentException.class);

ModuleVersion moduleVersion = new ModuleVersion();
moduleVersion.setNumber("1.1.3");
moduleVersion.setPreRelease("ea+interim");
}
}

Loading…
Cancel
Save