package jdk.incubator.jpackage.internal;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.*;
public class MacAppBundler extends AbstractImageBundler {
private static final ResourceBundle I18N = ResourceBundle.getBundle(
"jdk.incubator.jpackage.internal.resources.MacResources");
private static final String TEMPLATE_BUNDLE_ICON = "java.icns";
public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME =
new StandardBundlerParam<>(
Arguments.CLIOptions.MAC_BUNDLE_NAME.getId(),
String.class,
params -> null,
(s, p) -> s);
public static final BundlerParamInfo<String> MAC_CF_BUNDLE_VERSION =
new StandardBundlerParam<>(
"mac.CFBundleVersion",
String.class,
p -> {
String s = VERSION.fetchFrom(p);
if (validCFBundleVersion(s)) {
return s;
} else {
return "100";
}
},
(s, p) -> s);
public static final BundlerParamInfo<String> DEFAULT_ICNS_ICON =
new StandardBundlerParam<>(
".mac.default.icns",
String.class,
params -> TEMPLATE_BUNDLE_ICON,
(s, p) -> s);
public static final BundlerParamInfo<String> DEVELOPER_ID_APP_SIGNING_KEY =
new StandardBundlerParam<>(
"mac.signing-key-developer-id-app",
String.class,
params -> {
String result = MacBaseInstallerBundler.findKey(
"Developer ID Application: "
+ SIGNING_KEY_USER.fetchFrom(params),
SIGNING_KEYCHAIN.fetchFrom(params),
VERBOSE.fetchFrom(params));
if (result != null) {
MacCertificate certificate = new MacCertificate(result);
if (!certificate.isValid()) {
Log.error(MessageFormat.format(I18N.getString(
"error.certificate.expired"), result));
}
}
return result;
},
(s, p) -> s);
public static final BundlerParamInfo<String> BUNDLE_ID_SIGNING_PREFIX =
new StandardBundlerParam<>(
Arguments.CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId(),
String.class,
params -> IDENTIFIER.fetchFrom(params) + ".",
(s, p) -> s);
public static final BundlerParamInfo<File> ICON_ICNS =
new StandardBundlerParam<>(
"icon.icns",
File.class,
params -> {
File f = ICON.fetchFrom(params);
if (f != null && !f.getName().toLowerCase().endsWith(".icns")) {
Log.error(MessageFormat.format(
I18N.getString("message.icon-not-icns"), f));
return null;
}
return f;
},
(s, p) -> new File(s));
public static boolean validCFBundleVersion(String v) {
if (v == null) {
return false;
}
String p[] = v.split("\\.");
if (p.length > 3 || p.length < 1) {
Log.verbose(I18N.getString(
"message.version-string-too-many-components"));
return false;
}
try {
BigInteger n = new BigInteger(p[0]);
if (BigInteger.ONE.compareTo(n) > 0) {
Log.verbose(I18N.getString(
"message.version-string-first-number-not-zero"));
return false;
}
if (p.length > 1) {
n = new BigInteger(p[1]);
if (BigInteger.ZERO.compareTo(n) > 0) {
Log.verbose(I18N.getString(
"message.version-string-no-negative-numbers"));
return false;
}
}
if (p.length > 2) {
n = new BigInteger(p[2]);
if (BigInteger.ZERO.compareTo(n) > 0) {
Log.verbose(I18N.getString(
"message.version-string-no-negative-numbers"));
return false;
}
}
} catch (NumberFormatException ne) {
Log.verbose(I18N.getString("message.version-string-numbers-only"));
Log.verbose(ne);
return false;
}
return true;
}
@Override
public boolean validate(Map<String, ? super Object> params)
throws ConfigException {
try {
return doValidate(params);
} catch (RuntimeException re) {
if (re.getCause() instanceof ConfigException) {
throw (ConfigException) re.getCause();
} else {
throw new ConfigException(re);
}
}
}
private boolean doValidate(Map<String, ? super Object> params)
throws ConfigException {
imageBundleValidation(params);
if (StandardBundlerParam.getPredefinedAppImage(params) != null) {
return true;
}
if (!validCFBundleVersion(MAC_CF_BUNDLE_VERSION.fetchFrom(params))) {
throw new ConfigException(
I18N.getString("error.invalid-cfbundle-version"),
I18N.getString("error.invalid-cfbundle-version.advice"));
}
if (Optional.ofNullable(MacAppImageBuilder.
SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
String signingIdentity =
DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(params);
if (signingIdentity == null) {
throw new ConfigException(
I18N.getString("error.explicit-sign-no-cert"),
I18N.getString("error.explicit-sign-no-cert.advice"));
}
try {
ProcessBuilder pb = new ProcessBuilder("xcrun", "--help");
Process p = pb.start();
int code = p.waitFor();
if (code != 0) {
throw new ConfigException(
I18N.getString("error.no.xcode.signing"),
I18N.getString("error.no.xcode.signing.advice"));
}
} catch (IOException | InterruptedException ex) {
throw new ConfigException(ex);
}
}
return true;
}
File doBundle(Map<String, ? super Object> params, File outputDirectory,
boolean dependentTask) throws PackagerException {
if (StandardBundlerParam.isRuntimeInstaller(params)) {
return PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
} else {
return doAppBundle(params, outputDirectory, dependentTask);
}
}
File doAppBundle(Map<String, ? super Object> params, File outputDirectory,
boolean dependentTask) throws PackagerException {
try {
File rootDirectory = createRoot(params, outputDirectory,
dependentTask, APP_NAME.fetchFrom(params) + ".app");
AbstractAppImageBuilder appBuilder =
new MacAppImageBuilder(params, outputDirectory.toPath());
if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ) {
JLinkBundlerHelper.execute(params, appBuilder);
} else {
StandardBundlerParam.copyPredefinedRuntimeImage(
params, appBuilder);
}
return rootDirectory;
} catch (PackagerException pe) {
throw pe;
} catch (Exception ex) {
Log.verbose(ex);
throw new PackagerException(ex);
}
}
@Override
public String getName() {
return I18N.getString("app.bundler.name");
}
@Override
public String getID() {
return "mac.app";
}
@Override
public String getBundleType() {
return "IMAGE";
}
@Override
public File execute(Map<String, ? super Object> params,
File outputParentDir) throws PackagerException {
return doBundle(params, outputParentDir, false);
}
@Override
public boolean supported(boolean runtimeInstaller) {
return true;
}
@Override
public boolean isDefault() {
return false;
}
}