| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.tools.ant.taskdefs.modules; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.io.Reader; |
| |
| import java.nio.charset.Charset; |
| import java.nio.charset.StandardCharsets; |
| |
| import java.nio.file.Files; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.SimpleFileVisitor; |
| import java.nio.file.attribute.BasicFileAttributes; |
| |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.ArrayList; |
| |
| import java.util.Map; |
| import java.util.LinkedHashMap; |
| import java.util.Properties; |
| |
| import java.util.Collections; |
| import java.util.Objects; |
| |
| import java.util.spi.ToolProvider; |
| |
| import java.util.stream.Stream; |
| import java.util.stream.Collectors; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| |
| import org.apache.tools.ant.taskdefs.LogOutputStream; |
| |
| import org.apache.tools.ant.types.EnumeratedAttribute; |
| import org.apache.tools.ant.types.LogLevel; |
| import org.apache.tools.ant.types.Path; |
| import org.apache.tools.ant.types.Reference; |
| import org.apache.tools.ant.types.ResourceCollection; |
| |
| import org.apache.tools.ant.util.CompositeMapper; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.util.MergingMapper; |
| import org.apache.tools.ant.util.ResourceUtils; |
| import org.apache.tools.ant.util.TeeOutputStream; |
| |
| /** |
| * Assembles jmod files into an executable image. Equivalent to the |
| * JDK {@code jlink} command. |
| * <p> |
| * Supported attributes: |
| * <dl> |
| * <dt>{@code destDir} |
| * <dd>Root directory of created image. (required) |
| * <dt>{@code modulePath} |
| * <dd>Path of modules. Should be a list of .jmod files. Required, unless |
| * nested module path or modulepathref is present. |
| * <dt>{@code modulePathRef} |
| * <dd>Reference to path of modules. Referenced path should be |
| * a list of .jmod files. |
| * <dt>{@code modules} |
| * <dd>Comma-separated list of modules to assemble. Required, unless |
| * one or more nested {@code <module>} elements are present. |
| * <dt>{@code observableModules} |
| * <dd>Comma-separated list of explicit modules that comprise |
| * "universe" visible to tool while linking. |
| * <dt>{@code launchers} |
| * <dd>Comma-separated list of commands, each of the form |
| * <var>name</var>{@code =}<var>module</var> or |
| * <var>name</var>{@code =}<var>module</var>{@code /}<var>mainclass</var> |
| * <dt>{@code excludeFiles} |
| * <dd>Comma-separated list of patterns specifying files to exclude from |
| * linked image. |
| * Each is either a <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a> |
| * or {@code @}<var>filename</var>. |
| * <dt>{@code excludeResources} |
| * <dd>Comma-separated list of patterns specifying resources to exclude from jmods. |
| * Each is either a <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a> |
| * or {@code @}<var>filename</var>. |
| * <dt>{@code locales} |
| * <dd>Comma-separated list of extra locales to include, |
| * requires {@code jdk.localedata} module |
| * <dt>{@code resourceOrder} |
| * <dt>Comma-separated list of patterns specifying resource search order. |
| * Each is either a <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a> |
| * or {@code @}<var>filename</var>. |
| * <dt>{@code bindServices} |
| * <dd>boolean, whether to link service providers; default is false |
| * <dt>{@code ignoreSigning} |
| * <dd>boolean, whether to allow signed jar files; default is false |
| * <dt>{@code includeHeaders} |
| * <dd>boolean, whether to include header files; default is true |
| * <dt>{@code includeManPages} |
| * <dd>boolean, whether to include man pages; default is true |
| * <dt>{@code includeNativeCommands} |
| * <dd>boolean, whether to include native executables normally generated |
| * for image; default is true |
| * <dt>{@code debug} |
| * <dd>boolean, whether to include debug information; default is true |
| * <dt>{@code verboseLevel} |
| * <dd>If set, jlink will produce verbose output, which will be logged at |
| * the specified Ant log level ({@code DEBUG}, {@code VERBOSE}, |
| * {@code INFO}}, {@code WARN}, or {@code ERR}). |
| * <dt>{@code compress} |
| * <dd>compression level, one of: |
| * <dl> |
| * <dt>{@code 0} |
| * <dt>{@code none} |
| * <dd>no compression (default) |
| * <dt>{@code 1} |
| * <dt>{@code strings} |
| * <dd>constant string sharing |
| * <dt>{@code 2} |
| * <dt>{@code zip} |
| * <dd>zip compression |
| * </dl> |
| * <dt>{@code endianness} |
| * <dd>Must be {@code little} or {@code big}, default is native endianness |
| * <dt>{@code checkDuplicateLegal} |
| * <dd>Boolean. When merging legal notices from different modules |
| * because they have the same name, verify that their contents |
| * are identical. Default is false, which means any license files |
| * with the same name are assumed to have the same content, and no |
| * checking is done. |
| * <dt>{@code vmType} |
| * <dd>Hotspot VM in image, one of: |
| * <ul> |
| * <li>{@code client} |
| * <li>{@code server} |
| * <li>{@code minimal} |
| * <li>{@code all} (default) |
| * </ul> |
| * </dl> |
| * |
| * <p> |
| * Supported nested elements |
| * <dl> |
| * <dt>{@code <modulepath>} |
| * <dd>path element |
| * <dt>{@code <module>} |
| * <dd>May be specified multiple times. |
| * Only attribute is required {@code name} attribute. |
| * <dt>{@code <observableModule>} |
| * <dd>May be specified multiple times. |
| * Only attribute is required {@code name} attribute. |
| * <dt>{@code <launcher>} |
| * <dd>May be specified multiple times. Attributes: |
| * <ul> |
| * <li>{@code name} (required) |
| * <li>{@code module} (required) |
| * <li>{@code mainClass} (optional) |
| * </ul> |
| * <dt>{@code <locale>} |
| * <dd>May be specified multiple times. |
| * Only attribute is required {@code name} attribute. |
| * <dt>{@code <resourceOrder>} |
| * <dd>Explicit resource search order in image. May be specified multiple |
| * times. Exactly one of these attributes must be specified: |
| * <dl> |
| * <dt>{@code pattern} |
| * <dd>A <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a> |
| * <dt>{@code listFile} |
| * <dd>Text file containing list of resource names (not patterns), |
| * one per line |
| * </dl> |
| * If the {@code resourceOrder} attribute is also present on the task, its |
| * patterns are treated as if they occur before patterns in nested |
| * {@code <resourceOrder>} elements. |
| * <dt>{@code <excludeFiles>} |
| * <dd>Excludes files from linked image tree. May be specified multiple times. |
| * Exactly one of these attributes is required: |
| * <dl> |
| * <dt>{@code pattern} |
| * <dd>A <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a> |
| * <dt>{@code listFile} |
| * <dd>Text file containing list of file names (not patterns), |
| * one per line |
| * </dl> |
| * <dt>{@code <excludeResources>} |
| * <dd>Excludes resources from jmods. May be specified multiple times. |
| * Exactly one of these attributes is required: |
| * <dl> |
| * <dt>{@code pattern} |
| * <dd>A <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">standard PathMatcher pattern</a> |
| * <dt>{@code listFile} |
| * <dd>Text file containing list of resource names (not patterns), |
| * one per line |
| * </dl> |
| * <dt>{@code <compress>} |
| * <dd>Must have {@code level} attribute, whose permitted values are the same |
| * as the {@code compress} task attribute described above. |
| * May also have a {@code files} attribute, which is a comma-separated |
| * list of patterns, and/or nested {@code <files>} elements, each with |
| * either a {@code pattern} attribute or {@code listFile} attribute. |
| * <dt>{@code <releaseInfo>} |
| * <dd>Replaces, augments, or trims the image's release info properties. |
| * This may specify any of the following: |
| * <ul> |
| * <li>A {@code file} attribute, pointing to a Java properties file |
| * containing new release info properties that will entirely replace |
| * the current ones. |
| * <li>A {@code delete} attribute, containing comma-separated property keys |
| * to remove from application's release info, and/or any number of |
| * nested {@code <delete>} elements, each with a required {@code key} |
| * attribute. |
| * <li>One or more nested {@code <add>} elements, containing either |
| * {@code key} and {@code value} attributes, or a {@code file} |
| * attribute and an optional {@code charset} attribute. |
| * </ul> |
| * </dl> |
| * |
| * @see <a href="https://docs.oracle.com/en/java/javase/11/tools/jlink.html"><code>jlink</code> tool reference</a> |
| * |
| * @since 1.10.6 |
| */ |
| public class Link |
| extends Task { |
| /** |
| * Error message for improperly formatted launcher attribute. |
| */ |
| private static final String INVALID_LAUNCHER_STRING = |
| "Launcher command must take the form name=module " |
| + "or name=module/mainclass"; |
| |
| /** Path of directories containing linkable modules. */ |
| private Path modulePath; |
| |
| /** Modules to include in linked image. */ |
| private final List<ModuleSpec> modules = new ArrayList<>(); |
| |
| /** If non-empty, list of all modules linker is permitted to know about. */ |
| private final List<ModuleSpec> observableModules = new ArrayList<>(); |
| |
| /** |
| * Additional runnable programs which linker will place in image's |
| * <code>bin</code> directory. |
| */ |
| private final List<Launcher> launchers = new ArrayList<>(); |
| |
| /** |
| * Locales to explicitly include from {@code jdk.localdata} module. |
| * If empty, all locales are included. |
| */ |
| private final List<LocaleSpec> locales = new ArrayList<>(); |
| |
| /** Resource ordering. */ |
| private final List<PatternListEntry> ordering = new ArrayList<>(); |
| |
| /** Files to exclude from linked image. */ |
| private final List<PatternListEntry> excludedFiles = new ArrayList<>(); |
| |
| /** |
| * Resources in linked modules which should be excluded from linked image. |
| */ |
| private final List<PatternListEntry> excludedResources = new ArrayList<>(); |
| |
| /** |
| * Whether to include all service provides in linked image which are |
| * present in the module path and which are needed by modules explicitly |
| * linked. |
| */ |
| private boolean bindServices; |
| |
| /** |
| * Whether to ignore signed jars (and jmods based on signed jars) when |
| * linking, instead of emitting an error. |
| */ |
| private boolean ignoreSigning; |
| |
| /** Whether to include header files from linked modules in image. */ |
| private boolean includeHeaders = true; |
| |
| /** Whether to include man pages from linked modules in image. */ |
| private boolean includeManPages = true; |
| |
| /** Whether to include native commands from linked modules in image. */ |
| private boolean includeNativeCommands = true; |
| |
| /** Whether to include classes' debug information or strip it. */ |
| private boolean debug = true; |
| |
| /** |
| * The Ant logging level at which verbose output of linked should be |
| * emitted. If null, verbose output is disabled. |
| */ |
| private LogLevel verboseLevel; |
| |
| /** Directory into which linked image will be placed. */ |
| private File outputDir; |
| |
| /** Endianness of some files (?) in linked image. */ |
| private Endianness endianness; |
| |
| /** |
| * Simple compression level applied to linked image. |
| * This or {@link #compression} may be set, but not both. |
| */ |
| private CompressionLevel compressionLevel; |
| |
| /** |
| * Describes which files in image to compress, and how to compress them. |
| * This or {@link #compressionLevel} may be set, but not both. |
| */ |
| private Compression compression; |
| |
| /** |
| * Whether to check duplicate legal notices from different modules |
| * actually have identical content, not just indentical names, |
| * before merging them. |
| * <a href="https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java#L80">Forced to true as of Java 11.</a> |
| */ |
| private boolean checkDuplicateLegal; |
| |
| /** Type of VM in linked image. */ |
| private VMType vmType; |
| |
| /** Changes to linked image's default release info. */ |
| private final List<ReleaseInfo> releaseInfo = new ArrayList<>(); |
| |
| /** |
| * Adds child {@code <modulePath>} element. |
| * |
| * @return new, empty child element |
| * |
| * @see #setModulePath(Path) |
| */ |
| public Path createModulePath() { |
| if (modulePath == null) { |
| modulePath = new Path(getProject()); |
| } |
| return modulePath.createPath(); |
| } |
| |
| /** |
| * Attribute containing path of directories containing linkable modules. |
| * |
| * @return current module path, possibly {@code null} |
| * |
| * @see #setModulePath(Path) |
| * @see #createModulePath() |
| */ |
| public Path getModulePath() { |
| return modulePath; |
| } |
| |
| /** |
| * Sets attribute containing path of directories containing |
| * linkable modules. |
| * |
| * @param path new module path |
| * |
| * @see #getModulePath() |
| * @see #setModulePathRef(Reference) |
| * @see #createModulePath() |
| */ |
| public void setModulePath(final Path path) { |
| if (modulePath == null) { |
| this.modulePath = path; |
| } else { |
| modulePath.append(path); |
| } |
| } |
| |
| /** |
| * Sets module path as a reference. |
| * |
| * @param ref path reference |
| * |
| * @see #setModulePath(Path) |
| * @see #createModulePath() |
| */ |
| public void setModulePathRef(final Reference ref) { |
| createModulePath().setRefid(ref); |
| } |
| |
| /** |
| * Adds child {@code <module>} element, specifying a module to link. |
| * |
| * @return new, unconfigured child element |
| * |
| * @see #setModules(String) |
| */ |
| public ModuleSpec createModule() { |
| ModuleSpec module = new ModuleSpec(); |
| modules.add(module); |
| return module; |
| } |
| |
| /** |
| * Sets attribute containing list of modules to link. |
| * |
| * @param moduleList comma-separated list of module names |
| */ |
| public void setModules(final String moduleList) { |
| for (String moduleName : moduleList.split(",")) { |
| modules.add(new ModuleSpec(moduleName)); |
| } |
| } |
| |
| /** |
| * Creates child {@code <observableModule>} element that represents |
| * one of the modules the linker is permitted to know about. |
| * |
| * @return new, unconfigured child element |
| */ |
| public ModuleSpec createObservableModule() { |
| ModuleSpec module = new ModuleSpec(); |
| observableModules.add(module); |
| return module; |
| } |
| |
| /** |
| * Sets attribute containing modules linker is permitted to know about. |
| * |
| * @param moduleList comma-separated list of module names |
| */ |
| public void setObservableModules(final String moduleList) { |
| for (String moduleName : moduleList.split(",")) { |
| observableModules.add(new ModuleSpec(moduleName)); |
| } |
| } |
| |
| /** |
| * Creates child {@code <launcher>} element that can contain information |
| * on additional executable in the linked image. |
| * |
| * @return new, unconfigured child element |
| * |
| * @see #setLaunchers(String) |
| */ |
| public Launcher createLauncher() { |
| Launcher command = new Launcher(); |
| launchers.add(command); |
| return command; |
| } |
| |
| /** |
| * Sets attribute containing comma-separated list of information needed for |
| * additional executables in the linked image. Each item must be of the |
| * form * <var>name</var>{@code =}<var>module</var> or |
| * <var>name</var>{@code =}<var>module</var>{@code /}<var>mainclass</var>. |
| * |
| * @param launcherList comma-separated list of launcher data |
| */ |
| public void setLaunchers(final String launcherList) { |
| for (String launcherSpec : launcherList.split(",")) { |
| launchers.add(new Launcher(launcherSpec)); |
| } |
| } |
| |
| /** |
| * Creates child {@code <locale>} element that specifies a Java locale, |
| * or set of locales, to include from the {@code jdk.localedata} module |
| * in the linked image. |
| * |
| * @return new, unconfigured child element |
| */ |
| public LocaleSpec createLocale() { |
| LocaleSpec locale = new LocaleSpec(); |
| locales.add(locale); |
| return locale; |
| } |
| |
| /** |
| * Sets attribute containing a list of locale patterns, to specify |
| * Java locales to include from {@code jdk.localedata} module in |
| * linked image. Asterisks ({@code *}) are permitted for wildcard |
| * matches. |
| * |
| * @param localeList comma-separated list of locale patterns |
| */ |
| public void setLocales(final String localeList) { |
| for (String localeName : localeList.split(",")) { |
| locales.add(new LocaleSpec(localeName)); |
| } |
| } |
| |
| /** |
| * Creates child {@code <excludeFiles>} element that specifies |
| * files to exclude from linked modules when assembling linked image. |
| * |
| * @return new, unconfigured child element |
| * |
| * @see #setExcludeFiles(String) |
| */ |
| public PatternListEntry createExcludeFiles() { |
| PatternListEntry entry = new PatternListEntry(); |
| excludedFiles.add(entry); |
| return entry; |
| } |
| |
| /** |
| * Sets attribute containing a list of patterns denoting files |
| * to exclude from linked modules when assembling linked image. |
| * |
| * @param patternList comman-separated list of patterns |
| * |
| * @see Link.PatternListEntry |
| */ |
| public void setExcludeFiles(String patternList) { |
| for (String pattern : patternList.split(",")) { |
| excludedFiles.add(new PatternListEntry(pattern)); |
| } |
| } |
| |
| /** |
| * Creates child {@code <excludeResources>} element that specifies |
| * resources in linked modules that will be excluded from linked image. |
| * |
| * @return new, unconfigured child element |
| * |
| * @see #setExcludeResources(String) |
| */ |
| public PatternListEntry createExcludeResources() { |
| PatternListEntry entry = new PatternListEntry(); |
| excludedResources.add(entry); |
| return entry; |
| } |
| |
| /** |
| * Sets attribute containing a list of patterns denoting resources |
| * to exclude from linked modules in linked image. |
| * |
| * @param patternList comma-separated list of patterns |
| * |
| * @see #createExcludeResources() |
| * @see Link.PatternListEntry |
| */ |
| public void setExcludeResources(String patternList) { |
| for (String pattern : patternList.split(",")) { |
| excludedResources.add(new PatternListEntry(pattern)); |
| } |
| } |
| |
| /** |
| * Creates child {@code <resourceOrder} element that specifies |
| * explicit ordering of resources in linked image. |
| * |
| * @return new, unconfigured child element |
| * |
| * @see #setResourceOrder(String) |
| */ |
| public PatternListEntry createResourceOrder() { |
| PatternListEntry order = new PatternListEntry(); |
| ordering.add(order); |
| return order; |
| } |
| |
| /** |
| * Sets attribute containing a list of patterns that explicitly |
| * order resources in the linked image. Any patterns specified here |
| * will be placed before any patterns specified as |
| * {@linkplain #createResourceOrder() child elements}. |
| * |
| * @param patternList comma-separated list of patterns |
| * |
| * @see #createResourceOrder() |
| * @see Link.PatternListEntry |
| */ |
| public void setResourceOrder(final String patternList) { |
| List<PatternListEntry> orderList = new ArrayList<>(); |
| |
| for (String pattern : patternList.split(",")) { |
| orderList.add(new PatternListEntry(pattern)); |
| } |
| |
| // Attribute value comes before nested elements. |
| ordering.addAll(0, orderList); |
| } |
| |
| /** |
| * Attribute indicating whether linked image should pull in providers |
| * in the module path of services used by explicitly linked modules. |
| * |
| * @return true if linked will pull in service provides, false if not |
| * |
| * @see #setBindServices(boolean) |
| */ |
| public boolean getBindServices() { |
| return bindServices; |
| } |
| |
| /** |
| * Sets attribute indicating whether linked image should pull in providers |
| * in the module path of services used by explicitly linked modules. |
| * |
| * @param bind whether to include service providers |
| * |
| * @see #getBindServices() |
| */ |
| public void setBindServices(final boolean bind) { |
| this.bindServices = bind; |
| } |
| |
| /** |
| * Attribute indicating whether linker should allow modules made from |
| * signed jars. |
| * |
| * @return true if signed jars are allowed, false if modules based on |
| * signed jars cause an error |
| * |
| * @see #setIgnoreSigning(boolean) |
| */ |
| public boolean getIgnoreSigning() { |
| return ignoreSigning; |
| } |
| |
| /** |
| * Sets attribute indicating whether linker should allow modules made from |
| * signed jars. |
| * <p> |
| * Note: As of Java 11, this attribute is internally forced to true. See |
| * <a href="https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java#L80">the source</a>. |
| * |
| * @param ignore true to have linker allow signed jars, |
| * false to have linker emit an error for signed jars |
| * |
| * |
| * @see #getIgnoreSigning() |
| */ |
| public void setIgnoreSigning(final boolean ignore) { |
| this.ignoreSigning = ignore; |
| } |
| |
| /** |
| * Attribute indicating whether to include header files from linked modules |
| * in image. |
| * |
| * @return true if header files should be included, false to exclude them |
| * |
| * @see #setIncludeHeaders(boolean) |
| */ |
| public boolean getIncludeHeaders() { |
| return includeHeaders; |
| } |
| |
| /** |
| * Sets attribute indicating whether to include header files from |
| * linked modules in image. |
| * |
| * @param include true if header files should be included, |
| * false to exclude them |
| * |
| * @see #getIncludeHeaders() |
| */ |
| public void setIncludeHeaders(final boolean include) { |
| this.includeHeaders = include; |
| } |
| |
| /** |
| * Attribute indicating whether to include man pages from linked modules |
| * in image. |
| * |
| * @return true if man pages should be included, false to exclude them |
| * |
| * @see #setIncludeManPages(boolean) |
| */ |
| public boolean getIncludeManPages() { |
| return includeManPages; |
| } |
| |
| /** |
| * Sets attribute indicating whether to include man pages from |
| * linked modules in image. |
| * |
| * @param include true if man pages should be included, |
| * false to exclude them |
| * |
| * @see #getIncludeManPages() |
| */ |
| public void setIncludeManPages(final boolean include) { |
| this.includeManPages = include; |
| } |
| |
| /** |
| * Attribute indicating whether to include generated native commands, |
| * and native commands from linked modules, in image. |
| * |
| * @return true if native commands should be included, false to exclude them |
| * |
| * @see #setIncludeNativeCommands(boolean) |
| */ |
| public boolean getIncludeNativeCommands() { |
| return includeNativeCommands; |
| } |
| |
| /** |
| * Sets attribute indicating whether to include generated native commands, |
| * and native commands from linked modules, in image. |
| * |
| * @param include true if native commands should be included, |
| * false to exclude them |
| * |
| * @see #getIncludeNativeCommands() |
| */ |
| public void setIncludeNativeCommands(final boolean include) { |
| this.includeNativeCommands = include; |
| } |
| |
| /** |
| * Attribute indicating whether linker should keep or strip |
| * debug information in classes. |
| * |
| * @return true if debug information will be retained, |
| * false if it will be stripped |
| * |
| * @see #setDebug(boolean) |
| */ |
| public boolean getDebug() { |
| return debug; |
| } |
| |
| /** |
| * Sets attribute indicating whether linker should keep or strip |
| * debug information in classes. |
| * |
| * @param debug true if debug information should be retained, |
| * false if it should be stripped |
| * |
| * @see #getDebug() |
| */ |
| public void setDebug(final boolean debug) { |
| this.debug = debug; |
| } |
| |
| /** |
| * Attribute indicating whether linker should produce verbose output, |
| * and at what logging level that output should be shown. |
| * |
| * @return logging level at which to show linker's verbose output, |
| * or {@code null} to disable verbose output |
| * |
| * @see #setVerboseLevel(LogLevel) |
| */ |
| public LogLevel getVerboseLevel() { |
| return verboseLevel; |
| } |
| |
| /** |
| * Sets attribute indicating whether linker should produce verbose output, |
| * and at what logging level that output should be shown. |
| * |
| * @param level level logging level at which to show linker's |
| * verbose output, or {@code null} to disable verbose output |
| * |
| * @see #getVerboseLevel() |
| */ |
| public void setVerboseLevel(final LogLevel level) { |
| this.verboseLevel = level; |
| } |
| |
| /** |
| * Required attribute containing directory where linked image will be |
| * created. |
| * |
| * @return directory where linked image will reside |
| * |
| * @see #setDestDir(File) |
| */ |
| public File getDestDir() { |
| return outputDir; |
| } |
| |
| /** |
| * Sets attribute indicating directory where linked image will be created. |
| * |
| * @param dir directory in which image will be created by linker |
| * |
| * @see #getDestDir() |
| */ |
| public void setDestDir(final File dir) { |
| this.outputDir = dir; |
| } |
| |
| /** |
| * Attribute indicating level of compression linker will apply to image. |
| * This is exclusive with regard to {@link #createCompress()}: only one |
| * of the two may be specified. |
| * |
| * @return compression level to apply, or {@code null} for none |
| * |
| * @see #setCompress(Link.CompressionLevel) |
| * @see #createCompress() |
| */ |
| public CompressionLevel getCompress() { |
| return compressionLevel; |
| } |
| |
| /** |
| * Sets attribute indicating level of compression linker will apply |
| * to image. This is exclusive with regard to {@link #createCompress()}: |
| * only one of the two may be specified. |
| * |
| * @param level compression level to apply, or {@code null} for none |
| * |
| * @see #getCompress() |
| * @see #createCompress() |
| */ |
| public void setCompress(final CompressionLevel level) { |
| this.compressionLevel = level; |
| } |
| |
| /** |
| * Creates child {@code <compress>} element that specifies the level of |
| * compression the linker will apply, and optionally, which files in the |
| * image will be compressed. This is exclusive with regard to the |
| * {@link #setCompress compress} attribute: only one of the two may be |
| * specified. |
| * |
| * @return new, unconfigured child element |
| * |
| * @see #setCompress(Link.CompressionLevel) |
| */ |
| public Compression createCompress() { |
| if (compression != null) { |
| throw new BuildException( |
| "Only one nested compression element is permitted.", |
| getLocation()); |
| } |
| compression = new Compression(); |
| return compression; |
| } |
| |
| /** |
| * Attribute which indicates whether certain files in the linked image |
| * will be big-endian or little-endian. If {@code null}, the underlying |
| * platform's endianness is used. |
| * |
| * @return endianness to apply, or {@code null} to platform default |
| * |
| * @see #setEndianness(Link.Endianness) |
| */ |
| public Endianness getEndianness() { |
| return endianness; |
| } |
| |
| /** |
| * Sets attribute which indicates whether certain files in the linked image |
| * will be big-endian or little-endian. If {@code null}, the underlying |
| * platform's endianness is used. |
| * |
| * @param endianness endianness to apply, or {@code null} to use |
| * platform default |
| * |
| * @see #getEndianness() |
| */ |
| public void setEndianness(final Endianness endianness) { |
| this.endianness = endianness; |
| } |
| |
| /** |
| * Attribute indicating whether linker should check legal notices with |
| * duplicate names, and refuse to merge them (usually using symbolic links) |
| * if their respective content is not identical. |
| * |
| * @return true if legal notice files with same name should be checked |
| * for identical content, false to suppress check |
| * |
| * @see #setCheckDuplicateLegal(boolean) |
| */ |
| public boolean getCheckDuplicateLegal() { |
| return checkDuplicateLegal; |
| } |
| |
| /** |
| * Sets attribute indicating whether linker should check legal notices with |
| * duplicate names, and refuse to merge them (usually using symbolic links) |
| * if their respective content is not identical. |
| * |
| * @param check true if legal notice files with same name should be checked |
| * for identical content, false to suppress check |
| * |
| * @see #getCheckDuplicateLegal() |
| */ |
| public void setCheckDuplicateLegal(final boolean check) { |
| this.checkDuplicateLegal = check; |
| } |
| |
| /** |
| * Attribute indicating what type of JVM the linked image should have. |
| * If {@code null}, all JVM types are included. |
| * |
| * @return type of JVM linked image will have |
| * |
| * @see #setVmType(Link.VMType) |
| */ |
| public VMType getVmType() { |
| return vmType; |
| } |
| |
| /** |
| * Set attribute indicating what type of JVM the linked image should have. |
| * If {@code null}, all JVM types are included. |
| * |
| * @param type type of JVM linked image will have |
| * |
| * @see #getVmType() |
| */ |
| public void setVmType(final VMType type) { |
| this.vmType = type; |
| } |
| |
| /** |
| * Creates child {@code <releaseInfo>} element that modifies the default |
| * release properties of the linked image. |
| * |
| * @return new, unconfigured child element |
| */ |
| public ReleaseInfo createReleaseInfo() { |
| ReleaseInfo info = new ReleaseInfo(); |
| releaseInfo.add(info); |
| return info; |
| } |
| |
| /** |
| * Child element that explicitly names a Java module. |
| */ |
| public class ModuleSpec { |
| /** Module's name. Required. */ |
| private String name; |
| |
| /** Creates an unconfigured element. */ |
| public ModuleSpec() { |
| // Deliberately empty. |
| } |
| |
| /** |
| * Creates an element with the given module name. |
| * |
| * @param name module's name |
| */ |
| public ModuleSpec(final String name) { |
| setName(name); |
| } |
| |
| /** |
| * Attribute containing name of module this element represents. |
| * |
| * @return name of module |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Sets attribute representing the name of this module this element |
| * represents. |
| * |
| * @param name module's name |
| */ |
| public void setName(final String name) { |
| this.name = name; |
| } |
| |
| /** |
| * Verifies this element's state. |
| * |
| * @throws BuildException if name is not set |
| */ |
| public void validate() { |
| if (name == null) { |
| throw new BuildException("name is required for module.", |
| getLocation()); |
| } |
| } |
| } |
| |
| /** |
| * Child element that contains a pattern matching Java locales. |
| */ |
| public class LocaleSpec { |
| /** Pattern of locale names to match. */ |
| private String name; |
| |
| /** Creates an unconfigured element. */ |
| public LocaleSpec() { |
| // Deliberately empty. |
| } |
| |
| /** |
| * Creates an element with the given name pattern. |
| * |
| * @param name pattern of locale names to match |
| */ |
| public LocaleSpec(final String name) { |
| setName(name); |
| } |
| |
| /** |
| * Attribute containing a pattern which matches Java locale names. |
| * May be an explicit Java locale, or may contain an asterisk |
| * ({@code *)} for wildcard matching. |
| * |
| * @return this element's locale name pattern |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Sets attribute containing a pattern which matches Java locale names. |
| * May be an explicit Java locale, or may contain an asterisk |
| * ({@code *)} for wildcard matching. |
| * |
| * @param name new locale name or pattern matching locale names |
| */ |
| public void setName(final String name) { |
| this.name = name; |
| } |
| |
| /** |
| * Verifies this element's state. |
| * |
| * @throws BuildException if name is not set |
| */ |
| public void validate() { |
| if (name == null) { |
| throw new BuildException("name is required for locale.", |
| getLocation()); |
| } |
| } |
| } |
| |
| /** |
| * Child element type which specifies a jlink files pattern. Each |
| * instance may specify a string |
| * <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">PathMatcher pattern</a> |
| * or a text file containing a list of such patterns, one per line. |
| */ |
| public class PatternListEntry { |
| /** PathMatcher pattern of files to match. */ |
| private String pattern; |
| |
| /** Plain text list file with one PathMatcher pattern per line. */ |
| private File file; |
| |
| /** Creates an unconfigured element. */ |
| public PatternListEntry() { |
| // Deliberately empty. |
| } |
| |
| /** |
| * Creates a new element from either a pattern or listing file. |
| * If the argument starts with "{@code @}", the remainder of it |
| * is assumed to be a listing file; otherwise, it is treated as |
| * a PathMatcher pattern. |
| * |
| * @param pattern a PathMatcher pattern or {@code @}-filename |
| */ |
| public PatternListEntry(final String pattern) { |
| if (pattern.startsWith("@")) { |
| setListFile(new File(pattern.substring(1))); |
| } else { |
| setPattern(pattern); |
| } |
| } |
| |
| /** |
| * Returns this element's PathMatcher pattern attribute, if set. |
| * |
| * @return this element's files pattern |
| */ |
| public String getPattern() { |
| return pattern; |
| } |
| |
| /** |
| * Sets this element's |
| * <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29">PathMatcher pattern</a> |
| * attribute for matching files. |
| * |
| * @param pattern new files pattern |
| */ |
| public void setPattern(final String pattern) { |
| this.pattern = pattern; |
| } |
| |
| /** |
| * Returns this element's list file attribute, if set. |
| * |
| * @return this element's list file |
| * |
| * @see #setListFile(File) |
| */ |
| public File getListFile() { |
| return file; |
| } |
| |
| /** |
| * Sets this element's list file attribute. The file must be a |
| * plain text file with one PathMatcher pattern per line. |
| * |
| * @param file list file containing patterns |
| * |
| * @see #getListFile() |
| */ |
| public void setListFile(final File file) { |
| this.file = file; |
| } |
| |
| /** |
| * Verifies this element's state. |
| * |
| * @throws BuildException if both pattern and file are set |
| * @throws BuildException if neither pattern nor file is set |
| */ |
| public void validate() { |
| if ((pattern == null && file == null) |
| || (pattern != null && file != null)) { |
| |
| throw new BuildException( |
| "Each entry in a pattern list must specify " |
| + "exactly one of pattern or file.", getLocation()); |
| } |
| } |
| |
| /** |
| * Converts this element to a jlink command line attribute, |
| * either this element's bare pattern, or its list file |
| * preceded by "{@code @}". |
| * |
| * @return this element's information converted to a command line value |
| */ |
| public String toOptionValue() { |
| return pattern != null ? pattern : ("@" + file); |
| } |
| } |
| |
| /** |
| * Child element representing a custom launcher command in a linked image. |
| * A launcher has a name, which is typically used as a file name for an |
| * executable file, a Java module name, and optionally a class within |
| * that module which can act as a standard Java main class. |
| */ |
| public class Launcher { |
| /** This launcher's name, usually used to create an executable file. */ |
| private String name; |
| |
| /** The name of the Java module this launcher launches. */ |
| private String module; |
| |
| /** |
| * The class within this element's {@link #module} to run. |
| * Optional if the Java module specifies its own main class. |
| */ |
| private String mainClass; |
| |
| /** Creates a new, unconfigured element. */ |
| public Launcher() { |
| // Deliberately empty. |
| } |
| |
| /** |
| * Creates a new element from a {@code jlink}-compatible string |
| * specifier, which must take the form |
| * <var>name</var>{@code =}<var>module</var> or |
| * <var>name</var>{@code =}<var>module</var>{@code /}<var>mainclass</var>. |
| * |
| * @param textSpec name, module, and optional main class, as described |
| * above |
| * |
| * @throws NullPointerException if argument is {@code null} |
| * @throws BuildException if argument does not conform to above |
| * requirements |
| */ |
| public Launcher(final String textSpec) { |
| Objects.requireNonNull(textSpec, "Text cannot be null"); |
| |
| int equals = textSpec.lastIndexOf('='); |
| if (equals < 1) { |
| throw new BuildException(INVALID_LAUNCHER_STRING); |
| } |
| |
| setName(textSpec.substring(0, equals)); |
| |
| int slash = textSpec.indexOf('/', equals); |
| if (slash < 0) { |
| setModule(textSpec.substring(equals + 1)); |
| } else if (slash > equals + 1 && slash < textSpec.length() - 1) { |
| setModule(textSpec.substring(equals + 1, slash)); |
| setMainClass(textSpec.substring(slash + 1)); |
| } else { |
| throw new BuildException(INVALID_LAUNCHER_STRING); |
| } |
| } |
| |
| /** |
| * Returns this element's name attribute, typically used as the basis |
| * of an executable file name. |
| * |
| * @return this element's name |
| * |
| * @see #setName(String) |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Sets this element's name attribute, which is typically used by the |
| * linker to create an executable file with a similar name. Thus, |
| * the name should contain only characters safe for file names. |
| * |
| * @param name name of launcher |
| */ |
| public void setName(final String name) { |
| this.name = name; |
| } |
| |
| /** |
| * Returns the attribute of this element which contains the |
| * name of the Java module to execute. |
| * |
| * @return this element's module name |
| */ |
| public String getModule() { |
| return module; |
| } |
| |
| /** |
| * Sets the attribute of this element which contains the name of |
| * a Java module to execute. |
| * |
| * @param module name of module to execute |
| */ |
| public void setModule(final String module) { |
| this.module = module; |
| } |
| |
| /** |
| * Returns the attribute of this element which contains the main class |
| * to execute in this element's {@linkplain #getModule() module}, if |
| * that module doesn't define its main class. |
| * |
| * @return name of main class to execute |
| */ |
| public String getMainClass() { |
| return mainClass; |
| } |
| |
| /** |
| * Sets the attribute which contains the main class to execute in |
| * this element's {@linkplain #getModule() module}, if that module |
| * doesn't define its main class. |
| * |
| * @param className name of class to execute |
| */ |
| public void setMainClass(final String className) { |
| this.mainClass = className; |
| } |
| |
| /** |
| * Verifies this element's state. |
| * |
| * @throws BuildException if name or module is not set |
| */ |
| public void validate() { |
| if (name == null || name.isEmpty()) { |
| throw new BuildException("Launcher must have a name", |
| getLocation()); |
| } |
| if (module == null || module.isEmpty()) { |
| throw new BuildException("Launcher must have specify a module", |
| getLocation()); |
| } |
| } |
| |
| /** |
| * Returns this element's information in jlink launcher format: |
| * <var>name</var>{@code =}<var>module</var> or |
| * <var>name</var>{@code =}<var>module</var>{@code /}<var>mainclass</var>. |
| * |
| * @return name, module and optional main class in jlink format |
| */ |
| @Override |
| public String toString() { |
| if (mainClass != null) { |
| return name + "=" + module + "/" + mainClass; |
| } else { |
| return name + "=" + module; |
| } |
| } |
| } |
| |
| /** |
| * Possible values for linked image endianness: |
| * {@code little} and {@code big}. |
| */ |
| public static class Endianness |
| extends EnumeratedAttribute { |
| @Override |
| public String[] getValues() { |
| return new String[] { |
| "little", "big" |
| }; |
| } |
| } |
| |
| /** |
| * Possible values for JVM type in linked image: |
| * {@code client}, {@code server}, {@code minimal}, or {@code all}. |
| */ |
| public static class VMType |
| extends EnumeratedAttribute { |
| @Override |
| public String[] getValues() { |
| return new String[] { |
| "client", "server", "minimal", "all" |
| }; |
| } |
| } |
| |
| /** |
| * Possible attribute values for compression level of a linked image: |
| * <dl> |
| * <dt>{@code 0} |
| * <dt>{@code none} |
| * <dd>no compression (default) |
| * <dt>{@code 1} |
| * <dt>{@code strings} |
| * <dd>constant string sharing |
| * <dt>{@code 2} |
| * <dt>{@code zip} |
| * <dd>zip compression |
| * </dl> |
| */ |
| public static class CompressionLevel |
| extends EnumeratedAttribute { |
| private static final Map<String, String> KEYWORDS; |
| |
| static { |
| Map<String, String> map = new LinkedHashMap<>(); |
| map.put("0", "0"); |
| map.put("1", "1"); |
| map.put("2", "2"); |
| map.put("none", "0"); |
| map.put("strings", "1"); |
| map.put("zip", "2"); |
| |
| KEYWORDS = Collections.unmodifiableMap(map); |
| } |
| |
| @Override |
| public String[] getValues() { |
| return KEYWORDS.keySet().toArray(new String[0]); |
| } |
| |
| /** |
| * Converts this value to a string suitable for use in a |
| * jlink command. |
| * |
| * @return jlink keyword corresponding to this value |
| */ |
| String toCommandLineOption() { |
| return KEYWORDS.get(getValue()); |
| } |
| } |
| |
| /** |
| * Child element fully describing compression of a linked image. |
| * This includes the level, and optionally, the names of files to compress. |
| */ |
| public class Compression { |
| /** Compression level. Required attribute. */ |
| private CompressionLevel level; |
| |
| /** |
| * Patterns specifying files to compress. If empty, all files are |
| * compressed. |
| */ |
| private final List<PatternListEntry> patterns = new ArrayList<>(); |
| |
| /** |
| * Required attribute containing level of compression. |
| * |
| * @return compression level |
| */ |
| public CompressionLevel getLevel() { |
| return level; |
| } |
| |
| /** |
| * Sets attribute indicating level of compression. |
| * |
| * @param level type of compression to apply to linked image |
| */ |
| public void setLevel(final CompressionLevel level) { |
| this.level = level; |
| } |
| |
| /** |
| * Creates a nested element which can specify a pattern of files |
| * to compress. |
| * |
| * @return new, unconfigured child element |
| */ |
| public PatternListEntry createFiles() { |
| PatternListEntry pattern = new PatternListEntry(); |
| patterns.add(pattern); |
| return pattern; |
| } |
| |
| /** |
| * Sets an attribute that represents a list of file patterns to |
| * compress in the linked image, as a comma-separated list of |
| * PathMatcher patterns or pattern list files. |
| * |
| * @param patternList comma-separated list of patterns and/or file names |
| * |
| * @see Link.PatternListEntry |
| */ |
| public void setFiles(final String patternList) { |
| patterns.clear(); |
| for (String pattern : patternList.split(",")) { |
| patterns.add(new PatternListEntry(pattern)); |
| } |
| } |
| |
| /** |
| * Verifies this element's state. |
| * |
| * @throws BuildException if compression level is not set |
| * @throws BuildException if any nested patterns are invalid |
| */ |
| public void validate() { |
| if (level == null) { |
| throw new BuildException("Compression level must be specified.", |
| getLocation()); |
| } |
| patterns.forEach(PatternListEntry::validate); |
| } |
| |
| /** |
| * Converts this element to a single jlink option value. |
| * |
| * @return command line option representing this element's state |
| */ |
| public String toCommandLineOption() { |
| StringBuilder option = |
| new StringBuilder(level.toCommandLineOption()); |
| |
| if (!patterns.isEmpty()) { |
| String separator = ":filter="; |
| for (PatternListEntry entry : patterns) { |
| option.append(separator).append(entry.toOptionValue()); |
| separator = ","; |
| } |
| } |
| |
| return option.toString(); |
| } |
| } |
| |
| /** |
| * Grandchild element representing deletable key in a linked image's |
| * release properties. |
| */ |
| public class ReleaseInfoKey { |
| /** Required attribute holding property key to delete. */ |
| private String key; |
| |
| /** Creates a new, unconfigured element. */ |
| public ReleaseInfoKey() { |
| // Deliberately empty. |
| } |
| |
| /** |
| * Creates a new element with the specified key. |
| * |
| * @param key property key to delete from release info |
| */ |
| public ReleaseInfoKey(final String key) { |
| setKey(key); |
| } |
| |
| /** |
| * Attribute holding the release info property key to delete. |
| * |
| * @return property key to be deleted |
| */ |
| public String getKey() { |
| return key; |
| } |
| |
| /** |
| * Sets attribute containing property key to delete from |
| * linked image's release info. |
| * |
| * @param key propert key to be deleted |
| */ |
| public void setKey(final String key) { |
| this.key = key; |
| } |
| |
| /** |
| * Verifies this element's state is valid. |
| * |
| * @throws BuildException if key is not set |
| */ |
| public void validate() { |
| if (key == null) { |
| throw new BuildException( |
| "Release info key must define a 'key' attribute.", |
| getLocation()); |
| } |
| } |
| } |
| |
| /** |
| * Grandchild element describing additional release info properties for a |
| * linked image. To be valid, an instance must have either a non-null |
| * key and value, or a non-null file. |
| */ |
| public class ReleaseInfoEntry { |
| /** New release property's key. */ |
| private String key; |
| |
| /** New release property's value. */ |
| private String value; |
| |
| /** File containing additional release properties. */ |
| private File file; |
| |
| /** Charset of {@link #file}. */ |
| private String charset = StandardCharsets.ISO_8859_1.name(); |
| |
| /** Creates a new, unconfigured element. */ |
| public ReleaseInfoEntry() { |
| // Deliberately empty. |
| } |
| |
| /** |
| * Creates a new element which specifies a single additional property. |
| * |
| * @param key new property's key |
| * @param value new property's value |
| */ |
| public ReleaseInfoEntry(final String key, |
| final String value) { |
| setKey(key); |
| setValue(value); |
| } |
| |
| /** |
| * Attribute containing the key of this element's additional property. |
| * |
| * @return additional property's key |
| * |
| * @see #getValue() |
| */ |
| public String getKey() { |
| return key; |
| } |
| |
| /** |
| * Sets attribute containing the key of this element's |
| * additional property. |
| * |
| * @param key additional property's key |
| * |
| * @see #setValue(String) |
| */ |
| public void setKey(final String key) { |
| this.key = key; |
| } |
| |
| /** |
| * Attribute containing the value of this element's additional property. |
| * |
| * @return additional property's value |
| * |
| * @see #getKey() |
| */ |
| public String getValue() { |
| return value; |
| } |
| |
| /** |
| * Sets attributes containing the value of this element's |
| * additional property. |
| * |
| * @param value additional property's value |
| * |
| * @see #setKey(String) |
| */ |
| public void setValue(final String value) { |
| this.value = value; |
| } |
| |
| /** |
| * Attribute containing a Java properties file which contains |
| * additional release info properties. This is exclusive with |
| * respect to the {@linkplain #getKey() key} and |
| * {@linkplain #getValue() value} of this instance: either the |
| * file must be set, or the key and value must be set. |
| * |
| * @return this element's properties file |
| */ |
| public File getFile() { |
| return file; |
| } |
| |
| /** |
| * Sets attribute containing a Java properties file which contains |
| * additional release info properties. This is exclusive with |
| * respect to the {@linkplain #setKey(String) key} and |
| * {@linkplain #setValue(String) value} of this instance: either the |
| * file must be set, or the key and value must be set. |
| * |
| * @param file this element's properties file |
| */ |
| public void setFile(final File file) { |
| this.file = file; |
| } |
| |
| /** |
| * Attribute containing the character set of this object's |
| * {@linkplain #getFile() file}. This is {@code ISO_8859_1} |
| * by default, in accordance with the java.util.Properties default. |
| * |
| * @return character set of this element's file |
| */ |
| public String getCharset() { |
| return charset; |
| } |
| |
| /** |
| * Sets attribute containing the character set of this object's |
| * {@linkplain #setFile(File) file}. If not set, this is |
| * {@code ISO_8859_1} by default, in accordance with the |
| * java.util.Properties default. |
| * |
| * @param charset character set of this element's file |
| */ |
| public void setCharset(final String charset) { |
| this.charset = charset; |
| } |
| |
| /** |
| * Verifies the state of this element. |
| * |
| * @throws BuildException if file is set, and key and/or value are set |
| * @throws BuildException if file is not set, and key and value are not both set |
| * @throws BuildException if charset is not a valid Java Charset name |
| */ |
| public void validate() { |
| if (file == null && (key == null || value == null)) { |
| throw new BuildException( |
| "Release info must define 'key' and 'value' attributes, " |
| + "or a 'file' attribute.", getLocation()); |
| } |
| if (file != null && (key != null || value != null)) { |
| throw new BuildException( |
| "Release info cannot define both a file attribute and " |
| + "key/value attributes.", getLocation()); |
| } |
| |
| // This can't happen from a build file, but can theoretically |
| // happen if called from Java code. |
| if (charset == null) { |
| throw new BuildException("Charset cannot be null.", |
| getLocation()); |
| } |
| |
| try { |
| Charset.forName(charset); |
| } catch (IllegalArgumentException e) { |
| throw new BuildException(e, getLocation()); |
| } |
| } |
| |
| /** |
| * Converts this element to a Java properties object containing |
| * the additional properties this element represents. If this |
| * element's file is set, it is read; otherwise, a Properties |
| * object containing just one property, consisting of this element's |
| * key and value, is returned. |
| * |
| * @return new Properties object obtained from this element's file or |
| * its key and value |
| * |
| * @throws BuildException if file is set, but cannot be read |
| */ |
| public Properties toProperties() { |
| Properties props = new Properties(); |
| if (file != null) { |
| try (Reader reader = Files.newBufferedReader( |
| file.toPath(), Charset.forName(charset))) { |
| |
| props.load(reader); |
| } catch (IOException e) { |
| throw new BuildException( |
| "Cannot read release info file \"" + file + "\": " + e, |
| e, getLocation()); |
| } |
| } else { |
| props.setProperty(key, value); |
| } |
| |
| return props; |
| } |
| } |
| |
| /** |
| * Child element describing changes to the default release properties |
| * of a linked image. |
| */ |
| public class ReleaseInfo { |
| /** |
| * File that contains replacement release properties for linked image. |
| */ |
| private File file; |
| |
| /** |
| * Properties to add to default release properties of linked image. |
| */ |
| private final List<ReleaseInfoEntry> propertiesToAdd = new ArrayList<>(); |
| |
| /** |
| * Property keys to remove from release properties of linked image. |
| */ |
| private final List<ReleaseInfoKey> propertiesToDelete = new ArrayList<>(); |
| |
| /** |
| * Attribute specifying Java properties file which will replace the |
| * default release info properties for the linked image. |
| * |
| * @return release properties file |
| */ |
| public File getFile() { |
| return file; |
| } |
| |
| /** |
| * Sets attribute specifying Java properties file which will replace |
| * the default release info properties for the linked image. |
| * |
| * @param file replacement release properties file |
| */ |
| public void setFile(final File file) { |
| this.file = file; |
| } |
| |
| /** |
| * Creates an uninitialized child element which can represent properties |
| * to add to the default release properties of a linked image. |
| * |
| * @return new, unconfigured child element |
| */ |
| public ReleaseInfoEntry createAdd() { |
| ReleaseInfoEntry property = new ReleaseInfoEntry(); |
| propertiesToAdd.add(property); |
| return property; |
| } |
| |
| /** |
| * Creates an uninitialized child element which can represent |
| * a property key to delete from the release properties of |
| * a linked image. |
| * |
| * @return new, unconfigured child element |
| */ |
| public ReleaseInfoKey createDelete() { |
| ReleaseInfoKey key = new ReleaseInfoKey(); |
| propertiesToDelete.add(key); |
| return key; |
| } |
| |
| /** |
| * Sets attribute which contains a comma-separated list of |
| * property keys to delete from the release properties of |
| * a linked image. |
| * |
| * @param keyList comma-separated list of property keys |
| * |
| * @see #createDelete() |
| */ |
| public void setDelete(final String keyList) { |
| for (String key : keyList.split(",")) { |
| propertiesToDelete.add(new ReleaseInfoKey(key)); |
| } |
| } |
| |
| /** |
| * Verifies the state of this element. |
| * |
| * @throws BuildException if any child element is invalid |
| * |
| * @see Link.ReleaseInfoEntry#validate() |
| * @see Link.ReleaseInfoKey#validate() |
| */ |
| public void validate() { |
| propertiesToAdd.forEach(ReleaseInfoEntry::validate); |
| propertiesToDelete.forEach(ReleaseInfoKey::validate); |
| } |
| |
| /** |
| * Converts all of this element's state to a series of |
| * <code>jlink</code> options. |
| * |
| * @return new collection of jlink options based on this element's |
| * attributes and child elements |
| */ |
| public Collection<String> toCommandLineOptions() { |
| Collection<String> options = new ArrayList<>(); |
| |
| if (file != null) { |
| options.add("--release-info=" + file); |
| } |
| if (!propertiesToAdd.isEmpty()) { |
| StringBuilder option = new StringBuilder("--release-info=add"); |
| |
| for (ReleaseInfoEntry entry : propertiesToAdd) { |
| Properties props = entry.toProperties(); |
| for (String key : props.stringPropertyNames()) { |
| option.append(":").append(key).append("="); |
| option.append(props.getProperty(key)); |
| } |
| } |
| |
| options.add(option.toString()); |
| } |
| if (!propertiesToDelete.isEmpty()) { |
| StringBuilder option = |
| new StringBuilder("--release-info=del:keys="); |
| |
| String separator = ""; |
| for (ReleaseInfoKey key : propertiesToDelete) { |
| option.append(separator).append(key.getKey()); |
| // jlink docs aren't clear on whether property keys |
| // to delete should be separated by commas or colons. |
| separator = ","; |
| } |
| |
| options.add(option.toString()); |
| } |
| |
| return options; |
| } |
| } |
| |
| /** |
| * Invokes the jlink tool to create a new linked image, unless the |
| * output directory exists and all of its files are files are newer |
| * than all files in the module path. |
| * |
| * @throws BuildException if destDir is not set |
| * @throws BuildException if module path is unset or empty |
| * @throws BuildException if module list is empty |
| * @throws BuildException if compressionLevel attribute and compression |
| * child element are both specified |
| */ |
| @Override |
| public void execute() |
| throws BuildException { |
| if (outputDir == null) { |
| throw new BuildException("Destination directory is required.", |
| getLocation()); |
| } |
| |
| if (modulePath == null || modulePath.isEmpty()) { |
| throw new BuildException("Module path is required.", getLocation()); |
| } |
| |
| if (modules.isEmpty()) { |
| throw new BuildException("At least one module must be specified.", |
| getLocation()); |
| } |
| |
| if (outputDir.exists()) { |
| CompositeMapper imageMapper = new CompositeMapper(); |
| try (Stream<java.nio.file.Path> imageTree = |
| Files.walk(outputDir.toPath())) { |
| |
| /* |
| * Is this sufficient? What if part of the image tree was |
| * deleted or altered? Should we check for standard |
| * files and directories, like 'bin', 'lib', 'conf', 'legal', |
| * and 'release'? (Some, like 'include', may not be present, |
| * if the image was previously built with options that |
| * omitted them.) |
| */ |
| imageTree.forEach( |
| p -> imageMapper.add(new MergingMapper(p.toString()))); |
| |
| ResourceCollection outOfDate = |
| ResourceUtils.selectOutOfDateSources(this, modulePath, |
| imageMapper, getProject(), |
| FileUtils.getFileUtils().getFileTimestampGranularity()); |
| if (outOfDate.isEmpty()) { |
| log("Skipping image creation, since " |
| + "\"" + outputDir + "\" is already newer than " |
| + "all constituent modules.", Project.MSG_VERBOSE); |
| return; |
| } |
| } catch (IOException e) { |
| throw new BuildException( |
| "Could not scan \"" + outputDir + "\" " |
| + "for being up-to-date: " + e, e, getLocation()); |
| } |
| } |
| |
| modules.forEach(ModuleSpec::validate); |
| observableModules.forEach(ModuleSpec::validate); |
| launchers.forEach(Launcher::validate); |
| locales.forEach(LocaleSpec::validate); |
| ordering.forEach(PatternListEntry::validate); |
| excludedFiles.forEach(PatternListEntry::validate); |
| excludedResources.forEach(PatternListEntry::validate); |
| |
| Collection<String> args = buildJlinkArgs(); |
| |
| ToolProvider jlink = ToolProvider.findFirst("jlink").orElseThrow( |
| () -> new BuildException("jlink tool not found in JDK.", |
| getLocation())); |
| |
| if (outputDir.exists()) { |
| log("Deleting existing " + outputDir, Project.MSG_VERBOSE); |
| deleteTree(outputDir.toPath()); |
| } |
| |
| log("Executing: jlink " + String.join(" ", args), Project.MSG_VERBOSE); |
| |
| ByteArrayOutputStream stdout = new ByteArrayOutputStream(); |
| ByteArrayOutputStream stderr = new ByteArrayOutputStream(); |
| OutputStream sysout = stdout; |
| OutputStream syserr = stderr; |
| if (verboseLevel != null) { |
| sysout = new TeeOutputStream(stdout, new LogOutputStream(this, verboseLevel.getLevel())); |
| syserr = new TeeOutputStream(stderr, new LogOutputStream(this, verboseLevel.getLevel())); |
| } |
| |
| int exitCode; |
| try (PrintStream out = new PrintStream(sysout); |
| PrintStream err = new PrintStream(syserr)) { |
| |
| exitCode = jlink.run(out, err, args.toArray(new String[0])); |
| } |
| |
| if (exitCode != 0) { |
| StringBuilder message = new StringBuilder(); |
| message.append("jlink 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 " + outputDir.getAbsolutePath(), Project.MSG_INFO); |
| } |
| |
| /** |
| * Recursively deletes a file tree. |
| * |
| * @param dir root of tree to delete |
| * |
| * @throws BuildException if deletion fails |
| */ |
| private void deleteTree(java.nio.file.Path dir) { |
| try { |
| Files.walkFileTree(dir, new SimpleFileVisitor<java.nio.file.Path>() { |
| @Override |
| public FileVisitResult visitFile(final java.nio.file.Path file, |
| final BasicFileAttributes attr) |
| throws IOException { |
| Files.delete(file); |
| return FileVisitResult.CONTINUE; |
| } |
| |
| @Override |
| public FileVisitResult postVisitDirectory(final java.nio.file.Path dir, |
| IOException e) |
| throws IOException { |
| if (e == null) { |
| Files.delete(dir); |
| } |
| return super.postVisitDirectory(dir, e); |
| } |
| }); |
| } catch (IOException e) { |
| throw new BuildException( |
| "Could not delete \"" + dir + "\": " + e, e, getLocation()); |
| } |
| } |
| |
| /** |
| * Creates list of arguments to <code>jlink</code> tool, based on this |
| * instance's current state. |
| * |
| * @return new list of <code>jlink</code> arguments |
| * |
| * @throws BuildException if any inconsistencies attributes/elements |
| * is found |
| */ |
| private Collection<String> buildJlinkArgs() { |
| Collection<String> args = new ArrayList<>(); |
| |
| args.add("--output"); |
| args.add(outputDir.toString()); |
| |
| args.add("--module-path"); |
| args.add(modulePath.toString()); |
| |
| args.add("--add-modules"); |
| args.add(modules.stream().map(ModuleSpec::getName).collect( |
| Collectors.joining(","))); |
| |
| if (!observableModules.isEmpty()) { |
| args.add("--limit-modules"); |
| args.add(observableModules.stream().map(ModuleSpec::getName).collect( |
| Collectors.joining(","))); |
| } |
| |
| if (!locales.isEmpty()) { |
| args.add("--include-locales=" |
| + locales.stream().map(LocaleSpec::getName).collect( |
| Collectors.joining(","))); |
| } |
| |
| for (Launcher launcher : launchers) { |
| args.add("--launcher"); |
| args.add(launcher.toString()); |
| } |
| |
| if (!ordering.isEmpty()) { |
| args.add("--order-resources=" |
| + ordering.stream().map(PatternListEntry::toOptionValue).collect( |
| Collectors.joining(","))); |
| } |
| if (!excludedFiles.isEmpty()) { |
| args.add("--exclude-files=" |
| + excludedFiles.stream().map(PatternListEntry::toOptionValue).collect( |
| Collectors.joining(","))); |
| } |
| if (!excludedResources.isEmpty()) { |
| args.add("--exclude-resources=" |
| + excludedResources.stream().map(PatternListEntry::toOptionValue).collect( |
| Collectors.joining(","))); |
| } |
| |
| if (bindServices) { |
| args.add("--bind-services"); |
| } |
| if (ignoreSigning) { |
| args.add("--ignore-signing-information"); |
| } |
| if (!includeHeaders) { |
| args.add("--no-header-files"); |
| } |
| if (!includeManPages) { |
| args.add("--no-man-pages"); |
| } |
| if (!includeNativeCommands) { |
| args.add("--strip-native-commands"); |
| } |
| if (!debug) { |
| args.add("--strip-debug"); |
| } |
| if (verboseLevel != null) { |
| args.add("--verbose"); |
| } |
| |
| if (endianness != null) { |
| args.add("--endian"); |
| args.add(endianness.getValue()); |
| } |
| |
| if (compressionLevel != null) { |
| if (compression != null) { |
| throw new BuildException("compressionLevel attribute " |
| + "and <compression> child element cannot both be present.", |
| getLocation()); |
| } |
| args.add("--compress=" + compressionLevel.toCommandLineOption()); |
| } |
| if (compression != null) { |
| compression.validate(); |
| args.add("--compress=" + compression.toCommandLineOption()); |
| } |
| if (vmType != null) { |
| args.add("--vm=" + vmType.getValue()); |
| } |
| if (checkDuplicateLegal) { |
| args.add("--dedup-legal-notices=error-if-not-same-content"); |
| } |
| for (ReleaseInfo info : releaseInfo) { |
| info.validate(); |
| args.addAll(info.toCommandLineOptions()); |
| } |
| |
| return args; |
| } |
| } |