From e0fe86b53bdd0b208b10cc8a3e8ddc09910dfd75 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 24 Mar 2026 01:17:38 +0000 Subject: [PATCH] 8371924: --mac-app-store should be accepted option for app image signing Reviewed-by: almatvee --- .../jdk/jpackage/internal/MacFromOptions.java | 22 ++++----- .../internal/MacRuntimeValidator.java | 34 ++++++++++---- .../resources/MacResources.properties | 1 + .../jpackage/internal/cli/StandardOption.java | 2 +- .../jdk/jpackage/test/JPackageCommand.java | 14 +++++- .../jdk/jpackage/test/LauncherVerifier.java | 14 +++++- .../cli/OptionsValidationFailTest.excludes | 1 + .../jpackage/internal/cli/jpackage-options.md | 2 +- .../jpackage/macosx/SigningAppImageTest.java | 1 - .../macosx/SigningAppImageTwoStepsTest.java | 34 +++++++++++++- test/jdk/tools/jpackage/share/ErrorTest.java | 45 +++++++++++++++++-- 11 files changed, 138 insertions(+), 32 deletions(-) diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java index 20dcbcf5742..2c247ed3989 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java @@ -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); diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java index cfab12cbcba..340078849c7 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacRuntimeValidator.java @@ -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(); + } + } + } } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties index 90c4162b80e..95fff00152b 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties @@ -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 diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardOption.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardOption.java index c2338b87fad..438a5ac3e6f 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardOption.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardOption.java @@ -342,7 +342,7 @@ public final class StandardOption { public static final OptionValue MAC_SIGN = booleanOption("mac-sign").scope(MAC_SIGNING).addAliases("s").create(); - public static final OptionValue MAC_APP_STORE = booleanOption("mac-app-store").create(); + public static final OptionValue MAC_APP_STORE = booleanOption("mac-app-store").scope(MAC_SIGNING).create(); public static final OptionValue MAC_APP_CATEGORY = stringOption("mac-app-category").create(); diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index 6b1f67d50f4..4fad120d0f6 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -392,7 +392,6 @@ public class JPackageCommand extends CommandArguments { } public JPackageCommand setFakeRuntime() { - verifyMutable(); addPrerequisiteAction(cmd -> { cmd.setArgumentValue("--runtime-image", createInputRuntimeImage(RuntimeImageType.RUNTIME_TYPE_FAKE)); }); @@ -400,12 +399,22 @@ public class JPackageCommand extends CommandArguments { 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 action) { + verifyMutable(); prerequisiteActions.add(action); return this; } @@ -421,6 +430,7 @@ public class JPackageCommand extends CommandArguments { } JPackageCommand addVerifyAction(ThrowingConsumer action, ActionRole actionRole) { + verifyMutable(); verifyActions.add(action, actionRole); return this; } @@ -2033,7 +2043,7 @@ public class JPackageCommand extends CommandArguments { // `--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( diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java index f9fcfb905af..7657517bad5 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java @@ -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 { diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsValidationFailTest.excludes b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsValidationFailTest.excludes index 32968db0606..19478aaa4a7 100644 --- a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsValidationFailTest.excludes +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsValidationFailTest.excludes @@ -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]]) diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/jpackage-options.md b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/jpackage-options.md index 64d3c6c075b..59ae0d176c1 100644 --- a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/jpackage-options.md +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/jpackage-options.md @@ -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 | diff --git a/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java b/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java index 3a257a425b6..fc146270ab8 100644 --- a/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java @@ -64,7 +64,6 @@ public class SigningAppImageTest { var testAL = new AdditionalLauncher("testAL"); testAL.applyTo(cmd); - cmd.executeAndAssertHelloAppImageCreated(); MacSign.withKeychain(keychain -> { sign.addTo(cmd); diff --git a/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java b/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java index a6d94b59bd9..20af9572044 100644 --- a/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java @@ -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 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> 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 test() { diff --git a/test/jdk/tools/jpackage/share/ErrorTest.java b/test/jdk/tools/jpackage/share/ErrorTest.java index 2797115b202..fbaec8283e8 100644 --- a/test/jdk/tools/jpackage/share/ErrorTest.java +++ b/test/jdk/tools/jpackage/share/ErrorTest.java @@ -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()