Skip to content

Commit 6a061f9

Browse files
author
Alexey Semenyuk
committed
8379426: [macos] jpackage: runtime bundle version suffix is out of sync with the version property in the Info.plist file
Reviewed-by: almatvee
1 parent 6a8e953 commit 6a061f9

14 files changed

Lines changed: 582 additions & 211 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright (c) 2025, 2026, 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.io.Closeable;
29+
import java.io.IOException;
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
import java.util.Objects;
33+
import java.util.Optional;
34+
import java.util.function.Consumer;
35+
import java.util.stream.Collectors;
36+
import java.util.stream.Stream;
37+
import jdk.internal.util.OSVersion;
38+
39+
final class ActiveKeychainList implements Closeable {
40+
41+
static Optional<ActiveKeychainList> createForPlatform(List<Keychain> keychains) throws IOException {
42+
if (!keychains.isEmpty() && Globals.instance().findBooleanProperty(ActiveKeychainList.class).orElseGet(ActiveKeychainList::isRequired)) {
43+
return Optional.of(new ActiveKeychainList(keychains));
44+
} else {
45+
return Optional.empty();
46+
}
47+
}
48+
49+
static Optional<ActiveKeychainList> createForPlatform(Keychain... keychains) throws IOException {
50+
return createForPlatform(List.of(keychains));
51+
}
52+
53+
@SuppressWarnings("try")
54+
static void withKeychains(Consumer<List<Keychain>> keychainConsumer, List<Keychain> keychains) throws IOException {
55+
var keychainList = createForPlatform(keychains);
56+
if (keychainList.isEmpty()) {
57+
keychainConsumer.accept(keychains);
58+
} else {
59+
try (var kl = keychainList.get()) {
60+
keychainConsumer.accept(keychains);
61+
}
62+
}
63+
}
64+
65+
static void withKeychain(Consumer<Keychain> keychainConsumer, Keychain keychain) throws IOException {
66+
67+
Objects.requireNonNull(keychainConsumer);
68+
withKeychains(keychains -> {
69+
keychainConsumer.accept(keychains.getFirst());
70+
}, List.of(keychain));
71+
}
72+
73+
ActiveKeychainList(List<Keychain> requestedKeychains, List<Keychain> currentKeychains, boolean force) throws IOException {
74+
this.requestedKeychains = List.copyOf(requestedKeychains);
75+
this.oldKeychains = List.copyOf(currentKeychains);
76+
77+
final List<String> cmdline = new ArrayList<>(LIST_KEYCHAINS_CMD_PREFIX);
78+
addKeychains(cmdline, oldKeychains);
79+
80+
if (force) {
81+
this.currentKeychains = requestedKeychains;
82+
restoreKeychainsCmd = List.copyOf(cmdline);
83+
cmdline.subList(LIST_KEYCHAINS_CMD_PREFIX.size(), cmdline.size()).clear();
84+
addKeychains(cmdline, requestedKeychains);
85+
} else {
86+
final var currentKeychainPaths = oldKeychains.stream().map(Keychain::path).toList();
87+
88+
final var missingKeychains = requestedKeychains.stream().filter(k -> {
89+
return !currentKeychainPaths.contains(k.path());
90+
}).toList();
91+
92+
if (missingKeychains.isEmpty()) {
93+
this.currentKeychains = oldKeychains;
94+
restoreKeychainsCmd = List.of();
95+
} else {
96+
this.currentKeychains = Stream.of(oldKeychains, missingKeychains)
97+
.flatMap(List::stream).collect(Collectors.toUnmodifiableList());
98+
restoreKeychainsCmd = List.copyOf(cmdline);
99+
addKeychains(cmdline, missingKeychains);
100+
}
101+
}
102+
103+
Executor.of(cmdline).executeExpectSuccess();
104+
}
105+
106+
ActiveKeychainList(List<Keychain> keychains) throws IOException {
107+
this(keychains, Keychain.listKeychains(), false);
108+
}
109+
110+
List<Keychain> requestedKeychains() {
111+
return requestedKeychains;
112+
}
113+
114+
List<Keychain> currentKeychains() {
115+
return currentKeychains;
116+
}
117+
118+
List<Keychain> restoreKeychains() {
119+
return oldKeychains;
120+
}
121+
122+
@Override
123+
public void close() throws IOException {
124+
if (!restoreKeychainsCmd.isEmpty()) {
125+
Executor.of(restoreKeychainsCmd).executeExpectSuccess();
126+
}
127+
}
128+
129+
private static void addKeychains(List<String> cmdline, List<Keychain> keychains) {
130+
cmdline.addAll(keychains.stream().map(Keychain::asCliArg).toList());
131+
}
132+
133+
private static boolean isRequired() {
134+
// Required for OS X 10.12+
135+
return 0 <= OSVersion.current().compareTo(new OSVersion(10, 12));
136+
}
137+
138+
private final List<Keychain> requestedKeychains;
139+
private final List<Keychain> currentKeychains;
140+
private final List<Keychain> oldKeychains;
141+
private final List<String> restoreKeychainsCmd;
142+
143+
private final static List<String> LIST_KEYCHAINS_CMD_PREFIX = List.of("/usr/bin/security", "list-keychains", "-s");
144+
}

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

Lines changed: 0 additions & 65 deletions
This file was deleted.

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

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,24 @@
3636
import jdk.jpackage.internal.model.AppImageLayout;
3737
import jdk.jpackage.internal.model.AppImageSigningConfig;
3838
import jdk.jpackage.internal.model.Application;
39+
import jdk.jpackage.internal.model.ApplicationLaunchers;
40+
import jdk.jpackage.internal.model.ExternalApplication;
41+
import jdk.jpackage.internal.model.JPackageException;
3942
import jdk.jpackage.internal.model.Launcher;
4043
import jdk.jpackage.internal.model.MacApplication;
4144
import jdk.jpackage.internal.model.MacApplicationMixin;
42-
import jdk.jpackage.internal.model.JPackageException;
45+
import jdk.jpackage.internal.util.PListReader;
46+
import jdk.jpackage.internal.util.Result;
4347
import jdk.jpackage.internal.util.RootedPath;
4448

4549
final class MacApplicationBuilder {
4650

47-
MacApplicationBuilder(Application app) {
48-
this.app = Objects.requireNonNull(app);
51+
MacApplicationBuilder(ApplicationBuilder appBuilder) {
52+
this.superBuilder = Objects.requireNonNull(appBuilder);
4953
}
5054

5155
private MacApplicationBuilder(MacApplicationBuilder other) {
52-
this(other.app);
56+
this(other.superBuilder.copy());
5357
icon = other.icon;
5458
bundleName = other.bundleName;
5559
bundleIdentifier = other.bundleIdentifier;
@@ -94,18 +98,28 @@ MacApplicationBuilder signingBuilder(AppImageSigningConfigBuilder v) {
9498
return this;
9599
}
96100

101+
Optional<ExternalApplication> externalApplication() {
102+
return superBuilder.externalApplication();
103+
}
104+
105+
Optional<ApplicationLaunchers> launchers() {
106+
return superBuilder.launchers();
107+
}
108+
97109
MacApplication create() {
98110
if (externalInfoPlistFile != null) {
99111
return createCopyForExternalInfoPlistFile().create();
100112
}
101113

114+
var app = superBuilder.create();
115+
102116
validateAppVersion(app);
103117
validateAppContentDirs(app);
104118

105119
final var mixin = new MacApplicationMixin.Stub(
106120
validatedIcon(),
107-
validatedBundleName(),
108-
validatedBundleIdentifier(),
121+
validatedBundleName(app),
122+
validatedBundleIdentifier(app),
109123
validatedCategory(),
110124
appStore,
111125
createSigningConfig());
@@ -161,39 +175,58 @@ private static void validateAppContentDirs(Application app) {
161175
}
162176

163177
private MacApplicationBuilder createCopyForExternalInfoPlistFile() {
164-
try {
165-
final var plistFile = AppImageInfoPListFile.loadFromInfoPList(externalInfoPlistFile);
178+
final var builder = new MacApplicationBuilder(this);
166179

167-
final var builder = new MacApplicationBuilder(this);
180+
builder.externalInfoPlistFile(null);
168181

169-
builder.externalInfoPlistFile(null);
182+
Result<PListReader> plistResult = Result.of(() -> {
183+
return new PListReader(Files.readAllBytes(externalInfoPlistFile));
184+
}, Exception.class);
170185

186+
plistResult.value().ifPresent(plist -> {
171187
if (builder.bundleName == null) {
172-
builder.bundleName(plistFile.bundleName());
188+
plist.findValue("CFBundleName").ifPresent(builder::bundleName);
173189
}
174190

175191
if (builder.bundleIdentifier == null) {
176-
builder.bundleIdentifier(plistFile.bundleIdentifier());
192+
plist.findValue("CFBundleIdentifier").ifPresent(builder::bundleIdentifier);
177193
}
178194

179195
if (builder.category == null) {
180-
builder.category(plistFile.category());
196+
plist.findValue("LSApplicationCategoryType").ifPresent(builder::category);
181197
}
182198

183-
return builder;
184-
} catch (IOException ex) {
185-
throw new UncheckedIOException(ex);
186-
} catch (Exception ex) {
187-
throw new JPackageException(
188-
I18N.format("error.invalid-app-image-plist-file", externalInfoPlistFile), ex);
189-
}
199+
if (builder.superBuilder.version().isEmpty()) {
200+
plist.findValue("CFBundleVersion").ifPresent(builder.superBuilder::version);
201+
}
202+
});
203+
204+
plistResult.firstError().filter(_ -> {
205+
// If we are building a runtime and the Info.plist file of the predefined
206+
// runtime bundle is malformed or unavailable, ignore it.
207+
return !superBuilder.isRuntime();
208+
}).ifPresent(ex -> {
209+
// We are building an application from the predefined app image and
210+
// the Info.plist file in the predefined app image bundle is malformed or unavailable. Bail out.
211+
switch (ex) {
212+
case IOException ioex -> {
213+
throw new UncheckedIOException(ioex);
214+
}
215+
default -> {
216+
throw new JPackageException(
217+
I18N.format("error.invalid-app-image-plist-file", externalInfoPlistFile), ex);
218+
}
219+
}
220+
});
221+
222+
return builder;
190223
}
191224

192225
private Optional<AppImageSigningConfig> createSigningConfig() {
193226
return Optional.ofNullable(signingBuilder).map(AppImageSigningConfigBuilder::create);
194227
}
195228

196-
private String validatedBundleName() {
229+
private String validatedBundleName(Application app) {
197230
final var value = Optional.ofNullable(bundleName).orElseGet(() -> {
198231
final var appName = app.name();
199232
// Commented out for backward compatibility
@@ -212,7 +245,7 @@ private String validatedBundleName() {
212245
return value;
213246
}
214247

215-
private String validatedBundleIdentifier() {
248+
private String validatedBundleIdentifier(Application app) {
216249
final var value = Optional.ofNullable(bundleIdentifier).orElseGet(() -> {
217250
return app.mainLauncher()
218251
.flatMap(Launcher::startupInfo)
@@ -255,7 +288,7 @@ private record Defaults(String category) {
255288
private Path externalInfoPlistFile;
256289
private AppImageSigningConfigBuilder signingBuilder;
257290

258-
private final Application app;
291+
private final ApplicationBuilder superBuilder;
259292

260293
private static final Defaults DEFAULTS = new Defaults("utilities");
261294

0 commit comments

Comments
 (0)