Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2022, 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 @@ -117,8 +117,9 @@ private void createLauncherForEntryPoint(Map<String, ? super Object> params,
mainParams);
Path iconTarget = null;
if (iconResource != null) {
iconTarget = appLayout.destktopIntegrationDirectory().resolve(
APP_NAME.fetchFrom(params) + ".ico");
Path iconDir = StandardBundlerParam.TEMP_ROOT.fetchFrom(params).resolve(
"icons");
iconTarget = iconDir.resolve(APP_NAME.fetchFrom(params) + ".ico");
if (null == iconResource.saveToFile(iconTarget)) {
iconTarget = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,17 +443,11 @@ private String addShortcutComponent(XMLStreamWriter xml, Path launcherPath,

Path shortcutPath = folder.getPath(this).resolve(launcherBasename);
return addComponent(xml, shortcutPath, Component.Shortcut, unused -> {
final Path icoFile = IOUtils.addSuffix(
installedAppImage.destktopIntegrationDirectory().resolve(
launcherBasename), ".ico");

xml.writeAttribute("Name", launcherBasename);
xml.writeAttribute("WorkingDirectory", INSTALLDIR.toString());
xml.writeAttribute("Advertise", "no");
xml.writeAttribute("IconIndex", "0");
xml.writeAttribute("Target", String.format("[#%s]",
Component.File.idOf(launcherPath)));
xml.writeAttribute("Icon", Id.Icon.of(icoFile));
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022, 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 @@ -149,7 +149,8 @@ public ExceptionBox(Throwable throwable) {
}

@SuppressWarnings("unchecked")
public static void rethrowUnchecked(Throwable throwable) throws ExceptionBox {
public static RuntimeException rethrowUnchecked(Throwable throwable) throws
ExceptionBox {
if (throwable instanceof RuntimeException) {
throw (RuntimeException)throwable;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022, 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 All @@ -24,8 +24,12 @@
package jdk.jpackage.test;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;

public final class LauncherIconVerifier {
public LauncherIconVerifier() {
Expand Down Expand Up @@ -60,7 +64,11 @@ public void applyTo(JPackageCommand cmd) throws IOException {
Path iconPath = cmd.appLayout().destktopIntegrationDirectory().resolve(
curLauncherName + TKit.ICON_SUFFIX);

if (expectedDefault) {
if (TKit.isWindows()) {
TKit.assertPathExists(iconPath, false);
WinIconVerifier.instance.verifyLauncherIcon(cmd, launcherName,
expectedIcon, expectedDefault);
} else if (expectedDefault) {
TKit.assertPathExists(iconPath, true);
} else if (expectedIcon == null) {
TKit.assertPathExists(iconPath, false);
Expand All @@ -73,6 +81,136 @@ public void applyTo(JPackageCommand cmd) throws IOException {
}
}

private static class WinIconVerifier {

void verifyLauncherIcon(JPackageCommand cmd, String launcherName,
Path expectedIcon, boolean expectedDefault) {
TKit.withTempDirectory("icons", tmpDir -> {
Path launcher = cmd.appLauncherPath(launcherName);
Path iconWorkDir = tmpDir.resolve(launcher.getFileName());
Path iconContainer = iconWorkDir.resolve("container.exe");
Files.createDirectories(iconContainer.getParent());
Files.copy(getDefaultAppLauncher(expectedIcon == null
&& !expectedDefault), iconContainer);
if (expectedIcon != null) {
setIcon(expectedIcon, iconContainer);
}

Path extractedExpectedIcon = extractIconFromExecutable(
iconWorkDir, iconContainer, "expected");
Path extractedActualIcon = extractIconFromExecutable(iconWorkDir,
launcher, "actual");
TKit.assertTrue(-1 == Files.mismatch(extractedExpectedIcon,
extractedActualIcon),
String.format(
"Check icon file [%s] of %s launcher is a copy of source icon file [%s]",
extractedActualIcon,
Optional.ofNullable(launcherName).orElse("main"),
extractedExpectedIcon));
});
}

private WinIconVerifier() {
try {
executableRebranderClass = Class.forName(
"jdk.jpackage.internal.ExecutableRebrander");

lockResource = executableRebranderClass.getDeclaredMethod(
"lockResource", String.class);
// Note: this reflection call requires
// --add-opens jdk.jpackage/jdk.jpackage.internal=ALL-UNNAMED
lockResource.setAccessible(true);

unlockResource = executableRebranderClass.getDeclaredMethod(
"unlockResource", long.class);
unlockResource.setAccessible(true);

iconSwap = executableRebranderClass.getDeclaredMethod("iconSwap",
long.class, String.class);
iconSwap.setAccessible(true);
} catch (ClassNotFoundException | NoSuchMethodException
| SecurityException ex) {
throw Functional.rethrowUnchecked(ex);
}
}

private Path extractIconFromExecutable(Path outputDir, Path executable,
String label) {
Path psScript = outputDir.resolve(label + ".ps1");
Path extractedIcon = outputDir.resolve(label + ".bmp");
TKit.createTextFile(psScript, List.of(
"[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing')",
String.format(
"[System.Drawing.Icon]::ExtractAssociatedIcon(\"%s\").ToBitmap().Save(\"%s\", [System.Drawing.Imaging.ImageFormat]::Bmp)",
executable.toAbsolutePath().normalize(),
extractedIcon.toAbsolutePath().normalize()),
"exit 0"));

Executor.of("powershell", "-NoLogo", "-NoProfile", "-File",
psScript.toAbsolutePath().normalize().toString()).execute();

return extractedIcon;
}

private Path getDefaultAppLauncher(boolean noIcon) {
// Create app image with the sole purpose to get the default app launcher
Path defaultAppOutputDir = TKit.workDir().resolve(String.format(
"out-%d", ProcessHandle.current().pid()));
JPackageCommand cmd = JPackageCommand.helloAppImage().setFakeRuntime().setArgumentValue(
"--dest", defaultAppOutputDir);

String launcherName;
if (noIcon) {
launcherName = "no-icon";
new AdditionalLauncher(launcherName).setNoIcon().applyTo(cmd);
} else {
launcherName = null;
}

if (!Files.isExecutable(cmd.appLauncherPath(launcherName))) {
cmd.execute();
}
return cmd.appLauncherPath(launcherName);
}

private void setIcon(Path iconPath, Path launcherPath) {
TKit.trace(String.format("Set icon of [%s] launcher to [%s] file",
launcherPath, iconPath));
try {
launcherPath.toFile().setWritable(true, true);
try {
long lock = 0;
try {
lock = (Long) lockResource.invoke(null, new Object[]{
launcherPath.toAbsolutePath().normalize().toString()});
if (lock == 0) {
throw new RuntimeException(String.format(
"Failed to lock [%s] executable",
launcherPath));
}
iconSwap.invoke(null, new Object[]{lock,
iconPath.toAbsolutePath().normalize().toString()});
} finally {
if (lock != 0) {
unlockResource.invoke(null, new Object[]{lock});
}
}
} catch (IllegalAccessException | InvocationTargetException ex) {
throw Functional.rethrowUnchecked(ex);
}
} finally {
launcherPath.toFile().setWritable(false, true);
}
}

final static WinIconVerifier instance = new WinIconVerifier();

private final Class executableRebranderClass;
private final Method lockResource;
private final Method unlockResource;
private final Method iconSwap;
}

private String launcherName;
private Path expectedIcon;
private boolean expectedDefault;
Expand Down
8 changes: 6 additions & 2 deletions test/jdk/tools/jpackage/share/AddLauncherTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@
* @library /test/jdk/tools/jpackage/helpers
* @build jdk.jpackage.test.*
* @compile AddLauncherTest.java
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* @run main/othervm/timeout=360 -Xmx512m
* --add-opens jdk.jpackage/jdk.jpackage.internal=ALL-UNNAMED
* jdk.jpackage.test.Main
* --jpt-run=AddLauncherTest.test
*/

Expand All @@ -63,7 +65,9 @@
* @library /test/jdk/tools/jpackage/helpers
* @build jdk.jpackage.test.*
* @compile AddLauncherTest.java
* @run main/othervm/timeout=540 -Xmx512m jdk.jpackage.test.Main
* @run main/othervm/timeout=540 -Xmx512m
* --add-opens jdk.jpackage/jdk.jpackage.internal=ALL-UNNAMED
* jdk.jpackage.test.Main
* --jpt-run=AddLauncherTest
*/

Expand Down
4 changes: 3 additions & 1 deletion test/jdk/tools/jpackage/share/IconTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
* @library /test/jdk/tools/jpackage/helpers
* @build jdk.jpackage.test.*
* @compile IconTest.java
* @run main/othervm/timeout=540 -Xmx512m jdk.jpackage.test.Main
* @run main/othervm/timeout=540 -Xmx512m
* --add-opens jdk.jpackage/jdk.jpackage.internal=ALL-UNNAMED
* jdk.jpackage.test.Main
* --jpt-run=IconTest
*/

Expand Down
4 changes: 3 additions & 1 deletion test/jdk/tools/jpackage/share/MultiLauncherTwoPhaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@
* @key jpackagePlatformPackage
* @build jdk.jpackage.test.*
* @compile MultiLauncherTwoPhaseTest.java
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* @run main/othervm/timeout=360 -Xmx512m
* --add-opens jdk.jpackage/jdk.jpackage.internal=ALL-UNNAMED
* jdk.jpackage.test.Main
* --jpt-run=MultiLauncherTwoPhaseTest
*/

Expand Down