Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
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;
Expand Down Expand Up @@ -203,12 +201,14 @@ private static ApplicationBuilder createApplicationBuilder(Options options) {
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);

Expand Down Expand Up @@ -269,11 +269,13 @@ private static ApplicationWithDetails createMacApplicationInternal(Options optio
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;
Comment on lines +272 to +278
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see any difference between old and new code. Can you explain?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old code read the "app-store" property from the predefined image and ignored the value specified on the command line. The new code reads "app-store" property from the command line first. If not found, it reads it from the predefined image.

}

appBuilder.appStore(appStore);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -30,6 +30,10 @@
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 {
Expand All @@ -45,17 +49,29 @@ static void validateRuntimeHasJliLib(RuntimeLayout runtimeLayout) {
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();
}
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ public boolean test(Path path) {

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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,20 +392,29 @@ public JPackageCommand setInputToEmptyDirectory() {
}

public JPackageCommand setFakeRuntime() {
verifyMutable();
addPrerequisiteAction(cmd -> {
cmd.setArgumentValue("--runtime-image", createInputRuntimeImage(RuntimeImageType.RUNTIME_TYPE_FAKE));
});

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;
}
Expand All @@ -421,6 +430,7 @@ enum ActionRole {
}

JPackageCommand addVerifyAction(ThrowingConsumer<JPackageCommand, ? extends Exception> action, ActionRole actionRole) {
verifyMutable();
verifyActions.add(action, actionRole);
return this;
}
Expand Down Expand Up @@ -2033,7 +2043,7 @@ private enum DefaultToolProviderKey {
// `--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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,14 +372,21 @@ private void verifyMacEntitlements(JPackageCommand cmd) throws SAXException, IOE

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;
}
}

Expand All @@ -388,11 +395,14 @@ private void verifyMacEntitlements(JPackageCommand cmd) throws SAXException, IOE
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
1 change: 0 additions & 1 deletion test/jdk/tools/jpackage/macosx/SigningAppImageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public static void test(SignKeyOptionWithKeychain sign) {

var testAL = new AdditionalLauncher("testAL");
testAL.applyTo(cmd);
cmd.executeAndAssertHelloAppImageCreated();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it is removed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it runs jpackage without signing and duplicates line 70.


MacSign.withKeychain(keychain -> {
sign.addTo(cmd);
Expand Down
34 changes: 32 additions & 2 deletions test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -68,6 +69,22 @@ public static void test(TestSpec spec) {
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 {
Expand Down Expand Up @@ -133,7 +150,7 @@ private SignKeyOptionWithKeychain createSignKeyOption() {
private SignKeyOptionWithKeychain sign;
}

void test() {
JPackageCommand createAppImage() {
var appImageCmd = JPackageCommand.helloAppImage()
.setFakeRuntime()
.setArgumentValue("--dest", TKit.createTempDirectory("appimage"));
Expand All @@ -150,16 +167,29 @@ void test() {
}, 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() {
Expand Down
45 changes: 41 additions & 4 deletions test/jdk/tools/jpackage/share/ErrorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
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;
Expand All @@ -35,6 +34,7 @@
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;
Expand All @@ -56,6 +56,7 @@
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;
Expand Down Expand Up @@ -699,11 +700,48 @@ public static Collection<Object[]> testAdditionLaunchers() {
));
}

@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(() -> {
Expand Down Expand Up @@ -998,8 +1036,7 @@ public static Collection<Object[]> testMac() {
// 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()
Expand Down