mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-30 21:18:25 +00:00
8351073: [macos] jpackage produces invalid Java runtime DMG bundles
Reviewed-by: asemenyuk
This commit is contained in:
parent
a3843e8e6e
commit
03230f8565
@ -44,7 +44,9 @@ record CodesignConfig(Optional<SigningIdentity> identity, Optional<String> ident
|
||||
Objects.requireNonNull(keychain);
|
||||
|
||||
if (identity.isPresent() != identifierPrefix.isPresent()) {
|
||||
throw new IllegalArgumentException("Signing identity and identifier prefix mismatch");
|
||||
throw new IllegalArgumentException(
|
||||
"Signing identity (" + identity + ") and identifier prefix (" +
|
||||
identifierPrefix + ") mismatch");
|
||||
}
|
||||
|
||||
identifierPrefix.ifPresent(v -> {
|
||||
|
||||
@ -69,7 +69,7 @@ public class MacAppBundler extends AppImageBundler {
|
||||
}
|
||||
}
|
||||
|
||||
if (StandardBundlerParam.getPredefinedAppImage(params) != null) {
|
||||
if (StandardBundlerParam.hasPredefinedAppImage(params)) {
|
||||
if (!Optional.ofNullable(
|
||||
SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
|
||||
throw new ConfigException(
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import jdk.jpackage.internal.model.AppImageLayout;
|
||||
|
||||
/**
|
||||
* An abstraction of macOS Application bundle.
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles">https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles</a>
|
||||
*/
|
||||
record MacBundle(Path root) {
|
||||
|
||||
MacBundle {
|
||||
Objects.requireNonNull(root);
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return Files.isDirectory(contentsDir()) && Files.isDirectory(macOsDir()) && Files.isRegularFile(infoPlistFile());
|
||||
}
|
||||
|
||||
boolean isSigned() {
|
||||
return Files.isDirectory(contentsDir().resolve("_CodeSignature"));
|
||||
}
|
||||
|
||||
Path contentsDir() {
|
||||
return root.resolve("Contents");
|
||||
}
|
||||
|
||||
Path homeDir() {
|
||||
return contentsDir().resolve("Home");
|
||||
}
|
||||
|
||||
Path macOsDir() {
|
||||
return contentsDir().resolve("MacOS");
|
||||
}
|
||||
|
||||
Path resourcesDir() {
|
||||
return contentsDir().resolve("Resources");
|
||||
}
|
||||
|
||||
Path infoPlistFile() {
|
||||
return contentsDir().resolve("Info.plist");
|
||||
}
|
||||
|
||||
static boolean isDirectoryMacBundle(Path dir) {
|
||||
return new MacBundle(dir).isValid();
|
||||
}
|
||||
|
||||
static MacBundle fromAppImageLayout(AppImageLayout layout) {
|
||||
return new MacBundle(layout.rootDirectory());
|
||||
}
|
||||
}
|
||||
@ -147,7 +147,15 @@ final class MacFromParams {
|
||||
signingBuilder.entitlementsResourceName("sandbox.plist");
|
||||
}
|
||||
|
||||
app.mainLauncher().flatMap(Launcher::startupInfo).ifPresent(signingBuilder::signingIdentifierPrefix);
|
||||
final var bundleIdentifier = appBuilder.create().bundleIdentifier();
|
||||
app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse(
|
||||
signingBuilder::signingIdentifierPrefix,
|
||||
() -> {
|
||||
// Runtime installer does not have main launcher, so use
|
||||
// 'bundleIdentifier' as prefix by default.
|
||||
signingBuilder.signingIdentifierPrefix(
|
||||
bundleIdentifier + ".");
|
||||
});
|
||||
SIGN_IDENTIFIER_PREFIX.copyInto(params, signingBuilder::signingIdentifierPrefix);
|
||||
|
||||
ENTITLEMENTS.copyInto(params, signingBuilder::entitlements);
|
||||
@ -168,6 +176,12 @@ final class MacFromParams {
|
||||
.map(MacAppImageFileExtras::signed)
|
||||
.ifPresent(builder::predefinedAppImageSigned);
|
||||
|
||||
PREDEFINED_RUNTIME_IMAGE.findIn(params)
|
||||
.map(MacBundle::new)
|
||||
.filter(MacBundle::isValid)
|
||||
.map(MacBundle::isSigned)
|
||||
.ifPresent(builder::predefinedAppImageSigned);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -72,6 +73,7 @@ import jdk.jpackage.internal.model.MacPackage;
|
||||
import jdk.jpackage.internal.model.Package;
|
||||
import jdk.jpackage.internal.model.PackageType;
|
||||
import jdk.jpackage.internal.model.PackagerException;
|
||||
import jdk.jpackage.internal.util.FileUtils;
|
||||
import jdk.jpackage.internal.util.PathUtils;
|
||||
import jdk.jpackage.internal.util.function.ThrowingConsumer;
|
||||
|
||||
@ -91,6 +93,7 @@ final class MacPackagingPipeline {
|
||||
enum MacCopyAppImageTaskID implements TaskID {
|
||||
COPY_PACKAGE_FILE,
|
||||
COPY_RUNTIME_INFO_PLIST,
|
||||
COPY_RUNTIME_JLILIB,
|
||||
REPLACE_APP_IMAGE_FILE,
|
||||
COPY_SIGN
|
||||
}
|
||||
@ -115,10 +118,10 @@ final class MacPackagingPipeline {
|
||||
.task(CopyAppImageTaskID.COPY)
|
||||
.copyAction(MacPackagingPipeline::copyAppImage).add()
|
||||
.task(MacBuildApplicationTaskID.RUNTIME_INFO_PLIST)
|
||||
.applicationAction(MacPackagingPipeline::writeApplicationRuntimeInfoPlist)
|
||||
.appImageAction(MacPackagingPipeline::writeRuntimeInfoPlist)
|
||||
.addDependent(BuildApplicationTaskID.CONTENT).add()
|
||||
.task(MacBuildApplicationTaskID.COPY_JLILIB)
|
||||
.applicationAction(MacPackagingPipeline::copyJliLib)
|
||||
.appImageAction(MacPackagingPipeline::copyJliLib)
|
||||
.addDependency(BuildApplicationTaskID.RUNTIME)
|
||||
.addDependent(BuildApplicationTaskID.CONTENT).add()
|
||||
.task(MacBuildApplicationTaskID.APP_ICON)
|
||||
@ -138,13 +141,18 @@ final class MacPackagingPipeline {
|
||||
.addDependencies(CopyAppImageTaskID.COPY)
|
||||
.addDependents(PrimaryTaskID.COPY_APP_IMAGE).add()
|
||||
.task(MacCopyAppImageTaskID.COPY_RUNTIME_INFO_PLIST)
|
||||
.appImageAction(MacPackagingPipeline::writeRuntimeInfoPlist)
|
||||
.addDependencies(CopyAppImageTaskID.COPY)
|
||||
.addDependents(PrimaryTaskID.COPY_APP_IMAGE).add()
|
||||
.task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB)
|
||||
.noaction()
|
||||
.addDependencies(CopyAppImageTaskID.COPY)
|
||||
.addDependents(PrimaryTaskID.COPY_APP_IMAGE).add()
|
||||
.task(MacBuildApplicationTaskID.FA_ICONS)
|
||||
.applicationAction(MacPackagingPipeline::writeFileAssociationIcons)
|
||||
.addDependent(BuildApplicationTaskID.CONTENT).add()
|
||||
.task(MacBuildApplicationTaskID.APP_INFO_PLIST)
|
||||
.applicationAction(MacPackagingPipeline::writeAppInfoPlist)
|
||||
.applicationAction(MacPackagingPipeline::writeApplicationInfoPlist)
|
||||
.addDependent(BuildApplicationTaskID.CONTENT).add();
|
||||
|
||||
builder.task(MacBuildApplicationTaskID.SIGN)
|
||||
@ -172,16 +180,38 @@ final class MacPackagingPipeline {
|
||||
disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE);
|
||||
disabledTasks.add(CopyAppImageTaskID.COPY);
|
||||
disabledTasks.add(PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT);
|
||||
builder.task(MacCopyAppImageTaskID.REPLACE_APP_IMAGE_FILE).applicationAction(createWriteAppImageFileAction()).add();
|
||||
builder.task(MacCopyAppImageTaskID.REPLACE_APP_IMAGE_FILE)
|
||||
.applicationAction(createWriteAppImageFileAction()).add();
|
||||
builder.appImageLayoutForPackaging(Package::appImageLayout);
|
||||
} else if (p.isRuntimeInstaller() || ((MacPackage)p).predefinedAppImageSigned().orElse(false)) {
|
||||
// If this is a runtime package or a signed predefined app image,
|
||||
// don't create ".package" file and don't sign it.
|
||||
} else if (p.isRuntimeInstaller()) {
|
||||
|
||||
builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB)
|
||||
.appImageAction(MacPackagingPipeline::copyJliLib).add();
|
||||
|
||||
final var predefinedRuntimeBundle = Optional.of(
|
||||
new MacBundle(p.predefinedAppImage().orElseThrow())).filter(MacBundle::isValid);
|
||||
|
||||
// Don't create ".package" file.
|
||||
disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE);
|
||||
|
||||
if (predefinedRuntimeBundle.isPresent()) {
|
||||
// The predefined app image is a macOS bundle.
|
||||
// Disable all alterations of the input bundle, but keep the signing enabled.
|
||||
disabledTasks.addAll(List.of(MacCopyAppImageTaskID.values()));
|
||||
disabledTasks.remove(MacCopyAppImageTaskID.COPY_SIGN);
|
||||
}
|
||||
|
||||
if (predefinedRuntimeBundle.map(MacBundle::isSigned).orElse(false) && !((MacPackage)p).app().sign()) {
|
||||
// The predefined app image is a signed bundle; explicit signing is not requested for the package.
|
||||
// Disable the signing, i.e. don't re-sign the input bundle.
|
||||
disabledTasks.add(MacCopyAppImageTaskID.COPY_SIGN);
|
||||
}
|
||||
} else if (((MacPackage)p).predefinedAppImageSigned().orElse(false)) {
|
||||
// This is a signed predefined app image.
|
||||
// Don't create ".package" file.
|
||||
disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE);
|
||||
// Don't sign the image.
|
||||
disabledTasks.add(MacCopyAppImageTaskID.COPY_SIGN);
|
||||
// if (p.isRuntimeInstaller()) {
|
||||
// builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_INFO_PLIST).packageAction(MacPackagingPipeline::writeRuntimeRuntimeInfoPlist).add();
|
||||
// }
|
||||
}
|
||||
|
||||
for (final var taskId : disabledTasks) {
|
||||
@ -208,13 +238,27 @@ final class MacPackagingPipeline {
|
||||
|
||||
private static void copyAppImage(MacPackage pkg, AppImageDesc srcAppImage,
|
||||
AppImageDesc dstAppImage) throws IOException {
|
||||
PackagingPipeline.copyAppImage(srcAppImage, dstAppImage, !pkg.predefinedAppImageSigned().orElse(false));
|
||||
|
||||
boolean predefinedAppImageSigned = pkg.predefinedAppImageSigned().orElse(false);
|
||||
|
||||
var inputRootDirectory = srcAppImage.resolvedAppImagelayout().rootDirectory();
|
||||
|
||||
if (pkg.isRuntimeInstaller() && MacBundle.isDirectoryMacBundle(inputRootDirectory)) {
|
||||
// Building runtime package from the input runtime bundle.
|
||||
// Copy the input bundle verbatim.
|
||||
FileUtils.copyRecursive(
|
||||
inputRootDirectory,
|
||||
dstAppImage.resolvedAppImagelayout().rootDirectory(),
|
||||
LinkOption.NOFOLLOW_LINKS);
|
||||
} else {
|
||||
PackagingPipeline.copyAppImage(srcAppImage, dstAppImage, !predefinedAppImageSigned);
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyJliLib(
|
||||
AppImageBuildEnv<MacApplication, MacApplicationLayout> env) throws IOException {
|
||||
AppImageBuildEnv<MacApplication, AppImageLayout> env) throws IOException {
|
||||
|
||||
final var runtimeMacOSDir = env.resolvedLayout().runtimeRootDirectory().resolve("Contents/MacOS");
|
||||
final var runtimeBundle = runtimeBundle(env);
|
||||
|
||||
final var jliName = Path.of("libjli.dylib");
|
||||
|
||||
@ -223,8 +267,8 @@ final class MacPackagingPipeline {
|
||||
.filter(file -> file.getFileName().equals(jliName))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
Files.createDirectories(runtimeMacOSDir);
|
||||
Files.copy(jli, runtimeMacOSDir.resolve(jliName));
|
||||
Files.createDirectories(runtimeBundle.macOsDir());
|
||||
Files.copy(jli, runtimeBundle.macOsDir().resolve(jliName));
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,36 +291,47 @@ final class MacPackagingPipeline {
|
||||
"APPL????".getBytes(StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
|
||||
private static void writeRuntimeRuntimeInfoPlist(PackageBuildEnv<MacPackage, AppImageLayout> env) throws IOException {
|
||||
writeRuntimeInfoPlist(env.pkg().app(), env.env(), env.resolvedLayout().rootDirectory());
|
||||
}
|
||||
private static void writeRuntimeInfoPlist(
|
||||
AppImageBuildEnv<MacApplication, AppImageLayout> env) throws IOException {
|
||||
|
||||
private static void writeApplicationRuntimeInfoPlist(
|
||||
AppImageBuildEnv<MacApplication, MacApplicationLayout> env) throws IOException {
|
||||
writeRuntimeInfoPlist(env.app(), env.env(), env.resolvedLayout().runtimeRootDirectory());
|
||||
}
|
||||
|
||||
private static void writeRuntimeInfoPlist(MacApplication app, BuildEnv env, Path runtimeRootDirectory) throws IOException {
|
||||
final var app = env.app();
|
||||
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("CF_BUNDLE_IDENTIFIER", app.bundleIdentifier());
|
||||
data.put("CF_BUNDLE_NAME", app.bundleName());
|
||||
data.put("CF_BUNDLE_VERSION", app.version());
|
||||
data.put("CF_BUNDLE_SHORT_VERSION_STRING", app.shortVersion().toString());
|
||||
if (app.isRuntime()) {
|
||||
data.put("CF_BUNDLE_VENDOR", app.vendor());
|
||||
}
|
||||
|
||||
env.createResource("Runtime-Info.plist.template")
|
||||
.setPublicName("Runtime-Info.plist")
|
||||
.setCategory(I18N.getString("resource.runtime-info-plist"))
|
||||
final String template;
|
||||
final String publicName;
|
||||
final String category;
|
||||
|
||||
if (app.isRuntime()) {
|
||||
template = "Runtime-Info.plist.template";
|
||||
publicName = "Info.plist";
|
||||
category = "resource.runtime-info-plist";
|
||||
} else {
|
||||
template = "ApplicationRuntime-Info.plist.template";
|
||||
publicName = "Runtime-Info.plist";
|
||||
category = "resource.app-runtime-info-plist";
|
||||
}
|
||||
|
||||
env.env().createResource(template)
|
||||
.setPublicName(publicName)
|
||||
.setCategory(I18N.getString(category))
|
||||
.setSubstitutionData(data)
|
||||
.saveToFile(runtimeRootDirectory.resolve("Contents/Info.plist"));
|
||||
.saveToFile(runtimeBundle(env).infoPlistFile());
|
||||
}
|
||||
|
||||
private static void writeAppInfoPlist(
|
||||
private static void writeApplicationInfoPlist(
|
||||
AppImageBuildEnv<MacApplication, MacApplicationLayout> env) throws IOException {
|
||||
|
||||
final var app = env.app();
|
||||
|
||||
final var infoPlistFile = env.resolvedLayout().contentDirectory().resolve("Info.plist");
|
||||
final var infoPlistFile = MacBundle.fromAppImageLayout(env.resolvedLayout()).infoPlistFile();
|
||||
|
||||
Log.verbose(I18N.format("message.preparing-info-plist", PathUtils.normalizedAbsolutePathString(infoPlistFile)));
|
||||
|
||||
@ -308,7 +363,7 @@ final class MacPackagingPipeline {
|
||||
.saveToFile(infoPlistFile);
|
||||
}
|
||||
|
||||
private static void sign(AppImageBuildEnv<MacApplication, MacApplicationLayout> env) throws IOException {
|
||||
private static void sign(AppImageBuildEnv<MacApplication, AppImageLayout> env) throws IOException {
|
||||
|
||||
final var app = env.app();
|
||||
|
||||
@ -410,6 +465,14 @@ final class MacPackagingPipeline {
|
||||
}));
|
||||
}
|
||||
|
||||
private static MacBundle runtimeBundle(AppImageBuildEnv<MacApplication, AppImageLayout> env) {
|
||||
if (env.app().isRuntime()) {
|
||||
return new MacBundle(env.resolvedLayout().rootDirectory());
|
||||
} else {
|
||||
return new MacBundle(((MacApplicationLayout)env.resolvedLayout()).runtimeRootDirectory());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ApplicationIcon implements ApplicationImageTaskAction<MacApplication, MacApplicationLayout> {
|
||||
static Path getPath(Application app, ApplicationLayout appLayout) {
|
||||
return appLayout.desktopIntegrationDirectory().resolve(app.name() + ".icns");
|
||||
|
||||
@ -54,11 +54,13 @@ public interface MacApplication extends Application, MacApplicationMixin {
|
||||
|
||||
@Override
|
||||
default Path appImageDirName() {
|
||||
final String suffix;
|
||||
if (isRuntime()) {
|
||||
return Application.super.appImageDirName();
|
||||
suffix = ".jdk";
|
||||
} else {
|
||||
return Path.of(Application.super.appImageDirName().toString() + ".app");
|
||||
suffix = ".app";
|
||||
}
|
||||
return Path.of(Application.super.appImageDirName().toString() + suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>libjli.dylib</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>CF_BUNDLE_IDENTIFIER</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>7.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>CF_BUNDLE_NAME</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>CF_BUNDLE_SHORT_VERSION_STRING</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>CF_BUNDLE_VERSION</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -41,6 +41,7 @@ error.app-image.mac-sign.required=Error: --mac-sign option is required with pred
|
||||
error.tool.failed.with.output=Error: "{0}" failed with following output:
|
||||
resource.bundle-config-file=Bundle config file
|
||||
resource.app-info-plist=Application Info.plist
|
||||
resource.app-runtime-info-plist=Embedded Java Runtime Info.plist
|
||||
resource.runtime-info-plist=Java Runtime Info.plist
|
||||
resource.entitlements=Mac Entitlements
|
||||
resource.dmg-setup-script=DMG setup script
|
||||
|
||||
@ -20,5 +20,20 @@
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>CF_BUNDLE_VERSION</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>The application is requesting access to the microphone.</string>
|
||||
<key>JavaVM</key>
|
||||
<dict>
|
||||
<key>JVMCapabilities</key>
|
||||
<array>
|
||||
<string>CommandLine</string>
|
||||
</array>
|
||||
<key>JVMPlatformVersion</key>
|
||||
<string>CF_BUNDLE_VERSION</string>
|
||||
<key>JVMVendor</key>
|
||||
<string>CF_BUNDLE_VENDOR</string>
|
||||
<key>JVMVersion</key>
|
||||
<string>CF_BUNDLE_VERSION</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -41,12 +41,12 @@ import static jdk.jpackage.internal.StandardBundlerParam.LICENSE_FILE;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.LIMIT_MODULES;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.MODULE_PATH;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.NAME;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.SOURCE_DIR;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.VENDOR;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.VERSION;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.getPredefinedAppImage;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage;
|
||||
import static jdk.jpackage.internal.StandardBundlerParam.isRuntimeInstaller;
|
||||
|
||||
@ -143,7 +143,8 @@ final class FromParams {
|
||||
VERSION.copyInto(params, builder::version);
|
||||
ABOUT_URL.copyInto(params, builder::aboutURL);
|
||||
LICENSE_FILE.findIn(params).map(Path::of).ifPresent(builder::licenseFile);
|
||||
builder.predefinedAppImage(getPredefinedAppImage(params));
|
||||
PREDEFINED_APP_IMAGE.findIn(params).ifPresent(builder::predefinedAppImage);
|
||||
PREDEFINED_RUNTIME_IMAGE.findIn(params).ifPresent(builder::predefinedAppImage);
|
||||
INSTALL_DIR.findIn(params).map(Path::of).ifPresent(builder::installDir);
|
||||
|
||||
return builder;
|
||||
|
||||
@ -437,16 +437,8 @@ final class PackagingPipeline {
|
||||
srcAppImageDesc = new AppImageDesc(appImageLayoutForPackaging, env.appImageDir());
|
||||
dstAppImageDesc = srcAppImageDesc;
|
||||
} else {
|
||||
srcAppImageDesc = new AppImageDesc(pkg.app().imageLayout(), pkg.predefinedAppImage().orElseGet(() -> {
|
||||
// No predefined app image and no runtime builder.
|
||||
// This should be runtime packaging.
|
||||
if (pkg.isRuntimeInstaller()) {
|
||||
return env.appImageDir();
|
||||
} else {
|
||||
// Can't create app image without runtime builder.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}));
|
||||
srcAppImageDesc = new AppImageDesc(pkg.app().imageLayout(),
|
||||
pkg.predefinedAppImage().orElseThrow(UnsupportedOperationException::new));
|
||||
|
||||
if (taskConfig.get(CopyAppImageTaskID.COPY).action().isEmpty()) {
|
||||
// "copy app image" task action is undefined indicating
|
||||
|
||||
@ -148,8 +148,16 @@ public interface Package extends BundleSpec {
|
||||
Optional<Path> licenseFile();
|
||||
|
||||
/**
|
||||
* Gets the path to a directory with the application app image of this package
|
||||
* if available or an empty {@link Optional} instance otherwise.
|
||||
* Gets the path to a directory with the predefined app image of this package if
|
||||
* available or an empty {@link Optional} instance otherwise.
|
||||
* <p>
|
||||
* If {@link #isRuntimeInstaller()} returns {@code true}, the method returns the
|
||||
* path to a directory with the predefined runtime. The layout of this directory
|
||||
* should be of {@link RuntimeLayout} type.
|
||||
* <p>
|
||||
* If {@link #isRuntimeInstaller()} returns {@code false}, the method returns
|
||||
* the path to a directory with the predefined application image. The layout of
|
||||
* this directory should be of {@link ApplicationLayout} type.
|
||||
*
|
||||
* @return the path to a directory with the application app image of this
|
||||
* package
|
||||
|
||||
@ -1082,11 +1082,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
TKit.assertDirectoryExists(cmd.appRuntimeDirectory());
|
||||
if (TKit.isOSX()) {
|
||||
var libjliPath = cmd.appRuntimeDirectory().resolve("Contents/MacOS/libjli.dylib");
|
||||
if (cmd.isRuntime()) {
|
||||
TKit.assertPathExists(libjliPath, false);
|
||||
} else {
|
||||
TKit.assertFileExists(libjliPath);
|
||||
}
|
||||
TKit.assertFileExists(libjliPath);
|
||||
}
|
||||
}),
|
||||
MAC_BUNDLE_STRUCTURE(cmd -> {
|
||||
|
||||
@ -334,7 +334,7 @@ public final class MacHelper {
|
||||
installLocation = cmd.getArgumentValue("--install-dir", () -> defaultInstallLocation, Path::of);
|
||||
}
|
||||
|
||||
return installLocation.resolve(cmd.name() + (cmd.isRuntime() ? "" : ".app"));
|
||||
return installLocation.resolve(cmd.name() + (cmd.isRuntime() ? ".jdk" : ".app"));
|
||||
}
|
||||
|
||||
static Path getUninstallCommand(JPackageCommand cmd) {
|
||||
@ -400,22 +400,27 @@ public final class MacHelper {
|
||||
Executor.of("/usr/bin/xcrun", "--help").executeWithoutExitCodeCheck().getExitCode() == 0;
|
||||
}
|
||||
|
||||
private static Set<Path> createBundleContents(String... customItems) {
|
||||
return Stream.concat(Stream.of(customItems), Stream.of(
|
||||
"MacOS",
|
||||
"Info.plist",
|
||||
"_CodeSignature"
|
||||
)).map(Path::of).collect(toSet());
|
||||
}
|
||||
|
||||
static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
|
||||
"Contents/Home/lib/server/libjvm.dylib"));
|
||||
|
||||
private static final Method getServicePListFileName = initGetServicePListFileName();
|
||||
|
||||
private static final Set<Path> APP_BUNDLE_CONTENTS = Stream.of(
|
||||
"Info.plist",
|
||||
"MacOS",
|
||||
private static final Set<Path> APP_BUNDLE_CONTENTS = createBundleContents(
|
||||
"app",
|
||||
"runtime",
|
||||
"Resources",
|
||||
"PkgInfo",
|
||||
"_CodeSignature"
|
||||
).map(Path::of).collect(toSet());
|
||||
"PkgInfo"
|
||||
);
|
||||
|
||||
private static final Set<Path> RUNTIME_BUNDLE_CONTENTS = Stream.of(
|
||||
private static final Set<Path> RUNTIME_BUNDLE_CONTENTS = createBundleContents(
|
||||
"Home"
|
||||
).map(Path::of).collect(toSet());
|
||||
);
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ import jdk.jpackage.test.Annotations.Parameter;
|
||||
* jpackagerTest keychain with always allowed access to this keychain for user
|
||||
* which runs test.
|
||||
* note:
|
||||
* "jpackage.openjdk.java.net" can be over-ridden by systerm property
|
||||
* "jpackage.openjdk.java.net" can be over-ridden by system property
|
||||
* "jpackage.mac.signing.key.user.name", and
|
||||
* "jpackagerTest" can be over-ridden by system property
|
||||
* "jpackage.mac.signing.keychain"
|
||||
|
||||
@ -39,7 +39,7 @@ import jdk.jpackage.test.Annotations.Parameter;
|
||||
* jpackagerTest keychain with
|
||||
* always allowed access to this keychain for user which runs test.
|
||||
* note:
|
||||
* "jpackage.openjdk.java.net" can be over-ridden by systerm property
|
||||
* "jpackage.openjdk.java.net" can be over-ridden by system property
|
||||
* "jpackage.mac.signing.key.user.name", and
|
||||
* "jpackagerTest" can be over-ridden by system property
|
||||
* "jpackage.mac.signing.keychain"
|
||||
|
||||
@ -42,7 +42,7 @@ import jdk.jpackage.test.Annotations.Parameter;
|
||||
* jpackagerTest keychain with
|
||||
* always allowed access to this keychain for user which runs test.
|
||||
* note:
|
||||
* "jpackage.openjdk.java.net" can be over-ridden by systerm property
|
||||
* "jpackage.openjdk.java.net" can be over-ridden by system property
|
||||
* "jpackage.mac.signing.key.user.name", and
|
||||
* "jpackagerTest" can be over-ridden by system property
|
||||
* "jpackage.mac.signing.keychain"
|
||||
|
||||
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* 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.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.Executor;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.JavaTool;
|
||||
import jdk.jpackage.test.MacHelper;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.TKit;
|
||||
|
||||
/**
|
||||
* Tests generation of dmg and pkg with --mac-sign and related arguments.
|
||||
* Test will generate pkg and verifies its signature. It verifies that dmg
|
||||
* is not signed, but runtime image inside dmg is signed.
|
||||
*
|
||||
* Note: Specific UNICODE signing is not tested, since it is shared code
|
||||
* with app image signing and it will be covered by SigningPackageTest.
|
||||
*
|
||||
* Following combinations are tested:
|
||||
* 1) "--runtime-image" points to unsigned JDK bundle and --mac-sign is not
|
||||
* provided. Expected result: runtime image ad-hoc signed.
|
||||
* 2) "--runtime-image" points to unsigned JDK bundle and --mac-sign is
|
||||
* provided. Expected result: Everything is signed with provided certificate.
|
||||
* 3) "--runtime-image" points to signed JDK bundle and --mac-sign is not
|
||||
* provided. Expected result: runtime image is signed with original certificate.
|
||||
* 4) "--runtime-image" points to signed JDK bundle and --mac-sign is provided.
|
||||
* Expected result: runtime image is signed with provided certificate.
|
||||
* 5) "--runtime-image" points to JDK image and --mac-sign is not provided.
|
||||
* Expected result: runtime image ad-hoc signed.
|
||||
* 6) "--runtime-image" points to JDK image and --mac-sign is provided.
|
||||
* Expected result: Everything is signed with provided certificate.
|
||||
*
|
||||
* This test requires that the machine is configured with test certificate for
|
||||
* "Developer ID Installer: jpackage.openjdk.java.net" in
|
||||
* jpackagerTest keychain with
|
||||
* always allowed access to this keychain for user which runs test.
|
||||
* note:
|
||||
* "jpackage.openjdk.java.net" can be over-ridden by system property
|
||||
* "jpackage.mac.signing.key.user.name", and
|
||||
* "jpackagerTest" can be over-ridden by system property
|
||||
* "jpackage.mac.signing.keychain"
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary jpackage with --type pkg,dmg --runtime-image --mac-sign
|
||||
* @library /test/jdk/tools/jpackage/helpers
|
||||
* @library base
|
||||
* @key jpackagePlatformPackage
|
||||
* @build SigningBase
|
||||
* @build jdk.jpackage.test.*
|
||||
* @build SigningRuntimeImagePackageTest
|
||||
* @requires (jpackage.test.MacSignTests == "run")
|
||||
* @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
|
||||
* --jpt-run=SigningRuntimeImagePackageTest
|
||||
* --jpt-before-run=SigningBase.verifySignTestEnvReady
|
||||
*/
|
||||
public class SigningRuntimeImagePackageTest {
|
||||
|
||||
private static JPackageCommand addSignOptions(JPackageCommand cmd, int certIndex) {
|
||||
if (certIndex != SigningBase.CertIndex.INVALID_INDEX.value()) {
|
||||
cmd.addArguments(
|
||||
"--mac-sign",
|
||||
"--mac-signing-keychain", SigningBase.getKeyChain(),
|
||||
"--mac-signing-key-user-name", SigningBase.getDevName(certIndex));
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Path createInputRuntimeImage() throws IOException {
|
||||
|
||||
final Path runtimeImageDir;
|
||||
|
||||
if (JPackageCommand.DEFAULT_RUNTIME_IMAGE != null) {
|
||||
runtimeImageDir = JPackageCommand.DEFAULT_RUNTIME_IMAGE;
|
||||
} else {
|
||||
runtimeImageDir = TKit.createTempDirectory("runtime-image").resolve("data");
|
||||
|
||||
new Executor().setToolProvider(JavaTool.JLINK)
|
||||
.dumpOutput()
|
||||
.addArguments(
|
||||
"--output", runtimeImageDir.toString(),
|
||||
"--add-modules", "java.desktop",
|
||||
"--strip-debug",
|
||||
"--no-header-files",
|
||||
"--no-man-pages")
|
||||
.execute();
|
||||
}
|
||||
|
||||
return runtimeImageDir;
|
||||
}
|
||||
|
||||
private static Path createInputRuntimeBundle(int certIndex) throws IOException {
|
||||
|
||||
final var runtimeImage = createInputRuntimeImage();
|
||||
|
||||
final var runtimeBundleWorkDir = TKit.createTempDirectory("runtime-bundle");
|
||||
|
||||
final var unpackadeRuntimeBundleDir = runtimeBundleWorkDir.resolve("unpacked");
|
||||
|
||||
var cmd = new JPackageCommand()
|
||||
.useToolProvider(true)
|
||||
.ignoreDefaultRuntime(true)
|
||||
.dumpOutput(true)
|
||||
.setPackageType(PackageType.MAC_DMG)
|
||||
.setArgumentValue("--name", "foo")
|
||||
.addArguments("--runtime-image", runtimeImage)
|
||||
.addArguments("--dest", runtimeBundleWorkDir);
|
||||
|
||||
addSignOptions(cmd, certIndex);
|
||||
|
||||
cmd.execute();
|
||||
|
||||
MacHelper.withExplodedDmg(cmd, dmgImage -> {
|
||||
if (dmgImage.endsWith(cmd.appInstallationDirectory().getFileName())) {
|
||||
Executor.of("cp", "-R")
|
||||
.addArgument(dmgImage)
|
||||
.addArgument(unpackadeRuntimeBundleDir)
|
||||
.execute(0);
|
||||
}
|
||||
});
|
||||
|
||||
return unpackadeRuntimeBundleDir;
|
||||
}
|
||||
|
||||
@Test
|
||||
// useJDKBundle - If "true" predefined runtime image will be converted to
|
||||
// JDK bundle. If "false" JDK image will be used.
|
||||
// JDKBundleCert - Certificate to sign JDK bundle before calling jpackage.
|
||||
// signCert - Certificate to sign bundle produced by jpackage.
|
||||
// 1) unsigned JDK bundle and --mac-sign is not provided
|
||||
@Parameter({"true", "INVALID_INDEX", "INVALID_INDEX"})
|
||||
// 2) unsigned JDK bundle and --mac-sign is provided
|
||||
@Parameter({"true", "INVALID_INDEX", "ASCII_INDEX"})
|
||||
// 3) signed JDK bundle and --mac-sign is not provided
|
||||
@Parameter({"true", "UNICODE_INDEX", "INVALID_INDEX"})
|
||||
// 4) signed JDK bundle and --mac-sign is provided
|
||||
@Parameter({"true", "UNICODE_INDEX", "ASCII_INDEX"})
|
||||
// 5) JDK image and --mac-sign is not provided
|
||||
@Parameter({"false", "INVALID_INDEX", "INVALID_INDEX"})
|
||||
// 6) JDK image and --mac-sign is provided
|
||||
@Parameter({"false", "INVALID_INDEX", "ASCII_INDEX"})
|
||||
public static void test(boolean useJDKBundle,
|
||||
SigningBase.CertIndex jdkBundleCert,
|
||||
SigningBase.CertIndex signCert) throws Exception {
|
||||
|
||||
final Path inputRuntime[] = new Path[1];
|
||||
|
||||
new PackageTest()
|
||||
.addRunOnceInitializer(() -> {
|
||||
if (useJDKBundle) {
|
||||
inputRuntime[0] = createInputRuntimeBundle(jdkBundleCert.value());
|
||||
} else {
|
||||
inputRuntime[0] = createInputRuntimeImage();
|
||||
}
|
||||
})
|
||||
.addInitializer(cmd -> {
|
||||
cmd.addArguments("--runtime-image", inputRuntime[0]);
|
||||
// Remove --input parameter from jpackage command line as we don't
|
||||
// create input directory in the test and jpackage fails
|
||||
// if --input references non existent directory.
|
||||
cmd.removeArgumentWithValue("--input");
|
||||
addSignOptions(cmd, signCert.value());
|
||||
})
|
||||
.addInstallVerifier(cmd -> {
|
||||
final var certIndex = Stream.of(signCert, jdkBundleCert)
|
||||
.filter(Predicate.isEqual(SigningBase.CertIndex.INVALID_INDEX).negate())
|
||||
.findFirst().orElse(SigningBase.CertIndex.INVALID_INDEX).value();
|
||||
|
||||
final var signed = certIndex != SigningBase.CertIndex.INVALID_INDEX.value();
|
||||
|
||||
final var unfoldedBundleDir = cmd.appRuntimeDirectory();
|
||||
|
||||
final var libjli = unfoldedBundleDir.resolve("Contents/MacOS/libjli.dylib");
|
||||
|
||||
SigningBase.verifyCodesign(libjli, signed, certIndex);
|
||||
SigningBase.verifyCodesign(unfoldedBundleDir, signed, certIndex);
|
||||
if (signed) {
|
||||
SigningBase.verifySpctl(unfoldedBundleDir, "exec", certIndex);
|
||||
}
|
||||
})
|
||||
.run();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user