From 547b20a242ca0e1b2112d304c795b9ba0e0bb5f0 Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Thu, 1 Aug 2024 11:41:29 -0700 Subject: [PATCH] Draft implementations of JpackageOperation, JmodOperation and JlinkOperation --- .../AbstractToolProviderOperation.java | 142 +++ .../rife/bld/operations/JlinkOperation.java | 52 ++ .../rife/bld/operations/JlinkOptions.java | 284 ++++++ .../rife/bld/operations/JmodOperation.java | 88 ++ .../java/rife/bld/operations/JmodOptions.java | 291 ++++++ .../bld/operations/JpackageOperation.java | 52 ++ .../rife/bld/operations/JpackageOptions.java | 843 ++++++++++++++++++ .../rife/bld/operations/ZipCompression.java | 30 + .../bld/operations/TestJlinkOperation.java | 26 + .../bld/operations/TestJmodOperation.java | 26 + .../bld/operations/TestJpackageOperation.java | 56 ++ 11 files changed, 1890 insertions(+) create mode 100644 src/main/java/rife/bld/operations/AbstractToolProviderOperation.java create mode 100644 src/main/java/rife/bld/operations/JlinkOperation.java create mode 100644 src/main/java/rife/bld/operations/JlinkOptions.java create mode 100644 src/main/java/rife/bld/operations/JmodOperation.java create mode 100644 src/main/java/rife/bld/operations/JmodOptions.java create mode 100644 src/main/java/rife/bld/operations/JpackageOperation.java create mode 100644 src/main/java/rife/bld/operations/JpackageOptions.java create mode 100644 src/main/java/rife/bld/operations/ZipCompression.java create mode 100644 src/test/java/rife/bld/operations/TestJlinkOperation.java create mode 100644 src/test/java/rife/bld/operations/TestJmodOperation.java create mode 100644 src/test/java/rife/bld/operations/TestJpackageOperation.java 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..6113069 --- /dev/null +++ b/src/main/java/rife/bld/operations/AbstractToolProviderOperation.java @@ -0,0 +1,142 @@ +/* + * Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.operations; + +import rife.bld.operations.exceptions.ExitStatusException; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +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 Map toolArgs_ = new HashMap<>(); + private final String toolName_; + + /** + * Provides the name of the tool. + * + * @param toolName the tool name + */ + public AbstractToolProviderOperation(String toolName) { + toolName_ = toolName; + } + + /** + * Converts arguments to a list. + * + * @param args the argument-value pairs + * @return the arguments list + */ + static List argsToList(Map args) { + var list = new ArrayList(); + for (String arg : args.keySet()) { + var value = args.get(arg); + list.add(arg); + if (value != null && !value.isEmpty()) { + list.add(value); + } + } + return list; + } + + /** + * Adds tool command line argument. + * + * @param arg the argument to add + * @return this operation + */ + public T toolArg(String arg) { + toolArgs_.put(arg, null); + return (T) this; + } + + /** + * Add tool command line argument. + * + * @param arg the argument + * @param value the value + * @return this operation + */ + public T toolArg(String arg, String value) { + toolArgs_.put(arg, value); + return (T) this; + } + + /** + * Adds tool command line arguments. + * + * @param args the argument-value pairs to add + * @return this operation + */ + protected T toolArgs(Map args) { + toolArgs_.putAll(args); + return (T) this; + } + + /** + * Clears the tool command line arguments. + * + * @return this operation + */ + protected T clearToolArguments() { + toolArgs_.clear(); + return (T) this; + } + + @Override + public void execute() throws Exception { + if (toolArgs_.isEmpty()) { + System.err.println("No " + toolName_ + " arguments specified."); + throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); + } + var tool = ToolProvider.findFirst(toolName_).orElseThrow(() -> + new IllegalStateException("No " + toolName_ + " tool found.")); + + var argsList = argsToList(toolArgs_); + + var stderr = new StringWriter(); + var stdout = new StringWriter(); + try (var err = new PrintWriter(stderr); var out = new PrintWriter(stdout)) { + var status = tool.run(out, err, argsList.toArray(new String[0])); + out.flush(); + err.flush(); + + if (status != 0) { + System.out.println(tool.name() + " " + String.join(" ", argsList)); + } + + var output = stdout.toString(); + if (!output.isBlank()) { + System.out.println(output); + } + var error = stderr.toString(); + if (!error.isBlank()) { + System.err.println(error); + } + + ExitStatusException.throwOnFailure(status); + } + } + + /** + * Returns the tool command line arguments. + * + * @return the arguments + */ + public Map toolArgs() { + return toolArgs_; + } +} 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..c3d5d44 --- /dev/null +++ b/src/main/java/rife/bld/operations/JlinkOperation.java @@ -0,0 +1,52 @@ +/* + * 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.Map; + +/** + * Create run-time images using the jlink tool. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public class JlinkOperation extends AbstractToolProviderOperation { + private final JlinkOptions jlinkOptions_ = new JlinkOptions(); + + public JlinkOperation() { + super("jlink"); + } + + @Override + public void execute() throws Exception { + toolArgs(jlinkOptions_); + super.execute(); + } + + /** + * Retrieves the list of options for the jlink tool. + *

+ * This is a modifiable list that can be retrieved and changed. + * + * @return the list of jlink options + */ + public JlinkOptions jlinkOptions() { + return jlinkOptions_; + } + + /** + * 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; + } +} 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..d9969b2 --- /dev/null +++ b/src/main/java/rife/bld/operations/JlinkOptions.java @@ -0,0 +1,284 @@ +/** + * 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 jlink tool. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public class JlinkOptions extends HashMap { + /** + * All Modules Path. + */ + 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", null); + } else { + remove("--bind-services"); + } + return this; + } + + /** + * Compression to use in compressing resources. + *

+ * 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 JlinkOptions compress(ZipCompression compression) { + put("--compress", compression.level); + return this; + } + + /** + * Disable the plugin mentioned. + * + * @param plugin the plugin name + * @return this map of options + */ + public JlinkOptions disablePlugin(String plugin) { + put("--disable-plugin", plugin); + 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; + } + + /** + * Read options from file. + * + * @param filename the filename + * @return this map of options + */ + public JlinkOptions filename(String filename) { + put("@" + filename, null); + 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", null); + } 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 + */ + 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", null); + } 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", null); + } 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; + } + + /** + * 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", null); + } else { + remove("--strip-debug"); + } + return this; + } + + /** + * Strip the 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", null); + } 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; + } + + /** + * 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", null); + } 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; + } + } +} 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..071f703 --- /dev/null +++ b/src/main/java/rife/bld/operations/JmodOperation.java @@ -0,0 +1,88 @@ +/* + * 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.util.Map; + +/** + * Create JMOD files with the jmod tool. + * + * @author Erik C. Thauvin + * @since 2.0.2 + */ +public class JmodOperation extends AbstractToolProviderOperation { + private final JmodOptions jmodOptions_ = new JmodOptions(); + private OperationMode operationMode_; + + public JmodOperation() { + super("jmod"); + } + + @Override + public void execute() throws Exception { + if (operationMode_ == null) { + System.err.println("Operation mode not set."); + throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); + } + toolArg(operationMode_.mode); + toolArgs(jmodOptions_); + super.execute(); + } + + /** + * Retrieves the list of options for the jmod tool. + *

+ * This is a modifiable list that can be retrieved and changed. + * + * @return the list 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 required {@link OperationMode operation mode}. + * + * @param mode the mode + * @return this operation instance + */ + public JmodOperation operationMode(OperationMode mode) { + operationMode_ = mode; + return this; + } + + /** + * The operation modes. + */ + public enum OperationMode { + CREATE("create"), + DESCRIBE("describe"), + EXTRACT("extract"), + HASH("hash"), + 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..acd7152 --- /dev/null +++ b/src/main/java/rife/bld/operations/JmodOptions.java @@ -0,0 +1,291 @@ +/** + * 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.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. + *

+ * 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.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", null); + } 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", null); + } else { + remove("--dry-run"); + } + return this; + } + + /** + * Exclude files matching the supplied pattern list. + * + * @param pattern one or more pattern + * @return the list 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; + } + + /** + * Read options from the specified file. + * + * @param filename the filename + * @return this map of options + */ + public JmodOptions filename(String filename) { + put("@" + filename, null); + 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; + } + + /** + * 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..5fbdf0b --- /dev/null +++ b/src/main/java/rife/bld/operations/JpackageOperation.java @@ -0,0 +1,52 @@ +/* + * 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.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 JpackageOptions jpackageOptions_ = new JpackageOptions(); + + public JpackageOperation() { + super("jpackage"); + } + + @Override + public void execute() throws Exception { + toolArgs(jpackageOptions_); + super.execute(); + } + + /** + * 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..0167fd6 --- /dev/null +++ b/src/main/java/rife/bld/operations/JpackageOptions.java @@ -0,0 +1,843 @@ +/** + * 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. + * + * @param additionalContent one or more path + * @return this map of options + */ + public JpackageOptions appContent(String... additionalContent) { + put("--app-content", String.join(",", additionalContent)); + 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; + } + + /** + * 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 + * + * @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; + } + + /** + * Read options and/or mode from a file. + * + * @param filename the filename + * @return this map of options + */ + public JpackageOptions filename(String filename) { + put("@" + filename, null); + 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; + } + + /** + * 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(" ", AbstractToolProviderOperation.argsToList(options))); + return this; + } + + /** + * Request to create an installer that will register the main application launcher as a background service-type + * application. + * + * @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", null); + } 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 .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 the package dependencies + * @return this list of operation + */ + public JpackageOptions linuxPackageDeps(String packageDeps) { + put("--linux-package-deps", packageDeps); + 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 .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", null); + } else { + remove("--linux-shortcut"); + } + 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 + */ + 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 list of operation + */ + 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 + */ + public JpackageOptions module(String name, String mainClass) { + put("--module-name", name + "/" + mainClass); + 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 + * {@code java.base} module, the JDKs jmods directory will be added, if it exists. + * + * @param path the module path + * @return this map of options + */ + public JpackageOptions modulePath(String path) { + put("--module-path", path); + 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; + } + + /** + * 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", null); + } 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. + * + * @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; + } + + /** + * 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; + } + + /** + * 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 packageSigningPrefix the signing prefix + * @return this map of options + */ + public JpackageOptions macPackageSigningPrefix(String packageSigningPrefix) { + put("--mac-package-signing-prefix", packageSigningPrefix); + 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 prefix + * @return this map of options + */ + public JpackageOptions macSign(String prefix) { + put("--mac-sign", prefix); + 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; + } + + /** + * Team or user name portion in Apple signing identities. + * + * @param username the username + * @return this map of options + */ + public JpackageOptions macSigningKeyUserName(String username) { + put("--mac-signing-key-user-name", username); + 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; + } + + /** + * 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; + } + + /** + * 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", null); + } else { + remove("--mac-app-store"); + } + 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; + } + + /** + * 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", null); + } else { + remove("--verbose"); + } + 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", null); + } 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", null); + } 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 winMenuGroupM(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", null); + } 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", null); + } 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", null); + } 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; + } + + /** + * 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", null); + } else { + remove("--win-console"); + } + 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; + } + + /** + * 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..007ffd2 --- /dev/null +++ b/src/test/java/rife/bld/operations/TestJlinkOperation.java @@ -0,0 +1,26 @@ +/* + * 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.Test; +import rife.bld.operations.exceptions.ExitStatusException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TestJlinkOperation { + @Test + public void TestNoArguments() { + var jlink = new JlinkOperation(); + assertThrows(ExitStatusException.class, jlink::execute); + } + + @Test + public void TestVersion() { + var jlink = new JlinkOperation().toolArg("--version"); + assertDoesNotThrow(jlink::execute); + } +} 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..8767ecb --- /dev/null +++ b/src/test/java/rife/bld/operations/TestJmodOperation.java @@ -0,0 +1,26 @@ +/* + * 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.Test; +import rife.bld.operations.exceptions.ExitStatusException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TestJmodOperation { + @Test + public void TestNoArguments() { + var jmod = new JmodOperation(); + assertThrows(ExitStatusException.class, jmod::execute); + } + + @Test + public void TestVersion() { + var jmod = new JmodOperation().operationMode(JmodOperation.OperationMode.DESCRIBE).toolArg("--version"); + assertDoesNotThrow(jmod::execute); + } +} 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..4850fc2 --- /dev/null +++ b/src/test/java/rife/bld/operations/TestJpackageOperation.java @@ -0,0 +1,56 @@ +/* + * 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.Test; +import rife.bld.operations.exceptions.ExitStatusException; + +import java.io.File; +import java.nio.file.Files; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestJpackageOperation { + @Test + public void TestCreatePackage() throws Exception { + var tmpdir = Files.createTempDirectory("bld-jpackage-test").toFile(); + tmpdir.deleteOnExit(); + var options = new JpackageOptions() + .input("lib/bld") + .name("bld") + .mainJar("bld-wrapper.jar") + .javaOptions("--enable-preview") + .dest(tmpdir.getAbsolutePath()) + .verbose(true); + + var os = System.getProperty("os.name"); + if (os.startsWith("Windows")) { + options.type(JpackageOptions.PackageType.EXE); + } else if (os.startsWith("Linux")) { + options.type(JpackageOptions.PackageType.DEB); + } else if (os.startsWith("Mac")) { + options.type(JpackageOptions.PackageType.DMG); + } + + var jpackage = new JpackageOperation().jpackageOptions(options); + jpackage.execute(); + + var deb = new File(tmpdir, "bld_1.0-1_amd64.deb"); + assertTrue(deb.delete()); + } + + @Test + public void TestNoArguments() { + var jpackage = new JpackageOperation(); + assertThrows(ExitStatusException.class, jpackage::execute); + } + + @Test + public void TestVersion() { + var jpackage = new JpackageOperation().toolArg("--version"); + assertDoesNotThrow(jpackage::execute); + } +}