mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-24 05:40:39 +00:00
8378877: jpackage: improve rebranding of exe files on Windows
Reviewed-by: almatvee
This commit is contained in:
parent
86800eb2b3
commit
c13fdc044d
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2026, 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
|
||||
@ -40,9 +40,11 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.model.DottedVersion;
|
||||
import jdk.jpackage.internal.model.JPackageException;
|
||||
import jdk.jpackage.internal.model.WinApplication;
|
||||
import jdk.jpackage.internal.model.WinExePackage;
|
||||
import jdk.jpackage.internal.model.WinLauncher;
|
||||
@ -74,11 +76,11 @@ final class ExecutableRebrander {
|
||||
|
||||
this.props = new HashMap<>();
|
||||
|
||||
validateValueAndPut(this.props, Map.entry("COMPANY_NAME", props.vendor), "vendor");
|
||||
validateValueAndPut(this.props, Map.entry("FILE_DESCRIPTION",props.description), "description");
|
||||
validateValueAndPut(this.props, Map.entry("FILE_VERSION", props.version.toString()), "version");
|
||||
validateValueAndPut(this.props, Map.entry("LEGAL_COPYRIGHT", props.copyright), "copyright");
|
||||
validateValueAndPut(this.props, Map.entry("PRODUCT_NAME", props.name), "name");
|
||||
this.props.put("COMPANY_NAME", validateSingleLine(props.vendor));
|
||||
this.props.put("FILE_DESCRIPTION", validateSingleLine(props.description));
|
||||
this.props.put("FILE_VERSION", validateSingleLine(props.version.toString()));
|
||||
this.props.put("LEGAL_COPYRIGHT", validateSingleLine(props.copyright));
|
||||
this.props.put("PRODUCT_NAME", validateSingleLine(props.name));
|
||||
|
||||
this.props.put("FIXEDFILEINFO_FILE_VERSION", toFixedFileVersion(props.version));
|
||||
this.props.put("INTERNAL_NAME", props.executableName);
|
||||
@ -90,7 +92,7 @@ final class ExecutableRebrander {
|
||||
|
||||
UpdateResourceAction versionSwapper = resourceLock -> {
|
||||
if (versionSwap(resourceLock, propsArray) != 0) {
|
||||
throw I18N.buildException().message("error.version-swap", target).create(RuntimeException::new);
|
||||
throw new JPackageException(I18N.format("error.version-swap", target));
|
||||
}
|
||||
};
|
||||
|
||||
@ -100,7 +102,7 @@ final class ExecutableRebrander {
|
||||
.map(absIcon -> {
|
||||
return resourceLock -> {
|
||||
if (iconSwap(resourceLock, absIcon.toString()) != 0) {
|
||||
throw I18N.buildException().message("error.icon-swap", absIcon).create(RuntimeException::new);
|
||||
throw new JPackageException(I18N.format("error.icon-swap", absIcon));
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -118,43 +120,58 @@ final class ExecutableRebrander {
|
||||
|
||||
private static void rebrandExecutable(BuildEnv env, final Path target,
|
||||
List<UpdateResourceAction> actions) throws IOException {
|
||||
|
||||
Objects.requireNonNull(env);
|
||||
Objects.requireNonNull(target);
|
||||
Objects.requireNonNull(actions);
|
||||
actions.forEach(Objects::requireNonNull);
|
||||
|
||||
String tempDirectory = env.buildRoot().toAbsolutePath().toString();
|
||||
if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(tempDirectory)) {
|
||||
Log.verbose(I18N.format("message.potential.windows.defender.issue", tempDirectory));
|
||||
}
|
||||
|
||||
var shortTargetPath = ShortPathUtils.toShortPath(target);
|
||||
long resourceLock = lockResource(shortTargetPath.orElse(target).toString());
|
||||
if (resourceLock == 0) {
|
||||
throw I18N.buildException().message("error.lock-resource", shortTargetPath.orElse(target)).create(RuntimeException::new);
|
||||
}
|
||||
|
||||
final boolean resourceUnlockedSuccess;
|
||||
try {
|
||||
for (var action : actions) {
|
||||
action.editResource(resourceLock);
|
||||
}
|
||||
} finally {
|
||||
if (resourceLock == 0) {
|
||||
resourceUnlockedSuccess = true;
|
||||
} else {
|
||||
resourceUnlockedSuccess = unlockResource(resourceLock);
|
||||
if (shortTargetPath.isPresent()) {
|
||||
// Windows will rename the executable in the unlock operation.
|
||||
// Should restore executable's name.
|
||||
var tmpPath = target.getParent().resolve(
|
||||
target.getFileName().toString() + ".restore");
|
||||
Files.move(shortTargetPath.get(), tmpPath);
|
||||
Files.move(tmpPath, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
Globals.instance().objectFactory().<Void, RuntimeException>retryExecutor(RuntimeException.class).setExecutable(() -> {
|
||||
|
||||
if (!resourceUnlockedSuccess) {
|
||||
throw I18N.buildException().message("error.unlock-resource", target).create(RuntimeException::new);
|
||||
var shortTargetPath = ShortPathUtils.toShortPath(target);
|
||||
long resourceLock = lockResource(shortTargetPath.orElse(target).toString());
|
||||
if (resourceLock == 0) {
|
||||
throw new JPackageException(I18N.format("error.lock-resource", shortTargetPath.orElse(target)));
|
||||
}
|
||||
|
||||
final boolean resourceUnlockedSuccess;
|
||||
try {
|
||||
for (var action : actions) {
|
||||
try {
|
||||
action.editResource(resourceLock);
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (resourceLock == 0) {
|
||||
resourceUnlockedSuccess = true;
|
||||
} else {
|
||||
resourceUnlockedSuccess = unlockResource(resourceLock);
|
||||
if (shortTargetPath.isPresent()) {
|
||||
// Windows will rename the executable in the unlock operation.
|
||||
// Should restore executable's name.
|
||||
var tmpPath = target.getParent().resolve(
|
||||
target.getFileName().toString() + ".restore");
|
||||
try {
|
||||
Files.move(shortTargetPath.get(), tmpPath);
|
||||
Files.move(tmpPath, target);
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!resourceUnlockedSuccess) {
|
||||
throw new JPackageException(I18N.format("error.unlock-resource", shortTargetPath.orElse(target)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}).setMaxAttemptsCount(5).setAttemptTimeout(3, TimeUnit.SECONDS).execute();
|
||||
} catch (UncheckedIOException ex) {
|
||||
throw ex.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,14 +214,13 @@ final class ExecutableRebrander {
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateValueAndPut(Map<String, String> target,
|
||||
Map.Entry<String, String> e, String label) {
|
||||
if (e.getValue().contains("\r") || e.getValue().contains("\n")) {
|
||||
Log.error("Configuration parameter " + label
|
||||
+ " contains multiple lines of text, ignore it");
|
||||
e = Map.entry(e.getKey(), "");
|
||||
private static String validateSingleLine(String v) {
|
||||
Objects.requireNonNull(v);
|
||||
if (v.contains("\r") || v.contains("\n")) {
|
||||
throw new IllegalArgumentException("Configuration parameter contains multiple lines of text");
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
target.put(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
@ -212,19 +228,35 @@ final class ExecutableRebrander {
|
||||
public void editResource(long resourceLock) throws IOException;
|
||||
}
|
||||
|
||||
private static record ExecutableProperties(String vendor, String description,
|
||||
private record ExecutableProperties(String vendor, String description,
|
||||
DottedVersion version, String copyright, String name, String executableName) {
|
||||
static ExecutableProperties create(WinApplication app,
|
||||
WinLauncher launcher) {
|
||||
return new ExecutableProperties(app.vendor(), launcher.description(),
|
||||
app.winVersion(), app.copyright(), launcher.name(),
|
||||
|
||||
ExecutableProperties {
|
||||
Objects.requireNonNull(vendor);
|
||||
Objects.requireNonNull(description);
|
||||
Objects.requireNonNull(version);
|
||||
Objects.requireNonNull(copyright);
|
||||
Objects.requireNonNull(name);
|
||||
Objects.requireNonNull(executableName);
|
||||
}
|
||||
|
||||
static ExecutableProperties create(WinApplication app, WinLauncher launcher) {
|
||||
return new ExecutableProperties(
|
||||
app.vendor(),
|
||||
launcher.description(),
|
||||
app.winVersion(),
|
||||
app.copyright(),
|
||||
launcher.name(),
|
||||
launcher.executableNameWithSuffix());
|
||||
}
|
||||
|
||||
static ExecutableProperties create(WinExePackage pkg) {
|
||||
return new ExecutableProperties(pkg.app().vendor(),
|
||||
pkg.description(), DottedVersion.lazy(pkg.version()),
|
||||
pkg.app().copyright(), pkg.packageName(),
|
||||
return new ExecutableProperties(
|
||||
pkg.app().vendor(),
|
||||
pkg.description(),
|
||||
DottedVersion.lazy(pkg.version()),
|
||||
pkg.app().copyright(),
|
||||
pkg.packageName(),
|
||||
pkg.packageFileNameWithSuffix());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2023, 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.util.List;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.internal.util.OSVersion;
|
||||
|
||||
final class WindowsDefender {
|
||||
|
||||
private WindowsDefender() {}
|
||||
|
||||
static final boolean isThereAPotentialWindowsDefenderIssue(String dir) {
|
||||
boolean result = false;
|
||||
|
||||
if (OperatingSystem.isWindows() &&
|
||||
OSVersion.current().major() == 10) {
|
||||
|
||||
// If DisableRealtimeMonitoring is not enabled then there
|
||||
// may be a problem.
|
||||
if (!WindowsRegistry.readDisableRealtimeMonitoring() &&
|
||||
!isDirectoryInExclusionPath(dir)) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isDirectoryInExclusionPath(String dir) {
|
||||
boolean result = false;
|
||||
// If the user temp directory is not found in the exclusion
|
||||
// list then there may be a problem.
|
||||
List<String> paths = WindowsRegistry.readExclusionsPaths();
|
||||
for (String s : paths) {
|
||||
if (WindowsRegistry.comparePaths(s, dir)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static final String getUserTempDirectory() {
|
||||
String tempDirectory = System.getProperty("java.io.tmpdir");
|
||||
return tempDirectory;
|
||||
}
|
||||
}
|
||||
@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2024, 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.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("restricted")
|
||||
final class WindowsRegistry {
|
||||
|
||||
// Currently we only support HKEY_LOCAL_MACHINE. Native implementation will
|
||||
// require support for additinal HKEY if needed.
|
||||
private static final int HKEY_LOCAL_MACHINE = 1;
|
||||
|
||||
static {
|
||||
System.loadLibrary("jpackage");
|
||||
}
|
||||
|
||||
private WindowsRegistry() {}
|
||||
|
||||
/**
|
||||
* Reads the registry value for DisableRealtimeMonitoring.
|
||||
* @return true if DisableRealtimeMonitoring is set to 0x1,
|
||||
* false otherwise.
|
||||
*/
|
||||
static final boolean readDisableRealtimeMonitoring() {
|
||||
final String subKey = "Software\\Microsoft\\"
|
||||
+ "Windows Defender\\Real-Time Protection";
|
||||
final String value = "DisableRealtimeMonitoring";
|
||||
int result = readDwordValue(HKEY_LOCAL_MACHINE, subKey, value, 0);
|
||||
return (result == 1);
|
||||
}
|
||||
|
||||
static final List<String> readExclusionsPaths() {
|
||||
List<String> result = new ArrayList<>();
|
||||
final String subKey = "Software\\Microsoft\\"
|
||||
+ "Windows Defender\\Exclusions\\Paths";
|
||||
long lKey = openRegistryKey(HKEY_LOCAL_MACHINE, subKey);
|
||||
if (lKey == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
String valueName;
|
||||
int index = 0;
|
||||
do {
|
||||
valueName = enumRegistryValue(lKey, index);
|
||||
if (valueName != null) {
|
||||
result.add(valueName);
|
||||
index++;
|
||||
}
|
||||
} while (valueName != null);
|
||||
|
||||
closeRegistryKey(lKey);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads DWORD registry value.
|
||||
*
|
||||
* @param key one of HKEY predefine value
|
||||
* @param subKey registry sub key
|
||||
* @param value value to read
|
||||
* @param defaultValue default value in case if subKey or value not found
|
||||
* or any other errors occurred
|
||||
* @return value's data only if it was read successfully, otherwise
|
||||
* defaultValue
|
||||
*/
|
||||
private static native int readDwordValue(int key, String subKey,
|
||||
String value, int defaultValue);
|
||||
|
||||
/**
|
||||
* Open registry key.
|
||||
*
|
||||
* @param key one of HKEY predefine value
|
||||
* @param subKey registry sub key
|
||||
* @return native handle to open key
|
||||
*/
|
||||
private static native long openRegistryKey(int key, String subKey);
|
||||
|
||||
/**
|
||||
* Enumerates the values for registry key.
|
||||
*
|
||||
* @param lKey native handle to open key returned by openRegistryKey
|
||||
* @param index index of value starting from 0. Increment until this
|
||||
* function returns NULL which means no more values.
|
||||
* @return returns value or NULL if error or no more data
|
||||
*/
|
||||
private static native String enumRegistryValue(long lKey, int index);
|
||||
|
||||
/**
|
||||
* Close registry key.
|
||||
*
|
||||
* @param lKey native handle to open key returned by openRegistryKey
|
||||
*/
|
||||
private static native void closeRegistryKey(long lKey);
|
||||
|
||||
/**
|
||||
* Compares two Windows paths regardless case and if paths
|
||||
* are short or long.
|
||||
*
|
||||
* @param path1 path to compare
|
||||
* @param path2 path to compare
|
||||
* @return true if paths point to same location
|
||||
*/
|
||||
public static native boolean comparePaths(String path1, String path2);
|
||||
}
|
||||
@ -55,7 +55,6 @@ error.missing-service-installer='service-installer.exe' service installer not fo
|
||||
error.missing-service-installer.advice=Add 'service-installer.exe' service installer to the resource directory
|
||||
|
||||
message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
|
||||
message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
|
||||
message.tool-version=Detected [{0}] version [{1}].
|
||||
message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
|
||||
message.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user