blob: 47f0ef224500234909df5863658d4aa032b55962 [file] [log] [blame]
/*
* 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
*
* https://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.
*
* @since 1.10.6
*/
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;
}
}