blob: 93d84fd79b6fa4b61632a58c9fe9b7f1087d0b51 [file]
/*
* Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.internal;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import jdk.jpackage.internal.model.ConfigException;
import jdk.jpackage.internal.model.ExternalApplication;
import static jdk.jpackage.internal.ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT;
import static jdk.jpackage.internal.model.RuntimeBuilder.getDefaultModulePath;
/**
* Standard bundler parameters.
*
* Contains static definitions of all of the common bundler parameters.
* (additional platform specific and mode specific bundler parameters
* are defined in each of the specific bundlers)
*
* Also contains static methods that operate on maps of parameters.
*/
final class StandardBundlerParam {
private static final String JAVABASEJMOD = "java.base.jmod";
private static final String DEFAULT_VERSION = "1.0";
private static final String DEFAULT_RELEASE = "1";
private static final String[] DEFAULT_JLINK_OPTIONS = {
"--strip-native-commands",
"--strip-debug",
"--no-man-pages",
"--no-header-files"};
static final BundlerParamInfo<LauncherData> LAUNCHER_DATA = BundlerParamInfo.createBundlerParam(
LauncherData.class, LauncherData::create);
static final BundlerParamInfo<Path> SOURCE_DIR =
new BundlerParamInfo<>(
Arguments.CLIOptions.INPUT.getId(),
Path.class,
p -> null,
(s, p) -> Path.of(s)
);
static final BundlerParamInfo<Path> OUTPUT_DIR =
new BundlerParamInfo<>(
Arguments.CLIOptions.OUTPUT.getId(),
Path.class,
p -> Path.of("").toAbsolutePath(),
(s, p) -> Path.of(s)
);
// note that each bundler is likely to replace this one with
// their own converter
static final BundlerParamInfo<Path> MAIN_JAR =
new BundlerParamInfo<>(
Arguments.CLIOptions.MAIN_JAR.getId(),
Path.class,
params -> LAUNCHER_DATA.fetchFrom(params).mainJarName(),
null
);
static final BundlerParamInfo<ExternalApplication> PREDEFINED_APP_IMAGE_FILE = BundlerParamInfo.createBundlerParam(
ExternalApplication.class, params -> {
if (hasPredefinedAppImage(params)) {
var appImage = getPredefinedAppImage(params);
return AppImageFile.load(appImage, PLATFORM_APPLICATION_LAYOUT);
} else {
return null;
}
});
static final BundlerParamInfo<String> MAIN_CLASS =
new BundlerParamInfo<>(
Arguments.CLIOptions.APPCLASS.getId(),
String.class,
params -> {
if (isRuntimeInstaller(params)) {
return null;
} else if (hasPredefinedAppImage(params)) {
PREDEFINED_APP_IMAGE_FILE.fetchFrom(params).getMainClass();
}
return LAUNCHER_DATA.fetchFrom(params).qualifiedClassName();
},
(s, p) -> s
);
static final BundlerParamInfo<Path> PREDEFINED_RUNTIME_IMAGE =
new BundlerParamInfo<>(
Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(),
Path.class,
params -> null,
(s, p) -> Path.of(s)
);
static final BundlerParamInfo<Path> PREDEFINED_APP_IMAGE =
new BundlerParamInfo<>(
Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(),
Path.class,
params -> null,
(s, p) -> Path.of(s));
// this is the raw --app-name arg - used in APP_NAME and INSTALLER_NAME
static final BundlerParamInfo<String> NAME =
new BundlerParamInfo<>(
Arguments.CLIOptions.NAME.getId(),
String.class,
params -> null,
(s, p) -> s
);
// this is the application name, either from the app-image (if given),
// the name (if given) derived from the main-class, or the runtime image
static final BundlerParamInfo<String> APP_NAME =
new BundlerParamInfo<>(
"application-name",
String.class,
params -> {
String appName = NAME.fetchFrom(params);
if (hasPredefinedAppImage(params)) {
appName = PREDEFINED_APP_IMAGE_FILE.fetchFrom(params).getLauncherName();
} else if (appName == null) {
String s = MAIN_CLASS.fetchFrom(params);
if (s != null) {
int idx = s.lastIndexOf(".");
appName = (idx < 0) ? s : s.substring(idx+1);
} else if (isRuntimeInstaller(params)) {
Path f = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
if (f != null) {
appName = f.getFileName().toString();
}
}
}
return appName;
},
(s, p) -> s
);
static final BundlerParamInfo<String> INSTALLER_NAME =
new BundlerParamInfo<>(
"installer-name",
String.class,
params -> {
String installerName = NAME.fetchFrom(params);
return (installerName != null) ? installerName :
APP_NAME.fetchFrom(params);
},
(s, p) -> s
);
static final BundlerParamInfo<Path> ICON =
new BundlerParamInfo<>(
Arguments.CLIOptions.ICON.getId(),
Path.class,
params -> null,
(s, p) -> Path.of(s)
);
static final BundlerParamInfo<String> ABOUT_URL =
new BundlerParamInfo<>(
Arguments.CLIOptions.ABOUT_URL.getId(),
String.class,
params -> null,
(s, p) -> s
);
static final BundlerParamInfo<String> VENDOR =
new BundlerParamInfo<>(
Arguments.CLIOptions.VENDOR.getId(),
String.class,
params -> I18N.getString("param.vendor.default"),
(s, p) -> s
);
static final BundlerParamInfo<String> DESCRIPTION =
new BundlerParamInfo<>(
Arguments.CLIOptions.DESCRIPTION.getId(),
String.class,
params -> params.containsKey(APP_NAME.getID())
? APP_NAME.fetchFrom(params)
: I18N.getString("param.description.default"),
(s, p) -> s
);
static final BundlerParamInfo<String> COPYRIGHT =
new BundlerParamInfo<>(
Arguments.CLIOptions.COPYRIGHT.getId(),
String.class,
params -> MessageFormat.format(I18N.getString(
"param.copyright.default"), new Date()),
(s, p) -> s
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<List<String>> ARGUMENTS =
new BundlerParamInfo<>(
Arguments.CLIOptions.ARGUMENTS.getId(),
(Class<List<String>>) (Object) List.class,
params -> Collections.emptyList(),
(s, p) -> null
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<List<String>> JAVA_OPTIONS =
new BundlerParamInfo<>(
Arguments.CLIOptions.JAVA_OPTIONS.getId(),
(Class<List<String>>) (Object) List.class,
params -> Collections.emptyList(),
(s, p) -> Arrays.asList(s.split("\n\n"))
);
static final BundlerParamInfo<String> VERSION =
new BundlerParamInfo<>(
Arguments.CLIOptions.VERSION.getId(),
String.class,
StandardBundlerParam::getDefaultAppVersion,
(s, p) -> s
);
static final BundlerParamInfo<String> RELEASE =
new BundlerParamInfo<>(
Arguments.CLIOptions.RELEASE.getId(),
String.class,
params -> DEFAULT_RELEASE,
(s, p) -> s
);
public static final BundlerParamInfo<String> LICENSE_FILE =
new BundlerParamInfo<>(
Arguments.CLIOptions.LICENSE_FILE.getId(),
String.class,
params -> null,
(s, p) -> s
);
static final BundlerParamInfo<Path> TEMP_ROOT =
new BundlerParamInfo<>(
Arguments.CLIOptions.TEMP_ROOT.getId(),
Path.class,
params -> {
try {
return Files.createTempDirectory("jdk.jpackage");
} catch (IOException ioe) {
return null;
}
},
(s, p) -> Path.of(s)
);
public static final BundlerParamInfo<Path> CONFIG_ROOT =
new BundlerParamInfo<>(
"configRoot",
Path.class,
params -> {
Path root = TEMP_ROOT.fetchFrom(params).resolve("config");
try {
Files.createDirectories(root);
} catch (IOException ioe) {
return null;
}
return root;
},
(s, p) -> null
);
static final BundlerParamInfo<Boolean> VERBOSE =
new BundlerParamInfo<>(
Arguments.CLIOptions.VERBOSE.getId(),
Boolean.class,
params -> false,
// valueOf(null) is false, and we actually do want null
(s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
true : Boolean.valueOf(s)
);
static final BundlerParamInfo<Boolean> SHORTCUT_HINT =
new BundlerParamInfo<>(
"shortcut-hint", // not directly related to a CLI option
Boolean.class,
params -> true, // defaults to true
(s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
true : Boolean.valueOf(s)
);
static final BundlerParamInfo<Boolean> MENU_HINT =
new BundlerParamInfo<>(
"menu-hint", // not directly related to a CLI option
Boolean.class,
params -> true, // defaults to true
(s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
true : Boolean.valueOf(s)
);
static final BundlerParamInfo<Path> RESOURCE_DIR =
new BundlerParamInfo<>(
Arguments.CLIOptions.RESOURCE_DIR.getId(),
Path.class,
params -> null,
(s, p) -> Path.of(s)
);
static final BundlerParamInfo<String> INSTALL_DIR =
new BundlerParamInfo<>(
Arguments.CLIOptions.INSTALL_DIR.getId(),
String.class,
params -> null,
(s, p) -> s
);
static final BundlerParamInfo<Boolean> LAUNCHER_AS_SERVICE =
new BundlerParamInfo<>(
Arguments.CLIOptions.LAUNCHER_AS_SERVICE.getId(),
Boolean.class,
params -> false,
// valueOf(null) is false, and we actually do want null
(s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
true : Boolean.valueOf(s)
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<List<Map<String, ? super Object>>> ADD_LAUNCHERS =
new BundlerParamInfo<>(
Arguments.CLIOptions.ADD_LAUNCHER.getId(),
(Class<List<Map<String, ? super Object>>>) (Object)
List.class,
params -> new ArrayList<>(1),
// valueOf(null) is false, and we actually do want null
(s, p) -> null
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo
<List<Map<String, ? super Object>>> FILE_ASSOCIATIONS =
new BundlerParamInfo<>(
Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(),
(Class<List<Map<String, ? super Object>>>) (Object)
List.class,
params -> new ArrayList<>(1),
// valueOf(null) is false, and we actually do want null
(s, p) -> null
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<List<String>> FA_EXTENSIONS =
new BundlerParamInfo<>(
"fileAssociation.extension",
(Class<List<String>>) (Object) List.class,
params -> null, // null means not matched to an extension
(s, p) -> Arrays.asList(s.split("(,|\\s)+"))
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<List<String>> FA_CONTENT_TYPE =
new BundlerParamInfo<>(
"fileAssociation.contentType",
(Class<List<String>>) (Object) List.class,
params -> null,
// null means not matched to a content/mime type
(s, p) -> Arrays.asList(s.split("(,|\\s)+"))
);
static final BundlerParamInfo<String> FA_DESCRIPTION =
new BundlerParamInfo<>(
"fileAssociation.description",
String.class,
p -> null,
(s, p) -> s
);
static final BundlerParamInfo<Path> FA_ICON =
new BundlerParamInfo<>(
"fileAssociation.icon",
Path.class,
ICON::fetchFrom,
(s, p) -> Path.of(s)
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<List<Path>> DMG_CONTENT =
new BundlerParamInfo<>(
Arguments.CLIOptions.DMG_CONTENT.getId(),
(Class<List<Path>>) (Object)List.class,
p -> Collections.emptyList(),
(s, p) -> Stream.of(s.split(",")).map(Path::of).toList()
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<List<Path>> APP_CONTENT =
new BundlerParamInfo<>(
Arguments.CLIOptions.APP_CONTENT.getId(),
(Class<List<Path>>) (Object)List.class,
p->Collections.emptyList(),
(s, p) -> Stream.of(s.split(",")).map(Path::of).toList()
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<List<Path>> MODULE_PATH =
new BundlerParamInfo<>(
Arguments.CLIOptions.MODULE_PATH.getId(),
(Class<List<Path>>) (Object)List.class,
p -> getDefaultModulePath(),
(s, p) -> {
List<Path> modulePath = Stream.of(s.split(File.pathSeparator))
.map(Path::of)
.toList();
Path javaBasePath = findPathOfModule(modulePath, JAVABASEJMOD);
// Add the default JDK module path to the module path.
if (javaBasePath == null) {
List<Path> jdkModulePath = getDefaultModulePath();
if (jdkModulePath != null) {
modulePath = Stream.concat(modulePath.stream(),
jdkModulePath.stream()).toList();
javaBasePath = findPathOfModule(modulePath, JAVABASEJMOD);
}
}
if (javaBasePath == null ||
!Files.exists(javaBasePath)) {
Log.error(String.format(I18N.getString(
"warning.no.jdk.modules.found")));
}
return modulePath;
});
// Returns the path to the JDK modules in the user defined module path.
private static Path findPathOfModule( List<Path> modulePath, String moduleName) {
for (Path path : modulePath) {
Path moduleNamePath = path.resolve(moduleName);
if (Files.exists(moduleNamePath)) {
return path;
}
}
return null;
}
static final BundlerParamInfo<String> MODULE =
new BundlerParamInfo<>(
Arguments.CLIOptions.MODULE.getId(),
String.class,
p -> null,
(s, p) -> {
return String.valueOf(s);
});
@SuppressWarnings("unchecked")
static final BundlerParamInfo<Set<String>> ADD_MODULES =
new BundlerParamInfo<>(
Arguments.CLIOptions.ADD_MODULES.getId(),
(Class<Set<String>>) (Object) Set.class,
p -> new LinkedHashSet<String>(),
(s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(",")))
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<List<String>> JLINK_OPTIONS =
new BundlerParamInfo<>(
Arguments.CLIOptions.JLINK_OPTIONS.getId(),
(Class<List<String>>) (Object) List.class,
p -> Arrays.asList(DEFAULT_JLINK_OPTIONS),
(s, p) -> null);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<Set<String>> LIMIT_MODULES =
new BundlerParamInfo<>(
"limit-modules",
(Class<Set<String>>) (Object) Set.class,
p -> new LinkedHashSet<String>(),
(s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(",")))
);
static final BundlerParamInfo<Boolean> SIGN_BUNDLE =
new BundlerParamInfo<>(
Arguments.CLIOptions.MAC_SIGN.getId(),
Boolean.class,
params -> false,
(s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
null : Boolean.valueOf(s)
);
static boolean isRuntimeInstaller(Map<String, ? super Object> params) {
if (params.containsKey(MODULE.getID()) ||
params.containsKey(MAIN_JAR.getID()) ||
params.containsKey(PREDEFINED_APP_IMAGE.getID())) {
return false; // we are building or are given an application
}
// runtime installer requires --runtime-image, if this is false
// here then we should have thrown error validating args.
return params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID());
}
static boolean hasPredefinedAppImage(Map<String, ? super Object> params) {
return params.containsKey(PREDEFINED_APP_IMAGE.getID());
}
static Path getPredefinedAppImage(Map<String, ? super Object> params) {
Path applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params);
if (applicationImage != null && !IOUtils.exists(applicationImage)) {
throw new RuntimeException(
MessageFormat.format(I18N.getString(
"message.app-image-dir-does-not-exist"),
PREDEFINED_APP_IMAGE.getID(),
applicationImage.toString()));
}
return applicationImage;
}
private static String getDefaultAppVersion(Map<String, ? super Object> params) {
String appVersion = DEFAULT_VERSION;
if (isRuntimeInstaller(params)) {
return appVersion;
}
LauncherData launcherData = null;
try {
launcherData = LAUNCHER_DATA.fetchFrom(params);
} catch (RuntimeException ex) {
if (ex.getCause() instanceof ConfigException) {
return appVersion;
}
throw ex;
}
if (launcherData.isModular()) {
String moduleVersion = launcherData.getAppVersion();
if (moduleVersion != null) {
Log.verbose(MessageFormat.format(I18N.getString(
"message.module-version"),
moduleVersion,
launcherData.moduleName()));
appVersion = moduleVersion;
}
}
return appVersion;
}
}