8269387: jpackage --add-launcher should have option to not create shortcuts for additional launchers

Reviewed-by: asemenyuk, almatvee
This commit is contained in:
Andy Herrick 2021-07-15 17:04:54 +00:00
parent 746fe5dc68
commit 057992f206
11 changed files with 328 additions and 83 deletions

View File

@ -56,6 +56,7 @@ import static jdk.jpackage.internal.StandardBundlerParam.DESCRIPTION;
import static jdk.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS; import static jdk.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS;
import static jdk.jpackage.internal.StandardBundlerParam.ICON; import static jdk.jpackage.internal.StandardBundlerParam.ICON;
import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE;
import static jdk.jpackage.internal.StandardBundlerParam.SHORTCUT_HINT;;
/** /**
* Helper to create files for desktop integration. * Helper to create files for desktop integration.
@ -82,7 +83,7 @@ final class DesktopIntegration {
// Need desktop and icon files if one of conditions is met: // Need desktop and icon files if one of conditions is met:
// - there are file associations configured // - there are file associations configured
// - user explicitely requested to create a shortcut // - user explicitely requested to create a shortcut
boolean withDesktopFile = !associations.isEmpty() || SHORTCUT_HINT.fetchFrom(params); boolean withDesktopFile = !associations.isEmpty() || LINUX_SHORTCUT_HINT.fetchFrom(params);
var curIconResource = LinuxAppImageBuilder.createIconResource(DEFAULT_ICON, var curIconResource = LinuxAppImageBuilder.createIconResource(DEFAULT_ICON,
ICON_PNG, params, mainParams); ICON_PNG, params, mainParams);
@ -138,28 +139,34 @@ final class DesktopIntegration {
// Read launchers information from predefine app image // Read launchers information from predefine app image
if (launchers.isEmpty() && if (launchers.isEmpty() &&
PREDEFINED_APP_IMAGE.fetchFrom(params) != null) { PREDEFINED_APP_IMAGE.fetchFrom(params) != null) {
List<String> launcherPaths = AppImageFile.getLauncherNames( List<AppImageFile.LauncherInfo> launcherInfos =
AppImageFile.getLaunchers(
PREDEFINED_APP_IMAGE.fetchFrom(params), params); PREDEFINED_APP_IMAGE.fetchFrom(params), params);
if (!launcherPaths.isEmpty()) { if (!launcherInfos.isEmpty()) {
launcherPaths.remove(0); // Remove main launcher launcherInfos.remove(0); // Remove main launcher
} }
for (var launcherPath : launcherPaths) { for (var launcherInfo : launcherInfos) {
Map<String, ? super Object> launcherParams = new HashMap<>(); Map<String, ? super Object> launcherParams = new HashMap<>();
Arguments.putUnlessNull(launcherParams, CLIOptions.NAME.getId(), Arguments.putUnlessNull(launcherParams, CLIOptions.NAME.getId(),
launcherPath); launcherInfo.getName());
launcherParams = AddLauncherArguments.merge(params, launcherParams, launcherParams = AddLauncherArguments.merge(params,
ICON.getID(), ICON_PNG.getID(), ADD_LAUNCHERS.getID(), launcherParams, ICON.getID(), ICON_PNG.getID(),
FILE_ASSOCIATIONS.getID(), PREDEFINED_APP_IMAGE.getID()); ADD_LAUNCHERS.getID(), FILE_ASSOCIATIONS.getID(),
nestedIntegrations.add(new DesktopIntegration(thePackage, PREDEFINED_APP_IMAGE.getID());
launcherParams, params)); if (launcherInfo.isShortcut()) {
nestedIntegrations.add(new DesktopIntegration(thePackage,
launcherParams, params));
}
} }
} else { } else {
for (var launcherParams : launchers) { for (var launcherParams : launchers) {
launcherParams = AddLauncherArguments.merge(params, launcherParams, launcherParams = AddLauncherArguments.merge(params,
ICON.getID(), ICON_PNG.getID(), ADD_LAUNCHERS.getID(), launcherParams, ICON.getID(), ICON_PNG.getID(),
FILE_ASSOCIATIONS.getID()); ADD_LAUNCHERS.getID(), FILE_ASSOCIATIONS.getID());
nestedIntegrations.add(new DesktopIntegration(thePackage, if (SHORTCUT_HINT.fetchFrom(launcherParams)) {
launcherParams, params)); nestedIntegrations.add(new DesktopIntegration(thePackage,
launcherParams, params));
}
} }
} }
} }
@ -567,7 +574,7 @@ final class DesktopIntegration {
(s, p) -> s (s, p) -> s
); );
private static final StandardBundlerParam<Boolean> SHORTCUT_HINT = private static final StandardBundlerParam<Boolean> LINUX_SHORTCUT_HINT =
new StandardBundlerParam<>( new StandardBundlerParam<>(
Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(), Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(),
Boolean.class, Boolean.class,

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -32,6 +32,8 @@ import java.util.List;
import jdk.jpackage.internal.Arguments.CLIOptions; import jdk.jpackage.internal.Arguments.CLIOptions;
import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA;
import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME;
import static jdk.jpackage.internal.StandardBundlerParam.MENU_HINT;
import static jdk.jpackage.internal.StandardBundlerParam.SHORTCUT_HINT;
/* /*
* AddLauncherArguments * AddLauncherArguments
@ -59,7 +61,10 @@ import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME;
* arguments * arguments
* java-options * java-options
* win-console * win-console
* win-shortcut
* win-menu
* linux-app-category * linux-app-category
* linux-shortcut
* *
*/ */
class AddLauncherArguments { class AddLauncherArguments {
@ -109,17 +114,27 @@ class AddLauncherArguments {
Arguments.putUnlessNull(bundleParams, CLIOptions.RELEASE.getId(), Arguments.putUnlessNull(bundleParams, CLIOptions.RELEASE.getId(),
getOptionValue(CLIOptions.RELEASE)); getOptionValue(CLIOptions.RELEASE));
Arguments.putUnlessNull(bundleParams, CLIOptions.LINUX_CATEGORY.getId(),
getOptionValue(CLIOptions.LINUX_CATEGORY));
Arguments.putUnlessNull(bundleParams,
CLIOptions.WIN_CONSOLE_HINT.getId(),
getOptionValue(CLIOptions.WIN_CONSOLE_HINT));
String value = getOptionValue(CLIOptions.ICON); String value = getOptionValue(CLIOptions.ICON);
Arguments.putUnlessNull(bundleParams, CLIOptions.ICON.getId(), Arguments.putUnlessNull(bundleParams, CLIOptions.ICON.getId(),
(value == null) ? null : Path.of(value)); (value == null) ? null : Path.of(value));
if (Platform.isWindows()) {
Arguments.putUnlessNull(bundleParams,
CLIOptions.WIN_CONSOLE_HINT.getId(),
getOptionValue(CLIOptions.WIN_CONSOLE_HINT));
Arguments.putUnlessNull(bundleParams, SHORTCUT_HINT.getID(),
getOptionValue(CLIOptions.WIN_SHORTCUT_HINT));
Arguments.putUnlessNull(bundleParams, MENU_HINT.getID(),
getOptionValue(CLIOptions.WIN_MENU_HINT));
}
if (Platform.isLinux()) {
Arguments.putUnlessNull(bundleParams, CLIOptions.LINUX_CATEGORY.getId(),
getOptionValue(CLIOptions.LINUX_CATEGORY));
Arguments.putUnlessNull(bundleParams, SHORTCUT_HINT.getID(),
getOptionValue(CLIOptions.LINUX_SHORTCUT_HINT));
}
// "arguments" and "java-options" even if value is null: // "arguments" and "java-options" even if value is null:
if (allArgs.containsKey(CLIOptions.ARGUMENTS.getId())) { if (allArgs.containsKey(CLIOptions.ARGUMENTS.getId())) {
String argumentStr = getOptionValue(CLIOptions.ARGUMENTS); String argumentStr = getOptionValue(CLIOptions.ARGUMENTS);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -40,12 +40,16 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import static jdk.jpackage.internal.StandardBundlerParam.VERSION; import static jdk.jpackage.internal.StandardBundlerParam.VERSION;
import static jdk.jpackage.internal.StandardBundlerParam.ADD_LAUNCHERS; import static jdk.jpackage.internal.StandardBundlerParam.ADD_LAUNCHERS;
import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME;
import static jdk.jpackage.internal.StandardBundlerParam.SHORTCUT_HINT;
import static jdk.jpackage.internal.StandardBundlerParam.MENU_HINT;
public class AppImageFile { public class AppImageFile {
@ -53,7 +57,7 @@ public class AppImageFile {
private final String creatorVersion; private final String creatorVersion;
private final String creatorPlatform; private final String creatorPlatform;
private final String launcherName; private final String launcherName;
private final List<String> addLauncherNames; private final List<LauncherInfo> addLauncherInfos;
private static final String FILENAME = ".jpackage.xml"; private static final String FILENAME = ".jpackage.xml";
@ -66,10 +70,10 @@ public class AppImageFile {
this(null, null, null, null); this(null, null, null, null);
} }
private AppImageFile(String launcherName, List<String> addLauncherNames, private AppImageFile(String launcherName, List<LauncherInfo> launcherInfos,
String creatorVersion, String creatorPlatform) { String creatorVersion, String creatorPlatform) {
this.launcherName = launcherName; this.launcherName = launcherName;
this.addLauncherNames = addLauncherNames; this.addLauncherInfos = launcherInfos;
this.creatorVersion = creatorVersion; this.creatorVersion = creatorVersion;
this.creatorPlatform = creatorPlatform; this.creatorPlatform = creatorPlatform;
} }
@ -79,8 +83,8 @@ public class AppImageFile {
* Each item in the list is not null or empty string. * Each item in the list is not null or empty string.
* Returns empty list for application without additional launchers. * Returns empty list for application without additional launchers.
*/ */
List<String> getAddLauncherNames() { List<LauncherInfo> getAddLaunchers() {
return addLauncherNames; return addLauncherInfos;
} }
/** /**
@ -131,7 +135,10 @@ public class AppImageFile {
for (int i = 0; i < addLaunchers.size(); i++) { for (int i = 0; i < addLaunchers.size(); i++) {
Map<String, ? super Object> sl = addLaunchers.get(i); Map<String, ? super Object> sl = addLaunchers.get(i);
xml.writeStartElement("add-launcher"); xml.writeStartElement("add-launcher");
xml.writeCharacters(APP_NAME.fetchFrom(sl)); xml.writeAttribute("name", APP_NAME.fetchFrom(sl));
xml.writeAttribute("shortcut",
SHORTCUT_HINT.fetchFrom(sl).toString());
xml.writeAttribute("menu", MENU_HINT.fetchFrom(sl).toString());
xml.writeEndElement(); xml.writeEndElement();
} }
}); });
@ -156,7 +163,7 @@ public class AppImageFile {
return new AppImageFile(); return new AppImageFile();
} }
List<String> addLaunchers = new ArrayList<>(); List<LauncherInfo> launcherInfos = new ArrayList<>();
String platform = xpathQueryNullable(xPath, String platform = xpathQueryNullable(xPath,
"/jpackage-state/@platform", doc); "/jpackage-state/@platform", doc);
@ -164,16 +171,23 @@ public class AppImageFile {
String version = xpathQueryNullable(xPath, String version = xpathQueryNullable(xPath,
"/jpackage-state/@version", doc); "/jpackage-state/@version", doc);
NodeList launcherNameNodes = (NodeList) xPath.evaluate( NodeList launcherNodes = (NodeList) xPath.evaluate(
"/jpackage-state/add-launcher/text()", doc, "/jpackage-state/add-launcher", doc,
XPathConstants.NODESET); XPathConstants.NODESET);
for (int i = 0; i != launcherNameNodes.getLength(); i++) { for (int i = 0; i != launcherNodes.getLength(); i++) {
addLaunchers.add(launcherNameNodes.item(i).getNodeValue()); Node item = launcherNodes.item(i);
String name = getAttribute(item, "name");
String shortcut = getAttribute(item, "shortcut");
String menu = getAttribute(item, "menu");
launcherInfos.add(new LauncherInfo(name,
!("false".equals(shortcut)),
!("false".equals(menu))));
} }
AppImageFile file = new AppImageFile( AppImageFile file = new AppImageFile(
mainLauncher, addLaunchers, version, platform); mainLauncher, launcherInfos, version, platform);
if (!file.isValid()) { if (!file.isValid()) {
file = new AppImageFile(); file = new AppImageFile();
} }
@ -184,6 +198,12 @@ public class AppImageFile {
} }
} }
private static String getAttribute(Node item, String attr) {
NamedNodeMap attrs = item.getAttributes();
Node attrNode = attrs.getNamedItem(attr);
return ((attrNode == null) ? null : attrNode.getNodeValue());
}
public static Document readXml(Path appImageDir) throws IOException { public static Document readXml(Path appImageDir) throws IOException {
try { try {
Path path = getPathInAppImage(appImageDir); Path path = getPathInAppImage(appImageDir);
@ -202,18 +222,19 @@ public class AppImageFile {
} }
/** /**
* Returns list of launcher names configured for the application. * Returns list of LauncherInfo objects configured for the application.
* The first item in the returned list is main launcher name. * The first item in the returned list is main launcher.
* Following items in the list are names of additional launchers. * Following items in the list are names of additional launchers.
*/ */
static List<String> getLauncherNames(Path appImageDir, static List<LauncherInfo> getLaunchers(Path appImageDir,
Map<String, ? super Object> params) { Map<String, ? super Object> params) {
List<String> launchers = new ArrayList<>(); List<LauncherInfo> launchers = new ArrayList<>();
try { try {
AppImageFile appImageInfo = AppImageFile.load(appImageDir); AppImageFile appImageInfo = AppImageFile.load(appImageDir);
if (appImageInfo != null) { if (appImageInfo != null) {
launchers.add(appImageInfo.getLauncherName()); launchers.add(new LauncherInfo(
launchers.addAll(appImageInfo.getAddLauncherNames()); appImageInfo.getLauncherName(), true, true));
launchers.addAll(appImageInfo.getAddLaunchers());
return launchers; return launchers;
} }
} catch (NoSuchFileException nsfe) { } catch (NoSuchFileException nsfe) {
@ -226,10 +247,11 @@ public class AppImageFile {
"warning.invalid-app-image"), appImageDir)); "warning.invalid-app-image"), appImageDir));
} }
// this should never be the case, but maintaining behavior of
// creating default launchers without AppImageFile present
launchers.add(APP_NAME.fetchFrom(params)); ADD_LAUNCHERS.fetchFrom(params).stream().map(APP_NAME::fetchFrom).map(
ADD_LAUNCHERS.fetchFrom(params).stream().map(APP_NAME::fetchFrom).forEach( name -> new LauncherInfo(name, true, true)).forEach(launchers::add);
launchers::add);
return launchers; return launchers;
} }
@ -262,15 +284,37 @@ public class AppImageFile {
} }
private boolean isValid() { private boolean isValid() {
if (launcherName == null || launcherName.length() == 0 || if (launcherName == null || launcherName.length() == 0) {
addLauncherNames.indexOf("") != -1) {
// Some launchers have empty names. This is invalid.
return false; return false;
} }
for (var launcher : addLauncherInfos) {
// Add more validation. if ("".equals(launcher.getName())) {
return false;
}
}
return true; return true;
} }
static class LauncherInfo {
private String name;
private boolean shortcut;
private boolean menu;
public LauncherInfo(String name, boolean shortcut, boolean menu) {
this.name = name;
this.shortcut = shortcut;
this.menu = menu;
}
public String getName() {
return name;
}
public boolean isShortcut() {
return shortcut;
}
public boolean isMenu() {
return menu;
}
}
} }

View File

@ -311,6 +311,24 @@ class StandardBundlerParam<T> extends BundlerParamInfo<T> {
true : Boolean.valueOf(s) true : Boolean.valueOf(s)
); );
static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
new StandardBundlerParam<>(
"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 StandardBundlerParam<Boolean> MENU_HINT =
new StandardBundlerParam<>(
"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 StandardBundlerParam<Path> RESOURCE_DIR = static final StandardBundlerParam<Path> RESOURCE_DIR =
new StandardBundlerParam<>( new StandardBundlerParam<>(
Arguments.CLIOptions.RESOURCE_DIR.getId(), Arguments.CLIOptions.RESOURCE_DIR.getId(),

View File

@ -135,8 +135,9 @@ Generic Options:\n\
\ a list of key, value pairs\n\ \ a list of key, value pairs\n\
\ (absolute path or relative to the current directory)\n\ \ (absolute path or relative to the current directory)\n\
\ The keys "module", "main-jar", "main-class",\n\ \ The keys "module", "main-jar", "main-class",\n\
\ "arguments", "java-options", "app-version", "icon", and\n\ \ "arguments", "java-options", "app-version", "icon",\n\
\ "win-console" can be used.\n\ \ "win-console", "win-shortcut", "win-menu",\n\
\ "linux-app-category", and "linux-shortcut" can be used.\n\
\ These options are added to, or used to overwrite, the original\n\ \ These options are added to, or used to overwrite, the original\n\
\ command line options to build an additional alternative launcher.\n\ \ command line options to build an additional alternative launcher.\n\
\ The main application launcher will be built from the command line\n\ \ The main application launcher will be built from the command line\n\

View File

@ -135,8 +135,9 @@ Generic Options:\n\
\ a list of key, value pairs\n\ \ a list of key, value pairs\n\
\ (absolute path or relative to the current directory)\n\ \ (absolute path or relative to the current directory)\n\
\ The keys "module", "main-jar", "main-class",\n\ \ The keys "module", "main-jar", "main-class",\n\
\ "arguments", "java-options", "app-version", "icon", and\n\ \ "arguments", "java-options", "app-version", "icon",\n\
\ "win-console" can be used.\n\ \ "win-console", "win-shortcut", "win-menu",\n\
\ "linux-app-category", and "linux-shortcut" can be used.\n\
\ These options are added to, or used to overwrite, the original\n\ \ These options are added to, or used to overwrite, the original\n\
\ command line options to build an additional alternative launcher.\n\ \ command line options to build an additional alternative launcher.\n\
\ The main application launcher will be built from the command line\n\ \ The main application launcher will be built from the command line\n\

View File

@ -135,8 +135,9 @@ Generic Options:\n\
\ a list of key, value pairs\n\ \ a list of key, value pairs\n\
\ (absolute path or relative to the current directory)\n\ \ (absolute path or relative to the current directory)\n\
\ The keys "module", "main-jar", "main-class",\n\ \ The keys "module", "main-jar", "main-class",\n\
\ "arguments", "java-options", "app-version", "icon", and\n\ \ "arguments", "java-options", "app-version", "icon",\n\
\ "win-console" can be used.\n\ \ "win-console", "win-shortcut", "win-menu",\n\
\ "linux-app-category", and "linux-shortcut" can be used.\n\
\ These options are added to, or used to overwrite, the original\n\ \ These options are added to, or used to overwrite, the original\n\
\ command line options to build an additional alternative launcher.\n\ \ command line options to build an additional alternative launcher.\n\
\ The main application launcher will be built from the command line\n\ \ The main application launcher will be built from the command line\n\

View File

@ -107,12 +107,9 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
Collectors.toSet()); Collectors.toSet());
if (StandardBundlerParam.isRuntimeInstaller(params)) { if (StandardBundlerParam.isRuntimeInstaller(params)) {
launcherPaths = Collections.emptyList(); launchers = Collections.emptyList();
} else { } else {
launcherPaths = AppImageFile.getLauncherNames(appImageRoot, params).stream() launchers = AppImageFile.getLaunchers(appImageRoot, params);
.map(name -> installedAppImage.launchersDirectory().resolve(name))
.map(WixAppImageFragmentBuilder::addExeSuffixToPath)
.toList();
} }
programMenuFolderName = MENU_GROUP.fetchFrom(params); programMenuFolderName = MENU_GROUP.fetchFrom(params);
@ -411,13 +408,23 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
XMLStreamException, IOException { XMLStreamException, IOException {
List<String> componentIds = new ArrayList<>(); List<String> componentIds = new ArrayList<>();
Set<ShortcutsFolder> defineShortcutFolders = new HashSet<>(); Set<ShortcutsFolder> defineShortcutFolders = new HashSet<>();
for (var launcherPath : launcherPaths) { for (var launcher : launchers) {
for (var folder : shortcutFolders) { for (var folder : shortcutFolders) {
String componentId = addShortcutComponent(xml, launcherPath, Path launcherPath = addExeSuffixToPath(installedAppImage
folder); .launchersDirectory().resolve(launcher.getName()));
if (componentId != null) {
defineShortcutFolders.add(folder); if ((launcher.isMenu() &&
componentIds.add(componentId); (folder.equals(ShortcutsFolder.ProgramMenu))) ||
(launcher.isShortcut() &&
(folder.equals(ShortcutsFolder.Desktop)))) {
String componentId = addShortcutComponent(xml, launcherPath,
folder);
if (componentId != null) {
defineShortcutFolders.add(folder);
componentIds.add(componentId);
}
} }
} }
} }
@ -824,7 +831,7 @@ class WixAppImageFragmentBuilder extends WixFragmentBuilder {
private Set<ShortcutsFolder> shortcutFolders; private Set<ShortcutsFolder> shortcutFolders;
private List<Path> launcherPaths; private List<AppImageFile.LauncherInfo> launchers;
private ApplicationLayout appImage; private ApplicationLayout appImage;
private ApplicationLayout installedAppImage; private ApplicationLayout installedAppImage;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -81,6 +81,12 @@ public final class AdditionalLauncher {
return this; return this;
} }
public AdditionalLauncher setShortcuts(boolean menu, boolean shortcut) {
withMenuShortcut = menu;
withShortcut = shortcut;
return this;
}
public AdditionalLauncher setIcon(Path iconPath) { public AdditionalLauncher setIcon(Path iconPath) {
if (iconPath == NO_ICON) { if (iconPath == NO_ICON) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@ -143,6 +149,18 @@ public final class AdditionalLauncher {
properties.add(Map.entry("icon", iconPath)); properties.add(Map.entry("icon", iconPath));
} }
if (withShortcut != null) {
if (TKit.isLinux()) {
properties.add(Map.entry("linux-shortcut", withShortcut.toString()));
} else if (TKit.isWindows()) {
properties.add(Map.entry("win-shortcut", withShortcut.toString()));
}
}
if (TKit.isWindows() && withMenuShortcut != null) {
properties.add(Map.entry("win-menu", withMenuShortcut.toString()));
}
properties.addAll(rawProperties); properties.addAll(rawProperties);
createFileHandler.accept(propsFile, properties); createFileHandler.accept(propsFile, properties);
@ -178,7 +196,7 @@ public final class AdditionalLauncher {
() -> iconInResourceDir(cmd, name)); () -> iconInResourceDir(cmd, name));
while (effectiveIcon != NO_ICON) { while (effectiveIcon != NO_ICON) {
if (effectiveIcon != null) { if (effectiveIcon != null) {
withLinuxDesktopFile = true; withLinuxDesktopFile = Boolean.FALSE != withShortcut;
verifier.setExpectedIcon(effectiveIcon); verifier.setExpectedIcon(effectiveIcon);
break; break;
} }
@ -186,7 +204,7 @@ public final class AdditionalLauncher {
Path customMainLauncherIcon = cmd.getArgumentValue("--icon", Path customMainLauncherIcon = cmd.getArgumentValue("--icon",
() -> iconInResourceDir(cmd, null), Path::of); () -> iconInResourceDir(cmd, null), Path::of);
if (customMainLauncherIcon != null) { if (customMainLauncherIcon != null) {
withLinuxDesktopFile = true; withLinuxDesktopFile = Boolean.FALSE != withShortcut;
verifier.setExpectedIcon(customMainLauncherIcon); verifier.setExpectedIcon(customMainLauncherIcon);
break; break;
} }
@ -197,8 +215,8 @@ public final class AdditionalLauncher {
if (TKit.isLinux() && !cmd.isImagePackageType()) { if (TKit.isLinux() && !cmd.isImagePackageType()) {
if (effectiveIcon != NO_ICON && !withLinuxDesktopFile) { if (effectiveIcon != NO_ICON && !withLinuxDesktopFile) {
withLinuxDesktopFile = Stream.of("--linux-shortcut").anyMatch( withLinuxDesktopFile = (Boolean.FALSE != withShortcut) &&
cmd::hasArgument); Stream.of("--linux-shortcut").anyMatch(cmd::hasArgument);
verifier.setExpectedDefaultIcon(); verifier.setExpectedDefaultIcon();
} }
Path desktopFile = LinuxHelper.getDesktopFile(cmd, name); Path desktopFile = LinuxHelper.getDesktopFile(cmd, name);
@ -212,8 +230,21 @@ public final class AdditionalLauncher {
verifier.applyTo(cmd); verifier.applyTo(cmd);
} }
private void verifyShortcuts(JPackageCommand cmd) throws IOException {
if (TKit.isLinux() && !cmd.isImagePackageType()
&& withShortcut != null) {
Path desktopFile = LinuxHelper.getDesktopFile(cmd, name);
if (withShortcut) {
TKit.assertFileExists(desktopFile);
} else {
TKit.assertPathExists(desktopFile, false);
}
}
}
private void verify(JPackageCommand cmd) throws IOException { private void verify(JPackageCommand cmd) throws IOException {
verifyIcon(cmd); verifyIcon(cmd);
verifyShortcuts(cmd);
Path launcherPath = cmd.appLauncherPath(name); Path launcherPath = cmd.appLauncherPath(name);
@ -240,6 +271,8 @@ public final class AdditionalLauncher {
private final String name; private final String name;
private final List<Map.Entry<String, String>> rawProperties; private final List<Map.Entry<String, String>> rawProperties;
private BiConsumer<Path, List<Map.Entry<String, String>>> createFileHandler; private BiConsumer<Path, List<Map.Entry<String, String>>> createFileHandler;
private Boolean withMenuShortcut;
private Boolean withShortcut;
private final static Path NO_ICON = Path.of(""); private final static Path NO_ICON = Path.of("");
} }

View File

@ -104,8 +104,8 @@ public class AppImageFileTest {
"<launcher></launcher>", "<launcher></launcher>",
"</jpackage-state>"); "</jpackage-state>");
Assert.assertEquals("Foo", file.getLauncherName()); Assert.assertEquals("Foo", file.getLauncherName());
Assert.assertArrayEquals(new String[0],
file.getAddLauncherNames().toArray(String[]::new)); Assert.assertEquals(0, file.getAddLaunchers().size());
} }
@Test @Test
@ -119,7 +119,7 @@ public class AppImageFileTest {
} }
@Test @Test
public void testAddLauncherNames() throws IOException { public void testAddLaunchers() throws IOException {
Map<String, ? super Object> params = new LinkedHashMap<>(); Map<String, ? super Object> params = new LinkedHashMap<>();
List<Map<String, ? super Object>> launchersAsMap = new ArrayList<>(); List<Map<String, ? super Object>> launchersAsMap = new ArrayList<>();
@ -136,10 +136,14 @@ public class AppImageFileTest {
params.put("add-launcher", launchersAsMap); params.put("add-launcher", launchersAsMap);
AppImageFile aif = create(params); AppImageFile aif = create(params);
List<String> addLauncherNames = aif.getAddLauncherNames(); List<AppImageFile.LauncherInfo> addLaunchers = aif.getAddLaunchers();
Assert.assertEquals(2, addLauncherNames.size()); Assert.assertEquals(2, addLaunchers.size());
Assert.assertTrue(addLauncherNames.contains("Launcher2Name")); List<String> names = new ArrayList<String>();
Assert.assertTrue(addLauncherNames.contains("Launcher3Name")); names.add(addLaunchers.get(0).getName());
names.add(addLaunchers.get(1).getName());
Assert.assertTrue(names.contains("Launcher2Name"));
Assert.assertTrue(names.contains("Launcher3Name"));
} }
@ -150,7 +154,7 @@ public class AppImageFileTest {
private void assertInvalid(AppImageFile file) { private void assertInvalid(AppImageFile file) {
Assert.assertNull(file.getLauncherName()); Assert.assertNull(file.getLauncherName());
Assert.assertNull(file.getAddLauncherNames()); Assert.assertNull(file.getAddLaunchers());
} }
private AppImageFile createFromXml(String... xmlData) throws IOException { private AppImageFile createFromXml(String... xmlData) throws IOException {

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) 2021, 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.
*
* 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.
*/
import java.nio.file.Path;
import java.io.File;
import java.util.Map;
import java.lang.invoke.MethodHandles;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.FileAssociations;
import jdk.jpackage.test.AdditionalLauncher;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.Annotations.Test;
/**
* Test --add-launcher parameter with shortcuts (platform permitting).
* Output of the test should be AddLShortcutTest*.* installer.
* The output installer should provide the same functionality as the
* default installer (see description of the default installer in
* SimplePackageTest.java) plus install extra application launchers with and
* without various shortcuts to be tested manually.
*/
/*
* @test
* @summary jpackage with --add-launcher
* @key jpackagePlatformPackage
* @library ../helpers
* @build jdk.jpackage.test.*
* @modules jdk.jpackage/jdk.jpackage.internal
* @compile AddLShortcutTest.java
* @run main/othervm/timeout=540 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=AddLShortcutTest
*/
public class AddLShortcutTest {
@Test
public void test() {
// Configure several additional launchers with each combination of
// possible shortcut hints in add-launcher property file.
// default is true so Foo (no property), and Bar (properties set to "true")
// will have shortcuts while other launchers with some properties set
// to "false" will have none.
PackageTest packageTest = new PackageTest().configureHelloApp();
packageTest.addInitializer(cmd -> {
cmd.addArguments("--arguments", "Duke", "--arguments", "is",
"--arguments", "the", "--arguments", "King");
if (TKit.isWindows()) {
cmd.addArguments("--win-shortcut", "--win-menu");
} else if (TKit.isLinux()) {
cmd.addArguments("--linux-shortcut");
}
});
new FileAssociations(
MethodHandles.lookup().lookupClass().getSimpleName()).applyTo(
packageTest);
new AdditionalLauncher("Foo")
.setDefaultArguments("yep!")
.setIcon(GOLDEN_ICON)
.applyTo(packageTest);
new AdditionalLauncher("Bar")
.setDefaultArguments("one", "two", "three")
.setIcon(GOLDEN_ICON)
.setShortcuts(true, true)
.applyTo(packageTest);
new AdditionalLauncher("Launcher3")
.setDefaultArguments()
.setIcon(GOLDEN_ICON)
.setShortcuts(false, false)
.applyTo(packageTest);
new AdditionalLauncher("Launcher4")
.setDefaultArguments()
.setIcon(GOLDEN_ICON)
.setShortcuts(true, false)
.applyTo(packageTest);
new AdditionalLauncher("Launcher5")
.setDefaultArguments()
.setIcon(GOLDEN_ICON)
.setShortcuts(false, true)
.applyTo(packageTest);
packageTest.run();
}
private final static Path GOLDEN_ICON = TKit.TEST_SRC_ROOT.resolve(Path.of(
"resources", "icon" + TKit.ICON_SUFFIX));
}