| /* | |
| * Copyright (c) 2025, 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 | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package jdk.jpackage.internal; | |
| import static jdk.jpackage.internal.BundlerParamInfo.createBooleanBundlerParam; | |
| import static jdk.jpackage.internal.BundlerParamInfo.createPathBundlerParam; | |
| import static jdk.jpackage.internal.BundlerParamInfo.createStringBundlerParam; | |
| import static jdk.jpackage.internal.FromParams.createApplicationBuilder; | |
| import static jdk.jpackage.internal.FromParams.createApplicationBundlerParam; | |
| import static jdk.jpackage.internal.FromParams.createPackageBuilder; | |
| import static jdk.jpackage.internal.FromParams.createPackageBundlerParam; | |
| import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; | |
| import static jdk.jpackage.internal.StandardBundlerParam.DMG_CONTENT; | |
| import static jdk.jpackage.internal.StandardBundlerParam.ICON; | |
| import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; | |
| import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE; | |
| import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; | |
| import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; | |
| import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage; | |
| import static jdk.jpackage.internal.model.MacPackage.RUNTIME_PACKAGE_LAYOUT; | |
| import static jdk.jpackage.internal.model.StandardPackageType.MAC_DMG; | |
| import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG; | |
| import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; | |
| import java.io.IOException; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.util.Arrays; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Optional; | |
| import java.util.concurrent.Callable; | |
| import java.util.function.Predicate; | |
| import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo; | |
| import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException; | |
| import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector; | |
| import jdk.jpackage.internal.model.ApplicationLaunchers; | |
| import jdk.jpackage.internal.model.ConfigException; | |
| import jdk.jpackage.internal.model.FileAssociation; | |
| import jdk.jpackage.internal.model.Launcher; | |
| import jdk.jpackage.internal.model.MacApplication; | |
| import jdk.jpackage.internal.model.MacDmgPackage; | |
| import jdk.jpackage.internal.model.MacFileAssociation; | |
| import jdk.jpackage.internal.model.MacLauncher; | |
| import jdk.jpackage.internal.model.MacPkgPackage; | |
| import jdk.jpackage.internal.model.PackageType; | |
| import jdk.jpackage.internal.model.RuntimeLayout; | |
| import jdk.jpackage.internal.util.function.ExceptionBox; | |
| final class MacFromParams { | |
| private static MacApplication createMacApplication( | |
| Map<String, ? super Object> params) throws ConfigException, IOException { | |
| final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(params).map(predefinedRuntimeImage -> { | |
| if (Files.isDirectory(RUNTIME_PACKAGE_LAYOUT.resolveAt(predefinedRuntimeImage).runtimeDirectory())) { | |
| return RUNTIME_PACKAGE_LAYOUT; | |
| } else { | |
| return RuntimeLayout.DEFAULT; | |
| } | |
| }); | |
| final var launcherFromParams = new LauncherFromParams(Optional.of(MacFromParams::createMacFa)); | |
| final var superAppBuilder = createApplicationBuilder(params, toFunction(launcherParams -> { | |
| var launcher = launcherFromParams.create(launcherParams); | |
| return MacLauncher.create(launcher); | |
| }), APPLICATION_LAYOUT, predefinedRuntimeLayout); | |
| if (hasPredefinedAppImage(params)) { | |
| // Set the main launcher start up info. | |
| // AppImageFile assumes the main launcher start up info is available when | |
| // it is constructed from Application instance. | |
| // This happens when jpackage signs predefined app image. | |
| final var mainLauncherStartupInfo = new MainLauncherStartupInfo(PREDEFINED_APP_IMAGE_FILE.fetchFrom(params).getMainClass()); | |
| final var launchers = superAppBuilder.launchers().orElseThrow(); | |
| final var mainLauncher = ApplicationBuilder.overrideLauncherStartupInfo(launchers.mainLauncher(), mainLauncherStartupInfo); | |
| superAppBuilder.launchers(new ApplicationLaunchers(MacLauncher.create(mainLauncher), launchers.additionalLaunchers())); | |
| } | |
| final var app = superAppBuilder.create(); | |
| final var appBuilder = new MacApplicationBuilder(app); | |
| if (hasPredefinedAppImage(params)) { | |
| appBuilder.externalInfoPlistFile(PREDEFINED_APP_IMAGE.findIn(params).orElseThrow().resolve("Contents/Info.plist")); | |
| } | |
| ICON.copyInto(params, appBuilder::icon); | |
| MAC_CF_BUNDLE_NAME.copyInto(params, appBuilder::bundleName); | |
| MAC_CF_BUNDLE_IDENTIFIER.copyInto(params, appBuilder::bundleIdentifier); | |
| APP_CATEGORY.copyInto(params, appBuilder::category); | |
| final boolean sign; | |
| final boolean appStore; | |
| if (hasPredefinedAppImage(params) && PACKAGE_TYPE.findIn(params).filter(Predicate.isEqual("app-image")).isEmpty()) { | |
| final var appImageFileExtras = new MacAppImageFileExtras(PREDEFINED_APP_IMAGE_FILE.fetchFrom(params)); | |
| sign = appImageFileExtras.signed(); | |
| appStore = appImageFileExtras.appStore(); | |
| } else { | |
| sign = SIGN_BUNDLE.findIn(params).orElse(false); | |
| appStore = APP_STORE.findIn(params).orElse(false); | |
| } | |
| appBuilder.appStore(appStore); | |
| if (sign) { | |
| final var signingIdentityBuilder = createSigningIdentityBuilder(params); | |
| APP_IMAGE_SIGN_IDENTITY.copyInto(params, signingIdentityBuilder::signingIdentity); | |
| SIGNING_KEY_USER.findIn(params).ifPresent(userName -> { | |
| final StandardCertificateSelector domain; | |
| if (appStore) { | |
| domain = StandardCertificateSelector.APP_STORE_APP_IMAGE; | |
| } else { | |
| domain = StandardCertificateSelector.APP_IMAGE; | |
| } | |
| signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); | |
| }); | |
| final var signingBuilder = new AppImageSigningConfigBuilder(signingIdentityBuilder); | |
| if (appStore) { | |
| signingBuilder.entitlementsResourceName("sandbox.plist"); | |
| } | |
| app.mainLauncher().flatMap(Launcher::startupInfo).ifPresent(signingBuilder::signingIdentifierPrefix); | |
| SIGN_IDENTIFIER_PREFIX.copyInto(params, signingBuilder::signingIdentifierPrefix); | |
| ENTITLEMENTS.copyInto(params, signingBuilder::entitlements); | |
| appBuilder.signingBuilder(signingBuilder); | |
| } | |
| return appBuilder.create(); | |
| } | |
| private static MacPackageBuilder createMacPackageBuilder( | |
| Map<String, ? super Object> params, MacApplication app, | |
| PackageType type) throws ConfigException { | |
| final var builder = new MacPackageBuilder(createPackageBuilder(params, app, type)); | |
| PREDEFINED_APP_IMAGE_FILE.findIn(params) | |
| .map(MacAppImageFileExtras::new) | |
| .map(MacAppImageFileExtras::signed) | |
| .ifPresent(builder::predefinedAppImageSigned); | |
| return builder; | |
| } | |
| private static MacDmgPackage createMacDmgPackage( | |
| Map<String, ? super Object> params) throws ConfigException, IOException { | |
| final var app = APPLICATION.fetchFrom(params); | |
| final var superPkgBuilder = createMacPackageBuilder(params, app, MAC_DMG); | |
| final var pkgBuilder = new MacDmgPackageBuilder(superPkgBuilder); | |
| DMG_CONTENT.copyInto(params, pkgBuilder::dmgContent); | |
| return pkgBuilder.create(); | |
| } | |
| private record WithExpiredCertificateException<T>(Optional<T> obj, Optional<ExpiredCertificateException> certEx) { | |
| WithExpiredCertificateException { | |
| if (obj.isEmpty() == certEx.isEmpty()) { | |
| throw new IllegalArgumentException(); | |
| } | |
| } | |
| static <U> WithExpiredCertificateException<U> of(Callable<U> callable) { | |
| try { | |
| return new WithExpiredCertificateException<>(Optional.of(callable.call()), Optional.empty()); | |
| } catch (ExpiredCertificateException ex) { | |
| return new WithExpiredCertificateException<>(Optional.empty(), Optional.of(ex)); | |
| } catch (ExceptionBox ex) { | |
| if (ex.getCause() instanceof ExpiredCertificateException certEx) { | |
| return new WithExpiredCertificateException<>(Optional.empty(), Optional.of(certEx)); | |
| } | |
| throw ex; | |
| } catch (RuntimeException ex) { | |
| throw ex; | |
| } catch (Throwable t) { | |
| throw ExceptionBox.rethrowUnchecked(t); | |
| } | |
| } | |
| } | |
| private static MacPkgPackage createMacPkgPackage( | |
| Map<String, ? super Object> params) throws ConfigException, IOException { | |
| // This is over complicated to make "MacSignTest.testExpiredCertificate" test pass. | |
| final boolean sign = SIGN_BUNDLE.findIn(params).orElse(false); | |
| final boolean appStore = APP_STORE.findIn(params).orElse(false); | |
| final var appOrExpiredCertEx = WithExpiredCertificateException.of(() -> { | |
| return APPLICATION.fetchFrom(params); | |
| }); | |
| final Optional<MacPkgPackageBuilder> pkgBuilder; | |
| if (appOrExpiredCertEx.obj().isPresent()) { | |
| final var superPkgBuilder = createMacPackageBuilder(params, appOrExpiredCertEx.obj().orElseThrow(), MAC_PKG); | |
| pkgBuilder = Optional.of(new MacPkgPackageBuilder(superPkgBuilder)); | |
| } else { | |
| pkgBuilder = Optional.empty(); | |
| } | |
| if (sign) { | |
| final var signingIdentityBuilder = createSigningIdentityBuilder(params); | |
| INSTALLER_SIGN_IDENTITY.copyInto(params, signingIdentityBuilder::signingIdentity); | |
| SIGNING_KEY_USER.findIn(params).ifPresent(userName -> { | |
| final StandardCertificateSelector domain; | |
| if (appStore) { | |
| domain = StandardCertificateSelector.APP_STORE_PKG_INSTALLER; | |
| } else { | |
| domain = StandardCertificateSelector.PKG_INSTALLER; | |
| } | |
| signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); | |
| }); | |
| if (pkgBuilder.isPresent()) { | |
| pkgBuilder.orElseThrow().signingBuilder(signingIdentityBuilder); | |
| } else { | |
| final var expiredPkgCert = WithExpiredCertificateException.of(() -> { | |
| return signingIdentityBuilder.create(); | |
| }).certEx(); | |
| expiredPkgCert.map(ConfigException::getMessage).ifPresent(Log::error); | |
| throw appOrExpiredCertEx.certEx().orElseThrow(); | |
| } | |
| } | |
| return pkgBuilder.orElseThrow().create(); | |
| } | |
| private static SigningIdentityBuilder createSigningIdentityBuilder(Map<String, ? super Object> params) { | |
| final var builder = new SigningIdentityBuilder(); | |
| SIGNING_KEYCHAIN.copyInto(params, builder::keychain); | |
| return builder; | |
| } | |
| private static MacFileAssociation createMacFa(FileAssociation fa, Map<String, ? super Object> params) { | |
| final var builder = new MacFileAssociationBuilder(); | |
| FA_MAC_CFBUNDLETYPEROLE.copyInto(params, builder::cfBundleTypeRole); | |
| FA_MAC_LSHANDLERRANK.copyInto(params, builder::lsHandlerRank); | |
| FA_MAC_NSSTORETYPEKEY.copyInto(params, builder::nsPersistentStoreTypeKey); | |
| FA_MAC_NSDOCUMENTCLASS.copyInto(params, builder::nsDocumentClass); | |
| FA_MAC_LSTYPEISPACKAGE.copyInto(params, builder::lsTypeIsPackage); | |
| FA_MAC_LSDOCINPLACE.copyInto(params, builder::lsSupportsOpeningDocumentsInPlace); | |
| FA_MAC_UIDOCBROWSER.copyInto(params, builder::uiSupportsDocumentBrowser); | |
| FA_MAC_NSEXPORTABLETYPES.copyInto(params, builder::nsExportableTypes); | |
| FA_MAC_UTTYPECONFORMSTO.copyInto(params, builder::utTypeConformsTo); | |
| return toFunction(builder::create).apply(fa); | |
| } | |
| static final BundlerParamInfo<MacApplication> APPLICATION = createApplicationBundlerParam( | |
| MacFromParams::createMacApplication); | |
| static final BundlerParamInfo<MacDmgPackage> DMG_PACKAGE = createPackageBundlerParam( | |
| MacFromParams::createMacDmgPackage); | |
| static final BundlerParamInfo<MacPkgPackage> PKG_PACKAGE = createPackageBundlerParam( | |
| MacFromParams::createMacPkgPackage); | |
| private static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME = createStringBundlerParam( | |
| Arguments.CLIOptions.MAC_BUNDLE_NAME.getId()); | |
| private static final BundlerParamInfo<String> APP_CATEGORY = createStringBundlerParam( | |
| Arguments.CLIOptions.MAC_CATEGORY.getId()); | |
| private static final BundlerParamInfo<Path> ENTITLEMENTS = createPathBundlerParam( | |
| Arguments.CLIOptions.MAC_ENTITLEMENTS.getId()); | |
| private static final BundlerParamInfo<String> MAC_CF_BUNDLE_IDENTIFIER = createStringBundlerParam( | |
| Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId()); | |
| private static final BundlerParamInfo<String> SIGN_IDENTIFIER_PREFIX = createStringBundlerParam( | |
| Arguments.CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId()); | |
| private static final BundlerParamInfo<String> APP_IMAGE_SIGN_IDENTITY = createStringBundlerParam( | |
| Arguments.CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getId()); | |
| private static final BundlerParamInfo<String> INSTALLER_SIGN_IDENTITY = createStringBundlerParam( | |
| Arguments.CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getId()); | |
| private static final BundlerParamInfo<String> SIGNING_KEY_USER = createStringBundlerParam( | |
| Arguments.CLIOptions.MAC_SIGNING_KEY_NAME.getId()); | |
| private static final BundlerParamInfo<String> SIGNING_KEYCHAIN = createStringBundlerParam( | |
| Arguments.CLIOptions.MAC_SIGNING_KEYCHAIN.getId()); | |
| private static final BundlerParamInfo<String> PACKAGE_TYPE = createStringBundlerParam( | |
| Arguments.CLIOptions.PACKAGE_TYPE.getId()); | |
| private static final BundlerParamInfo<Boolean> APP_STORE = createBooleanBundlerParam( | |
| Arguments.CLIOptions.MAC_APP_STORE.getId()); | |
| private static final BundlerParamInfo<String> FA_MAC_CFBUNDLETYPEROLE = createStringBundlerParam( | |
| Arguments.MAC_CFBUNDLETYPEROLE); | |
| private static final BundlerParamInfo<String> FA_MAC_LSHANDLERRANK = createStringBundlerParam( | |
| Arguments.MAC_LSHANDLERRANK); | |
| private static final BundlerParamInfo<String> FA_MAC_NSSTORETYPEKEY = createStringBundlerParam( | |
| Arguments.MAC_NSSTORETYPEKEY); | |
| private static final BundlerParamInfo<String> FA_MAC_NSDOCUMENTCLASS = createStringBundlerParam( | |
| Arguments.MAC_NSDOCUMENTCLASS); | |
| private static final BundlerParamInfo<Boolean> FA_MAC_LSTYPEISPACKAGE = createBooleanBundlerParam( | |
| Arguments.MAC_LSTYPEISPACKAGE); | |
| private static final BundlerParamInfo<Boolean> FA_MAC_LSDOCINPLACE = createBooleanBundlerParam( | |
| Arguments.MAC_LSDOCINPLACE); | |
| private static final BundlerParamInfo<Boolean> FA_MAC_UIDOCBROWSER = createBooleanBundlerParam( | |
| Arguments.MAC_UIDOCBROWSER); | |
| @SuppressWarnings("unchecked") | |
| private static final BundlerParamInfo<List<String>> FA_MAC_NSEXPORTABLETYPES = | |
| new BundlerParamInfo<>( | |
| Arguments.MAC_NSEXPORTABLETYPES, | |
| (Class<List<String>>) (Object) List.class, | |
| null, | |
| (s, p) -> Arrays.asList(s.split("(,|\\s)+")) | |
| ); | |
| @SuppressWarnings("unchecked") | |
| private static final BundlerParamInfo<List<String>> FA_MAC_UTTYPECONFORMSTO = | |
| new BundlerParamInfo<>( | |
| Arguments.MAC_UTTYPECONFORMSTO, | |
| (Class<List<String>>) (Object) List.class, | |
| null, | |
| (s, p) -> Arrays.asList(s.split("(,|\\s)+")) | |
| ); | |
| } |