1 /* 2 * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jpackage.internal; 27 28 import java.io.IOException; 29 import java.io.PrintWriter; 30 import java.net.URI; 31 import java.net.URISyntaxException; 32 import java.nio.file.Files; 33 import java.nio.file.Path; 34 import java.text.MessageFormat; 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Objects; 40 import java.util.Optional; 41 import java.util.ResourceBundle; 42 43 import static jdk.jpackage.internal.StandardBundlerParam.CONFIG_ROOT; 44 import static jdk.jpackage.internal.StandardBundlerParam.TEMP_ROOT; 45 import static jdk.jpackage.internal.StandardBundlerParam.VERBOSE; 46 import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; 47 import static jdk.jpackage.internal.StandardBundlerParam.LICENSE_FILE; 48 import static jdk.jpackage.internal.StandardBundlerParam.VERSION; 49 import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; 50 import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN; 51 import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEY_USER; 52 import static jdk.jpackage.internal.MacAppImageBuilder.APP_STORE; 53 import static jdk.jpackage.internal.MacAppImageBuilder.MAC_CF_BUNDLE_IDENTIFIER; 54 import static jdk.jpackage.internal.OverridableResource.createResource; 55 56 public class MacPkgBundler extends MacBaseInstallerBundler { 57 58 private static final ResourceBundle I18N = ResourceBundle.getBundle( 59 "jdk.jpackage.internal.resources.MacResources"); 60 61 private static final String DEFAULT_BACKGROUND_IMAGE = "background_pkg.png"; 62 private static final String DEFAULT_PDF = "product-def.plist"; 63 64 private static final String TEMPLATE_PREINSTALL_SCRIPT = 65 "preinstall.template"; 66 private static final String TEMPLATE_POSTINSTALL_SCRIPT = 67 "postinstall.template"; 68 69 private static final BundlerParamInfo<Path> PACKAGES_ROOT = 70 new StandardBundlerParam<>( 71 "mac.pkg.packagesRoot", 72 Path.class, 73 params -> { 74 Path packagesRoot = 75 TEMP_ROOT.fetchFrom(params).resolve("packages"); 76 try { 77 Files.createDirectories(packagesRoot); 78 } catch (IOException ioe) { 79 return null; 80 } 81 return packagesRoot; 82 }, 83 (s, p) -> Path.of(s)); 84 85 86 protected final BundlerParamInfo<Path> SCRIPTS_DIR = 87 new StandardBundlerParam<>( 88 "mac.pkg.scriptsDir", 89 Path.class, 90 params -> { 91 Path scriptsDir = 92 CONFIG_ROOT.fetchFrom(params).resolve("scripts"); 93 try { 94 Files.createDirectories(scriptsDir); 95 } catch (IOException ioe) { 96 return null; 97 } 98 return scriptsDir; 99 }, 100 (s, p) -> Path.of(s)); 101 102 public static final 103 BundlerParamInfo<String> DEVELOPER_ID_INSTALLER_SIGNING_KEY = 104 new StandardBundlerParam<>( 105 "mac.signing-key-developer-id-installer", 106 String.class, 107 params -> { 108 String user = SIGNING_KEY_USER.fetchFrom(params); 109 String keychain = SIGNING_KEYCHAIN.fetchFrom(params); 110 String result = null; 111 if (APP_STORE.fetchFrom(params)) { 112 result = MacBaseInstallerBundler.findKey( 113 "3rd Party Mac Developer Installer: ", 114 user, keychain); 115 } 116 // if either not signing for app store or couldn't find 117 if (result == null) { 118 result = MacBaseInstallerBundler.findKey( 119 "Developer ID Installer: ", user, keychain); 120 } 121 122 if (result != null) { 123 MacCertificate certificate = new MacCertificate(result); 124 125 if (!certificate.isValid()) { 126 Log.error(MessageFormat.format( 127 I18N.getString("error.certificate.expired"), 128 result)); 129 } 130 } 131 132 return result; 133 }, 134 (s, p) -> s); 135 136 public static final BundlerParamInfo<String> INSTALLER_SUFFIX = 137 new StandardBundlerParam<> ( 138 "mac.pkg.installerName.suffix", 139 String.class, 140 params -> "", 141 (s, p) -> s); 142 bundle(Map<String, ? super Object> params, Path outdir)143 public Path bundle(Map<String, ? super Object> params, 144 Path outdir) throws PackagerException { 145 Log.verbose(MessageFormat.format(I18N.getString("message.building-pkg"), 146 APP_NAME.fetchFrom(params))); 147 148 IOUtils.writableOutputDir(outdir); 149 150 try { 151 Path appImageDir = prepareAppBundle(params); 152 153 if (appImageDir != null && prepareConfigFiles(params)) { 154 155 Path configScript = getConfig_Script(params); 156 if (IOUtils.exists(configScript)) { 157 IOUtils.run("bash", configScript); 158 } 159 160 return createPKG(params, outdir, appImageDir); 161 } 162 return null; 163 } catch (IOException ex) { 164 Log.verbose(ex); 165 throw new PackagerException(ex); 166 } 167 } 168 getPackages_AppPackage(Map<String, ? super Object> params)169 private Path getPackages_AppPackage(Map<String, ? super Object> params) { 170 return PACKAGES_ROOT.fetchFrom(params).resolve( 171 APP_NAME.fetchFrom(params) + "-app.pkg"); 172 } 173 getConfig_DistributionXMLFile( Map<String, ? super Object> params)174 private Path getConfig_DistributionXMLFile( 175 Map<String, ? super Object> params) { 176 return CONFIG_ROOT.fetchFrom(params).resolve("distribution.dist"); 177 } 178 getConfig_PDF(Map<String, ? super Object> params)179 private Path getConfig_PDF(Map<String, ? super Object> params) { 180 return CONFIG_ROOT.fetchFrom(params).resolve("product-def.plist"); 181 } 182 getConfig_BackgroundImage(Map<String, ? super Object> params)183 private Path getConfig_BackgroundImage(Map<String, ? super Object> params) { 184 return CONFIG_ROOT.fetchFrom(params).resolve( 185 APP_NAME.fetchFrom(params) + "-background.png"); 186 } 187 getConfig_BackgroundImageDarkAqua(Map<String, ? super Object> params)188 private Path getConfig_BackgroundImageDarkAqua(Map<String, ? super Object> params) { 189 return CONFIG_ROOT.fetchFrom(params).resolve( 190 APP_NAME.fetchFrom(params) + "-background-darkAqua.png"); 191 } 192 getScripts_PreinstallFile(Map<String, ? super Object> params)193 private Path getScripts_PreinstallFile(Map<String, ? super Object> params) { 194 return SCRIPTS_DIR.fetchFrom(params).resolve("preinstall"); 195 } 196 getScripts_PostinstallFile( Map<String, ? super Object> params)197 private Path getScripts_PostinstallFile( 198 Map<String, ? super Object> params) { 199 return SCRIPTS_DIR.fetchFrom(params).resolve("postinstall"); 200 } 201 getAppIdentifier(Map<String, ? super Object> params)202 private String getAppIdentifier(Map<String, ? super Object> params) { 203 return MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params); 204 } 205 preparePackageScripts(Map<String, ? super Object> params)206 private void preparePackageScripts(Map<String, ? super Object> params) 207 throws IOException { 208 Log.verbose(I18N.getString("message.preparing-scripts")); 209 210 Map<String, String> data = new HashMap<>(); 211 212 Path appLocation = Path.of(getInstallDir(params, false), 213 APP_NAME.fetchFrom(params) + ".app", "Contents", "app"); 214 215 data.put("INSTALL_LOCATION", getInstallDir(params, false)); 216 data.put("APP_LOCATION", appLocation.toString()); 217 218 createResource(TEMPLATE_PREINSTALL_SCRIPT, params) 219 .setCategory(I18N.getString("resource.pkg-preinstall-script")) 220 .setSubstitutionData(data) 221 .saveToFile(getScripts_PreinstallFile(params)); 222 getScripts_PreinstallFile(params).toFile().setExecutable(true, false); 223 224 createResource(TEMPLATE_POSTINSTALL_SCRIPT, params) 225 .setCategory(I18N.getString("resource.pkg-postinstall-script")) 226 .setSubstitutionData(data) 227 .saveToFile(getScripts_PostinstallFile(params)); 228 getScripts_PostinstallFile(params).toFile().setExecutable(true, false); 229 } 230 URLEncoding(String pkgName)231 private static String URLEncoding(String pkgName) throws URISyntaxException { 232 URI uri = new URI(null, null, pkgName, null); 233 return uri.toASCIIString(); 234 } 235 prepareDistributionXMLFile(Map<String, ? super Object> params)236 private void prepareDistributionXMLFile(Map<String, ? super Object> params) 237 throws IOException { 238 Path f = getConfig_DistributionXMLFile(params); 239 240 Log.verbose(MessageFormat.format(I18N.getString( 241 "message.preparing-distribution-dist"), f.toAbsolutePath().toString())); 242 243 IOUtils.createXml(f, xml -> { 244 xml.writeStartElement("installer-gui-script"); 245 xml.writeAttribute("minSpecVersion", "1"); 246 247 xml.writeStartElement("title"); 248 xml.writeCharacters(APP_NAME.fetchFrom(params)); 249 xml.writeEndElement(); 250 251 xml.writeStartElement("background"); 252 xml.writeAttribute("file", 253 getConfig_BackgroundImage(params).getFileName().toString()); 254 xml.writeAttribute("mime-type", "image/png"); 255 xml.writeAttribute("alignment", "bottomleft"); 256 xml.writeAttribute("scaling", "none"); 257 xml.writeEndElement(); 258 259 xml.writeStartElement("background-darkAqua"); 260 xml.writeAttribute("file", 261 getConfig_BackgroundImageDarkAqua(params).getFileName().toString()); 262 xml.writeAttribute("mime-type", "image/png"); 263 xml.writeAttribute("alignment", "bottomleft"); 264 xml.writeAttribute("scaling", "none"); 265 xml.writeEndElement(); 266 267 String licFileStr = LICENSE_FILE.fetchFrom(params); 268 if (licFileStr != null) { 269 Path licFile = Path.of(licFileStr); 270 xml.writeStartElement("license"); 271 xml.writeAttribute("file", licFile.toAbsolutePath().toString()); 272 xml.writeAttribute("mime-type", "text/rtf"); 273 xml.writeEndElement(); 274 } 275 276 /* 277 * Note that the content of the distribution file 278 * below is generated by productbuild --synthesize 279 */ 280 String appId = getAppIdentifier(params); 281 282 xml.writeStartElement("pkg-ref"); 283 xml.writeAttribute("id", appId); 284 xml.writeEndElement(); // </pkg-ref> 285 xml.writeStartElement("options"); 286 xml.writeAttribute("customize", "never"); 287 xml.writeAttribute("require-scripts", "false"); 288 xml.writeAttribute("hostArchitectures", 289 Platform.isArmMac() ? "arm64" : "x86_64"); 290 xml.writeEndElement(); // </options> 291 xml.writeStartElement("choices-outline"); 292 xml.writeStartElement("line"); 293 xml.writeAttribute("choice", "default"); 294 xml.writeStartElement("line"); 295 xml.writeAttribute("choice", appId); 296 xml.writeEndElement(); // </line> 297 xml.writeEndElement(); // </line> 298 xml.writeEndElement(); // </choices-outline> 299 xml.writeStartElement("choice"); 300 xml.writeAttribute("id", "default"); 301 xml.writeEndElement(); // </choice> 302 xml.writeStartElement("choice"); 303 xml.writeAttribute("id", appId); 304 xml.writeAttribute("visible", "false"); 305 xml.writeStartElement("pkg-ref"); 306 xml.writeAttribute("id", appId); 307 xml.writeEndElement(); // </pkg-ref> 308 xml.writeEndElement(); // </choice> 309 xml.writeStartElement("pkg-ref"); 310 xml.writeAttribute("id", appId); 311 xml.writeAttribute("version", VERSION.fetchFrom(params)); 312 xml.writeAttribute("onConclusion", "none"); 313 try { 314 xml.writeCharacters(URLEncoding( 315 getPackages_AppPackage(params).getFileName().toString())); 316 } catch (URISyntaxException ex) { 317 throw new IOException(ex); 318 } 319 xml.writeEndElement(); // </pkg-ref> 320 321 xml.writeEndElement(); // </installer-gui-script> 322 }); 323 } 324 prepareConfigFiles(Map<String, ? super Object> params)325 private boolean prepareConfigFiles(Map<String, ? super Object> params) 326 throws IOException { 327 328 createResource(DEFAULT_BACKGROUND_IMAGE, params) 329 .setCategory(I18N.getString("resource.pkg-background-image")) 330 .saveToFile(getConfig_BackgroundImage(params)); 331 332 createResource(DEFAULT_BACKGROUND_IMAGE, params) 333 .setCategory(I18N.getString("resource.pkg-background-image")) 334 .saveToFile(getConfig_BackgroundImageDarkAqua(params)); 335 336 createResource(DEFAULT_PDF, params) 337 .setCategory(I18N.getString("resource.pkg-pdf")) 338 .saveToFile(getConfig_PDF(params)); 339 340 prepareDistributionXMLFile(params); 341 342 createResource(null, params) 343 .setCategory(I18N.getString("resource.post-install-script")) 344 .saveToFile(getConfig_Script(params)); 345 346 return true; 347 } 348 349 // name of post-image script getConfig_Script(Map<String, ? super Object> params)350 private Path getConfig_Script(Map<String, ? super Object> params) { 351 return CONFIG_ROOT.fetchFrom(params).resolve( 352 APP_NAME.fetchFrom(params) + "-post-image.sh"); 353 } 354 patchCPLFile(Path cpl)355 private void patchCPLFile(Path cpl) throws IOException { 356 String cplData = Files.readString(cpl); 357 String[] lines = cplData.split("\n"); 358 try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(cpl))) { 359 int skip = 0; 360 // Used to skip Java.runtime bundle, since 361 // pkgbuild with --root will find two bundles app and Java runtime. 362 // We cannot generate component proprty list when using 363 // --component argument. 364 for (int i = 0; i < lines.length; i++) { 365 if (lines[i].trim().equals("<key>BundleIsRelocatable</key>")) { 366 out.println(lines[i]); 367 out.println("<false/>"); 368 i++; 369 } else if (lines[i].trim().equals("<key>ChildBundles</key>")) { 370 ++skip; 371 } else if ((skip > 0) && lines[i].trim().equals("</array>")) { 372 --skip; 373 } else { 374 if (skip == 0) { 375 out.println(lines[i]); 376 } 377 } 378 } 379 } 380 } 381 382 // pkgbuild includes all components from "--root" and subfolders, 383 // so if we have app image in folder which contains other images, then they 384 // will be included as well. It does have "--filter" option which use regex 385 // to exclude files/folder, but it will overwrite default one which excludes 386 // based on doc "any .svn or CVS directories, and any .DS_Store files". 387 // So easy aproach will be to copy user provided app-image into temp folder 388 // if root path contains other files. getRoot(Map<String, ? super Object> params, Path appLocation)389 private String getRoot(Map<String, ? super Object> params, 390 Path appLocation) throws IOException { 391 Path rootDir = appLocation.getParent() == null ? 392 Path.of(".") : appLocation.getParent(); 393 394 // Not needed for runtime installer and it might break runtime installer 395 // if parent does not have any other files 396 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 397 try (var fileList = Files.list(rootDir)) { 398 Path[] list = fileList.toArray(Path[]::new); 399 // We should only have app image and/or .DS_Store 400 if (list.length == 1) { 401 return rootDir.toString(); 402 } else if (list.length == 2) { 403 // Check case with app image and .DS_Store 404 if (list[0].toString().toLowerCase().endsWith(".ds_store") || 405 list[1].toString().toLowerCase().endsWith(".ds_store")) { 406 return rootDir.toString(); // Only app image and .DS_Store 407 } 408 } 409 } 410 } 411 412 // Copy to new root 413 Path newRoot = Files.createTempDirectory( 414 TEMP_ROOT.fetchFrom(params), "root-"); 415 416 Path source, dest; 417 418 if (StandardBundlerParam.isRuntimeInstaller(params)) { 419 // firs, is this already a runtime with 420 // <runtime>/Contents/Home - if so we need the Home dir 421 Path original = appLocation; 422 Path home = original.resolve("Contents/Home"); 423 source = (Files.exists(home)) ? home : original; 424 425 // Then we need to put back the <NAME>/Content/Home 426 dest = newRoot.resolve( 427 MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params) + "/Contents/Home"); 428 } else { 429 source = appLocation; 430 dest = newRoot.resolve(appLocation.getFileName()); 431 } 432 IOUtils.copyRecursive(source, dest); 433 434 return newRoot.toString(); 435 } 436 createPKG(Map<String, ? super Object> params, Path outdir, Path appLocation)437 private Path createPKG(Map<String, ? super Object> params, 438 Path outdir, Path appLocation) { 439 // generic find attempt 440 try { 441 Path appPKG = getPackages_AppPackage(params); 442 443 String root = getRoot(params, appLocation); 444 445 // Generate default CPL file 446 Path cpl = CONFIG_ROOT.fetchFrom(params).resolve("cpl.plist"); 447 ProcessBuilder pb = new ProcessBuilder("/usr/bin/pkgbuild", 448 "--root", 449 root, 450 "--install-location", 451 getInstallDir(params, false), 452 "--analyze", 453 cpl.toAbsolutePath().toString()); 454 455 IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT); 456 457 patchCPLFile(cpl); 458 459 // build application package 460 if (APP_STORE.fetchFrom(params)) { 461 pb = new ProcessBuilder("/usr/bin/pkgbuild", 462 "--root", 463 root, 464 "--install-location", 465 getInstallDir(params, false), 466 "--component-plist", 467 cpl.toAbsolutePath().toString(), 468 "--identifier", 469 MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params), 470 appPKG.toAbsolutePath().toString()); 471 IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT); 472 } else { 473 preparePackageScripts(params); 474 pb = new ProcessBuilder("/usr/bin/pkgbuild", 475 "--root", 476 root, 477 "--install-location", 478 getInstallDir(params, false), 479 "--component-plist", 480 cpl.toAbsolutePath().toString(), 481 "--scripts", 482 SCRIPTS_DIR.fetchFrom(params) 483 .toAbsolutePath().toString(), 484 "--identifier", 485 MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params), 486 appPKG.toAbsolutePath().toString()); 487 IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT); 488 } 489 490 // build final package 491 Path finalPKG = outdir.resolve(MAC_INSTALLER_NAME.fetchFrom(params) 492 + INSTALLER_SUFFIX.fetchFrom(params) 493 + ".pkg"); 494 Files.createDirectories(outdir); 495 496 List<String> commandLine = new ArrayList<>(); 497 commandLine.add("/usr/bin/productbuild"); 498 499 commandLine.add("--resources"); 500 commandLine.add(CONFIG_ROOT.fetchFrom(params).toAbsolutePath().toString()); 501 502 // maybe sign 503 if (Optional.ofNullable( 504 SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) { 505 if (Platform.getMajorVersion() > 10 || 506 (Platform.getMajorVersion() == 10 && 507 Platform.getMinorVersion() >= 12)) { 508 // we need this for OS X 10.12+ 509 Log.verbose(I18N.getString("message.signing.pkg")); 510 } 511 512 String signingIdentity = 513 DEVELOPER_ID_INSTALLER_SIGNING_KEY.fetchFrom(params); 514 if (signingIdentity != null) { 515 commandLine.add("--sign"); 516 commandLine.add(signingIdentity); 517 } 518 519 String keychainName = SIGNING_KEYCHAIN.fetchFrom(params); 520 if (keychainName != null && !keychainName.isEmpty()) { 521 commandLine.add("--keychain"); 522 commandLine.add(keychainName); 523 } 524 } 525 526 if (APP_STORE.fetchFrom(params)) { 527 commandLine.add("--product"); 528 commandLine.add(getConfig_PDF(params) 529 .toAbsolutePath().toString()); 530 commandLine.add("--component"); 531 Path p = Path.of(root, APP_NAME.fetchFrom(params) + ".app"); 532 commandLine.add(p.toAbsolutePath().toString()); 533 commandLine.add(getInstallDir(params, false)); 534 } else { 535 commandLine.add("--distribution"); 536 commandLine.add(getConfig_DistributionXMLFile(params) 537 .toAbsolutePath().toString()); 538 commandLine.add("--package-path"); 539 commandLine.add(PACKAGES_ROOT.fetchFrom(params) 540 .toAbsolutePath().toString()); 541 } 542 commandLine.add(finalPKG.toAbsolutePath().toString()); 543 544 pb = new ProcessBuilder(commandLine); 545 IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT); 546 547 return finalPKG; 548 } catch (Exception ignored) { 549 Log.verbose(ignored); 550 return null; 551 } 552 } 553 554 ////////////////////////////////////////////////////////////////////////// 555 // Implement Bundler 556 ////////////////////////////////////////////////////////////////////////// 557 558 @Override getName()559 public String getName() { 560 return I18N.getString("pkg.bundler.name"); 561 } 562 563 @Override getID()564 public String getID() { 565 return "pkg"; 566 } 567 isValidBundleIdentifier(String id)568 private static boolean isValidBundleIdentifier(String id) { 569 for (int i = 0; i < id.length(); i++) { 570 char a = id.charAt(i); 571 // We check for ASCII codes first which we accept. If check fails, 572 // check if it is acceptable extended ASCII or unicode character. 573 if ((a >= 'A' && a <= 'Z') || (a >= 'a' && a <= 'z') 574 || (a >= '0' && a <= '9') || (a == '-' || a == '.')) { 575 continue; 576 } 577 return false; 578 } 579 return true; 580 } 581 582 @Override validate(Map<String, ? super Object> params)583 public boolean validate(Map<String, ? super Object> params) 584 throws ConfigException { 585 try { 586 Objects.requireNonNull(params); 587 588 // run basic validation to ensure requirements are met 589 // we are not interested in return code, only possible exception 590 validateAppImageAndBundeler(params); 591 592 String identifier = MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params); 593 if (identifier == null) { 594 throw new ConfigException( 595 I18N.getString("message.app-image-requires-identifier"), 596 I18N.getString( 597 "message.app-image-requires-identifier.advice")); 598 } 599 if (!isValidBundleIdentifier(identifier)) { 600 throw new ConfigException( 601 MessageFormat.format(I18N.getString( 602 "message.invalid-identifier"), identifier), 603 I18N.getString("message.invalid-identifier.advice")); 604 } 605 606 // reject explicitly set sign to true and no valid signature key 607 if (Optional.ofNullable( 608 SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) { 609 String signingIdentity = 610 DEVELOPER_ID_INSTALLER_SIGNING_KEY.fetchFrom(params); 611 if (signingIdentity == null) { 612 throw new ConfigException( 613 I18N.getString("error.explicit-sign-no-cert"), 614 I18N.getString( 615 "error.explicit-sign-no-cert.advice")); 616 } 617 } 618 619 // hdiutil is always available so there's no need 620 // to test for availability. 621 622 return true; 623 } catch (RuntimeException re) { 624 if (re.getCause() instanceof ConfigException) { 625 throw (ConfigException) re.getCause(); 626 } else { 627 throw new ConfigException(re); 628 } 629 } 630 } 631 632 @Override execute(Map<String, ? super Object> params, Path outputParentDir)633 public Path execute(Map<String, ? super Object> params, 634 Path outputParentDir) throws PackagerException { 635 return bundle(params, outputParentDir); 636 } 637 638 @Override supported(boolean runtimeInstaller)639 public boolean supported(boolean runtimeInstaller) { 640 return true; 641 } 642 643 @Override isDefault()644 public boolean isDefault() { 645 return false; 646 } 647 648 } 649