http://git-wip-us.apache.org/repos/asf/ant/blob/343dff90/src/etc/testcases/taskdefs/link.xml ---------------------------------------------------------------------- diff --git a/src/etc/testcases/taskdefs/link.xml b/src/etc/testcases/taskdefs/link.xml new file mode 100644 index 0000000..3212475 --- /dev/null +++ b/src/etc/testcases/taskdefs/link.xml @@ -0,0 +1,1088 @@ +<?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="linktest" default="nil" xmlns:unless="ant:unless"> + <import file="../buildfiletest-base.xml"/> + + <target name="nil"/> + + <target name="-dirs"> + <mkdir dir="${input}"/> + + <property name="jmods" value="${output}/jmods"/> + <mkdir dir="${jmods}"/> + + <property name="manpages" value="${output}/manpages"/> + <mkdir dir="${manpages}"/> + + <property name="image" value="${output}/image"/> + + <!-- + Before Java 10, JDK's jmods directory must be explicitly included + in module paths. + --> + <condition property="jdkmods" value="" + else="${java.home}/jmods${path.separator}"> + <hasmethod classname="java.util.List" method="copyOf"/> + </condition> + </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.legal" value="${hello.root}/legal"/> + + <property name="hello.mod" value="org.apache.tools.ant.test.hello"/> + <property name="hello.pkg" value="org.apache.tools.ant.test.hello"/> + <property name="hello.pkg.path" value="org/apache/tools/ant/test/hello"/> + <property name="hello.pkg.dir" value="${hello.src}/${hello.pkg.path}"/> + + <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(String[] resources) { + Logger logger = Logger.getLogger(HelloWorld.class.getName()); + logger.info("HELLO WORLD"); + + for (String resource : resources) { + Object url = HelloWorld.class.getResource(resource); + logger.info(resource + " " + (url != null ? "present" : "absent")); + } + } + + public static void main(String[] args) { + new HelloWorld().run(args); + } +} + </echo> + <echo file="${hello.src}/module-info.java"> +module ${hello.mod} { + exports ${hello.pkg}; + requires java.logging; +} + </echo> + + <mkdir dir="${hello.classes}"/> + <javac srcdir="${hello.src}" destdir="${hello.classes}" + includeAntRuntime="false" debug="true"/> + <echo file="${hello.classes}/${hello.pkg.path}/resource1.txt" + message="First HelloWorld resource."/> + <echo file="${hello.classes}/${hello.pkg.path}/resource2.txt" + message="Second HelloWorld resource."/> + + <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.legal" value="${smile.root}/legal"/> + + <property name="smile.mod" value="org.apache.tools.ant.test.smile"/> + <property name="smile.pkg" value="org.apache.tools.ant.test.smile"/> + <property name="smile.pkg.path" value="org/apache/tools/ant/test/smile"/> + <property name="smile.pkg.dir" value="${smile.src}/${smile.pkg.path}"/> + + <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(String[] resources) { + Logger logger = Logger.getLogger(Smile.class.getName()); + logger.info("\u263a\u263b\u263a\u263b"); + + for (String resource : resources) { + Object url = HelloWorld.class.getResource(resource); + logger.info(resource + " " + (url != null ? "present" : "absent")); + } + } + + public static void main(String[] args) { + new Smile().run(args); + new HelloWorld().run(args); + } +} + </echo> + <echo file="${smile.src}/module-info.java"> +module ${smile.mod} { + exports ${smile.pkg}; + requires java.logging; + requires ${hello.mod}; +} + </echo> + + <mkdir dir="${smile.classes}"/> + <javac srcdir="${smile.src}" destdir="${smile.classes}" + includeAntRuntime="false" debug="true" + modulepath="${hello.jar}"/> + <echo file="${smile.classes}/${smile.pkg.path}/resource1.txt" + message="First Smile resource."/> + <echo file="${smile.classes}/${smile.pkg.path}/resource2.txt" + message="Second Smile resource."/> + + <mkdir dir="${smile.jar.dir}"/> + <jar destfile="${smile.jar}" basedir="${smile.classes}"/> + </target> + + <target name="-manpages" depends="-dirs"> + + <property name="man1" value="${manpages}/man1.1"/> + <property name="man2" value="${manpages}/man2.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> + + <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> + </target> + + <target name="-legal" depends="-smile"> + <property name="sharedlicense.name" value="USELESSLICENSE"/> + <property name="sharedlicense.text" + value="This license grants no particular rights."/> + + <mkdir dir="${hello.legal}"/> + <mkdir dir="${smile.legal}"/> + + <echo file="${hello.legal}/${sharedlicense.name}" + message="${sharedlicense.text}"/> + <echo file="${smile.legal}/${sharedlicense.name}" + message="${sharedlicense.text}"/> + + <property name="uniquelicense.name" value="UNIQUELICENSE"/> + <echo file="${hello.legal}/${uniquelicense.name}" + message="License for ${hello.mod}."/> + <echo file="${smile.legal}/${uniquelicense.name}" + message="License for ${smile.mod}."/> + </target> + + <target name="-jmods" depends="-smile,-legal"> + <property name="hello.jmod" value="${jmods}/hello.jmod"/> + <property name="smile.jmod" value="${jmods}/smile.jmod"/> + + <jmod destfile="${hello.jmod}" + classpath="${hello.jar}" + legalpath="${hello.legal}" + manpath="${manpages}"/> + + <jmod destfile="${smile.jmod}" + classpath="${smile.jar}" + legalpath="${smile.legal}" + manpath="${manpages}" + modulepath="${hello.jar.dir}"/> + </target> + + <target name="modulepath" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"/> + </target> + + <target name="imageNewerThanJmods" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"/> + + <chmod perm="a+w"> + <fileset dir="${image}"/> + </chmod> + + <property name="dateformat" value="yyyy-MM-dd HH:mm:ss.SSS"/> + <tstamp> + <format property="future" pattern="${dateformat}" offset="1" unit="hour"/> + </tstamp> + <touch datetime="${future}" pattern="${dateformat}"> + <fileset dir="${image}"/> + </touch> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"/> + </target> + + <target name="nomodulepath" depends="-jmods"> + <link destdir="${image}" + modules="${hello.mod},${smile.mod}"/> + </target> + + <target name="nomodules" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}"/> + </target> + + <target name="modulepathref" depends="-jmods"> + <path id="modules"> + <pathelement location="${jdkmods}" unless:blank="${jdkmods}"/> + <pathelement location="${jmods}"/> + </path> + <link destdir="${image}" modulepathref="modules" + modules="${hello.mod},${smile.mod}"/> + </target> + + <target name="modulepath-nested" depends="-jmods"> + <link destdir="${image}" modules="${hello.mod},${smile.mod}"> + <modulepath> + <pathelement location="${jdkmods}" unless:blank="${jdkmods}"/> + <pathelement location="${jmods}"/> + </modulepath> + </link> + </target> + + <target name="modulepath-both" depends="-jmods"> + <property name="mod1" value="${output}/mod1"/> + <property name="mod2" value="${output}/mod2"/> + + <mkdir dir="${mod1}"/> + <mkdir dir="${mod2}"/> + + <copy file="${hello.jmod}" todir="${mod1}"/> + <copy file="${smile.jmod}" todir="${mod2}"/> + + <link destdir="${image}" modulepath="${jdkmods}${mod1}" + modules="${hello.mod},${smile.mod}"> + <modulepath> + <pathelement location="${mod2}"/> + </modulepath> + </link> + </target> + + <target name="modules-nested" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}"> + <module name="${hello.mod}"/> + <module name="${smile.mod}"/> + </link> + </target> + + <target name="modules-nested-missing-name" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}"> + <module/> + <module name="${smile.mod}"/> + </link> + </target> + + <target name="modules-both" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" modules="${hello.mod}"> + <module name="${smile.mod}"/> + <!-- + Adding this launcher guarantees an error occurs unless + both modules are found. + --> + <launcher name="Smile" module="${smile.mod}" mainclass="${smile.main-class}"/> + </link> + </target> + + <target name="observable" depends="-jmods"> + <link destdir="${image}" modulepath="${jmods};${java.home}/jmods" + modules="${hello.mod},${smile.mod}" + observableModules="java.base"/> + </target> + + <target name="observable-nested" depends="-jmods"> + <link destdir="${image}" modulepath="${jmods};${java.home}/jmods" + modules="${hello.mod},${smile.mod}"> + <observableModule name="java.base"/> + </link> + </target> + + <target name="observable-nested-missing-name" depends="-jmods"> + <link destdir="${image}" modulepath="${jmods};${java.home}/jmods" + modules="${hello.mod},${smile.mod}"> + <observableModule/> + </link> + </target> + + <target name="observable-both" depends="-jmods"> + <link destdir="${image}" modulepath="${jmods};${java.home}/jmods" + modules="${hello.mod},${smile.mod}" + observableModules="java.base"> + <observableModule name="java.logging"/> + </link> + </target> + + <target name="launchers" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + launchers="Hello=${hello.mod}/${hello.main-class},Smile=${smile.mod}/${smile.main-class}"/> + </target> + + <target name="launchers-nested" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <launcher name="Hello" module="${hello.mod}" mainClass="${hello.main-class}"/> + <launcher name="Smile" module="${smile.mod}" mainClass="${smile.main-class}"/> + </link> + </target> + + <target name="launchers-nested-missing-name" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <launcher module="${hello.mod}" mainClass="${hello.main-class}"/> + <launcher name="Smile" module="${smile.mod}" mainClass="${smile.main-class}"/> + </link> + </target> + + <target name="launchers-nested-missing-module" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <launcher name="Hello" mainClass="${hello.main-class}"/> + <launcher name="Smile" module="${smile.mod}" mainClass="${smile.main-class}"/> + </link> + </target> + + <target name="launchers-both" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + launchers="Hello=${hello.mod}/${hello.main-class}"> + <launcher name="Smile" module="${smile.mod}" mainClass="${smile.main-class}"/> + </link> + </target> + + <target name="-localefinder" depends="-dirs"> + + <property name="localefinder.root" value="${input}/localefinder"/> + + <property name="localefinder.src" value="${localefinder.root}/src"/> + <property name="localefinder.classes" value="${localefinder.root}/classes"/> + <property name="localefinder.jar.dir" value="${localefinder.root}/jars"/> + <property name="localefinder.jar" value="${localefinder.jar.dir}/localefinder.jar"/> + + <property name="localefinder.mod" value="org.apache.tools.ant.test.localefinder"/> + <property name="localefinder.pkg" value="org.apache.tools.ant.test.localefinder"/> + <property name="localefinder.pkg.path" value="org/apache/tools/ant/test/localefinder"/> + <property name="localefinder.pkg.dir" value="${localefinder.src}/${localefinder.pkg.path}"/> + + <property name="localefinder.main-class" value="${localefinder.pkg}.LocaleFinder"/> + + <mkdir dir="${localefinder.pkg.dir}"/> + <echo file="${localefinder.pkg.dir}/LocaleFinder.java"><![CDATA[ +package ${localefinder.pkg}; + +import java.util.Locale; +import java.util.Arrays; +import java.util.Set; +import java.util.HashSet; + +public class LocaleFinder { + public static void main(String[] languagesToFind) { + Set<String> languages = new HashSet<>(); + for (Locale locale : Locale.getAvailableLocales()) { + languages.add(locale.getLanguage()); + } + + boolean matched = languages.containsAll(Arrays.asList(languagesToFind)); + System.exit(matched ? 0 : 1); + } +}]]> + </echo> + <echo file="${localefinder.src}/module-info.java"> +module ${localefinder.mod} { + exports ${localefinder.pkg}; + requires jdk.localedata; +} + </echo> + + <mkdir dir="${localefinder.classes}"/> + <javac srcdir="${localefinder.src}" + destdir="${localefinder.classes}" + includeAntRuntime="false" + debug="true"/> + + <mkdir dir="${localefinder.jar.dir}"/> + <jar destfile="${localefinder.jar}" basedir="${localefinder.classes}"/> + + <property name="localefinder.jmod" value="${jmods}/localefinder.jmod"/> + <jmod destfile="${localefinder.jmod}" classpath="${localefinder.jar}"/> + </target> + + <target name="locales" depends="-localefinder"> + <link destdir="${image}" modulepath="${jmods};${java.home}/jmods" + modules="${localefinder.mod},jdk.localedata" + locales="zh,in"/> + </target> + + <target name="locales-nested" depends="-localefinder"> + <link destdir="${image}" modulepath="${jmods};${java.home}/jmods" + modules="${localefinder.mod},jdk.localedata"> + <locale name="zh"/> + <locale name="in"/> + </link> + </target> + + <target name="locales-nested-missing-name" depends="-localefinder"> + <link destdir="${image}" modulepath="${jmods};${java.home}/jmods" + modules="${localefinder.mod},jdk.localedata"> + <locale/> + <locale name="in"/> + </link> + </target> + + <target name="locales-both" depends="-localefinder"> + <link destdir="${image}" modulepath="${jmods};${java.home}/jmods" + modules="${localefinder.mod},jdk.localedata" + locales="zh"> + <locale name="in"/> + </link> + </target> + + <target name="ordering" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + resourceOrder="**/resource1.txt"/> + </target> + + <target name="ordering-nested" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <resourceOrder pattern="**/resource1.txt"/> + </link> + </target> + + <target name="ordering-nested-file" depends="-jmods"> + <tempfile property="listfile" destdir="${output}" suffix=".lst"/> + <echo file="${listfile}" message="**/resource1.txt"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <resourceOrder listfile="${listfile}"/> + </link> + </target> + + <target name="ordering-nested-no-attr" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <resourceOrder/> + </link> + </target> + + <target name="ordering-nested-both" depends="-jmods"> + <tempfile property="listfile" destdir="${output}" suffix=".lst"/> + <echo file="${listfile}" message="**/resource1.txt"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <resourceOrder pattern="**/resource1.txt" listfile="${listfile}"/> + </link> + </target> + + <target name="ordering-both" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + resourceOrder="**/resource1.txt"> + <resourceOrder pattern="**/resource2.txt"/> + </link> + </target> + + <target name="excluderesources" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + excludeResources="**/resource1.txt"/> + </target> + + <target name="excluderesources-nested" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <excludeResources pattern="**/resource1.txt"/> + </link> + </target> + + <target name="excluderesources-nested-file" depends="-jmods"> + <tempfile property="listfile" destdir="${output}" suffix=".lst"/> + <echo file="${listfile}" message="**/resource1.txt"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <excludeResources listfile="${listfile}"/> + </link> + </target> + + <target name="excluderesources-nested-no-attr" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <excludeResources/> + </link> + </target> + + <target name="excluderesources-nested-both" depends="-jmods"> + <tempfile property="listfile" destdir="${output}" suffix=".lst"/> + <echo file="${listfile}" message="**/resource1.txt"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <excludeResources pattern="**/resource1.txt" listfile="${listfile}"/> + </link> + </target> + + <target name="excluderesources-both" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + excludeResources="**/resource1.txt"> + <excludeResources pattern="**/resource2.txt"/> + </link> + </target> + + <target name="excludefiles" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + excludeFiles="**/file1.txt"/> + </target> + + <target name="excludefiles-nested" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <excludeFiles pattern="**/file1.txt"/> + </link> + </target> + + <target name="excludefiles-nested-file" depends="-jmods"> + <tempfile property="listfile" destdir="${output}" suffix=".lst"/> + <echo file="${listfile}" message="**/file1.txt"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <excludeFiles listfile="${listfile}"/> + </link> + </target> + + <target name="excludefiles-nested-no-attr" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <excludeFiles/> + </link> + </target> + + <target name="excludefiles-nested-both" depends="-jmods"> + <tempfile property="listfile" destdir="${output}" suffix=".lst"/> + <echo file="${listfile}" message="**/file1.txt"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <excludeFiles pattern="**/file1.txt" listfile="${listfile}"/> + </link> + </target> + + <target name="excludefiles-both" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + excludeFiles="**/file1.txt"> + <excludeFiles pattern="**/file2.txt"/> + </link> + </target> + + <target name="includeheaders" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + includeHeaders="false"/> + </target> + + <target name="includemanpages" depends="-manpages,-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + includeManPages="false"/> + </target> + + <target name="includenativecommands" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + includeNativeCommands="false"/> + </target> + + <target name="compression" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"/> + + <property name="compressed-image" value="${output}/image2"/> + <link destdir="${compressed-image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + compress="zip"/> + </target> + + <target name="compression-nested" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"/> + + <property name="compressed-image" value="${output}/image2"/> + <link destdir="${compressed-image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <compress level="zip"/> + </link> + </target> + + <target name="compression-nested-no-attr" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <compress/> + </link> + </target> + + <target name="compression-both" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"/> + + <property name="compressed-image" value="${output}/image2"/> + <link destdir="${compressed-image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + compress="zip"> + <compress level="zip"/> + </link> + </target> + + <target name="endian" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" endianness="little"/> + </target> + + <target name="vm" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" vmType="server"/> + </target> + + <target name="releaseinfo-file" depends="-jmods"> + <property name="props" value="${output}/app.properties"/> + <echo file="${props}" message="test=true" encoding="ISO-8859-1"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo file="${props}"/> + </link> + </target> + + <target name="releaseinfo-delete" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <add key="test" value="true"/> + </releaseinfo> + <releaseinfo delete="test"/> + </link> + </target> + + <target name="releaseinfo-nested-delete" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <add key="test" value="true"/> + </releaseinfo> + <releaseinfo> + <delete key="test"/> + </releaseinfo> + </link> + </target> + + <target name="releaseinfo-nested-delete-no-key" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <delete/> + </releaseinfo> + </link> + </target> + + <target name="releaseinfo-nested-delete-both" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <add key="test" value="true"/> + <add key="foo" value="bar"/> + </releaseinfo> + <releaseinfo delete="test"> + <delete key="foo"/> + </releaseinfo> + </link> + </target> + + <target name="releaseinfo-add-file" depends="-jmods"> + <property name="props" value="${output}/app.properties"/> + <echo file="${props}" message="test=sí" encoding="ISO-8859-1"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <add file="${props}"/> + </releaseinfo> + </link> + </target> + + <target name="releaseinfo-add-file-charset" depends="-jmods"> + <property name="props" value="${output}/app.properties"/> + <echo file="${props}" message="test=sí" encoding="UTF-8"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <add file="${props}" charset="UTF-8"/> + </releaseinfo> + </link> + </target> + + <target name="releaseinfo-add-key" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <add key="test" value="true"/> + </releaseinfo> + </link> + </target> + + <target name="releaseinfo-add-no-value" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <add key="test"/> + </releaseinfo> + </link> + </target> + + <target name="releaseinfo-add-no-key" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <add value="true"/> + </releaseinfo> + </link> + </target> + + <target name="releaseinfo-add-file-and-key" depends="-jmods"> + <property name="props" value="${output}/app.properties"/> + <echo file="${props}" message="test=sí" encoding="UTF-8"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <add file="${props}" key="test"/> + </releaseinfo> + </link> + </target> + + <target name="releaseinfo-add-file-and-value" depends="-jmods"> + <property name="props" value="${output}/app.properties"/> + <echo file="${props}" message="test=sí" encoding="UTF-8"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"> + <releaseinfo> + <add file="${props}" value="true"/> + </releaseinfo> + </link> + </target> + + <target name="-thrower" depends="-dirs"> + + <property name="thrower.root" value="${input}/thrower"/> + + <property name="thrower.src" value="${thrower.root}/src"/> + <property name="thrower.classes" value="${thrower.root}/classes"/> + <property name="thrower.jar.dir" value="${thrower.root}/jars"/> + <property name="thrower.jar" value="${thrower.jar.dir}/thrower.jar"/> + + <property name="thrower.mod" value="org.apache.tools.ant.test.thrower"/> + <property name="thrower.pkg" value="org.apache.tools.ant.test.thrower"/> + <property name="thrower.pkg.path" value="org/apache/tools/ant/test/thrower"/> + <property name="thrower.pkg.dir" value="${thrower.src}/${thrower.pkg.path}"/> + + <property name="thrower.main-class" value="${thrower.pkg}.Thrower"/> + + <mkdir dir="${thrower.pkg.dir}"/> + <echo file="${thrower.pkg.dir}/Thrower.java"> +package ${thrower.pkg}; + +public class Thrower { + public static void main(String[] args) { + throw new RuntimeException("Deliberate exception."); + } +} + </echo> + <echo file="${thrower.src}/module-info.java"> +module ${thrower.mod} { + exports ${thrower.pkg}; +} + </echo> + + <mkdir dir="${thrower.classes}"/> + <javac srcdir="${thrower.src}" destdir="${thrower.classes}" + includeAntRuntime="false" debug="true"/> + + <mkdir dir="${thrower.jar.dir}"/> + <jar destfile="${thrower.jar}" basedir="${thrower.classes}"/> + </target> + + <target name="debug" depends="-thrower"> + <property name="thrower.jmod" value="${jmods}/thrower.jmod"/> + <jmod destfile="${thrower.jmod}" classpath="${thrower.jar}"/> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${thrower.mod}" debug="false"/> + </target> + + <target name="dedup" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}"/> + </target> + + <target name="dedup-identical" depends="-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + checkDuplicateLegal="true"/> + </target> + + <target name="-sign" depends="-smile,-legal"> + <property name="keystore.file" value="${output}/keystore"/> + <property name="keystore.alias" value="test"/> + <property name="keystore.password" value="swordfish"/> + + <condition property="execsuffix" value=".exe" else=""> + <os family="windows"/> + </condition> + <exec executable="${java.home}/bin/keytool${execsuffix}" failonerror="true"> + <arg value="-genkeypair"/> + <arg value="-keystore"/> + <arg file="${keystore.file}"/> + <arg value="-storepass"/> + <arg value="${keystore.password}"/> + <arg value="-keypass"/> + <arg value="${keystore.password}"/> + <arg value="-alias"/> + <arg value="${keystore.alias}"/> + <arg value="-dname"/> + <arg value="CN=Ant Unit Test, OU=Ant, O=Apache Software Foundation, L=Unknown, ST=Unknown, C=US"/> + </exec> + + <signjar jar="${hello.jar}" + keystore="${keystore.file}" + alias="${keystore.alias}" + storepass="${keystore.password}"/> + </target> + + <target name="ignoresigning" depends="-sign,-jmods"> + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${hello.mod},${smile.mod}" + ignoreSigning="true"/> + </target> + + <target name="bindservices" depends="-dirs"> + <property name="inc.root" value="${input}/inc"/> + + <property name="inc.src" value="${inc.root}/src"/> + <property name="inc.classes" value="${inc.root}/classes"/> + <property name="inc.jar.dir" value="${inc.root}/jars"/> + <property name="inc.jar" value="${inc.jar.dir}/inc.jar"/> + + <property name="inc.mod" value="org.apache.tools.ant.test.inc"/> + <property name="inc.pkg" value="org.apache.tools.ant.test.inc"/> + <property name="inc.pkg.path" value="org/apache/tools/ant/test/inc"/> + <property name="inc.pkg.dir" value="${inc.src}/${inc.pkg.path}"/> + + <property name="inc.main-class" value="${inc.pkg}.Incrementer"/> + + <mkdir dir="${inc.pkg.dir}"/> + <echo file="${inc.pkg.dir}/IncrementProvider.java"> +package ${inc.pkg}; + +public interface IncrementProvider { + int getIncrement(); +} + </echo> + <echo file="${inc.pkg.dir}/Incrementer.java"> +package ${inc.pkg}; + +import java.util.ServiceLoader; + +public class Incrementer { + public static void main(String[] args) { + for (IncrementProvider provider : ServiceLoader.load(IncrementProvider.class)) { + int n = 0; + for (int i = 0; i < 5; i++) { + n += provider.getIncrement(); + System.out.println(n); + } + } + } +} + </echo> + <echo file="${inc.src}/module-info.java"> +module ${inc.mod} { + exports ${inc.pkg}; + uses ${inc.pkg}.IncrementProvider; +} + </echo> + + <mkdir dir="${inc.classes}"/> + <javac srcdir="${inc.src}" destdir="${inc.classes}" + includeAntRuntime="false" debug="true"/> + + <mkdir dir="${inc.jar.dir}"/> + <jar destfile="${inc.jar}" basedir="${inc.classes}"/> + + <property name="provider.root" value="${input}/provider"/> + <property name="provider.src" value="${provider.root}/src"/> + <property name="provider.classes" value="${provider.root}/classes"/> + <property name="provider.jar.dir" value="${provider.root}/jars"/> + <property name="provider.jar" value="${provider.jar.dir}/provider.jar"/> + + <property name="provider.mod" value="org.apache.tools.ant.test.provider"/> + <property name="provider.pkg" value="org.apache.tools.ant.test.provider"/> + <property name="provider.pkg.path" value="org/apache/tools/ant/test/provider"/> + <property name="provider.pkg.dir" value="${provider.src}/${provider.pkg.path}"/> + + <mkdir dir="${provider.pkg.dir}"/> + <echo file="${provider.pkg.dir}/ByTwoProvider.java"> +package ${provider.pkg}; + +import ${inc.pkg}.IncrementProvider; + +public class ByTwoProvider +implements IncrementProvider { + @Override + public int getIncrement() { + return 2; + } +} + </echo> + <echo file="${provider.src}/module-info.java"> +module ${provider.mod} { + exports ${provider.pkg}; + requires transitive ${inc.mod}; + provides ${inc.pkg}.IncrementProvider with ${provider.pkg}.ByTwoProvider; +} + </echo> + + <mkdir dir="${provider.classes}"/> + <javac srcdir="${provider.src}" destdir="${provider.classes}" + includeAntRuntime="false" debug="true" + modulepath="${inc.classes}"/> + + <mkdir dir="${provider.jar.dir}"/> + <jar destfile="${provider.jar}" basedir="${provider.classes}"/> + + <property name="inc.jmod" value="${jmods}/inc.jmod"/> + <property name="provider.jmod" value="${jmods}/provider.jmod"/> + + <jmod destfile="${inc.jmod}" + classpath="${inc.jar}"/> + + <jmod destfile="${provider.jmod}" + classpath="${provider.jar}" + modulepath="${inc.jar.dir}"/> + + <property name="image2" value="${output}/image2"/> + + <link destdir="${image}" modulepath="${jdkmods}${jmods}" + modules="${inc.mod}" + bindServices="false"/> + + <link destdir="${image2}" modulepath="${jdkmods}${jmods}" + modules="${inc.mod}" + bindServices="true"/> + </target> +</project>
http://git-wip-us.apache.org/repos/asf/ant/blob/343dff90/src/main/org/apache/tools/ant/taskdefs/defaults.properties ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/src/main/org/apache/tools/ant/taskdefs/defaults.properties index a78cedb..d847ae9 100644 --- a/src/main/org/apache/tools/ant/taskdefs/defaults.properties +++ b/src/main/org/apache/tools/ant/taskdefs/defaults.properties @@ -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 http://git-wip-us.apache.org/repos/asf/ant/blob/343dff90/src/main/org/apache/tools/ant/taskdefs/modules/Jmod.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/modules/Jmod.java b/src/main/org/apache/tools/ant/taskdefs/modules/Jmod.java new file mode 100644 index 0000000..2284ed3 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/modules/Jmod.java @@ -0,0 +1,1282 @@ +/* + * 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.File; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.IOException; + +import java.nio.file.Files; + +import java.util.Collection; +import java.util.List; +import java.util.ArrayList; + +import java.util.Map; +import java.util.LinkedHashMap; + +import java.util.Collections; + +import java.util.spi.ToolProvider; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.ResourceUtils; + +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.ModuleVersion; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; + +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.types.resources.Union; + +/** + * Creates a linkable .jmod file from a modular jar file, and optionally from + * other resource files such as native libraries and documents. Equivalent + * to the JDK's + * <a href="https://docs.oracle.com/en/java/javase/11/tools/jmod.html">jmod</a> + * tool. + * <p> + * Supported attributes: + * <dl> + * <dt>{@code destFile} + * <dd>Required, jmod file to create. + * <dt>{@code classpath} + * <dt>{@code classpathref} + * <dd>Where to locate files to be placed in the jmod file. + * <dt>{@code modulepath} + * <dt>{@code modulepathref} + * <dd>Where to locate dependencies. + * <dt>{@code commandpath} + * <dt>{@code commandpathref} + * <dd>Directories containing native commands to include in jmod. + * <dt>{@code headerpath} + * <dt>{@code headerpathref} + * <dd>Directories containing header files to include in jmod. + * <dt>{@code configpath} + * <dt>{@code configpathref} + * <dd>Directories containing user-editable configuration files + * to include in jmod. + * <dt>{@code legalpath} + * <dt>{@code legalpathref} + * <dd>Directories containing legal licenses and notices to include in jmod. + * <dt>{@code nativelibpath} + * <dt>{@code nativelibpathref} + * <dd>Directories containing native libraries to include in jmod. + * <dt>{@code manpath} + * <dt>{@code manpathref} + * <dd>Directories containing man pages to include in jmod. + * <dt>{@code version} + * <dd>Module <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/module/ModuleDescriptor.Version.html">version</a>. + * <dt>{@code mainclass} + * <dd>Main class of module. + * <dt>{@code platform} + * <dd>The target platform for the jmod. A particular JDK's platform + * can be seen by running + * <code>jmod describe $JDK_HOME/jmods/java.base.jmod | grep -i platform</code>. + * <dt>{@code hashModulesPattern} + * <dd>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. + * <dt>{@code resolveByDefault} + * <dd>Boolean indicating whether the jmod should be one of + * the default resolved modules in an application. Default is true. + * <dt>{@code moduleWarnings} + * <dd>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} + * <dd>Warn if module is deprecated + * <dt>{@code leaving} + * <dd>Warn if module is deprecated for removal + * <dt>{@code incubating} + * <dd>Warn if module is an incubating (not yet official) module + * </dl> + * </dl> + * + * <p> + * Supported nested elements: + * <dl> + * <dt>{@code <classpath>} + * <dd>Path indicating where to locate files to be placed in the jmod file. + * <dt>{@code <modulepath>} + * <dd>Path indicating where to locate dependencies. + * <dt>{@code <commandpath>} + * <dd>Path of directories containing native commands to include in jmod. + * <dt>{@code <headerpath>} + * <dd>Path of directories containing header files to include in jmod. + * <dt>{@code <configpath>} + * <dd>Path of directories containing user-editable configuration files + * to include in jmod. + * <dt>{@code <legalpath>} + * <dd>Path of directories containing legal notices to include in jmod. + * <dt>{@code <nativelibpath>} + * <dd>Path of directories containing native libraries to include in jmod. + * <dt>{@code <manpath>} + * <dd>Path of directories containing man pages to include in jmod. + * <dt>{@code <version>} + * <dd><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. + * Must have a required {@code number} attribute. May also have optional + * {@code preRelease} and {@code build} attributes. + * <dt>{@code <moduleWarning>} + * <dd>Has one required attribute, {@code reason}. See {@code moduleWarnings} + * attribute above. This element may be specified multiple times. + * </dl> + * <p> + * destFile and classpath are required data. + */ +public class Jmod +extends Task { + /** Location of jmod file to be created. */ + private File jmodFile; + + /** + * Path of files (usually jar files or directories containing + * compiled classes) from which to create jmod. + */ + private Path classpath; + + /** + * Path of directories containing modules on which the modules + * in the classpath depend. + */ + private Path modulePath; + + /** + * Path of directories containing executable files to bundle in the + * created jmod. + */ + private Path commandPath; + + /** + * Path of directories containing configuration files to bundle in the + * created jmod. + */ + private Path configPath; + + /** + * Path of directories containing includable header files (such as for + * other languages) to bundle in the created jmod. + */ + private Path headerPath; + + /** + * Path of directories containing legal license files to bundle + * in the created jmod. + */ + private Path legalPath; + + /** + * Path of directories containing native libraries needed by classes + * in the modules comprising the created jmod. + */ + private Path nativeLibPath; + + /** + * Path of directories containing manual pages to bundle + * in the created jmod. + */ + private Path manPath; + + /** + * Module version of jmod. Either this or {@link #moduleVersion} + * may be set. + */ + private String version; + + /** Module version of jmod. Either this or {@link #version} may be set. */ + private ModuleVersion moduleVersion; + + /** + * Main class to execute, if Java attempts to execute jmod's module + * without specifying a main class explicitly. + */ + private String mainClass; + + /** + * Target platform of created jmod. Examples are {@code windows-amd64} + * and {@code linux-amd64}. Target platform is an attribute + * of each JDK, which can be seen by executing + * <code>jmod describe $JDK_HOME/jmods/java.base.jmod</code> and + * searching the output for a line starting with {@code platform}. + */ + private String platform; + + /** + * Regular expression matching names of modules which depend on the + * the created jmod's module, for which hashes should be added to the + * created jmod. + */ + private String hashModulesPattern; + + /** + * Whether the created jmod should be seen by Java when present in a + * module path, even if not explicitly named. Normally true. + */ + private boolean resolveByDefault = true; + + /** + * Reasons why module resolution during jmod creation may emit warnings. + */ + private final List<ResolutionWarningSpec> moduleWarnings = + new ArrayList<>(); + + /** + * Attribute containing the location of the jmod file to create. + * + * @return location of jmod file + * + * @see #setDestFile(File) + */ + public File getDestFile() { + return jmodFile; + } + + /** + * Sets attribute containing the location of the jmod file to create. + * This value is required. + * + * @param file location where jmod file will be created. + */ + public void setDestFile(final File file) { + this.jmodFile = file; + } + + /** + * Adds an unconfigured {@code <classpath>} child element which can + * specify the files which will comprise the created jmod. + * + * @return new, unconfigured child element + * + * @see #setClasspath(Path) + */ + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(getProject()); + } + return classpath.createPath(); + } + + /** + * Attribute which specifies the files (usually modular .jar files) + * which will comprise the created jmod file. + * + * @return path of constituent files + * + * @see #setClasspath(Path) + */ + public Path getClasspath() { + return classpath; + } + + /** + * Sets attribute specifying the files that will comprise the created jmod + * file. Usually this contains a single modular .jar file. + * <p> + * The classpath is required and must not be empty. + * + * @param path path of files that will comprise jmod + * + * @see #createClasspath() + */ + public void setClasspath(final Path path) { + if (classpath == null) { + this.classpath = path; + } else { + classpath.append(path); + } + } + + /** + * Sets {@linkplain #setClasspath(Path) classpath attribute} from a + * path reference. + * + * @param ref reference to path which will act as classpath + */ + public void setClasspathRef(final Reference ref) { + createClasspath().setRefid(ref); + } + + /** + * Creates a child {@code <modulePath>} element which can contain a + * path of directories containing modules upon which modules in the + * {@linkplain #setClasspath(Path) classpath} depend. + * + * @return new, unconfigured child element + * + * @see #setModulePath(Path) + */ + public Path createModulePath() { + if (modulePath == null) { + modulePath = new Path(getProject()); + } + return modulePath.createPath(); + } + + /** + * Attribute containing path of directories which contain modules on which + * the created jmod's {@linkplain #setClasspath(Path) constituent modules} + * depend. + * + * @return path of directories containing modules needed by + * classpath modules + * + * @see #setModulePath(Path) + */ + public Path getModulePath() { + return modulePath; + } + + /** + * Sets attribute containing path of directories which contain modules + * on which the created jmod's + * {@linkplain #setClasspath(Path) constituent modules} depend. + * + * @param path path of directories containing modules needed by + * classpath modules + * + * @see #createModulePath() + */ + public void setModulePath(final Path path) { + if (modulePath == null) { + this.modulePath = path; + } else { + modulePath.append(path); + } + } + + /** + * Sets {@linkplain #setModulePath(Path) module path} + * from a path reference. + * + * @param ref reference to path which will act as module path + */ + public void setModulePathRef(final Reference ref) { + createModulePath().setRefid(ref); + } + + /** + * Creates a child element which can contain a list of directories + * containing native executable files to include in the created jmod. + * + * @return new, unconfigured child element + * + * @see #setCommandPath(Path) + */ + public Path createCommandPath() { + if (commandPath == null) { + commandPath = new Path(getProject()); + } + return commandPath.createPath(); + } + + /** + * Attribute containing path of directories which contain native + * executable files to include in the created jmod. + * + * @return list of directories containing native executables + * + * @see #setCommandPath(Path) + */ + public Path getCommandPath() { + return commandPath; + } + + /** + * Sets attribute containing path of directories which contain native + * executable files to include in the created jmod. + * + * @param path list of directories containing native executables + * + * @see #createCommandPath() + */ + public void setCommandPath(final Path path) { + if (commandPath == null) { + this.commandPath = path; + } else { + commandPath.append(path); + } + } + + /** + * Sets {@linkplain #setCommandPath(Path) command path} + * from a path reference. + * + * @param ref reference to path which will act as command path + */ + public void setCommandPathRef(final Reference ref) { + createCommandPath().setRefid(ref); + } + + /** + * Creates a child element which can contain a list of directories + * containing user configuration files to include in the created jmod. + * + * @return new, unconfigured child element + * + * @see #setConfigPath(Path) + */ + public Path createConfigPath() { + if (configPath == null) { + configPath = new Path(getProject()); + } + return configPath.createPath(); + } + + /** + * Attribute containing list of directories which contain + * user configuration files. + * + * @return list of directories containing user configuration files + * + * @see #setConfigPath(Path) + */ + public Path getConfigPath() { + return configPath; + } + + /** + * Sets attribute containing list of directories which contain + * user configuration files. + * + * @param path list of directories containing user configuration files + * + * @see #createConfigPath() + */ + public void setConfigPath(final Path path) { + if (configPath == null) { + this.configPath = path; + } else { + configPath.append(path); + } + } + + /** + * Sets {@linkplain #setConfigPath(Path) configuration file path} + * from a path reference. + * + * @param ref reference to path which will act as configuration file path + */ + public void setConfigPathRef(final Reference ref) { + createConfigPath().setRefid(ref); + } + + /** + * Creates a child element which can contain a list of directories + * containing compile-time header files for third party use, to include + * in the created jmod. + * + * @return new, unconfigured child element + * + * @see #setHeaderPath(Path) + */ + public Path createHeaderPath() { + if (headerPath == null) { + headerPath = new Path(getProject()); + } + return headerPath.createPath(); + } + + /** + * Attribute containing a path of directories which hold compile-time + * header files for third party use, all of which will be included in the + * created jmod. + * + * @return path of directories containing header files + */ + public Path getHeaderPath() { + return headerPath; + } + + /** + * Sets attribute containing a path of directories which hold compile-time + * header files for third party use, all of which will be included in the + * created jmod. + * + * @param path path of directories containing header files + * + * @see #createHeaderPath() + */ + public void setHeaderPath(final Path path) { + if (headerPath == null) { + this.headerPath = path; + } else { + headerPath.append(path); + } + } + + /** + * Sets {@linkplain #setHeaderPath(Path) header path} + * from a path reference. + * + * @param ref reference to path which will act as header path + */ + public void setHeaderPathRef(final Reference ref) { + createHeaderPath().setRefid(ref); + } + + /** + * Creates a child element which can contain a list of directories + * containing license files to include in the created jmod. + * + * @return new, unconfigured child element + * + * @see #setLegalPath(Path) + */ + public Path createLegalPath() { + if (legalPath == null) { + legalPath = new Path(getProject()); + } + return legalPath.createPath(); + } + + /** + * Attribute containing list of directories which hold license files + * to include in the created jmod. + * + * @return path containing directories which hold license files + */ + public Path getLegalPath() { + return legalPath; + } + + /** + * Sets attribute containing list of directories which hold license files + * to include in the created jmod. + * + * @param path path containing directories which hold license files + * + * @see #createLegalPath() + */ + public void setLegalPath(final Path path) { + if (legalPath == null) { + this.legalPath = path; + } else { + legalPath.append(path); + } + } + + /** + * Sets {@linkplain #setLegalPath(Path) legal licenses path} + * from a path reference. + * + * @param ref reference to path which will act as legal path + */ + public void setLegalPathRef(final Reference ref) { + createLegalPath().setRefid(ref); + } + + /** + * Creates a child element which can contain a list of directories + * containing native libraries to include in the created jmod. + * + * @return new, unconfigured child element + * + * @see #setNativeLibPath(Path) + */ + public Path createNativeLibPath() { + if (nativeLibPath == null) { + nativeLibPath = new Path(getProject()); + } + return nativeLibPath.createPath(); + } + + /** + * Attribute containing list of directories which hold native libraries + * to include in the created jmod. + * + * @return path of directories containing native libraries + */ + public Path getNativeLibPath() { + return nativeLibPath; + } + + /** + * Sets attribute containing list of directories which hold native libraries + * to include in the created jmod. + * + * @param path path of directories containing native libraries + * + * @see #createNativeLibPath() + */ + public void setNativeLibPath(final Path path) { + if (nativeLibPath == null) { + this.nativeLibPath = path; + } else { + nativeLibPath.append(path); + } + } + + /** + * Sets {@linkplain #setNativeLibPath(Path) native library path} + * from a path reference. + * + * @param ref reference to path which will act as native library path + */ + public void setNativeLibPathRef(final Reference ref) { + createNativeLibPath().setRefid(ref); + } + + /** + * Creates a child element which can contain a list of directories + * containing man pages (program manuals, typically in troff format) + * to include in the created jmod. + * + * @return new, unconfigured child element + * + * @see #setManPath(Path) + */ + public Path createManPath() { + if (manPath == null) { + manPath = new Path(getProject()); + } + return manPath.createPath(); + } + + /** + * Attribute containing list of directories containing man pages + * to include in created jmod. Man pages are textual program manuals, + * typically in troff format. + * + * @return path containing directories which hold man pages to include + * in jmod + */ + public Path getManPath() { + return manPath; + } + + /** + * Sets attribute containing list of directories containing man pages + * to include in created jmod. Man pages are textual program manuals, + * typically in troff format. + * + * @param path path containing directories which hold man pages to include + * in jmod + * + * @see #createManPath() + */ + public void setManPath(final Path path) { + if (manPath == null) { + this.manPath = path; + } else { + manPath.append(path); + } + } + + /** + * Sets {@linkplain #setManPath(Path) man pages path} + * from a path reference. + * + * @param ref reference to path which will act as module path + */ + public void setManPathRef(final Reference ref) { + createManPath().setRefid(ref); + } + + /** + * Creates an uninitialized child element representing the version of + * the module represented by the created jmod. + * + * @return new, unconfigured child element + * + * @see #setVersion(String) + */ + public ModuleVersion createVersion() { + if (moduleVersion != null) { + throw new BuildException( + "No more than one <moduleVersion> element is allowed.", + getLocation()); + } + moduleVersion = new ModuleVersion(); + return moduleVersion; + } + + /** + * Attribute which specifies + * a <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/module/ModuleDescriptor.Version.html">module version</a> + * for created jmod. + * + * @return module version for created jmod + */ + public String getVersion() { + return version; + } + + /** + * Sets the <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/module/ModuleDescriptor.Version.html">module version</a> + * for the created jmod. + * + * @param version module version of created jmod + * + * @see #createVersion() + */ + public void setVersion(final String version) { + this.version = version; + } + + /** + * Attribute containing the class that acts as the executable entry point + * of the created jmod. + * + * @return fully-qualified name of jmod's main class + */ + public String getMainClass() { + return mainClass; + } + + /** + * Sets attribute containing the class that acts as the + * executable entry point of the created jmod. + * + * @param className fully-qualified name of jmod's main class + */ + public void setMainClass(final String className) { + this.mainClass = className; + } + + /** + * Attribute containing the platform for which the jmod + * will be built. Platform values are defined in the + * {@code java.base.jmod} of JDKs, and usually take the form + * <var>OS</var>{@code -}<var>architecture</var>. If unset, + * current platform is used. + * + * @return OS and architecture for which jmod will be built, or {@code null} + */ + public String getPlatform() { + return platform; + } + + /** + * Sets attribute containing the platform for which the jmod + * will be built. Platform values are defined in the + * {@code java.base.jmod} of JDKs, and usually take the form + * <var>OS</var>{@code -}<var>architecture</var>. If unset, + * current platform is used. + * <p> + * A JDK's platform can be viewed with a command like: + * <code>jmod describe $JDK_HOME/jmods/java.base.jmod | grep -i platform</code>. +o * + * @param platform platform for which jmod will be created, or {@code null} + */ + public void setPlatform(final String platform) { + this.platform = platform; + } + + /** + * Attribute containing a regular expression which specifies which + * of the modules that depend on the jmod being created should have + * hashes generated and added to the jmod. + * + * @return regex specifying which dependent modules should have + * their generated hashes included + */ + public String getHashModulesPattern() { + return hashModulesPattern; + } + + /** + * Sets attribute containing a regular expression which specifies which + * of the modules that depend on the jmod being created should have + * hashes generated and added to the jmod. + * + * @param pattern regex specifying which dependent modules should have + * their generated hashes included + */ + public void setHashModulesPattern(final String pattern) { + this.hashModulesPattern = pattern; + } + + /** + * Attribute indicating whether the created jmod should be visible + * in a module path, even when not specified explicitly. True by default. + * + * @return whether jmod should be visible in module paths + */ + public boolean getResolveByDefault() { + return resolveByDefault; + } + + /** + * Sets attribute indicating whether the created jmod should be visible + * in a module path, even when not specified explicitly. True by default. + * + * @param resolve whether jmod should be visible in module paths + */ + public void setResolveByDefault(final boolean resolve) { + this.resolveByDefault = resolve; + } + + /** + * Creates a child element which can specify the circumstances + * under which jmod creation emits warnings. + * + * @return new, unconfigured child element + * + * @see #setModuleWarnings(String) + */ + public ResolutionWarningSpec createModuleWarning() { + ResolutionWarningSpec warningSpec = new ResolutionWarningSpec(); + moduleWarnings.add(warningSpec); + return warningSpec; + } + + /** + * Sets attribute containing a comma-separated list of reasons for + * jmod creation to emit warnings. Valid values in list are: + * {@code deprecated}, {@code leaving}, {@code incubating}. + * + * @param warningList list containing one or more of the above values, + * separated by commas + * + * @see #createModuleWarning() + * @see Jmod.ResolutionWarningReason + */ + public void setModuleWarnings(final String warningList) { + for (String warning : warningList.split(",")) { + moduleWarnings.add(new ResolutionWarningSpec(warning)); + } + } + + /** + * Permissible reasons for jmod creation to emit warnings. + */ + public static class ResolutionWarningReason + extends EnumeratedAttribute { + /** + * String value indicating warnings are emitted for modules + * marked as deprecated (but not deprecated for removal). + */ + public static final String DEPRECATED = "deprecated"; + + /** + * String value indicating warnings are emitted for modules + * marked as deprecated for removal. + */ + public static final String LEAVING = "leaving"; + + /** + * String value indicating warnings are emitted for modules + * designated as "incubating" in the JDK. + */ + public static final String INCUBATING = "incubating"; + + /** Maps Ant task values to jmod option values. */ + private static final Map<String, String> VALUES_TO_OPTIONS; + + static { + Map<String, String> map = new LinkedHashMap<>(); + map.put(DEPRECATED, "deprecated"); + map.put(LEAVING, "deprecated-for-removal"); + map.put(INCUBATING, "incubating"); + + VALUES_TO_OPTIONS = Collections.unmodifiableMap(map); + } + + @Override + public String[] getValues() { + return VALUES_TO_OPTIONS.keySet().toArray(new String[0]); + } + + /** + * Converts this object's current value to a jmod tool + * option value. + * + * @return jmod option value + */ + String toCommandLineOption() { + return VALUES_TO_OPTIONS.get(getValue()); + } + + /** + * Converts a string to a {@code ResolutionWarningReason} instance. + * + * @param s string to convert + * + * @return {@code ResolutionWarningReason} instance corresponding to + * string argument + * + * @throws BuildException if argument is not a valid + * {@code ResolutionWarningReason} value + */ + public static ResolutionWarningReason valueOf(String s) { + return (ResolutionWarningReason) + getInstance(ResolutionWarningReason.class, s); + } + } + + /** + * Child element which enables jmod tool warnings. 'reason' attribute + * is required. + */ + public class ResolutionWarningSpec { + /** Condition which should trigger jmod warning output. */ + private ResolutionWarningReason reason; + + /** + * Creates an uninitialized element. + */ + public ResolutionWarningSpec() { + // Deliberately empty. + } + + /** + * Creates an element with the given reason attribute. + * + * @param reason non{@code null} {@link Jmod.ResolutionWarningReason} + * value + * + * @throws BuildException if argument is not a valid + * {@code ResolutionWarningReason} + */ + public ResolutionWarningSpec(String reason) { + setReason(ResolutionWarningReason.valueOf(reason)); + } + + /** + * Required attribute containing reason for emitting jmod warnings. + * + * @return condition which triggers jmod warnings + */ + public ResolutionWarningReason getReason() { + return reason; + } + + /** + * Sets attribute containing reason for emitting jmod warnings. + * + * @param reason condition which triggers jmod warnings + */ + public void setReason(ResolutionWarningReason reason) { + this.reason = reason; + } + + /** + * Verifies this object's state. + * + * @throws BuildException if this object's reason is {@code null} + */ + public void validate() { + if (reason == null) { + throw new BuildException("reason attribute is required", + getLocation()); + } + } + } + + /** + * Checks whether a resource is a directory. Used for checking validity + * of jmod path arguments which have to be directories. + * + * @param resource resource to check + * + * @return true if resource exists and is not a directory, + * false if it is a directory or does not exist + */ + private static boolean isRegularFile(Resource resource) { + return resource.isExists() && !resource.isDirectory(); + } + + /** + * Checks that all paths which are required to be directories only, + * refer only to directories. + * + * @throws BuildException if any path has an existing file + * which is a non-directory + */ + private void checkDirPaths() { + if (modulePath != null + && modulePath.stream().anyMatch(Jmod::isRegularFile)) { + + throw new BuildException( + "ModulePath must contain only directories.", getLocation()); + } + if (commandPath != null + && commandPath.stream().anyMatch(Jmod::isRegularFile)) { + + throw new BuildException( + "CommandPath must contain only directories.", getLocation()); + } + if (configPath != null + && configPath.stream().anyMatch(Jmod::isRegularFile)) { + + throw new BuildException( + "ConfigPath must contain only directories.", getLocation()); + } + if (headerPath != null + && headerPath.stream().anyMatch(Jmod::isRegularFile)) { + + throw new BuildException( + "HeaderPath must contain only directories.", getLocation()); + } + if (legalPath != null + && legalPath.stream().anyMatch(Jmod::isRegularFile)) { + + throw new BuildException( + "LegalPath must contain only directories.", getLocation()); + } + if (nativeLibPath != null + && nativeLibPath.stream().anyMatch(Jmod::isRegularFile)) { + + throw new BuildException( + "NativeLibPath must contain only directories.", getLocation()); + } + if (manPath != null + && manPath.stream().anyMatch(Jmod::isRegularFile)) { + + throw new BuildException( + "ManPath must contain only directories.", getLocation()); + } + } + + /** + * Creates a jmod file according to this task's properties + * and child elements. + * + * @throws BuildException if destFile is not set + * @throws BuildException if classpath is not set or is empty + * @throws BuildException if any path other than classpath refers to an + * existing file which is not a directory + * @throws BuildException if both {@code version} attribute and + * {@code <version>} child element are present + * @throws BuildException if {@code hashModulesPattern} is set, but + * module path is not defined + */ + @Override + public void execute() + throws BuildException { + + if (jmodFile == null) { + throw new BuildException("Destination file is required.", + getLocation()); + } + + if (classpath == null) { + throw new BuildException("Classpath is required.", + getLocation()); + } + + if (classpath.stream().noneMatch(Resource::isExists)) { + throw new BuildException( + "Classpath must contain at least one entry which exists.", + getLocation()); + } + + if (version != null && moduleVersion != null) { + throw new BuildException( + "version attribute and nested <version> element " + + "cannot both be present.", + getLocation()); + } + + if (hashModulesPattern != null && !hashModulesPattern.isEmpty() + && modulePath == null) { + + throw new BuildException( + "hashModulesPattern requires a module path, since " + + "it will generate hashes of the other modules which depend " + + "on the module being created.", + getLocation()); + } + + checkDirPaths(); + + Path[] dependentPaths = { + classpath, + modulePath, + commandPath, + configPath, + headerPath, + legalPath, + nativeLibPath, + manPath, + }; + Union allResources = new Union(getProject()); + for (Path path : dependentPaths) { + if (path != null) { + for (String entry : path.list()) { + File entryFile = new File(entry); + if (entryFile.isDirectory()) { + log("Will compare timestamp of all files in " + + "\"" + entryFile + "\" with timestamp of " + + jmodFile, Project.MSG_VERBOSE); + FileSet fileSet = new FileSet(); + fileSet.setDir(entryFile); + allResources.add(fileSet); + } else { + log("Will compare timestamp of \"" + entryFile + "\" " + + "with timestamp of " + jmodFile, + Project.MSG_VERBOSE); + allResources.add(new FileResource(entryFile)); + } + } + } + } + + ResourceCollection outOfDate = + ResourceUtils.selectOutOfDateSources(this, allResources, + new MergingMapper(jmodFile.toString()), + getProject(), + FileUtils.getFileUtils().getFileTimestampGranularity()); + + if (outOfDate.isEmpty()) { + log("Skipping jmod creation, since \"" + jmodFile + "\" " + + "is already newer than all files in paths.", + Project.MSG_VERBOSE); + return; + } + + Collection<String> args = buildJmodArgs(); + + try { + log("Deleting " + jmodFile + " if it exists.", Project.MSG_VERBOSE); + Files.deleteIfExists(jmodFile.toPath()); + } catch (IOException e) { + throw new BuildException( + "Could not remove old file \"" + jmodFile + "\": " + e, e, + getLocation()); + } + + ToolProvider jmod = ToolProvider.findFirst("jmod").orElseThrow( + () -> new BuildException("jmod tool not found in JDK.", + getLocation())); + + log("Executing: jmod " + String.join(" ", args), Project.MSG_VERBOSE); + + 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.toArray(new String[0])); + } + + if (exitCode != 0) { + StringBuilder message = new StringBuilder(); + message.append("jmod failed (exit code ").append(exitCode).append(")"); + if (stdout.size() > 0) { + message.append(", output is: ").append(stdout); + } + if (stderr.size() > 0) { + message.append(", error output is: ").append(stderr); + } + + throw new BuildException(message.toString(), getLocation()); + } + + log("Created " + jmodFile.getAbsolutePath(), Project.MSG_INFO); + } + + /** + * Creates list of arguments to <code>jmod</code> tool, based on this + * instance's current state. + * + * @return new list of <code>jmod</code> arguments + */ + private Collection<String> buildJmodArgs() { + Collection<String> args = new ArrayList<>(); + + args.add("create"); + + args.add("--class-path"); + args.add(classpath.toString()); + + // Paths + + if (modulePath != null && !modulePath.isEmpty()) { + args.add("--module-path"); + args.add(modulePath.toString()); + } + if (commandPath != null && !commandPath.isEmpty()) { + args.add("--cmds"); + args.add(commandPath.toString()); + } + if (configPath != null && !configPath.isEmpty()) { + args.add("--config"); + args.add(configPath.toString()); + } + if (headerPath != null && !headerPath.isEmpty()) { + args.add("--header-files"); + args.add(headerPath.toString()); + } + if (legalPath != null && !legalPath.isEmpty()) { + args.add("--legal-notices"); + args.add(legalPath.toString()); + } + if (nativeLibPath != null && !nativeLibPath.isEmpty()) { + args.add("--libs"); + args.add(nativeLibPath.toString()); + } + if (manPath != null && !manPath.isEmpty()) { + args.add("--man-pages"); + args.add(manPath.toString()); + } + + // Strings + + String versionStr = + (moduleVersion != null ? moduleVersion.toModuleVersionString() : version); + if (versionStr != null && !versionStr.isEmpty()) { + args.add("--module-version"); + args.add(versionStr); + } + + if (mainClass != null && !mainClass.isEmpty()) { + args.add("--main-class"); + args.add(mainClass); + } + if (platform != null && !platform.isEmpty()) { + args.add("--target-platform"); + args.add(platform); + } + if (hashModulesPattern != null && !hashModulesPattern.isEmpty()) { + args.add("--hash-modules"); + args.add(hashModulesPattern); + } + + // booleans + + if (!resolveByDefault) { + args.add("--do-not-resolve-by-default"); + } + for (ResolutionWarningSpec moduleWarning : moduleWarnings) { + moduleWarning.validate(); + args.add("--warn-if-resolved"); + args.add(moduleWarning.getReason().toCommandLineOption()); + } + + // Destination file + + args.add(jmodFile.toString()); + + return args; + } +}
