Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
8286850: [macos] Add support for signing user provided app image
Reviewed-by: asemenyuk
  • Loading branch information
Alexander Matveev committed Jun 3, 2022
1 parent 0b35460 commit 7a0c8b1
Show file tree
Hide file tree
Showing 27 changed files with 919 additions and 149 deletions.
Expand Up @@ -31,7 +31,7 @@
import java.util.Optional;
import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN;
import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEY_USER;
import static jdk.jpackage.internal.MacAppImageBuilder.APP_STORE;
import static jdk.jpackage.internal.StandardBundlerParam.APP_STORE;
import static jdk.jpackage.internal.StandardBundlerParam.MAIN_CLASS;
import static jdk.jpackage.internal.StandardBundlerParam.VERSION;
import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE;
Expand Down Expand Up @@ -105,16 +105,21 @@ private static void doValidate(Map<String, ? super Object> params)
throws ConfigException {

if (StandardBundlerParam.getPredefinedAppImage(params) != null) {
return;
}

// validate short version
try {
String version = VERSION.fetchFrom(params);
CFBundleVersion.of(version);
} catch (IllegalArgumentException ex) {
throw new ConfigException(ex.getMessage(), I18N.getString(
"error.invalid-cfbundle-version.advice"), ex);
if (!Optional.ofNullable(
SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
throw new ConfigException(
I18N.getString("error.app-image.mac-sign.required"),
null);
}
} else {
// validate short version
try {
String version = VERSION.fetchFrom(params);
CFBundleVersion.of(version);
} catch (IllegalArgumentException ex) {
throw new ConfigException(ex.getMessage(), I18N.getString(
"error.invalid-cfbundle-version.advice"), ex);
}
}

// reject explicitly set sign to true and no valid signature key
Expand Down
Expand Up @@ -67,6 +67,9 @@
import static jdk.jpackage.internal.StandardBundlerParam.VERSION;
import static jdk.jpackage.internal.StandardBundlerParam.ADD_LAUNCHERS;
import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE;
import static jdk.jpackage.internal.StandardBundlerParam.APP_STORE;
import static jdk.jpackage.internal.StandardBundlerParam.getPredefinedAppImage;
import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage;

public class MacAppImageBuilder extends AbstractAppImageBuilder {

Expand Down Expand Up @@ -144,16 +147,6 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
},
(s, p) -> Path.of(s));

public static final StandardBundlerParam<Boolean> APP_STORE =
new StandardBundlerParam<>(
Arguments.CLIOptions.MAC_APP_STORE.getId(),
Boolean.class,
params -> false,
// valueOf(null) is false, we actually do want null in some cases
(s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
null : Boolean.valueOf(s)
);

public static final BundlerParamInfo<Path> ENTITLEMENTS =
new StandardBundlerParam<>(
Arguments.CLIOptions.MAC_ENTITLEMENTS.getId(),
Expand Down Expand Up @@ -269,6 +262,12 @@ private void writeEntry(InputStream in, Path dstFile) throws IOException {
@Override
public void prepareApplicationFiles(Map<String, ? super Object> params)
throws IOException {
// If predefine app image is provided, then just sign it and return.
if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) {
doSigning(params);
return;
}

Files.createDirectories(macOSDir);

Map<String, ? super Object> originalParams = new HashMap<>(params);
Expand Down Expand Up @@ -815,9 +814,8 @@ private static void signAppBundle(

// sign the app itself
List<String> args = getCodesignArgs(true, appLocation, signingIdentity,
identifierPrefix, entitlements, keyChain);
ProcessBuilder pb
= new ProcessBuilder(args.toArray(new String[args.size()]));
identifierPrefix, entitlements, keyChain);
ProcessBuilder pb = new ProcessBuilder(args);

IOUtils.exec(pb);
}
Expand Down
Expand Up @@ -51,7 +51,7 @@
import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE;
import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN;
import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEY_USER;
import static jdk.jpackage.internal.MacAppImageBuilder.APP_STORE;
import static jdk.jpackage.internal.StandardBundlerParam.APP_STORE;
import static jdk.jpackage.internal.MacAppImageBuilder.MAC_CF_BUNDLE_IDENTIFIER;
import static jdk.jpackage.internal.OverridableResource.createResource;
import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR;
Expand Down
Expand Up @@ -43,6 +43,7 @@ error.no.xcode.signing=Xcode with command line developer tools is required for s
error.no.xcode.signing.advice=Install Xcode with command line developer tools.
error.cert.not.found=No certificate found matching [{0}] using keychain [{1}]
error.multiple.certs.found=WARNING: Multiple certificates found matching [{0}] using keychain [{1}], using first one
error.app-image.mac-sign.required=Error: --mac-sign option is required with predefined application image and with type [app-image]
resource.bundle-config-file=Bundle config file
resource.app-info-plist=Application Info.plist
resource.runtime-info-plist=Java Runtime Info.plist
Expand Down
Expand Up @@ -43,6 +43,7 @@ error.no.xcode.signing=F\u00FCr die Signatur ist Xcode mit Befehlszeilen-Entwick
error.no.xcode.signing.advice=Installieren Sie Xcode mit Befehlszeilen-Entwicklertools.
error.cert.not.found=Kein Zertifikat gefunden, das [{0}] mit Schl\u00FCsselbund [{1}] entspricht
error.multiple.certs.found=WARNUNG: Mehrere Zertifikate gefunden, die [{0}] mit Schl\u00FCsselbund [{1}] entsprechen. Es wird das erste Zertifikat verwendet
error.app-image.mac-sign.required=Error: --mac-sign option is required with predefined application image and with type [app-image]
resource.bundle-config-file=Bundle-Konfigurationsdatei
resource.app-info-plist=Info.plist der Anwendung
resource.runtime-info-plist=Info.plist der Java-Laufzeit
Expand Down
Expand Up @@ -43,6 +43,7 @@ error.no.xcode.signing=\u7F72\u540D\u306B\u306F\u3001Xcode\u3068\u30B3\u30DE\u30
error.no.xcode.signing.advice=Xcode\u3068\u30B3\u30DE\u30F3\u30C9\u30E9\u30A4\u30F3\u30FB\u30C7\u30D9\u30ED\u30C3\u30D1\u30FB\u30C4\u30FC\u30EB\u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3057\u3066\u304F\u3060\u3055\u3044\u3002
error.cert.not.found=\u30AD\u30FC\u30C1\u30A7\u30FC\u30F3[{1}]\u3092\u4F7F\u7528\u3059\u308B[{0}]\u3068\u4E00\u81F4\u3059\u308B\u8A3C\u660E\u66F8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093
error.multiple.certs.found=\u8B66\u544A: \u30AD\u30FC\u30C1\u30A7\u30FC\u30F3[{1}]\u3092\u4F7F\u7528\u3059\u308B[{0}]\u3068\u4E00\u81F4\u3059\u308B\u8907\u6570\u306E\u8A3C\u660E\u66F8\u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F\u3002\u6700\u521D\u306E\u3082\u306E\u3092\u4F7F\u7528\u3057\u307E\u3059
error.app-image.mac-sign.required=Error: --mac-sign option is required with predefined application image and with type [app-image]
resource.bundle-config-file=\u30D0\u30F3\u30C9\u30EB\u69CB\u6210\u30D5\u30A1\u30A4\u30EB
resource.app-info-plist=\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306EInfo.plist
resource.runtime-info-plist=Java\u30E9\u30F3\u30BF\u30A4\u30E0\u306EInfo.plist
Expand Down
Expand Up @@ -43,6 +43,7 @@ error.no.xcode.signing=\u9700\u8981\u4F7F\u7528\u5E26\u547D\u4EE4\u884C\u5F00\u5
error.no.xcode.signing.advice=\u5B89\u88C5\u5E26\u547D\u4EE4\u884C\u5F00\u53D1\u4EBA\u5458\u5DE5\u5177\u7684 Xcode\u3002
error.cert.not.found=\u4F7F\u7528\u5BC6\u94A5\u94FE [{1}] \u627E\u4E0D\u5230\u4E0E [{0}] \u5339\u914D\u7684\u8BC1\u4E66
error.multiple.certs.found=\u8B66\u544A\uFF1A\u4F7F\u7528\u5BC6\u94A5\u94FE [{1}] \u627E\u5230\u591A\u4E2A\u4E0E [{0}] \u5339\u914D\u7684\u8BC1\u4E66\uFF0C\u5C06\u4F7F\u7528\u7B2C\u4E00\u4E2A\u8BC1\u4E66
error.app-image.mac-sign.required=Error: --mac-sign option is required with predefined application image and with type [app-image]
resource.bundle-config-file=\u5305\u914D\u7F6E\u6587\u4EF6
resource.app-info-plist=\u5E94\u7528\u7A0B\u5E8F Info.plist
resource.runtime-info-plist=Java \u8FD0\u884C\u65F6 Info.plist
Expand Down
Expand Up @@ -160,16 +160,28 @@ private Path createAppBundle(Map<String, ? super Object> params,
Path outputDirectory) throws PackagerException, IOException,
ConfigException {

Path rootDirectory = createRoot(params, outputDirectory);
boolean hasAppImage =
PREDEFINED_APP_IMAGE.fetchFrom(params) != null;
boolean hasRuntimeImage =
PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) != null;

Path rootDirectory = hasAppImage ?
PREDEFINED_APP_IMAGE.fetchFrom(params) :
createRoot(params, outputDirectory);

AbstractAppImageBuilder appBuilder = appImageSupplier.apply(rootDirectory);
if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ) {
JLinkBundlerHelper.execute(params,
appBuilder.getAppLayout().runtimeHomeDirectory());
} else {
StandardBundlerParam.copyPredefinedRuntimeImage(
params, appBuilder.getAppLayout());
if (!hasAppImage) {
if (!hasRuntimeImage) {
JLinkBundlerHelper.execute(params,
appBuilder.getAppLayout().runtimeHomeDirectory());
} else {
StandardBundlerParam.copyPredefinedRuntimeImage(
params, appBuilder.getAppLayout());
}
}

appBuilder.prepareApplicationFiles(params);

return rootDirectory;
}

Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022, 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 @@ -49,19 +49,23 @@
import static jdk.jpackage.internal.StandardBundlerParam.VERSION;
import static jdk.jpackage.internal.StandardBundlerParam.ADD_LAUNCHERS;
import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME;
import static jdk.jpackage.internal.StandardBundlerParam.MAIN_CLASS;
import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_AS_SERVICE;
import static jdk.jpackage.internal.StandardBundlerParam.SHORTCUT_HINT;
import static jdk.jpackage.internal.StandardBundlerParam.MENU_HINT;
import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE;
import static jdk.jpackage.internal.StandardBundlerParam.APP_STORE;

public final class AppImageFile {

// These values will be loaded from AppImage xml file.
private final String creatorVersion;
private final String creatorPlatform;
private final String launcherName;
private final String mainClass;
private final List<LauncherInfo> addLauncherInfos;
private final boolean signed;
private final boolean appStore;

private static final String FILENAME = ".jpackage.xml";

Expand All @@ -71,16 +75,19 @@ public final class AppImageFile {


private AppImageFile() {
this(null, null, null, null, null);
this(null, null, null, null, null, null, null);
}

private AppImageFile(String launcherName, List<LauncherInfo> launcherInfos,
String creatorVersion, String creatorPlatform, String signedStr) {
private AppImageFile(String launcherName, String mainClass,
List<LauncherInfo> launcherInfos, String creatorVersion,
String creatorPlatform, String signedStr, String appStoreStr) {
this.launcherName = launcherName;
this.mainClass = mainClass;
this.addLauncherInfos = launcherInfos;
this.creatorVersion = creatorVersion;
this.creatorPlatform = creatorPlatform;
this.signed = "true".equals(signedStr);
this.appStore = "true".equals(appStoreStr);
}

/**
Expand All @@ -99,10 +106,21 @@ String getLauncherName() {
return launcherName;
}

/**
* Returns main class name. Never returns null or empty value.
*/
String getMainClass() {
return mainClass;
}

boolean isSigned() {
return signed;
}

boolean isAppStore() {
return appStore;
}

void verifyCompatible() throws ConfigException {
// Just do nothing for now.
}
Expand Down Expand Up @@ -138,10 +156,18 @@ static void save(Path appImageDir, Map<String, Object> params)
xml.writeCharacters(APP_NAME.fetchFrom(params));
xml.writeEndElement();

xml.writeStartElement("main-class");
xml.writeCharacters(MAIN_CLASS.fetchFrom(params));
xml.writeEndElement();

xml.writeStartElement("signed");
xml.writeCharacters(SIGN_BUNDLE.fetchFrom(params).toString());
xml.writeEndElement();

xml.writeStartElement("app-store");
xml.writeCharacters(APP_STORE.fetchFrom(params).toString());
xml.writeEndElement();

List<Map<String, ? super Object>> addLaunchers =
ADD_LAUNCHERS.fetchFrom(params);

Expand Down Expand Up @@ -176,6 +202,13 @@ static AppImageFile load(Path appImageDir) throws IOException {
return new AppImageFile();
}

String mainClass = xpathQueryNullable(xPath,
"/jpackage-state/main-class/text()", doc);
if (mainClass == null) {
// No main class, this is fatal.
return new AppImageFile();
}

List<LauncherInfo> launcherInfos = new ArrayList<>();

String platform = xpathQueryNullable(xPath,
Expand All @@ -185,7 +218,10 @@ static AppImageFile load(Path appImageDir) throws IOException {
"/jpackage-state/@version", doc);

String signedStr = xpathQueryNullable(xPath,
"/jpackage-state/@signed", doc);
"/jpackage-state/signed/text()", doc);

String appStoreStr = xpathQueryNullable(xPath,
"/jpackage-state/app-store/text()", doc);

NodeList launcherNodes = (NodeList) xPath.evaluate(
"/jpackage-state/add-launcher", doc,
Expand All @@ -195,8 +231,8 @@ static AppImageFile load(Path appImageDir) throws IOException {
launcherInfos.add(new LauncherInfo(launcherNodes.item(i)));
}

AppImageFile file = new AppImageFile(
mainLauncher, launcherInfos, version, platform, signedStr);
AppImageFile file = new AppImageFile(mainLauncher, mainClass,
launcherInfos, version, platform, signedStr, appStoreStr);
if (!file.isValid()) {
file = new AppImageFile();
}
Expand Down Expand Up @@ -275,6 +311,16 @@ public static String extractAppName(Path appImageDir) {
}
}

public static String extractMainClass(Path appImageDir) {
try {
return AppImageFile.load(appImageDir).getMainClass();
} catch (IOException ioe) {
Log.verbose(MessageFormat.format(I18N.getString(
"warning.foreign-app-image"), appImageDir));
return null;
}
}

private static String xpathQueryNullable(XPath xPath, String xpathExpr,
Document xml) throws XPathExpressionException {
NodeList nodes = (NodeList) xPath.evaluate(xpathExpr, xml,
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2022, 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 @@ -590,6 +590,7 @@ private void validateArguments() throws PackagerException {
boolean hasRuntime = allOptions.contains(
CLIOptions.PREDEFINED_RUNTIME_IMAGE);
boolean installerOnly = !imageOnly && hasAppImage;
boolean isMac = Platform.isMac();
runtimeInstaller = !imageOnly && hasRuntime && !hasAppImage &&
!hasMainModule && !hasMainJar;

Expand All @@ -599,11 +600,17 @@ private void validateArguments() throws PackagerException {
throw new PackagerException("ERR_UnsupportedOption",
option.getIdWithPrefix());
}
if (imageOnly) {
if ((imageOnly && !isMac) || (imageOnly && !hasAppImage && isMac)) {
if (!ValidOptions.checkIfImageSupported(option)) {
throw new PackagerException("ERR_InvalidTypeOption",
option.getIdWithPrefix(), type);
}
} else if (imageOnly && hasAppImage && isMac) { // Signing app image
if (!ValidOptions.checkIfSigningSupported(option)) {
throw new PackagerException(
"ERR_InvalidOptionWithAppImageSigning",
option.getIdWithPrefix());
}
} else if (installerOnly || runtimeInstaller) {
if (!ValidOptions.checkIfInstallerSupported(option)) {
if (runtimeInstaller) {
Expand Down Expand Up @@ -642,8 +649,8 @@ private void validateArguments() throws PackagerException {
if (hasMainJar && hasMainModule) {
throw new PackagerException("ERR_BothMainJarAndModule");
}
if (imageOnly && !hasMainJar && !hasMainModule) {
throw new PackagerException("ERR_NoEntryPoint");
if (imageOnly && !hasAppImage && !hasMainJar && !hasMainModule) {
throw new PackagerException("ERR_NoEntryPoint");
}
}

Expand Down

0 comments on commit 7a0c8b1

Please sign in to comment.