diff --git a/src/main/java/rife/bld/operations/AbstractToolProviderOperation.java b/src/main/java/rife/bld/operations/AbstractToolProviderOperation.java new file mode 100644 index 0000000..b6889ba --- /dev/null +++ b/src/main/java/rife/bld/operations/AbstractToolProviderOperation.java @@ -0,0 +1,218 @@ +/* + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import rife.bld.operations.exceptions.ExitStatusException; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.spi.ToolProvider; + +/** + * Provides common features for tool providers. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public abstract class AbstractToolProviderOperation> + extends AbstractOperation> { + private final List toolArgs_ = new ArrayList<>(); + private final String toolName_; + + /** + * Provides the name of the tool. + * + * @param toolName the tool name + */ + public AbstractToolProviderOperation(String toolName) { + toolName_ = toolName; + } + + /** + * Runs an instance of the tool. + *

+ * On success, command line arguments are automatically cleared. + * + * @throws Exception if an error occurred + */ + @Override + public void execute() throws Exception { + if (toolArgs_.isEmpty()) { + System.err.println("No " + toolName_ + " command line arguments specified."); + throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); + } + + var tool = ToolProvider.findFirst(toolName_).orElseThrow(() -> + new IllegalStateException("No " + toolName_ + " tool found.")); + + var status = tool.run(System.out, System.err, toolArgs_.toArray(new String[0])); + if (status != 0) { + System.out.println(tool.name() + ' ' + String.join(" ", toolArgs_)); + } + + ExitStatusException.throwOnFailure(status); + + toolArgs_.clear(); + } + + /** + * Adds arguments to pass to the tool. + * + * @param arg one or more argument + * @return this operation + */ + @SuppressWarnings("unchecked") + public T toolArgs(String... arg) { + toolArgs(List.of(arg)); + return (T) this; + } + + /** + * Adds arguments to pass to the tool. + * + * @param args the argument to add + * @return this operation + */ + @SuppressWarnings({"unchecked", "UnusedReturnValue"}) + public T toolArgs(List args) { + toolArgs_.addAll(args); + return (T) this; + } + + /** + * Returns the tool's arguments. + * + * @return the arguments + */ + public List toolArgs() { + return toolArgs_; + } + + /** + * Parses arguments to pass to the tool from the given files. + * + * @param files the list of files + * @return this operation instance + * @throws FileNotFoundException if a file cannot be found + */ + @SuppressWarnings({"unchecked", "UnusedReturnValue"}) + public T toolArgsFromFile(List files) throws IOException { + var args = new ArrayList(); + + for (var file : files) { + try (var reader = Files.newBufferedReader(Paths.get(file), Charset.defaultCharset())) { + var tokenizer = new CommandLineTokenizer(reader); + String token; + while ((token = tokenizer.nextToken()) != null) { + args.add(token); + } + } + } + + toolArgs(args); + + return (T) this; + } + + /** + * Adds arguments to pass to the tool. + * + * @param args the argument-value pairs to add + * @return this operation + */ + @SuppressWarnings({"unchecked", "UnusedReturnValue"}) + protected T toolArgs(Map args) { + args.forEach((k, v) -> { + toolArgs_.add(k); + if (v != null && !v.isEmpty()) { + toolArgs_.add(v); + } + }); + return (T) this; + } + + /** + * Tokenize command line arguments. + * + *

    + *
  • Arguments containing spaces should be quoted
  • + *
  • Escape sequences and comments are supported
  • + *
+ */ + public static class CommandLineTokenizer { + private final StringBuilder buf_ = new StringBuilder(); + private final Reader input_; + private int ch_; + + public CommandLineTokenizer(Reader input) throws IOException { + input_ = input; + ch_ = input.read(); + } + + public String nextToken() throws IOException { + trimWhitespaceOrComments(); + if (ch_ == -1) { + return null; + } + + buf_.setLength(0); // reset buffer + + char quote = 0; + while (ch_ != -1) { + if (ch_ == '\'' || ch_ == '"') { // quotes + if (quote == 0) { // begin quote + quote = (char) ch_; + } else if (quote == ch_) { // end quote + quote = 0; + } else { + buf_.append((char) ch_); + } + } else if (ch_ == '\\') { // escaped + ch_ = input_.read(); + buf_.append(handleEscapeSequence()); + } else if (quote == 0 && Character.isWhitespace(ch_)) { // whitespaces + break; + } else { + buf_.append((char) ch_); + } + ch_ = input_.read(); + } + return buf_.toString(); + } + + private char handleEscapeSequence() { + return switch (ch_) { + case -1 -> '\\'; + case 'n' -> '\n'; + case 'r' -> '\r'; + case 't' -> '\t'; + case 'f' -> '\f'; + default -> (char) ch_; + }; + } + + private void trimWhitespaceOrComments() throws IOException { + while (ch_ != -1) { + if (Character.isWhitespace(ch_)) { // Skip whitespaces + ch_ = input_.read(); + } else if (ch_ == '#') { + // Skip the entire comment until a new line or end of input + do { + ch_ = input_.read(); + } while (ch_ != -1 && ch_ != '\n' && ch_ != '\r'); + } else { + break; + } + } + } + } +} diff --git a/src/main/java/rife/bld/operations/JlinkOperation.java b/src/main/java/rife/bld/operations/JlinkOperation.java new file mode 100644 index 0000000..31aafc7 --- /dev/null +++ b/src/main/java/rife/bld/operations/JlinkOperation.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Create run-time images using the jlink tool. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public class JlinkOperation extends AbstractToolProviderOperation { + private final List cmdFiles_ = new ArrayList<>(); + private final List disabledPlugins_ = new ArrayList<>(); + private final JlinkOptions jlinkOptions_ = new JlinkOptions(); + + public JlinkOperation() { + super("jlink"); + } + + + /** + * Read options and/or mode from file(s). + * + * @param file one or more file + * @return this operation instance + */ + public JlinkOperation cmdFiles(String... file) { + cmdFiles_.addAll(List.of(file)); + return this; + } + + /** + * Retrieves the list of files containing options or mode. + * + * @return the list of files + */ + public List cmdFiles() { + return cmdFiles_; + } + + /** + * Disable the plugin mentioned. + * + * @param plugin the plugin name + * @return this map of options + */ + public JlinkOperation disablePlugin(String... plugin) { + disabledPlugins_.addAll(List.of(plugin)); + return this; + } + + @Override + public void execute() throws Exception { + toolArgsFromFile(cmdFiles_); + disabledPlugins_.forEach(plugin -> toolArgs("--disable-plugin", plugin)); + toolArgs(jlinkOptions_); + super.execute(); + } + + /** + * Provides a list of options to provide to the jlink tool. + *

+ * A copy will be created to allow this list to be independently modifiable. + * + * @param options the argument-value pairs + * @return this operation instance + */ + public JlinkOperation jlinkOptions(Map options) { + jlinkOptions_.putAll(options); + return this; + } + + /** + * Retrieves the list of options for the jlink tool. + *

+ * This is a modifiable list that can be retrieved and changed. + * + * @return the map of jlink options + */ + public JlinkOptions jlinkOptions() { + return jlinkOptions_; + } +} diff --git a/src/main/java/rife/bld/operations/JlinkOptions.java b/src/main/java/rife/bld/operations/JlinkOptions.java new file mode 100644 index 0000000..cbe71cf --- /dev/null +++ b/src/main/java/rife/bld/operations/JlinkOptions.java @@ -0,0 +1,329 @@ +/* + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Options for jlink tool. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public class JlinkOptions extends HashMap { + /** + * All Modules Path. + */ + @SuppressWarnings("unused") + public final static String ALL_MODULE_PATH = "ALL-MODULE-PATH"; + + /** + * Root modules to resolve in addition to the initial modules. + *

+ * Module can also be {@link #ALL_MODULE_PATH} + * + * @param modules one or more module + * @return this map of options + */ + public JlinkOptions addModules(String... modules) { + put("--add-modules", String.join(",", modules)); + return this; + } + + /** + * Link in service provider modules and their dependencies. + * + * @param bindServices {@code true} to bind services, {@code false} otherwise + * @return this map of options + */ + public JlinkOptions bindServices(boolean bindServices) { + if (bindServices) { + put("--bind-services"); + } else { + remove("--bind-services"); + } + return this; + } + + /** + * Compression to use in compressing resources. + *

+ * Requires Java 21 or higher. Use {@link #compress(CompressionLevel)} for lower versions. + *

+ * Where {@link ZipCompression#ZIP_0 ZIP_0} provides no compression and {@link ZipCompression#ZIP_9 ZIP_9} provides + * the best compression. + *

Default is {@link ZipCompression#ZIP_6 ZIP_6} + * + * @param compression the {@link ZipCompression compression} level + * @return this map of options + * @see #compress(ZipCompression) + */ + public JlinkOptions compress(ZipCompression compression) { + put("--compress", compression.level); + return this; + } + + /** + * Enable compression of resources. + *

+ * Use {@link #compress(ZipCompression)} on Java 21 or higher. + * + * @param compression the {@link CompressionLevel compression} level + * @return this map of options + * @see #compress(CompressionLevel) + */ + public JlinkOptions compress(CompressionLevel compression) { + put("--compress", compression.level); + return this; + } + + + /** + * Byte order of generated jimage. + *

+ * Default: native + * + * @param endian the byte order + * @return this map of options + */ + public JlinkOptions endian(Endian endian) { + put("--endian", endian.byteOrder); + return this; + } + + /** + * Suppress a fatal error when signed modular JARs are linked in the image. + * + * @param ignoreSigningInformation {@code true} to ignore signing information, {@code false} otherwise + * @return this map of options + */ + public JlinkOptions ignoreSigningInformation(boolean ignoreSigningInformation) { + if (ignoreSigningInformation) { + put("--ignore-signing-information"); + } else { + remove("--ignore-signing-information"); + } + return this; + } + + /** + * Add a launcher command of the given name for the module. + * + * @param name the name + * @param module the module + * @return this map of options + */ + @SuppressWarnings("UnusedReturnValue") + public JlinkOptions launcher(String name, String module) { + put("--launcher", name + "=" + module); + return this; + } + + /** + * Add a launcher command of the given name for the module and the main class. + * + * @param name the name + * @param module the module + * @param mainClass the main class + * @return this map of options + */ + public JlinkOptions launcher(String name, String module, String mainClass) { + put("--launcher", name + "=" + module + "/" + mainClass); + return this; + } + + /** + * Limit the universe of observable modules. + * + * @param module one or more module + * @return this map of options + */ + public JlinkOptions limitModule(String... module) { + put("--limit-modules", String.join(",", module)); + return this; + } + + /** + * Module path. + *

+ * If not specified, the JDKs jmods directory will be used, if it exists. If specified, but it does not contain the + * java.base module, the JDKs jmods directory will be added, if it exists. + * + * @param path the module path + * @return this map of options + */ + public JlinkOptions modulePath(String path) { + put("--module-path", path); + return this; + } + + /** + * Exclude include header files. + * + * @param noHeaderFiles {@code true} to exclude header files, {@code false} otherwise + * @return this map of options + */ + public JlinkOptions noHeaderFiles(boolean noHeaderFiles) { + if (noHeaderFiles) { + put("--no-header-files"); + } else { + remove("--no-header-files"); + } + return this; + } + + /** + * Exclude man pages. + * + * @param noManPages {@code true} to exclude man pages, {@code false} otherwise + * @return this map of options + */ + public JlinkOptions noManPages(boolean noManPages) { + if (noManPages) { + put("--no-man-pages"); + } else { + remove("--no-man-pages"); + } + return this; + } + + /** + * Location of output path. + * + * @param path the output path + * @return this map of options + */ + public JlinkOptions output(String path) { + put("--output", path); + return this; + } + + /** + * Associates {@code null} with the specified key in this map. If the map previously contained a mapping for the + * key, the old value is replaced. + * + * @param key key with which the specified value is to be associated + */ + public void put(String key) { + put(key, null); + } + + /** + * Suggest providers that implement the given service types from the module path. + * + * @param filename the filename + * @return this map of options + */ + public JlinkOptions saveOpts(String filename) { + put("--save-opts", filename); + return this; + } + + /** + * Strip debug information. + * + * @param stripDebug {@code true} to strip debug info, {@code false} otherwise + * @return this map of options + */ + public JlinkOptions stripDebug(boolean stripDebug) { + if (stripDebug) { + put("--strip-debug"); + } else { + remove("--strip-debug"); + } + return this; + } + + /** + * Strip native commands. + * + * @param stripNativeCommands {@code true} to strip, {@code false} otherwise + * @return this map of options + */ + public JlinkOptions stripNativeCommands(boolean stripNativeCommands) { + if (stripNativeCommands) { + put("--strip-native-commands"); + } else { + remove("--strip-native-commands"); + } + return this; + } + + /** + * Suggest providers that implement the given service types from the module path. + * + * @param name one or more provider name + * @return this map of options + */ + public JlinkOptions suggestProviders(String... name) { + put("--suggest-providers", String.join(",", name)); + return this; + } + + public List toList() { + var list = new ArrayList(); + forEach((k, v) -> { + list.add(k); + if (v != null && !v.isEmpty()) { + list.add(v); + } + }); + return list; + } + + /** + * Enable verbose tracing. + * + * @param verbose {@code true} to enable verbose tracing, {@code false} otherwise. + * @return this map of options + */ + public JlinkOptions verbose(boolean verbose) { + if (verbose) { + put("--verbose"); + } else { + remove("--verbose"); + } + return this; + } + + /** + * The byte orders. + */ + public enum Endian { + BIG("big"), LITTLE("little"); + + public final String byteOrder; + + Endian(String byteOrder) { + this.byteOrder = byteOrder; + } + } + + /** + * Resources compression levels. + */ + public enum CompressionLevel { + /** + * Level 0: No compression + */ + NO_COMPRESSION("0"), + /** + * Level 1: Constant string sharing + */ + CONSTANT_STRING_SHARING("1"), + /** + * Level 2: ZIP + */ + ZIP("2"); + + public final String level; + + CompressionLevel(String level) { + this.level = level; + } + } +} diff --git a/src/main/java/rife/bld/operations/JmodOperation.java b/src/main/java/rife/bld/operations/JmodOperation.java new file mode 100644 index 0000000..85f11d1 --- /dev/null +++ b/src/main/java/rife/bld/operations/JmodOperation.java @@ -0,0 +1,153 @@ +/* + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Create JMOD files with the jmod tool. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public class JmodOperation extends AbstractToolProviderOperation { + private final List cmdFiles = new ArrayList<>(); + private final JmodOptions jmodOptions_ = new JmodOptions(); + private String jmodFile_; + private OperationMode operationMode_; + + public JmodOperation() { + super("jmod"); + } + + /** + * Retrieves the list of files containing options or mode. + * + * @return the list of files + */ + public List cmdFiles() { + return cmdFiles; + } + + /** + * Read options and/or mode from file(s). + * + * @param file one or more file + * @return this operation instance + */ + public JmodOperation cmdFiles(String... file) { + cmdFiles.addAll(List.of(file)); + return this; + } + + @Override + public void execute() throws Exception { + if (operationMode_ != null) { + toolArgs(operationMode_.mode); + } + + toolArgsFromFile(cmdFiles); + toolArgs(jmodOptions_); + + if (jmodFile_ != null) { + toolArgs(jmodFile_); + } + + super.execute(); + } + + /** + * Retrieves the name of the JMOD file to create or from which to retrieve information. + * + * @return the JMOD file + */ + public String jmodFile() { + return jmodFile_; + } + + /** + * Specifies name of the JMOD file to create or from which to retrieve information. + *

+ * The JMOD file is required. + * + * @param file the JMOD file + * @return this operation instance + */ + public JmodOperation jmodFile(String file) { + jmodFile_ = file; + return this; + } + + /** + * Retrieves the list of options for the jmod tool. + *

+ * This is a modifiable list that can be retrieved and changed. + * + * @return the map of jmod options + */ + public JmodOptions jmodOptions() { + return jmodOptions_; + } + + /** + * Provides a list of options to provide to the jmod tool. + *

+ * A copy will be created to allow this list to be independently modifiable. + * + * @param options the list of jmod options + * @return this operation instance + */ + public JmodOperation jmodOptions(Map options) { + jmodOptions_.putAll(options); + return this; + } + + /** + * Provides the {@link OperationMode operation mode}. + *

+ * The operation mode is required. + * + * @param mode the mode + * @return this operation instance + */ + public JmodOperation operationMode(OperationMode mode) { + operationMode_ = mode; + return this; + } + + /** + * The operation modes. + */ + public enum OperationMode { + /** + * Creates a new JMOD archive file. + */ + CREATE("create"), + /** + * Prints the module details. + */ + DESCRIBE("describe"), + /** + * Extracts all the files from the JMOD archive file. + */ + EXTRACT("extract"), + /** + * Determines leaf modules and records the hashes of the dependencies that directly and indirectly require them. + */ + HASH("hash"), + /** + * Prints the names of all the entries. + */ + LIST("list"); + + final String mode; + + OperationMode(String mode) { + this.mode = mode; + } + } +} diff --git a/src/main/java/rife/bld/operations/JmodOptions.java b/src/main/java/rife/bld/operations/JmodOptions.java new file mode 100644 index 0000000..3510655 --- /dev/null +++ b/src/main/java/rife/bld/operations/JmodOptions.java @@ -0,0 +1,294 @@ +/* + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Options for jmod tool. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public class JmodOptions extends HashMap { + /** + * Application jar files|dir containing classes. + * + * @param classpath the classpath + * @return this map of options + */ + public JmodOptions classpath(String classpath) { + put("--class-path", classpath); + return this; + } + + /** + * Location of native commands. + * + * @param path the location + * @return this map of options + */ + public JmodOptions cmds(String path) { + put("--cmds", path); + return this; + } + + /** + * Compression to use when creating the JMOD archive. + *

+ * Requires Java 20 or higher. + *

+ * Where {@link ZipCompression#ZIP_0 ZIP_0} provides no compression and {@link ZipCompression#ZIP_9 ZIP_9} provides the + * best compression. + *

+ * Default is {@link ZipCompression#ZIP_6 ZIP_6} + * + * @param compression the {@link ZipCompression compression} level + * @return this map of options + */ + public JmodOptions compress(ZipCompression compression) { + put("--compress", compression.level); + return this; + } + + /** + * Location of user-editable config files + * + * @param path the path to the config files + * @return this map of options + */ + public JmodOptions config(String path) { + put("--config", path); + return this; + } + + /** + * Date and time for the timestamps of entries. + * + * @param date the date + * @return this map of options + */ + public JmodOptions date(ZonedDateTime date) { + put("--date", date.truncatedTo(ChronoUnit.SECONDS).format(DateTimeFormatter.ISO_INSTANT)); + return this; + } + + /** + * Target directory for extract + * + * @param path the directory path + * @return this map of options + */ + public JmodOptions dir(String path) { + put("--dir", path); + return this; + } + + /** + * Exclude from the default root set of modules. + * + * @param doNotResolveByDefault {@code true} to not resolve, {@code false} otherwise + * @return this map of options + */ + public JmodOptions doNotResolveByDefault(boolean doNotResolveByDefault) { + if (doNotResolveByDefault) { + put("--do-not-resolve-by-default"); + } else { + remove("--do-not-resolve-by-default"); + } + return this; + } + + /** + * Dry run of hash mode. + * + * @param dryRun {@code true} for dry run, {@code false} otherwise + * @return this list of operation + */ + public JmodOptions dryRun(boolean dryRun) { + if (dryRun) { + put("--dry-run"); + } else { + remove("--dry-run"); + } + return this; + } + + /** + * Exclude files matching the supplied pattern list. + * + * @param pattern one or more pattern + * @return the map of options + */ + public JmodOptions exclude(FilePattern... pattern) { + var args = new ArrayList(); + for (var p : pattern) { + if (p.type == FilePatternType.GLOB) { + args.add("glob:" + p.pattern); + } else if (p.type == FilePatternType.REGEX) { + args.add("regex:" + p.pattern); + } + } + put("--exclude", String.join(",", args)); + return this; + } + + /** + * Compute and record hashes to tie a packaged module with modules matching the given regular expression pattern and + * depending upon it directly or indirectly. The hashes are recorded in the JMOD file being created, or a JMOD file + * or modular JAR on the module path specified the jmod hash command. + * + * @param regexPattern the regular expression pattern + * @return this map of options + */ + public JmodOptions hashModules(String regexPattern) { + put("--hash-modules", regexPattern); + return this; + } + + /** + * Location of header files. + * + * @param path the location + * @return this map of options + */ + public JmodOptions headerFiles(String path) { + put("--header-files", path); + return this; + } + + /** + * Location of legal notices. + * + * @param path the location + * @return this map of options + */ + public JmodOptions legalNotices(String path) { + put("--legal-notices", path); + return this; + } + + /** + * Location of native libraries. + * + * @param path the location + * @return this map of options + */ + public JmodOptions libs(String path) { + put("--libs", path); + return this; + } + + /** + * Main class. + * + * @param name the class name + * @return this list of operation + */ + public JmodOptions mainClass(String name) { + put("--main-class", name); + return this; + } + + /** + * Location of man pages. + * + * @param path the location + * @return this map of options + */ + public JmodOptions manPages(String path) { + put("--man-pages", path); + return this; + } + + /** + * Module path. + * + * @param path the module path + * @return this map of options + */ + public JmodOptions modulePath(String path) { + put("--module-path", path); + return this; + } + + /** + * Module version. + * + * @param version the module version. + * @return this map of options + */ + public JmodOptions moduleVersion(String version) { + put("--module-version", version); + return this; + } + + /** + * Associates {@code null} with the specified key in this map. If the map previously contained a mapping for the + * key, the old value is replaced. + * + * @param key key with which the specified value is to be associated + */ + public void put(String key) { + put(key, null); + } + + /** + * Target platform. + * + * @param platform the platform + * @return this list of operation + */ + public JmodOptions targetPlatform(String platform) { + put("--target-platform", platform); + return this; + } + + /** + * Hint for a tool to issue a warning if the module is resolved. + * + * @param reason the reason + * @return this map of options + */ + public JmodOptions warnIfResolved(ResolvedReason reason) { + put("--warn-if-resolved", reason.reason); + return this; + } + + /** + * The resolved reasons. + */ + public enum ResolvedReason { + DEPRECATED("deprecated"), + DEPRECATED_FOR_REMOVAL("deprecated-for-removal"), + INCUBATING("incubating"); + + final String reason; + + ResolvedReason(String reason) { + this.reason = reason; + } + } + + /** + * The file pattern types. + */ + public enum FilePatternType { + GLOB, REGEX + } + + /** + * Defines a file pattern and pattern type. + * + * @param type the pattern type + * @param pattern the pattern + */ + public record FilePattern(FilePatternType type, String pattern) { + } +} diff --git a/src/main/java/rife/bld/operations/JpackageOperation.java b/src/main/java/rife/bld/operations/JpackageOperation.java new file mode 100644 index 0000000..7b8509a --- /dev/null +++ b/src/main/java/rife/bld/operations/JpackageOperation.java @@ -0,0 +1,75 @@ +/* + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Package self-contained Java applications with the jpackage tool. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public class JpackageOperation extends AbstractToolProviderOperation { + private final List cmdFiles_ = new ArrayList<>(); + private final JpackageOptions jpackageOptions_ = new JpackageOptions(); + + public JpackageOperation() { + super("jpackage"); + } + + @Override + public void execute() throws Exception { + toolArgs(cmdFiles_.stream().map(opt -> '@' + opt).toList()); + toolArgs(jpackageOptions_); + super.execute(); + } + + /** + * Retrieves the list of files containing options or mode. + * + * @return the list of files + */ + public List fileOptions() { + return cmdFiles_; + } + + /** + * Read options and/or mode from file(s). + * + * @param file one or more file + * @return this operation instance + */ + public JpackageOperation cmdFiles(String... file) { + cmdFiles_.addAll(List.of(file)); + return this; + } + + /** + * Retrieves the list of options for the jpackage tool. + *

+ * This is a modifiable list that can be retrieved and changed. + * + * @return the map of jpackage options + */ + public JpackageOptions jpackageOptions() { + return jpackageOptions_; + } + + /** + * Provides a list of options to provide to the jpackage tool. + *

+ * A copy will be created to allow this list to be independently modifiable. + * + * @param options the map of jpackage options + * @return this operation instance + */ + public JpackageOperation jpackageOptions(Map options) { + jpackageOptions_.putAll(options); + return this; + } +} diff --git a/src/main/java/rife/bld/operations/JpackageOptions.java b/src/main/java/rife/bld/operations/JpackageOptions.java new file mode 100644 index 0000000..8702562 --- /dev/null +++ b/src/main/java/rife/bld/operations/JpackageOptions.java @@ -0,0 +1,888 @@ +/* + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import java.util.HashMap; + +/** + * Options for jpackage tool. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public class JpackageOptions extends HashMap { + /** + * URL of the application's home page. + * + * @param url the URL + * @return this map of options + */ + public JpackageOptions aboutUrl(String url) { + put("--about-url", url); + return this; + } + + /** + * List of application launchers. + *

+ * The main application launcher will be built from the command line options. + *

+ * Additional alternative launchers can be built using this option, and this option can be used to build multiple + * additional launchers. + * + * @param launcher one or more {@link Launcher} + * @return this map of options + */ + public JpackageOptions addLauncher(Launcher... launcher) { + for (var l : launcher) { + put("--add-launcher", l.name + '=' + l.path); + } + return this; + } + + /** + * List of modules to add. + *

+ * This module list, along with the main module (if specified) will be passed to jlink as the + * {@link JlinkOptions#addModules(String...) addModules} argument. If not specified, either just the main module + * (if {@link #module(String, String) module} is specified), or the default set of modules (if + * {@link #mainJar(String) mainJar} is specified) are used. + * + * @param modules one or more module + * @return this map of options + */ + public JpackageOptions addModules(String... modules) { + put("--add-modules", String.join(",", modules)); + return this; + } + + /** + * List of paths to files and/or directories to add to the application payload. + *

+ * Requires Java 20 or higher. + * + * @param additionalContent one or more path + * @return this map of options + */ + public JpackageOptions appContent(String... additionalContent) { + put("--app-content", String.join(",", additionalContent)); + return this; + } + + /** + * Location of the predefined application image that is used to build an installable package. + * + * @param path absolute path or relative to the current directory + * @return this map of options + */ + public JpackageOptions appImage(String path) { + put("--app-image", path); + return this; + } + + /** + * Version of the application and/or package. + * + * @param version the version + * @return this map of options + */ + public JpackageOptions appVersion(String version) { + put("--app-version", version); + return this; + } + + /** + * Command line arguments to pass to main class if no command line arguments are given to the launcher. + * + * @param argument one or more argument + * @return this map of options + */ + public JpackageOptions arguments(String... argument) { + put("--arguments", String.join(" ", argument)); + return this; + } + + /** + * Copyright of the application. + * + * @param copyright the copyright + * @return this map of options + */ + public JpackageOptions copyright(String copyright) { + put("--copyright", copyright); + return this; + } + + /** + * Description of the application. + * + * @param description the description + * @return this map of options + */ + public JpackageOptions description(String description) { + put("--description", description); + return this; + } + + /** + * Path where generated output file is placed. + *

+ * Defaults to the current working directory. + * + * @param path absolute path or relative to the current directory + * @return this map of options + */ + public JpackageOptions dest(String path) { + put("--dest", path); + return this; + } + + /** + * Path to a Properties file that contains list of key, value pairs. + *

+ * The keys {@code extension}, {@code mime-type}, {@code icon}, and {@code description} can be used to describe the + * association. + * + * @param path absolute path or relative to the current directory + * @return this map of options + */ + public JpackageOptions fileAssociations(String... path) { + put("--file-associations", String.join(",", path)); + return this; + } + + /** + * Path of the icon of the application package. + * + * @param path absolute path or relative to the current directory + * @return this map of options + */ + public JpackageOptions icon(String path) { + put("--icon", path); + return this; + } + + /** + * Path of the input directory that contains the files to be packaged. + *

+ * All files in the input directory will be packaged into the application image. + * + * @param path absolute path or relative to the current directory + * @return this map of options + */ + public JpackageOptions input(String path) { + put("--input", path); + return this; + } + + /** + * Absolute path of the installation directory of the application. + * + * @param path the absolute directory path + * @return this map of options + */ + public JpackageOptions installDir(String path) { + put("--install-dir", path); + return this; + } + + /** + * Options to pass to the Java runtime. + * + * @param options the options + * @return this map of options + */ + public JpackageOptions javaOptions(String... options) { + put("--java-options", String.join(" ", options)); + return this; + } + + /** + * List of options to pass to jlink. + *

+ * If not specified, defaults to {@link JlinkOptions#stripNativeCommands(boolean) stripNativeCommands} + * {@link JlinkOptions#stripDebug(boolean) stripDebug} {@link JlinkOptions#noManPages(boolean) noManPages} + * {@link JlinkOptions#noHeaderFiles(boolean) noHeaderFiles}. + * + * @param options the {@link JlinkOptions} + * @return this map of options + */ + public JpackageOptions jlinkOptions(JlinkOptions options) { + put("--jlink-options", String.join(" ", options.toList())); + return this; + } + + /** + * Request to create an installer that will register the main application launcher as a background service-type + * application. + *

+ * Requires Java 20 or higher. + * + * @param launcherAsService {@code true} to register the launcher as a service; {@code false} otherwise + * @return this map of options + */ + public JpackageOptions launcherAsService(boolean launcherAsService) { + if (launcherAsService) { + put("--launcher-as-service"); + } else { + remove("--launcher-as-service"); + } + return this; + } + + /** + * Path to the license file. + * + * @param path absolute path or relative to the current directory + * @return this map of options + */ + public JpackageOptions licenseFile(String path) { + put("--license-file", path); + return this; + } + + /** + * Group value of the RPM {@code .spec} file or Section value of DEB control file. + * + * @param appCategory the application category + * @return this map of options + */ + public JpackageOptions linuxAppCategory(String appCategory) { + put("--linux-app-category", appCategory); + return this; + } + + /** + * Release value of the RPM {@code .spec} file or Debian revision value of the DEB control file. + * + * @param appRelease the release value + * @return this map of options + */ + public JpackageOptions linuxAppRelease(String appRelease) { + put("--linux-app-release", appRelease); + return this; + } + + /** + * Maintainer for {@code .deb} package. + * + * @param maintainer the maintainer + * @return this map of options + */ + public JpackageOptions linuxDebMaintainer(String maintainer) { + put("--linux-deb-maintainer", maintainer); + return this; + } + + /** + * Menu group this application is placed in. + * + * @param menuGroup the menu group + * @return this map of options + */ + public JpackageOptions linuxMenuGroup(String menuGroup) { + put("--linux-menu-group", menuGroup); + return this; + } + + /** + * Required packages or capabilities for the application. + * + * @param packageDeps {@code true} if required, {@code false} otherwise + * @return this map of options + */ + public JpackageOptions linuxPackageDeps(boolean packageDeps) { + if (packageDeps) { + put("--linux-package-deps"); + } else { + remove("--linux-package-deps"); + } + return this; + } + + /** + * Name for Linux package, defaults to the application name. + * + * @param packageName the package name + * @return this map of options + */ + public JpackageOptions linuxPackageName(String packageName) { + put("--linux-package-name", packageName); + return this; + } + + /** + * Type of the license. + *

+ * {@code License: } of the RPM {@code .spec} + * + * @param licenseType the license type + * @return this map of options + */ + public JpackageOptions linuxRpmLicenseType(String licenseType) { + put("--linux-rpm-license-type", licenseType); + return this; + } + + /** + * Creates a shortcut for the application. + * + * @param shortcut {@code true| to create a shortcut, {@code false} otherwise + * @return this map of options + */ + public JpackageOptions linuxShortcut(boolean shortcut) { + if (shortcut) { + put("--linux-shortcut"); + } else { + remove("--linux-shortcut"); + } + return this; + } + + /** + * String used to construct {@code LSApplicationCategoryType} in application plist. + *

+ * The default value is {@code utilities}. + * + * @param appCategory the category + * @return this map of options + */ + public JpackageOptions macAppCategory(String appCategory) { + put("--mac-app-category", appCategory); + return this; + } + + /** + * Identity used to sign application image. + *

+ * This value will be passed directly to {@code --sign} option of {@code codesign} tool. + *

+ * This option cannot be combined with {@link #macSigningKeyUserName(String) macSignKeyUserName}. + * + * @param identity the identity + * @return this map of options + */ + public JpackageOptions macAppImageSignIdentity(String identity) { + put("--mac-app-image-sign-identity", identity); + return this; + } + + /** + * Indicates that the jpackage output is intended for the Mac App Store. + * + * @param appStore {@code true} if intended for the Mac App Store, {@code false} otherwise + * @return this map of options + */ + public JpackageOptions macAppStore(boolean appStore) { + if (appStore) { + put("--mac-app-store"); + } else { + remove("--mac-app-store"); + } + return this; + } + + /** + * Include all the referenced content in the dmg. + * + * @param additionalContent one or more path + * @return this map of options + */ + public JpackageOptions macDmgContent(String... additionalContent) { + put("--mac-dmg-content", String.join(",", additionalContent)); + return this; + } + + /** + * Path to file containing entitlements to use when signing executables and libraries in the bundle. + * + * @param path the fie path + * @return this map of options + */ + public JpackageOptions macEntitlements(String path) { + put("--mac-entitlements", path); + return this; + } + + /** + * Identity used to sign "pkg" installer. + *

+ * This value will be passed directly to {@code --sign} option of {@code productbuild} tool. + *

+ * This option cannot be combined with {@link #macSigningKeyUserName(String) macSignKeyUserName}. + * + * @param identity the identity + * @return this map of options + */ + public JpackageOptions macInstallerSignIdentity(String identity) { + put("--mac-installer-sign-identity", identity); + return this; + } + + /** + * An identifier that uniquely identifies the application for macOS. + *

+ * Defaults to the main class name. + *

+ * May only use alphanumeric ({@code A-Z,a-z,0-9}), hyphen ({@code -}), and period ({@code .}) characters. + * + * @param packageIdentifier the package identifier + * @return this map of options + */ + public JpackageOptions macPackageIdentifier(String packageIdentifier) { + put("--mac-package-identifier", packageIdentifier); + return this; + } + + /** + * Name of the application as it appears in the Menu Bar. + *

+ * This can be different from the application name. + *

+ * This name must be less than 16 characters long and be suitable for displaying in the menu bar and the application + * Info window. + *

+ * Defaults to the application name. + * + * @param name the package name + * @return this map of options + */ + public JpackageOptions macPackageName(String name) { + put("--mac-package-name", name); + return this; + } + + /** + * When signing the application package, this value is prefixed to all components that need to be signed that don't + * have an existing package identifier. + * + * @param prefix the signing prefix + * @return this map of options + */ + public JpackageOptions macPackageSigningPrefix(String prefix) { + put("--mac-package-signing-prefix", prefix); + return this; + } + + /** + * Request that the package or the predefined application image be signed. + * + * @param sign {@code true} to sign, {@code false} otherwise + * @return this map of options + */ + public JpackageOptions macSign(boolean sign) { + if (sign) { + put("--mac-sign"); + } else { + remove("--mac-sign"); + } + return this; + } + + /** + * Team or user name portion in Apple signing identities. + *

+ * For direct control of the signing identity used to sign application images or installers use + * {@link #macAppImageSignIdentity(String) macAppImageSignIdentity} and/or + * {@link #macInstallerSignIdentity(String) macInstallerSignIdentity}. + *

+ * This option cannot be combined with {@link #macAppImageSignIdentity(String) macAppImageSignIdentity} or + * {@link #macInstallerSignIdentity(String) macInstallerSignIdentity}. + * + * @param username the username + * @return this map of options + */ + public JpackageOptions macSigningKeyUserName(String username) { + put("--mac-signing-key-user-name", username); + return this; + } + + /** + * Name of the keychain to search for the signing identity. + *

+ * If not specified, the standard keychains are used. + * + * @param keychain the keychain name + * @return this map of options + */ + public JpackageOptions macSigningKeychain(String keychain) { + put("--mac-signing-keychain", keychain); + return this; + } + + /** + * Qualified name of the application main class to execute. + *

+ * This option can only be used if {@link #mainJar(String) mainJar} is specified. + * + * @param mainClass the main class + * @return this map of options + */ + public JpackageOptions mainClass(String mainClass) { + put("--main-class", mainClass); + return this; + } + + /** + * The main JAR of the application; containing the main class. + *

+ * Either {@link #module(String, String) module} or {@link #mainJar(String) mainJar} option can be specified but + * not both. + * + * @param jar the path relative to the input path + * @return this map of options + */ + @SuppressWarnings("JavadocDeclaration") + public JpackageOptions mainJar(String jar) { + put("--main-jar", jar); + return this; + } + + /** + * The main module and main class of the application. + *

+ * This module must be located on the {@link #modulePath(String...) module path}. + *

+ * When this option is specified, the main module will be linked in the Java runtime image. + *

+ * Either {@link #module(String, String) module} or {@link #mainJar(String) mainJar} option can be specified but + * not both. + * + * @param name the module name + * @return this map of options + */ + public JpackageOptions module(String name) { + put("--module", name); + return this; + } + + /** + * The main module and main class of the application. + *

+ * This module must be located on the {@link #modulePath(String...) module path}. + *

+ * When this option is specified, the main module will be linked in the Java runtime image. + *

+ * Either {@link #module(String, String) module} or {@link #mainJar(String) mainJar} option can be specified but + * not both. + * + * @param name the module name + * @param mainClass the main class + * @return this map of options + */ + @SuppressWarnings("JavadocDeclaration") + public JpackageOptions module(String name, String mainClass) { + put("--module-name", name + "/" + mainClass); + return this; + } + + /** + * List of module paths. + *

+ * Each path is either a directory of modules or the path to a modular jar. + *

+ * Each path is absolute or relative to the current directory. + * + * @param path one or more path + * @return this map of options + */ + public JpackageOptions modulePath(String... path) { + put("--module-path", String.join(":", path)); + return this; + } + + /** + * Name of the application and/or package. + * + * @param name the name + * @return this map of options + */ + public JpackageOptions name(String name) { + put("--name", name); + return this; + } + + /** + * Associates {@code null} with the specified key in this map. If the map previously contained a mapping for the + * key, the old value is replaced. + * + * @param key key with which the specified value is to be associated + */ + public void put(String key) { + put(key, null); + } + + /** + * Path to override jpackage resources. + *

+ * Icons, template files, and other resources of jpackage can be over-ridden by adding replacement resources to + * this directory. + * + * @param path absolute path or relative to the current directory + * @return this map of options + */ + public JpackageOptions resourceDir(String path) { + put("--resource-dir", path); + return this; + } + + /** + * Path of the predefined runtime image that will be copied into the application image. + *

+ * If not specified, jpackage will run jlink to create the runtime image using options: + * {@link JlinkOptions#stripNativeCommands(boolean) stripNativeCommands} + * {@link JlinkOptions#stripDebug(boolean) stripDebug} {@link JlinkOptions#noManPages(boolean) noManPages} + * {@link JlinkOptions#noHeaderFiles(boolean) noHeaderFiles} + *

+ * Option is required when creating a runtime package. + * + * @param path absolute path or relative to the current directory + * @return this map of options + */ + public JpackageOptions runtimeImage(String path) { + put("--runtime-image", path); + return this; + } + + /** + * Strip debug information. + * + * @param stripDebug {@code true} to strip debug info, {@code false} otherwise + * @return this map of options + */ + public JpackageOptions stripDebug(boolean stripDebug) { + if (stripDebug) { + put("--strip-debug"); + } else { + remove("--strip-debug"); + } + return this; + } + + /** + * Path of a new or empty directory used to create temporary files. + *

+ * If specified, the temp dir will not be removed upon the task completion and must be removed manually. + *

+ * If not specified, a temporary directory will be created and removed upon the task completion. + * + * @param path absolute path or relative to the current directory + * @return this map of options + */ + public JpackageOptions temp(String path) { + put("--temp", path); + return this; + } + + /** + * The type of package to create. + *

+ * If this option is not specified a platform dependent default type will be created. + * + * @param type the package type + * @return this map of options + */ + public JpackageOptions type(PackageType type) { + put("--type", type.type); + return this; + } + + /** + * Vendor of the application. + * + * @param vendor the vendor + * @return this map of options + */ + public JpackageOptions vendor(String vendor) { + put("--vendor", vendor); + return this; + } + + /** + * Enables verbose output. + * + * @param verbose {@code true} to enable verbose tracing, {@code false} otherwise. + * @return this map of options + */ + public JpackageOptions verbose(boolean verbose) { + if (verbose) { + put("--verbose"); + } else { + remove("--verbose"); + } + return this; + } + + /** + * Creates a console launcher for the application, should be specified for application which requires console + * interactions. + * + * @param winConsole {@code true} to create a console launcher, {@code false} otherwise + * @return this map of options + */ + public JpackageOptions winConsole(boolean winConsole) { + if (winConsole) { + put("--win-console"); + } else { + remove("--win-console"); + } + return this; + } + + /** + * Adds a dialog to enable the user to choose a directory in which the product is installed. + * + * @param winDirChooser {@code true} to let the user choose a directory, {@code false} otherwise + * @return this map of options + */ + public JpackageOptions winDirChooser(boolean winDirChooser) { + if (winDirChooser) { + put("--win-dir-chooser"); + } else { + remove("--win-dir-chooser"); + } + return this; + } + + /** + * URL where user can obtain further information or technical support. + * + * @param helpUrl the help URL + * @return this map of options + */ + public JpackageOptions winHelpUrl(String helpUrl) { + put("--win-help-url", helpUrl); + return this; + } + + /** + * Request to add a Start Menu shortcut for this application. + * + * @param winMenu {@code true} to add a start menu shortcut, {@code false} otherwise + * @return this map of options + */ + public JpackageOptions winMenu(boolean winMenu) { + if (winMenu) { + put("--win-menu"); + } else { + remove("--win-menu"); + } + return this; + } + + /** + * Start Menu group this application is placed in. + * + * @param menuGroup the menu group + * @return this map of options + */ + public JpackageOptions winMenuGroup(String menuGroup) { + put("--win-menu-group", menuGroup); + return this; + } + + /** + * Request to perform an install on a per-user basis. + * + * @param winPerUserInstall {@code true} for per-user install, {@code false} otherwise + * @return this map of options + */ + public JpackageOptions winPerUserInstall(boolean winPerUserInstall) { + if (winPerUserInstall) { + put("--win-per-user-install"); + } else { + remove("--win-per-user-install"); + } + return this; + } + + /** + * Request to create a desktop shortcut for this application. + * + * @param winShortcut {@code true} to create a shortcut, {@code false} otherwise + * @return this map of options + */ + public JpackageOptions winShortcut(boolean winShortcut) { + if (winShortcut) { + put("--win-shortcut"); + } else { + remove("--win-shortcut"); + } + return this; + } + + /** + * Adds a dialog to enable the user to choose if shortcuts will be created by installer. + * + * @param shortcutPrompt {@code true} to add a prompt; {@code false} otherwise + * @return this map of options + */ + public JpackageOptions winShortcutPrompt(boolean shortcutPrompt) { + if (shortcutPrompt) { + put("--win-shortcut-prompt"); + } else { + remove("--win-shortcut-prompt"); + } + return this; + } + + /** + * URL of available application update information. + * + * @param url the URL + * @return this map of options + */ + public JpackageOptions winUpdateUrl(String url) { + put("--win-update-url", url); + return this; + } + + /** + * UUID associated with upgrades for this package. + * + * @param uuid the uuid + * @return this map of options + */ + public JpackageOptions winUpgradeUuid(String uuid) { + put("--win-upgrade-uuid", uuid); + return this; + } + + /** + * The package types. + */ + public enum PackageType { + APP_IMAGE("app_image"), + DEB("deb"), + DMG("dmg"), + EXE("exe"), + MSI("msi"), + PKG("pkg"), + RPM("rpm"); + + final String type; + + PackageType(String type) { + this.type = type; + } + } + + /** + * Name of launcher, and a path to a Properties file that contains a list of key, value pairs. + *

+ * The keys {@code module}, {@code main-jar}, {@code main-class}, {@code description}, + * {@code arguments}, {@code java-options}, {@code app-version}, {@code icon}, + * {@code launcher-as-service}, {@code win-console}, {@code win-shortcut}, {@code win-menu}, + * {@code linux-app-category}, and {@code linux-shortcut} can be used. + *

+ * These options are added to, or used to overwrite, the original command line options to build an additional + * alternative launcher. + * + * @param name the name + * @param path absolute path or relative to the current directory + */ + public record Launcher(String name, String path) { + } +} diff --git a/src/main/java/rife/bld/operations/ZipCompression.java b/src/main/java/rife/bld/operations/ZipCompression.java new file mode 100644 index 0000000..4d5722a --- /dev/null +++ b/src/main/java/rife/bld/operations/ZipCompression.java @@ -0,0 +1,30 @@ +/** + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +/** + * The zip compression levels for jlink and jmod. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public enum ZipCompression { + ZIP_0("zip-0"), + ZIP_1("zip-1"), + ZIP_2("zip-2"), + ZIP_3("zip-3"), + ZIP_4("zip-4"), + ZIP_5("zip-5"), + ZIP_6("zip-6"), + ZIP_7("zip-7"), + ZIP_8("zip-8"), + ZIP_9("zip-9"); + + public final String level; + + ZipCompression(String level) { + this.level = level; + } +} diff --git a/src/test/java/rife/bld/operations/TestJlinkOperation.java b/src/test/java/rife/bld/operations/TestJlinkOperation.java new file mode 100644 index 0000000..88086e7 --- /dev/null +++ b/src/test/java/rife/bld/operations/TestJlinkOperation.java @@ -0,0 +1,163 @@ +/* + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import rife.bld.operations.exceptions.ExitStatusException; +import rife.tools.FileUtils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; +import static rife.bld.operations.JlinkOptions.CompressionLevel; + +public class TestJlinkOperation { + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + private final PrintStream stdout = System.out; + + @AfterEach + public void tearDown() { + System.setOut(stdout); + } + + @Test + void testArguments() { + var args = new HashMap(); + args.put("--add-modules", "module-1,module-2"); + args.put("--bind-services", null); + args.put("--compress", "2"); + args.put("--endian", "big"); + args.put("--ignore-signing-information", null); + args.put("--launcher", "name=module/mainclass"); + args.put("--limit-modules", "module-1,module-2"); + args.put("--module-path", "module-path"); + args.put("--no-header-files", null); + args.put("--no-man-pages", null); + args.put("--output", "output"); + args.put("--save-opts", "save-opts"); + args.put("--strip-debug", null); + args.put("--suggest-providers", "provider-1,provider-2"); + args.put("--verbose", null); + + var options = new JlinkOptions() + .addModules(args.get("--add-modules").split(",")) + .bindServices(true) + .compress(CompressionLevel.ZIP) + .endian(JlinkOptions.Endian.BIG) + .ignoreSigningInformation(true) + .launcher("name", "module", "mainclass") + .limitModule(args.get("--limit-modules").split(",")) + .modulePath(args.get("--module-path")) + .noHeaderFiles(true) + .noManPages(true) + .output(args.get("--output")) + .saveOpts(args.get("--save-opts")) + .stripDebug(true) + .suggestProviders(args.get("--suggest-providers").split(",")) + .verbose(true); + + assertEquals(options.size(), args.size(), "Wrong number of arguments"); + + for (var arg : args.entrySet()) { + assertTrue(options.containsKey(arg.getKey()), arg.getValue() + " not found"); + assertEquals(arg.getValue(), options.get(arg.getKey()), arg.getKey()); + } + + options.launcher("name-2", "module-2"); + assertEquals("name-2=module-2", options.get("--launcher"), "incorrect launcher"); + } + + @Test + void testCmdFiles() { + System.setOut(new PrintStream(outputStreamCaptor)); + final JlinkOperation jlink; + if (System.getProperty("os.name").startsWith("Windows")) { + jlink = new JlinkOperation().cmdFiles("src/test/resources/jlink/options_jlink_win.txt"); + } else { + jlink = new JlinkOperation().cmdFiles("src/test/resources/jlink/options_jlink.txt"); + } + assertDoesNotThrow(jlink::execute); + var out = outputStreamCaptor.toString(); + assertTrue(out.contains("List of available plugins:"), out); + } + + @Test + void testCmdFilesVersion() { + System.setOut(new PrintStream(outputStreamCaptor)); + var jlink = new JlinkOperation().cmdFiles("src/test/resources/jlink/options_verbose.txt", + "src/test/resources/jlink/options_version.txt"); + assertDoesNotThrow(jlink::execute); + var out = outputStreamCaptor.toString(); + assertTrue(out.matches("[\\d.]+[\\r\\n]+"), out); + } + + @Test + void testDisablePlugin() { + System.setOut(new PrintStream(outputStreamCaptor)); + var jlink = new JlinkOperation() + .disablePlugin("vm") + .disablePlugin("system-modules") + .toolArgs("--list-plugins"); + assertDoesNotThrow(jlink::execute); + var out = outputStreamCaptor.toString(); + assertTrue(out.contains("List of available plugins:"), out); + } + + @Test + void testExecute() throws IOException { + var tmpdir = Files.createTempDirectory("bld-jlink-test").toFile(); + try { + var output = new File(tmpdir, "jlink"); + + var options = new JlinkOptions() + .modulePath("src/test/resources/jlink/build/jmod") + .addModules("dev.mccue.tree") + .launcher("tree", "dev.mccue.tree", "dev.mccue.tree.Tree") + .output(output.getAbsolutePath()); + if (Runtime.version().version().get(0) >= 21) { + options.compress(ZipCompression.ZIP_6); + } else { + options.compress(CompressionLevel.ZIP); + } + + var jlink = new JlinkOperation().jlinkOptions(options); + + assertDoesNotThrow(jlink::execute); + assertTrue(output.exists(), "Output dir does not exist"); + } finally { + FileUtils.deleteDirectory(tmpdir); + } + } + + @Test + void testHelp() { + var jlink = new JlinkOperation().toolArgs("--help"); + assertDoesNotThrow(jlink::execute); + assertTrue(jlink.toolArgs().isEmpty(), "args not empty"); + } + + @Test + void testNoArguments() { + var jlink = new JlinkOperation(); + assertTrue(jlink.jlinkOptions().isEmpty(), "jlink options not empty"); + assertTrue(jlink.cmdFiles().isEmpty(), "file options not empty"); + assertThrows(ExitStatusException.class, jlink::execute); + } + + @Test + void testVersion() { + System.setOut(new PrintStream(outputStreamCaptor)); + var jlink = new JlinkOperation().toolArgs("--verbose", "--version"); + assertDoesNotThrow(jlink::execute); + var out = outputStreamCaptor.toString(); + assertTrue(out.matches("[\\d.]+[\\r\\n]+"), out); + } +} diff --git a/src/test/java/rife/bld/operations/TestJmodOperation.java b/src/test/java/rife/bld/operations/TestJmodOperation.java new file mode 100644 index 0000000..ac68b0d --- /dev/null +++ b/src/test/java/rife/bld/operations/TestJmodOperation.java @@ -0,0 +1,191 @@ +/* + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import rife.bld.operations.exceptions.ExitStatusException; +import rife.tools.FileUtils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; +import static rife.bld.operations.JmodOperation.OperationMode; + +public class TestJmodOperation { + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + private final PrintStream stdout = System.out; + + @AfterEach + public void tearDown() { + System.setOut(stdout); + } + + @Test + void testArguments() { + var args = new HashMap(); + args.put("--class-path", "classpath"); + args.put("--cmds", "cmds"); + args.put("--config", "config"); + args.put("--date", "1997-08-29T09:14:00Z"); + args.put("--dir", "dir"); + args.put("--do-not-resolve-by-default", null); + args.put("--dry-run", null); + args.put("--exclude", "glob:glob,regex:regex"); + args.put("--hash-modules", "regex"); + args.put("--header-files", "header-files"); + args.put("--legal-notices", "legal-notices"); + args.put("--libs", "libs"); + args.put("--main-class", "main-class"); + args.put("--man-pages", "man-pages"); + args.put("--module-path", "module-path"); + args.put("--module-version", "module-version"); + args.put("--target-platform", "target-platform"); + args.put("--warn-if-resolved", "deprecated"); + + var options = new JmodOptions() + .classpath(args.get("--class-path")) + .cmds(args.get("--cmds")) + .config(args.get("--config")) + .date(ZonedDateTime.of(1997, 8, 29, 2, 14, 0, 1, ZoneId.of("America/Los_Angeles"))) + .dir(args.get("--dir")) + .doNotResolveByDefault(true) + .dryRun(true) + .exclude(new JmodOptions.FilePattern(JmodOptions.FilePatternType.GLOB, "glob"), + new JmodOptions.FilePattern(JmodOptions.FilePatternType.REGEX, "regex")) + .hashModules(args.get("--hash-modules")) + .headerFiles(args.get("--header-files")) + .legalNotices(args.get("--legal-notices")) + .libs(args.get("--libs")) + .mainClass(args.get("--main-class")) + .manPages(args.get("--man-pages")) + .modulePath(args.get("--module-path")) + .moduleVersion(args.get("--module-version")) + .targetPlatform(args.get("--target-platform")) + .warnIfResolved(JmodOptions.ResolvedReason.DEPRECATED); + + assertEquals(options.size(), args.size(), "Wrong number of arguments"); + + for (var arg : args.entrySet()) { + assertTrue(options.containsKey(arg.getKey()), arg.getValue() + " not found"); + assertEquals(arg.getValue(), options.get(arg.getKey()), arg.getKey()); + } + } + + @Test + void testCmdFiles() { + System.setOut(new PrintStream(outputStreamCaptor)); + var jmod = new JmodOperation().cmdFiles("src/test/resources/jlink/options_version.txt"); + assertDoesNotThrow(jmod::execute); + var out = outputStreamCaptor.toString(); + assertTrue(out.matches("[\\d.]+[\\r\\n]+"), out); + } + + @Test + void testCmdFilesCreate() throws IOException { + var tmpdir = Files.createTempDirectory("bld-jmod-test").toFile(); + try { + var mod = new File(tmpdir, "dev.mccue.tree.jmod"); + + var jmod = new JmodOperation() + .cmdFiles("src/test/resources/jlink/options_jmod.txt") + .jmodFile(mod.getAbsolutePath()); + + assertDoesNotThrow(jmod::execute); + assertTrue(mod.exists(), "mod does not exist"); + } finally { + FileUtils.deleteDirectory(tmpdir); + } + } + + @Test + void testCreate() throws IOException { + var tmpdir = Files.createTempDirectory("bld-jmod-test").toFile(); + try { + var mod = new File(tmpdir, "dev.mccue.tree.jmod"); + + var options = new JmodOptions() + .date(ZonedDateTime.now()) + .legalNotices("src/test/resources/jlink/dev.mccue.apple/legal") + .classpath("src/test/resources/jlink/build/jar/dev.mccue.apple.jar"); + var jmod = new JmodOperation() + .operationMode(OperationMode.CREATE) + .jmodFile(mod.getAbsolutePath()) + .jmodOptions(options); + + assertDoesNotThrow(jmod::execute); + assertTrue(mod.exists(), "mod does not exist"); + } finally { + FileUtils.deleteDirectory(tmpdir); + } + } + + @Test + void testExecute() throws IOException { + var tmpdir = Files.createTempDirectory("bld-jmod-test").toFile(); + try { + var mod = new File(tmpdir, "dev.mccue.tree.jmod"); + + var options = new JmodOptions().classpath("src/test/resources/jlink/build/jar/dev.mccue.tree.jar"); + if (Runtime.version().version().get(0) >= 20) { + options.compress(ZipCompression.ZIP_9); + } + + var jmod = new JmodOperation() + .operationMode(OperationMode.CREATE) + .jmodFile(mod.getAbsolutePath()) + .jmodOptions(options); + + assertDoesNotThrow(jmod::execute); + assertTrue(mod.exists(), "mod does not exist"); + + jmod.jmodOptions().clear(); + System.setOut(new PrintStream(outputStreamCaptor)); + + jmod.operationMode(OperationMode.DESCRIBE); + assertDoesNotThrow(jmod::execute, "describe mod failed"); + assertTrue(outputStreamCaptor.toString().contains("dev.mccue.tree"), + "missing dev.mccue.tee in:\n" + outputStreamCaptor); + + jmod.operationMode(OperationMode.LIST); + assertDoesNotThrow(jmod::execute, "list mod failed"); + assertTrue(outputStreamCaptor.toString().contains("module-info.class"), + "missing module-info.class in:\n" + outputStreamCaptor); + } finally { + FileUtils.deleteDirectory(tmpdir); + } + } + + @Test + void testHelp() { + var jmod = new JmodOperation().toolArgs("--help-extra"); + assertDoesNotThrow(jmod::execute); + assertTrue(jmod.toolArgs().isEmpty(), "args not empty"); + } + + @Test + void testNoArguments() { + var jmod = new JmodOperation(); + assertTrue(jmod.cmdFiles().isEmpty(), "file options not empty"); + assertTrue(jmod.jmodOptions().isEmpty(), "jmod options not empty"); + assertThrows(ExitStatusException.class, jmod::execute); + } + + @Test + void testVersion() { + System.setOut(new PrintStream(outputStreamCaptor)); + var jmod = new JmodOperation().toolArgs("--version"); + assertDoesNotThrow(jmod::execute); + var out = outputStreamCaptor.toString(); + assertTrue(out.matches("[\\d.]+[\\r\\n]+"), out); + } +} diff --git a/src/test/java/rife/bld/operations/TestJpackageOperation.java b/src/test/java/rife/bld/operations/TestJpackageOperation.java new file mode 100644 index 0000000..35657b0 --- /dev/null +++ b/src/test/java/rife/bld/operations/TestJpackageOperation.java @@ -0,0 +1,231 @@ +/* + * Copyright 2024 Erik C. Thauvin (https://erik.thauvin.net/) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import rife.bld.operations.exceptions.ExitStatusException; +import rife.tools.FileUtils; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.file.Files; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; +import static rife.bld.operations.JpackageOptions.Launcher; +import static rife.bld.operations.JpackageOptions.PackageType; + +public class TestJpackageOperation { + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + private final PrintStream stdout = System.out; + + @AfterEach + public void tearDown() { + System.setOut(stdout); + } + + @Test + void testArguments() { + var args = new HashMap(); + args.put("--about-url", "about-url"); + args.put("--add-launcher", "name=path"); + args.put("--add-modules", "modules-1,modules-2"); + args.put("--app-content", "content-1,content-2"); + args.put("--app-image", "app-image"); + args.put("--app-version", "app-version"); + args.put("--arguments", "argument1 argument2"); + args.put("--copyright", "copyright"); + args.put("--description", "description"); + args.put("--dest", "dest"); + args.put("--file-associations", "file-associations"); + args.put("--icon", "icon"); + args.put("--input", "input"); + args.put("--install-dir", "install-dir"); + args.put("--java-options", "java-options"); + args.put("--jlink-options", "--strip-debug --add-modules module-1,module-2"); + args.put("--launcher-as-service", null); + args.put("--license-file", "license-file"); + args.put("--linux-app-category", "linux-app-category"); + args.put("--linux-app-release", "linux-app-release"); + args.put("--linux-deb-maintainer", "linux-deb-maintainer"); + args.put("--linux-menu-group", "linux-menu-group"); + args.put("--linux-package-deps", null); + args.put("--linux-package-name", "linux-package-name"); + args.put("--linux-rpm-license-type", "linux-rpm-license-type"); + args.put("--linux-shortcut", null); + args.put("--mac-app-category", "mac-app-category"); + args.put("--mac-app-image-sign-identity", "mac-app-image-sign-identity"); + args.put("--mac-app-store", null); + args.put("--mac-dmg-content", "mac-dmg-content"); + args.put("--mac-entitlements", "mac-entitlements"); + args.put("--mac-installer-sign-identity", "mac-installer-sign-identity"); + args.put("--mac-package-identifier", "mac-package-identifier"); + args.put("--mac-package-name", "mac-package-name"); + args.put("--mac-package-signing-prefix", "mac-package-signing-prefix"); + args.put("--mac-sign", null); + args.put("--mac-signing-key-user-name", "mac-signing-key-user-name"); + args.put("--mac-signing-keychain", "mac-signing-keychain"); + args.put("--main-class", "main-class"); + args.put("--main-jar", "main-jar"); + args.put("--module", "module"); + args.put("--module-path", "module-path-1:module-path-2"); + args.put("--name", "name"); + args.put("--resource-dir", "resource-dir"); + args.put("--runtime-image", "runtime-image"); + args.put("--strip-debug", null); + args.put("--temp", "temp"); + args.put("--type", "exe"); + args.put("--vendor", "vendor"); + args.put("--verbose", null); + args.put("--win-console", null); + args.put("--win-dir-chooser", null); + args.put("--win-help-url", "win-help-url"); + args.put("--win-menu", null); + args.put("--win-menu-group", "win-menu-group"); + args.put("--win-per-user-install", null); + args.put("--win-shortcut", null); + args.put("--win-shortcut-prompt", null); + args.put("--win-update-url", "win-update-url"); + args.put("--win-upgrade-uuid", "win-upgrade-uuid"); + + var options = new JpackageOptions() + .aboutUrl(args.get("--about-url")) + .addLauncher(new Launcher("name", "path")) + .addModules(args.get("--add-modules").split(",")) + .appContent(args.get("--app-content").split(",")) + .appImage(args.get("--app-image")) + .appVersion(args.get("--app-version")) + .arguments(args.get("--arguments").split(" ")) + .copyright(args.get("--copyright")) + .description(args.get("--description")) + .dest(args.get("--dest")) + .fileAssociations(args.get("--file-associations").split(",")) + .icon(args.get("--icon")) + .input(args.get("--input")) + .installDir(args.get("--install-dir")) + .javaOptions(args.get("--java-options").split(",")) + .jlinkOptions(new JlinkOptions().stripDebug(true).addModules("module-1", "module-2")) + .launcherAsService(true) + .licenseFile(args.get("--license-file")) + .linuxAppCategory(args.get("--linux-app-category")) + .linuxAppRelease(args.get("--linux-app-release")) + .linuxDebMaintainer(args.get("--linux-deb-maintainer")) + .linuxMenuGroup(args.get("--linux-menu-group")) + .linuxPackageDeps(true) + .linuxPackageName(args.get("--linux-package-name")) + .linuxRpmLicenseType(args.get("--linux-rpm-license-type")) + .linuxShortcut(true) + .macAppCategory(args.get("--mac-app-category")) + .macAppImageSignIdentity(args.get("--mac-app-image-sign-identity")) + .macAppStore(true) + .macDmgContent(args.get("--mac-dmg-content")) + .macEntitlements(args.get("--mac-entitlements")) + .macInstallerSignIdentity(args.get("--mac-installer-sign-identity")) + .macPackageIdentifier(args.get("--mac-package-identifier")) + .macPackageName(args.get("--mac-package-name")) + .macPackageSigningPrefix(args.get("--mac-package-signing-prefix")) + .macSign(true) + .macSigningKeyUserName(args.get("--mac-signing-key-user-name")) + .macSigningKeychain(args.get("--mac-signing-keychain")) + .mainClass(args.get("--main-class")) + .mainJar(args.get("--main-jar")) + .module(args.get("--module")) + .modulePath(args.get("--module-path").split(",")) + .name(args.get("--name")) + .resourceDir(args.get("--resource-dir")) + .runtimeImage(args.get("--runtime-image")) + .stripDebug(true) + .temp(args.get("--temp")) + .type(PackageType.EXE) + .vendor(args.get("--vendor")) + .verbose(true) + .winConsole(true) + .winDirChooser(true) + .winHelpUrl(args.get("--win-help-url")) + .winMenu(true) + .winMenuGroup(args.get("--win-menu-group")) + .winPerUserInstall(true) + .winShortcut(true) + .winShortcutPrompt(true) + .winUpdateUrl(args.get("--win-update-url")) + .winUpgradeUuid(args.get("--win-upgrade-uuid")); + + assertEquals(options.size(), args.size(), "Wrong number of arguments"); + + for (var arg : args.entrySet()) { + assertTrue(options.containsKey(arg.getKey()), arg.getValue() + " not found"); + assertEquals(arg.getValue(), options.get(arg.getKey()), arg.getKey()); + } + + } + + @Test + void testCreatePackage() throws Exception { + var tmpdir = Files.createTempDirectory("bld-jpackage-test").toFile(); + try { + var jlinkOptions = new JlinkOptions() + .compress(JlinkOptions.CompressionLevel.ZIP) + .stripNativeCommands(true); + var options = new JpackageOptions() + .input("lib/bld") + .name("bld") + .mainJar("bld-wrapper.jar") + .javaOptions("--enable-preview") + .dest(tmpdir.getAbsolutePath()) + .verbose(true) + .jlinkOptions(jlinkOptions); + var os = System.getProperty("os.version"); + if (os.endsWith("MANJARO")) { + options.type(PackageType.DEB); + } + + var jpackage = new JpackageOperation().jpackageOptions(options); + jpackage.execute(); + + var files = tmpdir.listFiles(); + assertNotNull(files, "files should not be null"); + assertTrue(files.length > 0, "no files found"); + + assertTrue(files[0].getName().matches("bld.*\\.[A-Za-z]{3}"), "Package not found"); + } finally { + FileUtils.deleteDirectory(tmpdir); + } + } + + @Test + void testCmdFiles() { + System.setOut(new PrintStream(outputStreamCaptor)); + var jpackage = new JpackageOperation().cmdFiles("src/test/resources/jlink/options_verbose.txt", + "src/test/resources/jlink/options_version.txt"); + assertDoesNotThrow(jpackage::execute); + var out = outputStreamCaptor.toString(); + assertTrue(out.matches("[\\d.]+[\\r\\n]+"), out); + } + + @Test + void testHelp() { + var jpackage = new JpackageOperation().toolArgs("--help"); + assertDoesNotThrow(jpackage::execute); + assertTrue(jpackage.toolArgs().isEmpty(), "args not empty"); + } + + @Test + void testNoArguments() { + var jpackage = new JpackageOperation(); + assertTrue(jpackage.fileOptions().isEmpty(), "file options not empty"); + assertTrue(jpackage.jpackageOptions().isEmpty(), "jpackage options not empty"); + assertThrows(ExitStatusException.class, jpackage::execute); + } + + @Test + void testVersion() { + System.setOut(new PrintStream(outputStreamCaptor)); + var jpackage = new JpackageOperation().toolArgs("--verbose", "--version"); + assertDoesNotThrow(jpackage::execute); + var out = outputStreamCaptor.toString(); + assertTrue(out.matches("[\\d.]+[\\r\\n]+"), out); + } +} diff --git a/src/test/resources/jlink/.gitignore b/src/test/resources/jlink/.gitignore new file mode 100644 index 0000000..edea9d9 --- /dev/null +++ b/src/test/resources/jlink/.gitignore @@ -0,0 +1,2 @@ +build/javac +build/jar \ No newline at end of file diff --git a/src/test/resources/jlink/Makefile b/src/test/resources/jlink/Makefile new file mode 100644 index 0000000..c7f0557 --- /dev/null +++ b/src/test/resources/jlink/Makefile @@ -0,0 +1,40 @@ +.PHONY: clean +clean: + rm -rf build + +.PHONY: compile +compile: clean + javac -d build/javac --module-source-path "./*/src" --module dev.mccue.tree,dev.mccue.apple + +.PHONY: package +package: compile + mkdir -p build/jar + + jar --create --file build/jar/dev.mccue.apple.jar \ + -C build/javac/dev.mccue.apple . \ + -C dev.mccue.apple/res . + jar --create --file build/jar/dev.mccue.tree.jar \ + -C build/javac/dev.mccue.tree . + +.PHONY: link +link: package + mkdir -p build/jmod + + jmod create \ + --legal-notices dev.mccue.apple/legal \ + --class-path build/jar/dev.mccue.apple.jar \ + build/jmod/dev.mccue.apple.jmod + + jmod create \ + --class-path build/jar/dev.mccue.tree.jar \ + build/jmod/dev.mccue.tree.jmod + + jlink \ + --module-path build/jmod \ + --add-modules dev.mccue.tree \ + --launcher tree=dev.mccue.tree/dev.mccue.tree.Tree \ + --output build/jlink + +.PHONY: test +test: link + ./build/jlink/bin/tree diff --git a/src/test/resources/jlink/build/jar/dev.mccue.apple.jar b/src/test/resources/jlink/build/jar/dev.mccue.apple.jar new file mode 100644 index 0000000..cc64501 Binary files /dev/null and b/src/test/resources/jlink/build/jar/dev.mccue.apple.jar differ diff --git a/src/test/resources/jlink/build/jar/dev.mccue.tree.jar b/src/test/resources/jlink/build/jar/dev.mccue.tree.jar new file mode 100644 index 0000000..93abdd3 Binary files /dev/null and b/src/test/resources/jlink/build/jar/dev.mccue.tree.jar differ diff --git a/src/test/resources/jlink/build/jmod/dev.mccue.apple.jmod b/src/test/resources/jlink/build/jmod/dev.mccue.apple.jmod new file mode 100644 index 0000000..e4b33c1 Binary files /dev/null and b/src/test/resources/jlink/build/jmod/dev.mccue.apple.jmod differ diff --git a/src/test/resources/jlink/build/jmod/dev.mccue.tree.jmod b/src/test/resources/jlink/build/jmod/dev.mccue.tree.jmod new file mode 100644 index 0000000..cceacbd Binary files /dev/null and b/src/test/resources/jlink/build/jmod/dev.mccue.tree.jmod differ diff --git a/src/test/resources/jlink/dev.mccue.apple/dev.mccue.apple.iml b/src/test/resources/jlink/dev.mccue.apple/dev.mccue.apple.iml new file mode 100644 index 0000000..c90834f --- /dev/null +++ b/src/test/resources/jlink/dev.mccue.apple/dev.mccue.apple.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/jlink/dev.mccue.apple/legal/LICENSE b/src/test/resources/jlink/dev.mccue.apple/legal/LICENSE new file mode 100644 index 0000000..6a332dd --- /dev/null +++ b/src/test/resources/jlink/dev.mccue.apple/legal/LICENSE @@ -0,0 +1 @@ +Apples are GOD'S creation. You have no right to them. \ No newline at end of file diff --git a/src/test/resources/jlink/dev.mccue.apple/res/dev/mccue/apple/color.txt b/src/test/resources/jlink/dev.mccue.apple/res/dev/mccue/apple/color.txt new file mode 100644 index 0000000..46f29e8 --- /dev/null +++ b/src/test/resources/jlink/dev.mccue.apple/res/dev/mccue/apple/color.txt @@ -0,0 +1 @@ +red \ No newline at end of file diff --git a/src/test/resources/jlink/dev.mccue.apple/src/dev/mccue/apple/Apple.java b/src/test/resources/jlink/dev.mccue.apple/src/dev/mccue/apple/Apple.java new file mode 100644 index 0000000..8afbc8b --- /dev/null +++ b/src/test/resources/jlink/dev.mccue.apple/src/dev/mccue/apple/Apple.java @@ -0,0 +1,20 @@ +package dev.mccue.apple; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Objects; + +public final class Apple { + public String color() { + try { + return new String( + Objects.requireNonNull( + Apple.class.getResourceAsStream("/dev/mccue/apple/color.txt") + ).readAllBytes() + ); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + } +} diff --git a/src/test/resources/jlink/dev.mccue.apple/src/module-info.java b/src/test/resources/jlink/dev.mccue.apple/src/module-info.java new file mode 100644 index 0000000..1e2b64b --- /dev/null +++ b/src/test/resources/jlink/dev.mccue.apple/src/module-info.java @@ -0,0 +1,3 @@ +module dev.mccue.apple { + exports dev.mccue.apple; +} \ No newline at end of file diff --git a/src/test/resources/jlink/dev.mccue.tree/dev.mccue.tree.iml b/src/test/resources/jlink/dev.mccue.tree/dev.mccue.tree.iml new file mode 100644 index 0000000..3dcb8cc --- /dev/null +++ b/src/test/resources/jlink/dev.mccue.tree/dev.mccue.tree.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/jlink/dev.mccue.tree/src/dev/mccue/tree/Tree.java b/src/test/resources/jlink/dev.mccue.tree/src/dev/mccue/tree/Tree.java new file mode 100644 index 0000000..aa8ebe9 --- /dev/null +++ b/src/test/resources/jlink/dev.mccue.tree/src/dev/mccue/tree/Tree.java @@ -0,0 +1,15 @@ +package dev.mccue.tree; + +import dev.mccue.apple.Apple; + +public final class Tree { + public Apple bearFruit() { + return new Apple(); + } + + public static void main(String[] args) { + System.out.println( + new Tree().bearFruit().color() + ); + } +} diff --git a/src/test/resources/jlink/dev.mccue.tree/src/module-info.java b/src/test/resources/jlink/dev.mccue.tree/src/module-info.java new file mode 100644 index 0000000..e481ad5 --- /dev/null +++ b/src/test/resources/jlink/dev.mccue.tree/src/module-info.java @@ -0,0 +1,3 @@ +module dev.mccue.tree { + requires dev.mccue.apple; +} \ No newline at end of file diff --git a/src/test/resources/jlink/options_jlink.txt b/src/test/resources/jlink/options_jlink.txt new file mode 100644 index 0000000..64cd9e6 --- /dev/null +++ b/src/test/resources/jlink/options_jlink.txt @@ -0,0 +1,5 @@ + --verbose --version #--foo + --module-path "\'foo --bar\' \"bar --foo\" --bar" --list-plugins +--output foo --bind-services + +# bar \ No newline at end of file diff --git a/src/test/resources/jlink/options_jlink_win.txt b/src/test/resources/jlink/options_jlink_win.txt new file mode 100644 index 0000000..e47aa53 --- /dev/null +++ b/src/test/resources/jlink/options_jlink_win.txt @@ -0,0 +1,5 @@ + --verbose --version #--foo + --module-path "\'foo --bar\' --bar" --list-plugins +--output foo --bind-services + +# bar \ No newline at end of file diff --git a/src/test/resources/jlink/options_jmod.txt b/src/test/resources/jlink/options_jmod.txt new file mode 100644 index 0000000..81caa7e --- /dev/null +++ b/src/test/resources/jlink/options_jmod.txt @@ -0,0 +1,3 @@ +create +--legal-notices src/test/resources/jlink/dev.mccue.apple/legal +--class-path src/test/resources/jlink/build/jar/dev.mccue.apple.jar \ No newline at end of file diff --git a/src/test/resources/jlink/options_verbose.txt b/src/test/resources/jlink/options_verbose.txt new file mode 100644 index 0000000..4342561 --- /dev/null +++ b/src/test/resources/jlink/options_verbose.txt @@ -0,0 +1 @@ +--verbose \ No newline at end of file diff --git a/src/test/resources/jlink/options_version.txt b/src/test/resources/jlink/options_version.txt new file mode 100644 index 0000000..e0f9217 --- /dev/null +++ b/src/test/resources/jlink/options_version.txt @@ -0,0 +1 @@ +--version \ No newline at end of file