mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-28 16:50:10 +00:00
8371924: --mac-app-store should be accepted option for app image signing
Reviewed-by: almatvee
This commit is contained in:
parent
d85fbd38cd
commit
e0fe86b53b
@ -27,8 +27,6 @@ package jdk.jpackage.internal;
|
||||
import static jdk.jpackage.internal.FromOptions.buildApplicationBuilder;
|
||||
import static jdk.jpackage.internal.FromOptions.createPackageBuilder;
|
||||
import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT;
|
||||
import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib;
|
||||
import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir;
|
||||
import static jdk.jpackage.internal.OptionUtils.isBundlingOperation;
|
||||
import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_PKG;
|
||||
import static jdk.jpackage.internal.cli.StandardOption.APPCLASS;
|
||||
@ -203,12 +201,14 @@ final class MacFromOptions {
|
||||
final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(options)
|
||||
.map(MacPackage::guessRuntimeLayout);
|
||||
|
||||
predefinedRuntimeLayout.ifPresent(layout -> {
|
||||
validateRuntimeHasJliLib(layout);
|
||||
if (MAC_APP_STORE.containsIn(options)) {
|
||||
validateRuntimeHasNoBinDir(layout);
|
||||
}
|
||||
});
|
||||
predefinedRuntimeLayout.ifPresent(MacRuntimeValidator::validateRuntimeHasJliLib);
|
||||
|
||||
if (MAC_APP_STORE.containsIn(options)) {
|
||||
PREDEFINED_APP_IMAGE.findIn(options)
|
||||
.map(APPLICATION_LAYOUT::resolveAt)
|
||||
.ifPresent(MacRuntimeValidator::validateRuntimeHasNoBinDir);
|
||||
predefinedRuntimeLayout.ifPresent(MacRuntimeValidator::validateRuntimeHasNoBinDir);
|
||||
}
|
||||
|
||||
final var launcherFromOptions = new LauncherFromOptions().faMapper(MacFromOptions::createMacFa);
|
||||
|
||||
@ -269,11 +269,13 @@ final class MacFromOptions {
|
||||
final boolean sign = MAC_SIGN.getFrom(options);
|
||||
final boolean appStore;
|
||||
|
||||
if (PREDEFINED_APP_IMAGE.containsIn(options)) {
|
||||
if (MAC_APP_STORE.containsIn(options)) {
|
||||
appStore = MAC_APP_STORE.getFrom(options);
|
||||
} else if (PREDEFINED_APP_IMAGE.containsIn(options)) {
|
||||
final var appImageFileOptions = appBuilder.externalApplication().orElseThrow().extra();
|
||||
appStore = MAC_APP_STORE.getFrom(appImageFileOptions);
|
||||
} else {
|
||||
appStore = MAC_APP_STORE.getFrom(options);
|
||||
appStore = false;
|
||||
}
|
||||
|
||||
appBuilder.appStore(appStore);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 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
|
||||
@ -30,6 +30,10 @@ import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Predicate;
|
||||
import jdk.jpackage.internal.model.AppImageLayout;
|
||||
import jdk.jpackage.internal.model.ApplicationLayout;
|
||||
import jdk.jpackage.internal.model.ConfigException;
|
||||
import jdk.jpackage.internal.model.JPackageException;
|
||||
import jdk.jpackage.internal.model.RuntimeLayout;
|
||||
|
||||
final class MacRuntimeValidator {
|
||||
@ -45,17 +49,29 @@ final class MacRuntimeValidator {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
|
||||
throw I18N.buildConfigException("error.invalid-runtime-image-missing-file",
|
||||
throw new JPackageException(I18N.format("error.invalid-runtime-image-missing-file",
|
||||
runtimeLayout.rootDirectory(),
|
||||
runtimeLayout.unresolve().runtimeDirectory().resolve("lib/**").resolve(jliName)).create();
|
||||
runtimeLayout.unresolve().runtimeDirectory().resolve("lib/**").resolve(jliName)));
|
||||
}
|
||||
|
||||
static void validateRuntimeHasNoBinDir(RuntimeLayout runtimeLayout) {
|
||||
if (Files.isDirectory(runtimeLayout.runtimeDirectory().resolve("bin"))) {
|
||||
throw I18N.buildConfigException()
|
||||
.message("error.invalid-runtime-image-bin-dir", runtimeLayout.rootDirectory())
|
||||
.advice("error.invalid-runtime-image-bin-dir.advice", "--mac-app-store")
|
||||
.create();
|
||||
static void validateRuntimeHasNoBinDir(AppImageLayout appImageLayout) {
|
||||
if (Files.isDirectory(appImageLayout.runtimeDirectory().resolve("bin"))) {
|
||||
switch (appImageLayout) {
|
||||
case RuntimeLayout runtimeLayout -> {
|
||||
throw new ConfigException(
|
||||
I18N.format("error.invalid-runtime-image-bin-dir", runtimeLayout.rootDirectory()),
|
||||
I18N.format("error.invalid-runtime-image-bin-dir.advice", "--mac-app-store"));
|
||||
}
|
||||
case ApplicationLayout appLayout -> {
|
||||
throw new JPackageException(I18N.format("error.invalid-app-image-runtime-image-bin-dir",
|
||||
appLayout.rootDirectory().relativize(appLayout.runtimeDirectory()),
|
||||
appLayout.rootDirectory()));
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ error.cert.not.found=No certificate found matching [{0}] using keychain [{1}]
|
||||
error.multiple.certs.found=Multiple certificates matching name [{0}] found in keychain [{1}]
|
||||
error.app-image.mac-sign.required=--mac-sign option is required with predefined application image and with type [app-image]
|
||||
error.invalid-runtime-image-missing-file=Runtime image "{0}" is missing "{1}" file
|
||||
error.invalid-app-image-runtime-image-bin-dir=Runtime directory {0} in the predefined application image [{1}] should not contain "bin" folder
|
||||
error.invalid-runtime-image-bin-dir=Runtime image "{0}" should not contain "bin" folder
|
||||
error.invalid-runtime-image-bin-dir.advice=Use --strip-native-commands jlink option when generating runtime image used with {0} option
|
||||
error.invalid-app-image-plist-file=Invalid "{0}" file in the predefined application image
|
||||
|
||||
@ -342,7 +342,7 @@ public final class StandardOption {
|
||||
|
||||
public static final OptionValue<Boolean> MAC_SIGN = booleanOption("mac-sign").scope(MAC_SIGNING).addAliases("s").create();
|
||||
|
||||
public static final OptionValue<Boolean> MAC_APP_STORE = booleanOption("mac-app-store").create();
|
||||
public static final OptionValue<Boolean> MAC_APP_STORE = booleanOption("mac-app-store").scope(MAC_SIGNING).create();
|
||||
|
||||
public static final OptionValue<String> MAC_APP_CATEGORY = stringOption("mac-app-category").create();
|
||||
|
||||
|
||||
@ -392,7 +392,6 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
}
|
||||
|
||||
public JPackageCommand setFakeRuntime() {
|
||||
verifyMutable();
|
||||
addPrerequisiteAction(cmd -> {
|
||||
cmd.setArgumentValue("--runtime-image", createInputRuntimeImage(RuntimeImageType.RUNTIME_TYPE_FAKE));
|
||||
});
|
||||
@ -400,12 +399,22 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public JPackageCommand usePredefinedAppImage(JPackageCommand appImageCmd) {
|
||||
appImageCmd.verifyIsOfType(PackageType.IMAGE);
|
||||
verifyIsOfType(PackageType.IMAGE);
|
||||
appImageCmd.getVerifyActionsWithRole(ActionRole.LAUNCHER_VERIFIER).forEach(verifier -> {
|
||||
addVerifyAction(verifier, ActionRole.LAUNCHER_VERIFIER);
|
||||
});
|
||||
return usePredefinedAppImage(appImageCmd.outputBundle());
|
||||
}
|
||||
|
||||
public JPackageCommand usePredefinedAppImage(Path predefinedAppImagePath) {
|
||||
return setArgumentValue("--app-image", Objects.requireNonNull(predefinedAppImagePath))
|
||||
.removeArgumentWithValue("--input");
|
||||
}
|
||||
|
||||
JPackageCommand addPrerequisiteAction(ThrowingConsumer<JPackageCommand, ? extends Exception> action) {
|
||||
verifyMutable();
|
||||
prerequisiteActions.add(action);
|
||||
return this;
|
||||
}
|
||||
@ -421,6 +430,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
}
|
||||
|
||||
JPackageCommand addVerifyAction(ThrowingConsumer<JPackageCommand, ? extends Exception> action, ActionRole actionRole) {
|
||||
verifyMutable();
|
||||
verifyActions.add(action, actionRole);
|
||||
return this;
|
||||
}
|
||||
@ -2033,7 +2043,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
// `--runtime-image` parameter set.
|
||||
public static final Path DEFAULT_RUNTIME_IMAGE = Optional.ofNullable(TKit.getConfigProperty("runtime-image")).map(Path::of).orElse(null);
|
||||
|
||||
public final static String DEFAULT_VERSION = "1.0";
|
||||
public static final String DEFAULT_VERSION = "1.0";
|
||||
|
||||
// [HH:mm:ss.SSS]
|
||||
private static final Pattern TIMESTAMP_REGEXP = Pattern.compile(
|
||||
|
||||
@ -372,14 +372,21 @@ public final class LauncherVerifier {
|
||||
|
||||
TKit.assertTrue(entitlements.isPresent(), String.format("Check [%s] launcher is signed with entitlements", name));
|
||||
|
||||
String expectedEntitlementsOrigin;
|
||||
|
||||
var customFile = Optional.ofNullable(cmd.getArgumentValue("--mac-entitlements")).map(Path::of);
|
||||
if (customFile.isEmpty()) {
|
||||
if (customFile.isPresent()) {
|
||||
expectedEntitlementsOrigin = String.format("custom entitlements from [%s] file", customFile.get());
|
||||
} else {
|
||||
// Try from the resource dir.
|
||||
var resourceDirFile = Optional.ofNullable(cmd.getArgumentValue("--resource-dir")).map(Path::of).map(resourceDir -> {
|
||||
return resourceDir.resolve(cmd.name() + ".entitlements");
|
||||
}).filter(Files::exists);
|
||||
if (resourceDirFile.isPresent()) {
|
||||
customFile = resourceDirFile;
|
||||
expectedEntitlementsOrigin = "custom entitlements from the resource directory";
|
||||
} else {
|
||||
expectedEntitlementsOrigin = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,11 +395,14 @@ public final class LauncherVerifier {
|
||||
expected = new PListReader(Files.readAllBytes(customFile.orElseThrow())).toMap(true);
|
||||
} else if (cmd.hasArgument("--mac-app-store")) {
|
||||
expected = DefaultEntitlements.APP_STORE;
|
||||
expectedEntitlementsOrigin = "App Store entitlements";
|
||||
} else {
|
||||
expectedEntitlementsOrigin = "default entitlements";
|
||||
expected = DefaultEntitlements.STANDARD;
|
||||
}
|
||||
|
||||
TKit.assertEquals(expected, entitlements.orElseThrow().toMap(true), String.format("Check [%s] launcher is signed with expected entitlements", name));
|
||||
TKit.assertEquals(expected, entitlements.orElseThrow().toMap(true),
|
||||
String.format("Check [%s] launcher is signed with %s", name, expectedEntitlementsOrigin));
|
||||
}
|
||||
|
||||
private void executeLauncher(JPackageCommand cmd) throws IOException {
|
||||
|
||||
@ -40,6 +40,7 @@ ErrorTest.test(WIN_MSI; app-desc=Hello; args-add=[--app-version, 1234]; errors=[
|
||||
ErrorTest.test(WIN_MSI; app-desc=Hello; args-add=[--app-version, 256.1]; errors=[message.error-header+[error.msi-product-version-major-out-of-range], message.advice-header+[error.version-string-wrong-format.advice]])
|
||||
ErrorTest.test(WIN_MSI; app-desc=Hello; args-add=[--launcher-as-service]; errors=[message.error-header+[error.missing-service-installer], message.advice-header+[error.missing-service-installer.advice]])
|
||||
ErrorTest.test(args-add=[@foo]; errors=[message.error-header+[ERR_CannotParseOptions, foo]])
|
||||
ErrorTest.testMacSignAppStoreInvalidRuntime
|
||||
ErrorTest.testMacSignWithoutIdentity(IMAGE; app-desc=Hello; args-add=[--mac-sign, --mac-signing-keychain, @@EMPTY_KEYCHAIN@@]; errors=[message.error-header+[error.cert.not.found, CODE_SIGN, EMPTY_KEYCHAIN]])
|
||||
ErrorTest.testMacSignWithoutIdentity(IMAGE; args-add=[--app-image, @@APP_IMAGE_WITH_SHORT_NAME@@, --mac-sign, --mac-signing-keychain, @@EMPTY_KEYCHAIN@@]; errors=[message.error-header+[error.cert.not.found, CODE_SIGN, EMPTY_KEYCHAIN]])
|
||||
ErrorTest.testMacSignWithoutIdentity(MAC_DMG; app-desc=Hello; args-add=[--mac-sign, --mac-signing-keychain, @@EMPTY_KEYCHAIN@@]; errors=[message.error-header+[error.cert.not.found, CODE_SIGN, EMPTY_KEYCHAIN]])
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
| --linux-shortcut | linux-deb, linux-rpm | x | x | x | USE_LAST |
|
||||
| --mac-app-category | mac-bundle | x | x | | USE_LAST |
|
||||
| --mac-app-image-sign-identity | mac | x | x | | USE_LAST |
|
||||
| --mac-app-store | mac-bundle | x | x | | USE_LAST |
|
||||
| --mac-app-store | mac | x | x | | USE_LAST |
|
||||
| --mac-dmg-content | mac-dmg | x | x | | CONCATENATE |
|
||||
| --mac-entitlements | mac | x | x | | USE_LAST |
|
||||
| --mac-installer-sign-identity | mac-pkg | x | x | | USE_LAST |
|
||||
|
||||
@ -64,7 +64,6 @@ public class SigningAppImageTest {
|
||||
|
||||
var testAL = new AdditionalLauncher("testAL");
|
||||
testAL.applyTo(cmd);
|
||||
cmd.executeAndAssertHelloAppImageCreated();
|
||||
|
||||
MacSign.withKeychain(keychain -> {
|
||||
sign.addTo(cmd);
|
||||
|
||||
@ -26,6 +26,7 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.test.AdditionalLauncher;
|
||||
@ -68,6 +69,22 @@ public class SigningAppImageTwoStepsTest {
|
||||
spec.test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public static void testAppStore() {
|
||||
|
||||
var sign = new SignKeyOptionWithKeychain(
|
||||
SignKeyOption.Type.SIGN_KEY_USER_SHORT_NAME,
|
||||
SigningBase.StandardCertificateRequest.CODESIGN,
|
||||
SigningBase.StandardKeychain.MAIN.keychain());
|
||||
|
||||
var spec = new TestSpec(Optional.empty(), sign);
|
||||
|
||||
spec.signAppImage(spec.createAppImage(), Optional.of(cmd -> {
|
||||
cmd.addArgument("--mac-app-store");
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
public record TestSpec(Optional<SignKeyOptionWithKeychain> signAppImage, SignKeyOptionWithKeychain sign) {
|
||||
|
||||
public TestSpec {
|
||||
@ -133,7 +150,7 @@ public class SigningAppImageTwoStepsTest {
|
||||
private SignKeyOptionWithKeychain sign;
|
||||
}
|
||||
|
||||
void test() {
|
||||
JPackageCommand createAppImage() {
|
||||
var appImageCmd = JPackageCommand.helloAppImage()
|
||||
.setFakeRuntime()
|
||||
.setArgumentValue("--dest", TKit.createTempDirectory("appimage"));
|
||||
@ -150,16 +167,29 @@ public class SigningAppImageTwoStepsTest {
|
||||
}, signOption.keychain());
|
||||
}, appImageCmd::execute);
|
||||
|
||||
return appImageCmd;
|
||||
}
|
||||
|
||||
void signAppImage(JPackageCommand appImageCmd, Optional<Consumer<JPackageCommand>> mutator) {
|
||||
Objects.requireNonNull(appImageCmd);
|
||||
Objects.requireNonNull(mutator);
|
||||
|
||||
MacSign.withKeychain(keychain -> {
|
||||
var cmd = new JPackageCommand()
|
||||
.setPackageType(PackageType.IMAGE)
|
||||
.addArguments("--app-image", appImageCmd.outputBundle())
|
||||
.usePredefinedAppImage(appImageCmd)
|
||||
.mutate(sign::addTo);
|
||||
|
||||
mutator.ifPresent(cmd::mutate);
|
||||
|
||||
cmd.executeAndAssertHelloAppImageCreated();
|
||||
MacSignVerify.verifyAppImageSigned(cmd, sign.certRequest());
|
||||
}, sign.keychain());
|
||||
}
|
||||
|
||||
void test() {
|
||||
signAppImage(createAppImage(), Optional.empty());
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<Object[]> test() {
|
||||
|
||||
@ -26,7 +26,6 @@ import static java.util.stream.Collectors.toMap;
|
||||
import static jdk.internal.util.OperatingSystem.LINUX;
|
||||
import static jdk.internal.util.OperatingSystem.MACOS;
|
||||
import static jdk.internal.util.OperatingSystem.WINDOWS;
|
||||
import static jdk.jpackage.internal.util.PListWriter.writeDict;
|
||||
import static jdk.jpackage.internal.util.PListWriter.writePList;
|
||||
import static jdk.jpackage.internal.util.XmlUtils.createXml;
|
||||
import static jdk.jpackage.internal.util.XmlUtils.toXmlConsumer;
|
||||
@ -35,6 +34,7 @@ import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
|
||||
import static jdk.jpackage.test.JPackageCommand.makeAdvice;
|
||||
import static jdk.jpackage.test.JPackageCommand.makeError;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@ -56,6 +56,7 @@ import jdk.jpackage.internal.util.TokenReplace;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
import jdk.jpackage.test.Annotations.ParameterSupplier;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.ApplicationLayout;
|
||||
import jdk.jpackage.test.CannedArgument;
|
||||
import jdk.jpackage.test.CannedFormattedString;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
@ -699,11 +700,48 @@ public final class ErrorTest {
|
||||
));
|
||||
}
|
||||
|
||||
@Test(ifOS = MACOS)
|
||||
public static void testMacSignAppStoreInvalidRuntime() throws IOException {
|
||||
|
||||
// Create app image with the runtime directory content that will fail the subsequent signing jpackage command.
|
||||
var appImageCmd = JPackageCommand.helloAppImage().setFakeRuntime();
|
||||
appImageCmd.executeAndAssertImageCreated();
|
||||
Files.createDirectory(appImageCmd.appLayout().runtimeHomeDirectory().resolve("bin"));
|
||||
|
||||
final var keychain = SignEnvMock.SingleCertificateKeychain.FOO.keychain();
|
||||
|
||||
var spec = testSpec()
|
||||
.noAppDesc()
|
||||
.addArgs("--mac-app-store", "--mac-sign", "--app-image", appImageCmd.outputBundle().toString())
|
||||
.error("error.invalid-app-image-runtime-image-bin-dir",
|
||||
ApplicationLayout.macAppImage().runtimeHomeDirectory(), appImageCmd.outputBundle())
|
||||
.create();
|
||||
|
||||
TKit.withNewState(() -> {
|
||||
var script = Script.build()
|
||||
// Disable the mutation making mocks "run once".
|
||||
.commandMockBuilderMutator(null)
|
||||
// Replace "/usr/bin/security" with the mock bound to the keychain mock.
|
||||
.map(MacSignMockUtils.securityMock(SignEnvMock.VALUE))
|
||||
// Don't mock other external commands.
|
||||
.use(VerbatimCommandMock.INSTANCE)
|
||||
.createLoop();
|
||||
|
||||
// Create jpackage tool provider using the /usr/bin/security mock.
|
||||
var jpackage = JPackageMockUtils.createJPackageToolProvider(OperatingSystem.MACOS, script);
|
||||
|
||||
// Override the default jpackage tool provider with the one using the /usr/bin/security mock.
|
||||
JPackageCommand.useToolProviderByDefault(jpackage);
|
||||
|
||||
spec.test();
|
||||
});
|
||||
}
|
||||
|
||||
@Test(ifOS = MACOS)
|
||||
@ParameterSupplier
|
||||
@ParameterSupplier("testMacPkgSignWithoutIdentity")
|
||||
public static void testMacSignWithoutIdentity(TestSpec spec) {
|
||||
// The test called JPackage Command.useToolProviderBy Default(),
|
||||
// The test calls JPackageCommand.useToolProviderByDefault(),
|
||||
// which alters global variables in the test library,
|
||||
// so run the test case with a new global state to isolate the alteration of the globals.
|
||||
TKit.withNewState(() -> {
|
||||
@ -998,8 +1036,7 @@ public final class ErrorTest {
|
||||
// Test a few app-image options that should not be used when signing external app image
|
||||
testCases.addAll(Stream.of(
|
||||
new ArgumentGroup("--app-version", "2.0"),
|
||||
new ArgumentGroup("--name", "foo"),
|
||||
new ArgumentGroup("--mac-app-store")
|
||||
new ArgumentGroup("--name", "foo")
|
||||
).flatMap(argGroup -> {
|
||||
var withoutSign = testSpec()
|
||||
.noAppDesc()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user