Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import jdk.jpackage.internal.model.FileAssociation;
import jdk.jpackage.internal.model.LauncherShortcut;
import jdk.jpackage.internal.model.LinuxLauncher;
import jdk.jpackage.internal.model.LinuxPackage;
import jdk.jpackage.internal.model.Package;
Expand Down Expand Up @@ -237,6 +238,23 @@ private Map<String, String> createDataForDesktopFile() {
data.put("DEPLOY_BUNDLE_CATEGORY", pkg.menuGroupName());
data.put("APPLICATION_LAUNCHER", Enquoter.forPropertyValues().applyTo(
installedLayout.launchersDirectory().resolve(launcher.executableNameWithSuffix()).toString()));
data.put("STARTUP_DIRECTORY", launcher.shortcut()
.flatMap(LauncherShortcut::startupDirectory)
.map(startupDirectory -> {
switch (startupDirectory) {
case DEFAULT -> {
return (Path)null;
}
case APP_DIR -> {
return installedLayout.appDirectory();
}
default -> {
throw new AssertionError();
}
}
}).map(str -> {
return "Path=" + str;
}).orElse(null));

return data;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,8 @@ private static LinuxPackage createLinuxDebPackage(
static final BundlerParamInfo<LinuxPackage> DEB_PACKAGE = createPackageBundlerParam(
LinuxFromParams::createLinuxDebPackage);

private static final BundlerParamInfo<Boolean> LINUX_SHORTCUT_HINT = new BundlerParamInfo<>(
Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(),
Boolean.class,
params -> false,
(s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? false : Boolean.valueOf(s)
);
private static final BundlerParamInfo<String> LINUX_SHORTCUT_HINT = createStringBundlerParam(
Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId());

private static final BundlerParamInfo<String> LINUX_CATEGORY = createStringBundlerParam(
Arguments.CLIOptions.LINUX_CATEGORY.getId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Name=APPLICATION_NAME
Comment=APPLICATION_DESCRIPTION
Exec=APPLICATION_LAUNCHER
STARTUP_DIRECTORY
Icon=APPLICATION_ICON
Terminal=false
Type=Application
Expand Down
41 changes: 30 additions & 11 deletions src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.ResourceBundle;
Expand Down Expand Up @@ -348,16 +349,13 @@ public enum CLIOptions {

WIN_UPDATE_URL ("win-update-url", OptionCategories.PLATFORM_WIN),

WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, () -> {
setOptionValue("win-menu", true);
}),
WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN,
createArgumentWithOptionalValueAction("win-menu")),

WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN),

WIN_SHORTCUT_HINT ("win-shortcut",
OptionCategories.PLATFORM_WIN, () -> {
setOptionValue("win-shortcut", true);
}),
WIN_SHORTCUT_HINT ("win-shortcut", OptionCategories.PLATFORM_WIN,
createArgumentWithOptionalValueAction("win-shortcut")),

WIN_SHORTCUT_PROMPT ("win-shortcut-prompt",
OptionCategories.PLATFORM_WIN, () -> {
Expand Down Expand Up @@ -396,10 +394,8 @@ public enum CLIOptions {
LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps",
OptionCategories.PLATFORM_LINUX),

LINUX_SHORTCUT_HINT ("linux-shortcut",
OptionCategories.PLATFORM_LINUX, () -> {
setOptionValue("linux-shortcut", true);
}),
LINUX_SHORTCUT_HINT ("linux-shortcut", OptionCategories.PLATFORM_LINUX,
createArgumentWithOptionalValueAction("linux-shortcut")),

LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX);

Expand Down Expand Up @@ -478,9 +474,32 @@ private static void nextArg() {
context().pos++;
}

private static void prevArg() {
Objects.checkIndex(context().pos, context().argList.size());
context().pos--;
}

private static boolean hasNextArg() {
return context().pos < context().argList.size();
}

private static Runnable createArgumentWithOptionalValueAction(String option) {
Copy link
Member

Choose a reason for hiding this comment

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

I am not sure, but did you test case when --win-menu or --win-shortcut or --linux-shortcut is last argument without value? With new code var value = popArg(); will be "" and we will call setOptionValue(option, value). Before change it will be set to true.

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right. I reworked the tests, and they start failing if the last argument is --win-menu, --win-shortcut, or --linux-shortcut.

Objects.requireNonNull(option);
return () -> {
nextArg();
if (hasNextArg()) {
var value = getArg();
if (value.startsWith("-")) {
prevArg();
setOptionValue(option, true);
} else {
setOptionValue(option, value);
}
} else {
setOptionValue(option, true);
}
};
}
}

enum OptionCategories {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import static jdk.jpackage.internal.StandardBundlerParam.VERSION;
import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage;
import static jdk.jpackage.internal.StandardBundlerParam.isRuntimeInstaller;
import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction;

import java.io.IOException;
import java.nio.file.Path;
Expand All @@ -69,6 +70,7 @@
import jdk.jpackage.internal.model.LauncherShortcut;
import jdk.jpackage.internal.model.LauncherShortcutStartupDirectory;
import jdk.jpackage.internal.model.PackageType;
import jdk.jpackage.internal.model.ParseUtils;
import jdk.jpackage.internal.model.RuntimeLayout;
import jdk.jpackage.internal.util.function.ThrowingFunction;

Expand Down Expand Up @@ -171,29 +173,31 @@ static Optional<jdk.jpackage.internal.model.Package> getCurrentPackage(Map<Strin
}

static Optional<LauncherShortcut> findLauncherShortcut(
BundlerParamInfo<Boolean> shortcutParam,
BundlerParamInfo<String> shortcutParam,
Map<String, ? super Object> mainParams,
Map<String, ? super Object> launcherParams) {

Optional<Boolean> launcherValue;
Optional<String> launcherValue;
if (launcherParams == mainParams) {
// The main launcher
launcherValue = Optional.empty();
} else {
launcherValue = shortcutParam.findIn(launcherParams);
}

return launcherValue.map(withShortcut -> {
if (withShortcut) {
return Optional.of(LauncherShortcutStartupDirectory.DEFAULT);
} else {
return Optional.<LauncherShortcutStartupDirectory>empty();
}
}).or(() -> {
return shortcutParam.findIn(mainParams).map(_ -> {
return Optional.of(LauncherShortcutStartupDirectory.DEFAULT);
});
}).map(LauncherShortcut::new);
return launcherValue.map(ParseUtils::parseLauncherShortcutForAddLauncher).or(() -> {
return Optional.ofNullable(mainParams.get(shortcutParam.getID())).map(toFunction(value -> {
if (value instanceof Boolean) {
return new LauncherShortcut(LauncherShortcutStartupDirectory.DEFAULT);
} else {
try {
return ParseUtils.parseLauncherShortcutForMainLauncher((String)value);
} catch (IllegalArgumentException ex) {
throw I18N.buildConfigException("error.invalid-option-value", value, "--" + shortcutParam.getID()).create();
}
}
}));
});
}

private static ApplicationLaunchers createLaunchers(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ public enum LauncherShortcutStartupDirectory {
* On Linux, it indicates that a shortcut doesn't have the startup directory
* configured explicitly.
*/
DEFAULT("true");
DEFAULT("true"),

/**
* The 'app' directory in the installed application app image. This is the
* directory that is referenced with {@link ApplicationLayout#appDirectory()}
* method.
*/
APP_DIR("app-dir");

LauncherShortcutStartupDirectory(String stringValue) {
this.stringValue = Objects.requireNonNull(stringValue);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 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.model;

import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

/**
* Collection of functions to create instances of types defined in this package from strings.
*/
public final class ParseUtils {

private ParseUtils() {
}

public static LauncherShortcut parseLauncherShortcutForMainLauncher(String str) {
return parse(str, LauncherShortcutStartupDirectory.APP_DIR).map(LauncherShortcut::new).orElseThrow(IllegalArgumentException::new);
}

public static LauncherShortcut parseLauncherShortcutForAddLauncher(String str) {
return parse(str, LauncherShortcutStartupDirectory.values()).map(LauncherShortcut::new).orElseGet(() -> {
if (Boolean.valueOf(str)) {
return new LauncherShortcut(LauncherShortcutStartupDirectory.DEFAULT);
} else {
return new LauncherShortcut();
}
});
}

private static Optional<LauncherShortcutStartupDirectory> parse(String str, LauncherShortcutStartupDirectory... recognizedValues) {
Objects.requireNonNull(str);
return Stream.of(recognizedValues).filter(v -> {
return str.equals(v.asStringValue());
}).findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ error.invalid-app-image=Error: app-image dir "{0}" generated by another jpackage

error.invalid-install-dir=Invalid installation directory "{0}"

error.invalid-option-value=Invalid value "{0}" of option {1}

MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\
Advice to fix: {2}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ private static WinMsiPackage createWinMsiPackage(Map<String, ? super Object> par
static final BundlerParamInfo<WinMsiPackage> MSI_PACKAGE = createPackageBundlerParam(
WinFromParams::createWinMsiPackage);

private static final BundlerParamInfo<Boolean> WIN_MENU_HINT = createBooleanBundlerParam(
private static final BundlerParamInfo<String> WIN_MENU_HINT = createStringBundlerParam(
Arguments.CLIOptions.WIN_MENU_HINT.getId());

private static final BundlerParamInfo<Boolean> WIN_SHORTCUT_HINT = createBooleanBundlerParam(
private static final BundlerParamInfo<String> WIN_SHORTCUT_HINT = createStringBundlerParam(
Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId());

public static final BundlerParamInfo<Boolean> CONSOLE_HINT = createBooleanBundlerParam(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,9 @@ private void addShortcutComponentGroup(XMLStreamWriter xml) throws
case DEFAULT -> {
return INSTALLDIR;
}
case APP_DIR -> {
return installedAppImage.appDirectory();
}
default -> {
throw new AssertionError();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public enum LauncherShortcut {

public enum StartupDirectory {
DEFAULT("true"),
APP_DIR("app-dir"),
;

StartupDirectory(String stringValue) {
Expand Down Expand Up @@ -147,7 +148,14 @@ record Stub(

private Optional<StartupDirectory> findMainLauncherShortcut(JPackageCommand cmd) {
if (cmd.hasArgument(optionName())) {
return Optional.of(StartupDirectory.DEFAULT);
var value = Optional.ofNullable(cmd.getArgumentValue(optionName())).filter(optionValue -> {
return !optionValue.startsWith("-");
});
if (value.isPresent()) {
return value.flatMap(StartupDirectory::parse);
} else {
return Optional.of(StartupDirectory.DEFAULT);
}
} else {
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,9 @@ private static void verifyDesktopFile(JPackageCommand cmd, Optional<AppImageFile
case DEFAULT -> {
return (Path)null;
}
case APP_DIR -> {
return cmd.pathToPackageFile(appLayout.appDirectory());
}
default -> {
throw new AssertionError();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,17 @@ private static Collection<Shortcut> expectLauncherShortcuts(JPackageCommand cmd,
final var installDir = Path.of(installRoot.getMsiPropertyName()).resolve(getInstallationSubDirectory(cmd));

final Function<StartupDirectory, Path> workDir = startupDirectory -> {
return installDir;
switch (startupDirectory) {
case DEFAULT -> {
return installDir;
}
case APP_DIR -> {
return ApplicationLayout.windowsAppImage().resolveAt(installDir).appDirectory();
}
default -> {
throw new IllegalArgumentException();
}
}
};

if (winMenu.isPresent()) {
Expand Down
Loading