Skip to content

Commit 03230f8

Browse files
author
Alexander Matveev
committed
8351073: [macos] jpackage produces invalid Java runtime DMG bundles
Reviewed-by: asemenyuk
1 parent a3843e8 commit 03230f8

18 files changed

+480
-67
lines changed

src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/CodesignConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ record CodesignConfig(Optional<SigningIdentity> identity, Optional<String> ident
4444
Objects.requireNonNull(keychain);
4545

4646
if (identity.isPresent() != identifierPrefix.isPresent()) {
47-
throw new IllegalArgumentException("Signing identity and identifier prefix mismatch");
47+
throw new IllegalArgumentException(
48+
"Signing identity (" + identity + ") and identifier prefix (" +
49+
identifierPrefix + ") mismatch");
4850
}
4951

5052
identifierPrefix.ifPresent(v -> {

src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private static void doValidate(Map<String, ? super Object> params)
6969
}
7070
}
7171

72-
if (StandardBundlerParam.getPredefinedAppImage(params) != null) {
72+
if (StandardBundlerParam.hasPredefinedAppImage(params)) {
7373
if (!Optional.ofNullable(
7474
SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
7575
throw new ConfigException(
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package jdk.jpackage.internal;
27+
28+
import java.nio.file.Files;
29+
import java.nio.file.Path;
30+
import java.util.Objects;
31+
import jdk.jpackage.internal.model.AppImageLayout;
32+
33+
/**
34+
* An abstraction of macOS Application bundle.
35+
*
36+
* @see <a href="https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles">https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles</a>
37+
*/
38+
record MacBundle(Path root) {
39+
40+
MacBundle {
41+
Objects.requireNonNull(root);
42+
}
43+
44+
boolean isValid() {
45+
return Files.isDirectory(contentsDir()) && Files.isDirectory(macOsDir()) && Files.isRegularFile(infoPlistFile());
46+
}
47+
48+
boolean isSigned() {
49+
return Files.isDirectory(contentsDir().resolve("_CodeSignature"));
50+
}
51+
52+
Path contentsDir() {
53+
return root.resolve("Contents");
54+
}
55+
56+
Path homeDir() {
57+
return contentsDir().resolve("Home");
58+
}
59+
60+
Path macOsDir() {
61+
return contentsDir().resolve("MacOS");
62+
}
63+
64+
Path resourcesDir() {
65+
return contentsDir().resolve("Resources");
66+
}
67+
68+
Path infoPlistFile() {
69+
return contentsDir().resolve("Info.plist");
70+
}
71+
72+
static boolean isDirectoryMacBundle(Path dir) {
73+
return new MacBundle(dir).isValid();
74+
}
75+
76+
static MacBundle fromAppImageLayout(AppImageLayout layout) {
77+
return new MacBundle(layout.rootDirectory());
78+
}
79+
}

src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,15 @@ private static MacApplication createMacApplication(
147147
signingBuilder.entitlementsResourceName("sandbox.plist");
148148
}
149149

150-
app.mainLauncher().flatMap(Launcher::startupInfo).ifPresent(signingBuilder::signingIdentifierPrefix);
150+
final var bundleIdentifier = appBuilder.create().bundleIdentifier();
151+
app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse(
152+
signingBuilder::signingIdentifierPrefix,
153+
() -> {
154+
// Runtime installer does not have main launcher, so use
155+
// 'bundleIdentifier' as prefix by default.
156+
signingBuilder.signingIdentifierPrefix(
157+
bundleIdentifier + ".");
158+
});
151159
SIGN_IDENTIFIER_PREFIX.copyInto(params, signingBuilder::signingIdentifierPrefix);
152160

153161
ENTITLEMENTS.copyInto(params, signingBuilder::entitlements);
@@ -168,6 +176,12 @@ private static MacPackageBuilder createMacPackageBuilder(
168176
.map(MacAppImageFileExtras::signed)
169177
.ifPresent(builder::predefinedAppImageSigned);
170178

179+
PREDEFINED_RUNTIME_IMAGE.findIn(params)
180+
.map(MacBundle::new)
181+
.filter(MacBundle::isValid)
182+
.map(MacBundle::isSigned)
183+
.ifPresent(builder::predefinedAppImageSigned);
184+
171185
return builder;
172186
}
173187

src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java

Lines changed: 94 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import java.io.StringWriter;
4242
import java.nio.charset.StandardCharsets;
4343
import java.nio.file.Files;
44+
import java.nio.file.LinkOption;
4445
import java.nio.file.Path;
4546
import java.util.ArrayList;
4647
import java.util.HashMap;
@@ -72,6 +73,7 @@
7273
import jdk.jpackage.internal.model.Package;
7374
import jdk.jpackage.internal.model.PackageType;
7475
import jdk.jpackage.internal.model.PackagerException;
76+
import jdk.jpackage.internal.util.FileUtils;
7577
import jdk.jpackage.internal.util.PathUtils;
7678
import jdk.jpackage.internal.util.function.ThrowingConsumer;
7779

@@ -91,6 +93,7 @@ enum MacBuildApplicationTaskID implements TaskID {
9193
enum MacCopyAppImageTaskID implements TaskID {
9294
COPY_PACKAGE_FILE,
9395
COPY_RUNTIME_INFO_PLIST,
96+
COPY_RUNTIME_JLILIB,
9497
REPLACE_APP_IMAGE_FILE,
9598
COPY_SIGN
9699
}
@@ -115,10 +118,10 @@ static PackagingPipeline.Builder build(Optional<Package> pkg) {
115118
.task(CopyAppImageTaskID.COPY)
116119
.copyAction(MacPackagingPipeline::copyAppImage).add()
117120
.task(MacBuildApplicationTaskID.RUNTIME_INFO_PLIST)
118-
.applicationAction(MacPackagingPipeline::writeApplicationRuntimeInfoPlist)
121+
.appImageAction(MacPackagingPipeline::writeRuntimeInfoPlist)
119122
.addDependent(BuildApplicationTaskID.CONTENT).add()
120123
.task(MacBuildApplicationTaskID.COPY_JLILIB)
121-
.applicationAction(MacPackagingPipeline::copyJliLib)
124+
.appImageAction(MacPackagingPipeline::copyJliLib)
122125
.addDependency(BuildApplicationTaskID.RUNTIME)
123126
.addDependent(BuildApplicationTaskID.CONTENT).add()
124127
.task(MacBuildApplicationTaskID.APP_ICON)
@@ -138,13 +141,18 @@ static PackagingPipeline.Builder build(Optional<Package> pkg) {
138141
.addDependencies(CopyAppImageTaskID.COPY)
139142
.addDependents(PrimaryTaskID.COPY_APP_IMAGE).add()
140143
.task(MacCopyAppImageTaskID.COPY_RUNTIME_INFO_PLIST)
144+
.appImageAction(MacPackagingPipeline::writeRuntimeInfoPlist)
145+
.addDependencies(CopyAppImageTaskID.COPY)
146+
.addDependents(PrimaryTaskID.COPY_APP_IMAGE).add()
147+
.task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB)
148+
.noaction()
141149
.addDependencies(CopyAppImageTaskID.COPY)
142150
.addDependents(PrimaryTaskID.COPY_APP_IMAGE).add()
143151
.task(MacBuildApplicationTaskID.FA_ICONS)
144152
.applicationAction(MacPackagingPipeline::writeFileAssociationIcons)
145153
.addDependent(BuildApplicationTaskID.CONTENT).add()
146154
.task(MacBuildApplicationTaskID.APP_INFO_PLIST)
147-
.applicationAction(MacPackagingPipeline::writeAppInfoPlist)
155+
.applicationAction(MacPackagingPipeline::writeApplicationInfoPlist)
148156
.addDependent(BuildApplicationTaskID.CONTENT).add();
149157

150158
builder.task(MacBuildApplicationTaskID.SIGN)
@@ -172,16 +180,38 @@ static PackagingPipeline.Builder build(Optional<Package> pkg) {
172180
disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE);
173181
disabledTasks.add(CopyAppImageTaskID.COPY);
174182
disabledTasks.add(PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT);
175-
builder.task(MacCopyAppImageTaskID.REPLACE_APP_IMAGE_FILE).applicationAction(createWriteAppImageFileAction()).add();
183+
builder.task(MacCopyAppImageTaskID.REPLACE_APP_IMAGE_FILE)
184+
.applicationAction(createWriteAppImageFileAction()).add();
176185
builder.appImageLayoutForPackaging(Package::appImageLayout);
177-
} else if (p.isRuntimeInstaller() || ((MacPackage)p).predefinedAppImageSigned().orElse(false)) {
178-
// If this is a runtime package or a signed predefined app image,
179-
// don't create ".package" file and don't sign it.
186+
} else if (p.isRuntimeInstaller()) {
187+
188+
builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB)
189+
.appImageAction(MacPackagingPipeline::copyJliLib).add();
190+
191+
final var predefinedRuntimeBundle = Optional.of(
192+
new MacBundle(p.predefinedAppImage().orElseThrow())).filter(MacBundle::isValid);
193+
194+
// Don't create ".package" file.
195+
disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE);
196+
197+
if (predefinedRuntimeBundle.isPresent()) {
198+
// The predefined app image is a macOS bundle.
199+
// Disable all alterations of the input bundle, but keep the signing enabled.
200+
disabledTasks.addAll(List.of(MacCopyAppImageTaskID.values()));
201+
disabledTasks.remove(MacCopyAppImageTaskID.COPY_SIGN);
202+
}
203+
204+
if (predefinedRuntimeBundle.map(MacBundle::isSigned).orElse(false) && !((MacPackage)p).app().sign()) {
205+
// The predefined app image is a signed bundle; explicit signing is not requested for the package.
206+
// Disable the signing, i.e. don't re-sign the input bundle.
207+
disabledTasks.add(MacCopyAppImageTaskID.COPY_SIGN);
208+
}
209+
} else if (((MacPackage)p).predefinedAppImageSigned().orElse(false)) {
210+
// This is a signed predefined app image.
211+
// Don't create ".package" file.
180212
disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE);
213+
// Don't sign the image.
181214
disabledTasks.add(MacCopyAppImageTaskID.COPY_SIGN);
182-
// if (p.isRuntimeInstaller()) {
183-
// builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_INFO_PLIST).packageAction(MacPackagingPipeline::writeRuntimeRuntimeInfoPlist).add();
184-
// }
185215
}
186216

187217
for (final var taskId : disabledTasks) {
@@ -208,13 +238,27 @@ static Package createSignAppImagePackage(MacApplication app, BuildEnv env) {
208238

209239
private static void copyAppImage(MacPackage pkg, AppImageDesc srcAppImage,
210240
AppImageDesc dstAppImage) throws IOException {
211-
PackagingPipeline.copyAppImage(srcAppImage, dstAppImage, !pkg.predefinedAppImageSigned().orElse(false));
241+
242+
boolean predefinedAppImageSigned = pkg.predefinedAppImageSigned().orElse(false);
243+
244+
var inputRootDirectory = srcAppImage.resolvedAppImagelayout().rootDirectory();
245+
246+
if (pkg.isRuntimeInstaller() && MacBundle.isDirectoryMacBundle(inputRootDirectory)) {
247+
// Building runtime package from the input runtime bundle.
248+
// Copy the input bundle verbatim.
249+
FileUtils.copyRecursive(
250+
inputRootDirectory,
251+
dstAppImage.resolvedAppImagelayout().rootDirectory(),
252+
LinkOption.NOFOLLOW_LINKS);
253+
} else {
254+
PackagingPipeline.copyAppImage(srcAppImage, dstAppImage, !predefinedAppImageSigned);
255+
}
212256
}
213257

214258
private static void copyJliLib(
215-
AppImageBuildEnv<MacApplication, MacApplicationLayout> env) throws IOException {
259+
AppImageBuildEnv<MacApplication, AppImageLayout> env) throws IOException {
216260

217-
final var runtimeMacOSDir = env.resolvedLayout().runtimeRootDirectory().resolve("Contents/MacOS");
261+
final var runtimeBundle = runtimeBundle(env);
218262

219263
final var jliName = Path.of("libjli.dylib");
220264

@@ -223,8 +267,8 @@ private static void copyJliLib(
223267
.filter(file -> file.getFileName().equals(jliName))
224268
.findFirst()
225269
.orElseThrow();
226-
Files.createDirectories(runtimeMacOSDir);
227-
Files.copy(jli, runtimeMacOSDir.resolve(jliName));
270+
Files.createDirectories(runtimeBundle.macOsDir());
271+
Files.copy(jli, runtimeBundle.macOsDir().resolve(jliName));
228272
}
229273
}
230274

@@ -247,36 +291,47 @@ private static void writePkgInfoFile(
247291
"APPL????".getBytes(StandardCharsets.ISO_8859_1));
248292
}
249293

250-
private static void writeRuntimeRuntimeInfoPlist(PackageBuildEnv<MacPackage, AppImageLayout> env) throws IOException {
251-
writeRuntimeInfoPlist(env.pkg().app(), env.env(), env.resolvedLayout().rootDirectory());
252-
}
253-
254-
private static void writeApplicationRuntimeInfoPlist(
255-
AppImageBuildEnv<MacApplication, MacApplicationLayout> env) throws IOException {
256-
writeRuntimeInfoPlist(env.app(), env.env(), env.resolvedLayout().runtimeRootDirectory());
257-
}
294+
private static void writeRuntimeInfoPlist(
295+
AppImageBuildEnv<MacApplication, AppImageLayout> env) throws IOException {
258296

259-
private static void writeRuntimeInfoPlist(MacApplication app, BuildEnv env, Path runtimeRootDirectory) throws IOException {
297+
final var app = env.app();
260298

261299
Map<String, String> data = new HashMap<>();
262300
data.put("CF_BUNDLE_IDENTIFIER", app.bundleIdentifier());
263301
data.put("CF_BUNDLE_NAME", app.bundleName());
264302
data.put("CF_BUNDLE_VERSION", app.version());
265303
data.put("CF_BUNDLE_SHORT_VERSION_STRING", app.shortVersion().toString());
304+
if (app.isRuntime()) {
305+
data.put("CF_BUNDLE_VENDOR", app.vendor());
306+
}
307+
308+
final String template;
309+
final String publicName;
310+
final String category;
311+
312+
if (app.isRuntime()) {
313+
template = "Runtime-Info.plist.template";
314+
publicName = "Info.plist";
315+
category = "resource.runtime-info-plist";
316+
} else {
317+
template = "ApplicationRuntime-Info.plist.template";
318+
publicName = "Runtime-Info.plist";
319+
category = "resource.app-runtime-info-plist";
320+
}
266321

267-
env.createResource("Runtime-Info.plist.template")
268-
.setPublicName("Runtime-Info.plist")
269-
.setCategory(I18N.getString("resource.runtime-info-plist"))
322+
env.env().createResource(template)
323+
.setPublicName(publicName)
324+
.setCategory(I18N.getString(category))
270325
.setSubstitutionData(data)
271-
.saveToFile(runtimeRootDirectory.resolve("Contents/Info.plist"));
326+
.saveToFile(runtimeBundle(env).infoPlistFile());
272327
}
273328

274-
private static void writeAppInfoPlist(
329+
private static void writeApplicationInfoPlist(
275330
AppImageBuildEnv<MacApplication, MacApplicationLayout> env) throws IOException {
276331

277332
final var app = env.app();
278333

279-
final var infoPlistFile = env.resolvedLayout().contentDirectory().resolve("Info.plist");
334+
final var infoPlistFile = MacBundle.fromAppImageLayout(env.resolvedLayout()).infoPlistFile();
280335

281336
Log.verbose(I18N.format("message.preparing-info-plist", PathUtils.normalizedAbsolutePathString(infoPlistFile)));
282337

@@ -308,7 +363,7 @@ private static void writeAppInfoPlist(
308363
.saveToFile(infoPlistFile);
309364
}
310365

311-
private static void sign(AppImageBuildEnv<MacApplication, MacApplicationLayout> env) throws IOException {
366+
private static void sign(AppImageBuildEnv<MacApplication, AppImageLayout> env) throws IOException {
312367

313368
final var app = env.app();
314369

@@ -410,6 +465,14 @@ private static void addFaToUTExportedTypeDeclarations(XMLStreamWriter xml,
410465
}));
411466
}
412467

468+
private static MacBundle runtimeBundle(AppImageBuildEnv<MacApplication, AppImageLayout> env) {
469+
if (env.app().isRuntime()) {
470+
return new MacBundle(env.resolvedLayout().rootDirectory());
471+
} else {
472+
return new MacBundle(((MacApplicationLayout)env.resolvedLayout()).runtimeRootDirectory());
473+
}
474+
}
475+
413476
private static class ApplicationIcon implements ApplicationImageTaskAction<MacApplication, MacApplicationLayout> {
414477
static Path getPath(Application app, ApplicationLayout appLayout) {
415478
return appLayout.desktopIntegrationDirectory().resolve(app.name() + ".icns");

src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@ default DottedVersion shortVersion() {
5454

5555
@Override
5656
default Path appImageDirName() {
57+
final String suffix;
5758
if (isRuntime()) {
58-
return Application.super.appImageDirName();
59+
suffix = ".jdk";
5960
} else {
60-
return Path.of(Application.super.appImageDirName().toString() + ".app");
61+
suffix = ".app";
6162
}
63+
return Path.of(Application.super.appImageDirName().toString() + suffix);
6264
}
6365

6466
/**

0 commit comments

Comments
 (0)