Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
8269387: jpackage --add-launcher should have option to not create sho…
…rtcuts for additional launchers

Reviewed-by: asemenyuk, almatvee
  • Loading branch information
Andy Herrick committed Jul 15, 2021
1 parent 746fe5d commit 057992f
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 83 deletions.
Expand Up @@ -56,6 +56,7 @@
import static jdk.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS;
import static jdk.jpackage.internal.StandardBundlerParam.ICON;
import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE;
import static jdk.jpackage.internal.StandardBundlerParam.SHORTCUT_HINT;;

/**
* Helper to create files for desktop integration.
Expand All @@ -82,7 +83,7 @@ private DesktopIntegration(PlatformPackage thePackage,
// Need desktop and icon files if one of conditions is met:
// - there are file associations configured
// - 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,
ICON_PNG, params, mainParams);
Expand Down Expand Up @@ -138,28 +139,34 @@ private DesktopIntegration(PlatformPackage thePackage,
// Read launchers information from predefine app image
if (launchers.isEmpty() &&
PREDEFINED_APP_IMAGE.fetchFrom(params) != null) {
List<String> launcherPaths = AppImageFile.getLauncherNames(
List<AppImageFile.LauncherInfo> launcherInfos =
AppImageFile.getLaunchers(
PREDEFINED_APP_IMAGE.fetchFrom(params), params);
if (!launcherPaths.isEmpty()) {
launcherPaths.remove(0); // Remove main launcher
if (!launcherInfos.isEmpty()) {
launcherInfos.remove(0); // Remove main launcher
}
for (var launcherPath : launcherPaths) {
for (var launcherInfo : launcherInfos) {
Map<String, ? super Object> launcherParams = new HashMap<>();
Arguments.putUnlessNull(launcherParams, CLIOptions.NAME.getId(),
launcherPath);
launcherParams = AddLauncherArguments.merge(params, launcherParams,
ICON.getID(), ICON_PNG.getID(), ADD_LAUNCHERS.getID(),
FILE_ASSOCIATIONS.getID(), PREDEFINED_APP_IMAGE.getID());
nestedIntegrations.add(new DesktopIntegration(thePackage,
launcherParams, params));
launcherInfo.getName());
launcherParams = AddLauncherArguments.merge(params,
launcherParams, ICON.getID(), ICON_PNG.getID(),
ADD_LAUNCHERS.getID(), FILE_ASSOCIATIONS.getID(),
PREDEFINED_APP_IMAGE.getID());
if (launcherInfo.isShortcut()) {
nestedIntegrations.add(new DesktopIntegration(thePackage,
launcherParams, params));
}
}
} else {
for (var launcherParams : launchers) {
launcherParams = AddLauncherArguments.merge(params, launcherParams,
ICON.getID(), ICON_PNG.getID(), ADD_LAUNCHERS.getID(),
FILE_ASSOCIATIONS.getID());
nestedIntegrations.add(new DesktopIntegration(thePackage,
launcherParams, params));
launcherParams = AddLauncherArguments.merge(params,
launcherParams, ICON.getID(), ICON_PNG.getID(),
ADD_LAUNCHERS.getID(), FILE_ASSOCIATIONS.getID());
if (SHORTCUT_HINT.fetchFrom(launcherParams)) {
nestedIntegrations.add(new DesktopIntegration(thePackage,
launcherParams, params));
}
}
}
}
Expand Down Expand Up @@ -567,7 +574,7 @@ private static class LinuxFileAssociation {
(s, p) -> s
);

private static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
private static final StandardBundlerParam<Boolean> LINUX_SHORTCUT_HINT =
new StandardBundlerParam<>(
Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(),
Boolean.class,
Expand Down
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -32,6 +32,8 @@
import jdk.jpackage.internal.Arguments.CLIOptions;
import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA;
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
Expand Down Expand Up @@ -59,7 +61,10 @@
* arguments
* java-options
* win-console
* win-shortcut
* win-menu
* linux-app-category
* linux-shortcut
*
*/
class AddLauncherArguments {
Expand Down Expand Up @@ -109,17 +114,27 @@ private void initLauncherMap() {
Arguments.putUnlessNull(bundleParams, CLIOptions.RELEASE.getId(),
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);
Arguments.putUnlessNull(bundleParams, CLIOptions.ICON.getId(),
(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:
if (allArgs.containsKey(CLIOptions.ARGUMENTS.getId())) {
String argumentStr = getOptionValue(CLIOptions.ARGUMENTS);
Expand Down
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -40,20 +40,24 @@
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.xml.sax.SAXException;

import static jdk.jpackage.internal.StandardBundlerParam.VERSION;
import static jdk.jpackage.internal.StandardBundlerParam.ADD_LAUNCHERS;
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 {

// These values will be loaded from AppImage xml file.
private final String creatorVersion;
private final String creatorPlatform;
private final String launcherName;
private final List<String> addLauncherNames;
private final List<LauncherInfo> addLauncherInfos;

private static final String FILENAME = ".jpackage.xml";

Expand All @@ -66,10 +70,10 @@ private AppImageFile() {
this(null, null, null, null);
}

private AppImageFile(String launcherName, List<String> addLauncherNames,
private AppImageFile(String launcherName, List<LauncherInfo> launcherInfos,
String creatorVersion, String creatorPlatform) {
this.launcherName = launcherName;
this.addLauncherNames = addLauncherNames;
this.addLauncherInfos = launcherInfos;
this.creatorVersion = creatorVersion;
this.creatorPlatform = creatorPlatform;
}
Expand All @@ -79,8 +83,8 @@ private AppImageFile(String launcherName, List<String> addLauncherNames,
* Each item in the list is not null or empty string.
* Returns empty list for application without additional launchers.
*/
List<String> getAddLauncherNames() {
return addLauncherNames;
List<LauncherInfo> getAddLaunchers() {
return addLauncherInfos;
}

/**
Expand Down Expand Up @@ -131,7 +135,10 @@ static void save(Path appImageDir, Map<String, Object> params)
for (int i = 0; i < addLaunchers.size(); i++) {
Map<String, ? super Object> sl = addLaunchers.get(i);
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();
}
});
Expand All @@ -156,24 +163,31 @@ static AppImageFile load(Path appImageDir) throws IOException {
return new AppImageFile();
}

List<String> addLaunchers = new ArrayList<>();
List<LauncherInfo> launcherInfos = new ArrayList<>();

String platform = xpathQueryNullable(xPath,
"/jpackage-state/@platform", doc);

String version = xpathQueryNullable(xPath,
"/jpackage-state/@version", doc);

NodeList launcherNameNodes = (NodeList) xPath.evaluate(
"/jpackage-state/add-launcher/text()", doc,
NodeList launcherNodes = (NodeList) xPath.evaluate(
"/jpackage-state/add-launcher", doc,
XPathConstants.NODESET);

for (int i = 0; i != launcherNameNodes.getLength(); i++) {
addLaunchers.add(launcherNameNodes.item(i).getNodeValue());
for (int i = 0; i != launcherNodes.getLength(); i++) {
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(
mainLauncher, addLaunchers, version, platform);
mainLauncher, launcherInfos, version, platform);
if (!file.isValid()) {
file = new AppImageFile();
}
Expand All @@ -184,6 +198,12 @@ static AppImageFile load(Path appImageDir) throws IOException {
}
}

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 {
try {
Path path = getPathInAppImage(appImageDir);
Expand All @@ -202,18 +222,19 @@ public static Document readXml(Path appImageDir) throws IOException {
}

/**
* Returns list of launcher names configured for the application.
* The first item in the returned list is main launcher name.
* Returns list of LauncherInfo objects configured for the application.
* The first item in the returned list is main launcher.
* 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) {
List<String> launchers = new ArrayList<>();
List<LauncherInfo> launchers = new ArrayList<>();
try {
AppImageFile appImageInfo = AppImageFile.load(appImageDir);
if (appImageInfo != null) {
launchers.add(appImageInfo.getLauncherName());
launchers.addAll(appImageInfo.getAddLauncherNames());
launchers.add(new LauncherInfo(
appImageInfo.getLauncherName(), true, true));
launchers.addAll(appImageInfo.getAddLaunchers());
return launchers;
}
} catch (NoSuchFileException nsfe) {
Expand All @@ -226,10 +247,11 @@ static List<String> getLauncherNames(Path 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).forEach(
launchers::add);
ADD_LAUNCHERS.fetchFrom(params).stream().map(APP_NAME::fetchFrom).map(
name -> new LauncherInfo(name, true, true)).forEach(launchers::add);
return launchers;
}

Expand Down Expand Up @@ -262,15 +284,37 @@ private static String getPlatform() {
}

private boolean isValid() {
if (launcherName == null || launcherName.length() == 0 ||
addLauncherNames.indexOf("") != -1) {
// Some launchers have empty names. This is invalid.
if (launcherName == null || launcherName.length() == 0) {
return false;
}

// Add more validation.
for (var launcher : addLauncherInfos) {
if ("".equals(launcher.getName())) {
return false;
}
}

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;
}
}

}
Expand Up @@ -311,6 +311,24 @@
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 =
new StandardBundlerParam<>(
Arguments.CLIOptions.RESOURCE_DIR.getId(),
Expand Down
Expand Up @@ -135,8 +135,9 @@ Generic Options:\n\
\ a list of key, value pairs\n\
\ (absolute path or relative to the current directory)\n\
\ The keys "module", "main-jar", "main-class",\n\
\ "arguments", "java-options", "app-version", "icon", and\n\
\ "win-console" can be used.\n\
\ "arguments", "java-options", "app-version", "icon",\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\
\ command line options to build an additional alternative launcher.\n\
\ The main application launcher will be built from the command line\n\
Expand Down
Expand Up @@ -135,8 +135,9 @@ Generic Options:\n\
\ a list of key, value pairs\n\
\ (absolute path or relative to the current directory)\n\
\ The keys "module", "main-jar", "main-class",\n\
\ "arguments", "java-options", "app-version", "icon", and\n\
\ "win-console" can be used.\n\
\ "arguments", "java-options", "app-version", "icon",\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\
\ command line options to build an additional alternative launcher.\n\
\ The main application launcher will be built from the command line\n\
Expand Down

1 comment on commit 057992f

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.