Github user jaikiran commented on a diff in the pull request: https://github.com/apache/ant/pull/80#discussion_r240889855 --- Diff: 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(); --- End diff -- Hi Craig, like Stefan already said, this patch is a really good addition to Ant. Thank you for that. The only question/suggestion I have is, should we instead just pass `System.out` and `System.err` instead of creating a `ByteArrayOutputStream`? That way the actual output and errors are logged instead of we deciding to print it only when the tool execution fails.
--- --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@ant.apache.org For additional commands, e-mail: dev-h...@ant.apache.org