1 /* 2 * Copyright (c) 2012, 2015, 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 package com.oracle.tools.packager.mac; 26 27 import com.oracle.tools.packager.AbstractImageBundler; 28 import com.oracle.tools.packager.BundlerParamInfo; 29 import com.oracle.tools.packager.EnumeratedBundlerParam; 30 import com.oracle.tools.packager.JreUtils; 31 import com.oracle.tools.packager.JreUtils.Rule; 32 import com.oracle.tools.packager.StandardBundlerParam; 33 import com.oracle.tools.packager.Log; 34 import com.sun.javafx.tools.packager.bundlers.BundleParams; 35 import com.oracle.tools.packager.ConfigException; 36 import com.oracle.tools.packager.IOUtils; 37 import com.oracle.tools.packager.RelativeFileSet; 38 import com.oracle.tools.packager.UnsupportedPlatformException; 39 40 import java.io.*; 41 import java.math.BigInteger; 42 import java.net.MalformedURLException; 43 import java.net.URL; 44 import java.nio.file.Files; 45 import java.nio.file.Paths; 46 import java.text.MessageFormat; 47 import java.util.*; 48 import java.util.regex.Matcher; 49 import java.util.regex.Pattern; 50 51 import static com.oracle.tools.packager.StandardBundlerParam.*; 52 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.SIGNING_KEYCHAIN; 53 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.SIGNING_KEY_USER; 54 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.getPredefinedImage; 55 56 public class MacAppBundler extends AbstractImageBundler { 57 58 private static final ResourceBundle I18N = 59 ResourceBundle.getBundle(MacAppBundler.class.getName()); 60 61 public final static String MAC_BUNDLER_PREFIX = 62 BUNDLER_PREFIX + "macosx" + File.separator; 63 64 private static final String EXECUTABLE_NAME = "JavaAppLauncher"; 65 private final static String LIBRARY_NAME = "libpackager.dylib"; 66 private static final String TEMPLATE_BUNDLE_ICON = "GenericApp.icns"; 67 private static final String OS_TYPE_CODE = "APPL"; 68 private static final String TEMPLATE_INFO_PLIST_LEGACY = "Info.plist.template"; 69 private static final String TEMPLATE_INFO_PLIST_LITE = "Info-lite.plist.template"; 70 71 private static Map<String, String> getMacCategories() { 72 Map<String, String> map = new HashMap<>(); 73 map.put("Business", "public.app-category.business"); 74 map.put("Developer Tools", "public.app-category.developer-tools"); 75 map.put("Education", "public.app-category.education"); 76 map.put("Entertainment", "public.app-category.entertainment"); 77 map.put("Finance", "public.app-category.finance"); 78 map.put("Games", "public.app-category.games"); 79 map.put("Graphics & Design", "public.app-category.graphics-design"); 80 map.put("Healthcare & Fitness", "public.app-category.healthcare-fitness"); 81 map.put("Lifestyle", "public.app-category.lifestyle"); 82 map.put("Medical", "public.app-category.medical"); 83 map.put("Music", "public.app-category.music"); 84 map.put("News", "public.app-category.news"); 85 map.put("Photography", "public.app-category.photography"); 86 map.put("Productivity", "public.app-category.productivity"); 87 map.put("Reference", "public.app-category.reference"); 88 map.put("Social Networking", "public.app-category.social-networking"); 89 map.put("Sports", "public.app-category.sports"); 90 map.put("Travel", "public.app-category.travel"); 91 map.put("Utilities", "public.app-category.utilities"); 92 map.put("Video", "public.app-category.video"); 93 map.put("Weather", "public.app-category.weather"); 94 95 map.put("Action Games", "public.app-category.action-games"); 96 map.put("Adventure Games", "public.app-category.adventure-games"); 97 map.put("Arcade Games", "public.app-category.arcade-games"); 98 map.put("Board Games", "public.app-category.board-games"); 99 map.put("Card Games", "public.app-category.card-games"); 100 map.put("Casino Games", "public.app-category.casino-games"); 101 map.put("Dice Games", "public.app-category.dice-games"); 102 map.put("Educational Games", "public.app-category.educational-games"); 103 map.put("Family Games", "public.app-category.family-games"); 104 map.put("Kids Games", "public.app-category.kids-games"); 105 map.put("Music Games", "public.app-category.music-games"); 106 map.put("Puzzle Games", "public.app-category.puzzle-games"); 107 map.put("Racing Games", "public.app-category.racing-games"); 108 map.put("Role Playing Games", "public.app-category.role-playing-games"); 109 map.put("Simulation Games", "public.app-category.simulation-games"); 110 map.put("Sports Games", "public.app-category.sports-games"); 111 map.put("Strategy Games", "public.app-category.strategy-games"); 112 map.put("Trivia Games", "public.app-category.trivia-games"); 113 map.put("Word Games", "public.app-category.word-games"); 114 115 return map; 116 } 117 118 public static final BundlerParamInfo<Boolean> MAC_CONFIGURE_LAUNCHER_IN_PLIST = 119 new StandardBundlerParam<>( 120 I18N.getString("param.configure-launcher-in-plist"), 121 I18N.getString("param.configure-launcher-in-plist.description"), 122 "mac.configure-launcher-in-plist", 123 Boolean.class, 124 params -> Boolean.FALSE, 125 (s, p) -> Boolean.valueOf(s)); 126 127 public static final EnumeratedBundlerParam<String> MAC_CATEGORY = 128 new EnumeratedBundlerParam<>( 129 I18N.getString("param.category-name"), 130 I18N.getString("param.category-name.description"), 131 "mac.category", 132 String.class, 133 params -> params.containsKey(CATEGORY.getID()) 134 ? CATEGORY.fetchFrom(params) 135 : "Unknown", 136 (s, p) -> s, 137 getMacCategories(), 138 false //strict - for MacStoreBundler this should be strict 139 ); 140 141 public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME = 142 new StandardBundlerParam<>( 143 I18N.getString("param.cfbundle-name.name"), 144 I18N.getString("param.cfbundle-name.description"), 145 "mac.CFBundleName", 146 String.class, 147 params -> null, 148 (s, p) -> s); 149 150 public static final BundlerParamInfo<String> MAC_CF_BUNDLE_IDENTIFIER = 151 new StandardBundlerParam<>( 152 I18N.getString("param.cfbundle-identifier.name"), 153 I18N.getString("param.cfbundle-identifier.description"), 154 "mac.CFBundleIdentifier", 155 String.class, 156 IDENTIFIER::fetchFrom, 157 (s, p) -> s); 158 159 public static final BundlerParamInfo<String> MAC_CF_BUNDLE_VERSION = 160 new StandardBundlerParam<>( 161 I18N.getString("param.cfbundle-version.name"), 162 I18N.getString("param.cfbundle-version.description"), 163 "mac.CFBundleVersion", 164 String.class, 165 p -> { 166 String s = VERSION.fetchFrom(p); 167 if (validCFBundleVersion(s)) { 168 return s; 169 } else { 170 return "100"; 171 } 172 }, 173 (s, p) -> s); 174 175 public static final BundlerParamInfo<File> CONFIG_ROOT = new StandardBundlerParam<>( 176 I18N.getString("param.config-root.name"), 177 I18N.getString("param.config-root.description"), 178 "configRoot", 179 File.class, 180 params -> { 181 File configRoot = new File(BUILD_ROOT.fetchFrom(params), "macosx"); 182 configRoot.mkdirs(); 183 return configRoot; 184 }, 185 (s, p) -> new File(s)); 186 187 public static final BundlerParamInfo<URL> RAW_EXECUTABLE_URL = new StandardBundlerParam<>( 188 I18N.getString("param.raw-executable-url.name"), 189 I18N.getString("param.raw-executable-url.description"), 190 "mac.launcher.url", 191 URL.class, 192 params -> MacResources.class.getResource(EXECUTABLE_NAME), 193 (s, p) -> { 194 try { 195 return new URL(s); 196 } catch (MalformedURLException e) { 197 Log.info(e.toString()); 198 return null; 199 } 200 }); 201 202 public static final BundlerParamInfo<String> DEFAULT_ICNS_ICON = new StandardBundlerParam<>( 203 I18N.getString("param.default-icon-icns"), 204 I18N.getString("param.default-icon-icns.description"), 205 ".mac.default.icns", 206 String.class, 207 params -> TEMPLATE_BUNDLE_ICON, 208 (s, p) -> s); 209 210 public static final BundlerParamInfo<Rule[]> MAC_RULES = new StandardBundlerParam<>( 211 "", 212 "", 213 ".mac.runtime.rules", 214 Rule[].class, 215 MacAppBundler::createMacRuntimeRules, 216 (s, p) -> null 217 ); 218 219 public static final BundlerParamInfo<RelativeFileSet> MAC_RUNTIME = new StandardBundlerParam<>( 220 I18N.getString("param.runtime.name"), 221 I18N.getString("param.runtime.description"), 222 BundleParams.PARAM_RUNTIME, 223 RelativeFileSet.class, 224 params -> extractMacRuntime(System.getProperty("java.home"), params), 225 MacAppBundler::extractMacRuntime 226 ); 227 228 public static final BundlerParamInfo<String> DEVELOPER_ID_APP_SIGNING_KEY = new StandardBundlerParam<>( 229 I18N.getString("param.signing-key-developer-id-app.name"), 230 I18N.getString("param.signing-key-developer-id-app.description"), 231 "mac.signing-key-developer-id-app", 232 String.class, 233 params -> MacBaseInstallerBundler.findKey("Developer ID Application: " + SIGNING_KEY_USER.fetchFrom(params), SIGNING_KEYCHAIN.fetchFrom(params), VERBOSE.fetchFrom(params)), 234 (s, p) -> s); 235 236 public static final BundlerParamInfo<String> BUNDLE_ID_SIGNING_PREFIX = new StandardBundlerParam<>( 237 I18N.getString("param.bundle-id-signing-prefix.name"), 238 I18N.getString("param.bundle-id-signing-prefix.description"), 239 "mac.bundle-id-signing-prefix", 240 String.class, 241 params -> IDENTIFIER.fetchFrom(params) + ".", 242 (s, p) -> s); 243 244 public static final BundlerParamInfo<File> ICON_ICNS = new StandardBundlerParam<>( 245 I18N.getString("param.icon-icns.name"), 246 I18N.getString("param.icon-icns.description"), 247 "icon.icns", 248 File.class, 249 params -> { 250 File f = ICON.fetchFrom(params); 251 if (f != null && !f.getName().toLowerCase().endsWith(".icns")) { 252 Log.info(MessageFormat.format(I18N.getString("message.icon-not-icns"), f)); 253 return null; 254 } 255 return f; 256 }, 257 (s, p) -> new File(s)); 258 259 public static RelativeFileSet extractMacRuntime(String base, Map<String, ? super Object> params) { 260 if (base.isEmpty()) { 261 return null; 262 } 263 264 File workingBase = new File(base); 265 workingBase = workingBase.getAbsoluteFile(); 266 try { 267 workingBase = workingBase.getCanonicalFile(); 268 } catch (IOException ignore) { 269 // we tried, workingBase will remain absolute and not canonical. 270 } 271 272 if (workingBase.getName().equals("jre")) { 273 workingBase = workingBase.getParentFile(); 274 } 275 if (workingBase.getName().equals("Home")) { 276 workingBase = workingBase.getParentFile(); 277 } 278 if (workingBase.getName().equals("Contents")) { 279 workingBase = workingBase.getParentFile(); 280 } 281 return JreUtils.extractJreAsRelativeFileSet(workingBase.toString(), 282 MAC_RULES.fetchFrom(params), true); 283 } 284 285 public MacAppBundler() { 286 super(); 287 baseResourceLoader = MacResources.class; 288 } 289 290 @Override 291 protected String getCacheLocation(Map<String, ? super Object> params) { 292 return "$CACHEDIR/"; 293 } 294 295 296 public static boolean validCFBundleVersion(String v) { 297 // CFBundleVersion (String - iOS, OS X) specifies the build version 298 // number of the bundle, which identifies an iteration (released or 299 // unreleased) of the bundle. The build version number should be a 300 // string comprised of three non-negative, period-separated integers 301 // with the first integer being greater than zero. The string should 302 // only contain numeric (0-9) and period (.) characters. Leading zeros 303 // are truncated from each integer and will be ignored (that is, 304 // 1.02.3 is equivalent to 1.2.3). This key is not localizable. 305 306 if (v == null) { 307 return false; 308 } 309 310 String p[] = v.split("\\."); 311 if (p.length > 3 || p.length < 1) { 312 Log.verbose(I18N.getString("message.version-string-too-many-components")); 313 return false; 314 } 315 316 try { 317 BigInteger n = new BigInteger(p[0]); 318 if (BigInteger.ONE.compareTo(n) > 0) { 319 Log.verbose(I18N.getString("message.version-string-first-number-not-zero")); 320 return false; 321 } 322 if (p.length > 1) { 323 n = new BigInteger(p[1]); 324 if (BigInteger.ZERO.compareTo(n) > 0) { 325 Log.verbose(I18N.getString("message.version-string-no-negative-numbers")); 326 return false; 327 } 328 } 329 if (p.length > 2) { 330 n = new BigInteger(p[2]); 331 if (BigInteger.ZERO.compareTo(n) > 0) { 332 Log.verbose(I18N.getString("message.version-string-no-negative-numbers")); 333 return false; 334 } 335 } 336 } catch (NumberFormatException ne) { 337 Log.verbose(I18N.getString("message.version-string-numbers-only")); 338 Log.verbose(ne); 339 return false; 340 } 341 342 return true; 343 } 344 345 @Override 346 public boolean validate(Map<String, ? super Object> params) throws UnsupportedPlatformException, ConfigException { 347 try { 348 return doValidate(params); 349 } catch (RuntimeException re) { 350 if (re.getCause() instanceof ConfigException) { 351 throw (ConfigException) re.getCause(); 352 } else { 353 throw new ConfigException(re); 354 } 355 } 356 } 357 358 //to be used by chained bundlers, e.g. by EXE bundler to avoid 359 // skipping validation if p.type does not include "image" 360 public boolean doValidate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException { 361 if (!System.getProperty("os.name").toLowerCase().contains("os x")) { 362 throw new UnsupportedPlatformException(); 363 } 364 365 imageBundleValidation(p); 366 367 if (getPredefinedImage(p) != null) { 368 return true; 369 } 370 371 // make sure we are pointing at the right JDK. 372 RelativeFileSet runtime = MAC_RUNTIME.fetchFrom(p); 373 if (runtime != null) { 374 runtime = new RelativeFileSet(runtime); 375 if ("jre".equals(runtime.getBaseDirectory().getName())) { 376 runtime.upshift(); 377 } 378 if ("Home".equals(runtime.getBaseDirectory().getName())) { 379 runtime.upshift(); 380 } 381 if ("Contents".equals(runtime.getBaseDirectory().getName())) { 382 runtime.upshift(); 383 } 384 } 385 386 //validate required inputs 387 testRuntime(runtime, new String[] { 388 "Contents/Home/(jre/)?lib/[^/]+/libjvm.dylib", // most reliable 389 "Contents/Home/(jre/)?lib/rt.jar", // fallback canary for JDK 8 390 }); 391 if (USE_FX_PACKAGING.fetchFrom(p)) { 392 testRuntime(runtime, new String[] {"Contents/Home/(jre/)?lib/ext/jfxrt.jar", "Contents/Home/(jre/)?lib/jfxrt.jar"}); 393 } 394 395 // validate short version 396 if (!validCFBundleVersion(MAC_CF_BUNDLE_VERSION.fetchFrom(p))) { 397 throw new ConfigException( 398 I18N.getString("error.invalid-cfbundle-version"), 399 I18N.getString("error.invalid-cfbundle-version.advice")); 400 } 401 402 // reject explicitly set sign to true and no valid signature key 403 if (Optional.ofNullable(SIGN_BUNDLE.fetchFrom(p)).orElse(Boolean.FALSE)) { 404 String signingIdentity = DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(p); 405 if (signingIdentity == null) { 406 throw new ConfigException( 407 I18N.getString("error.explicit-sign-no-cert"), 408 I18N.getString("error.explicit-sign-no-cert.advice")); 409 } 410 } 411 412 return true; 413 } 414 415 private File getConfig_InfoPlist(Map<String, ? super Object> params) { 416 return new File(CONFIG_ROOT.fetchFrom(params), "Info.plist"); 417 } 418 419 private File getConfig_Icon(Map<String, ? super Object> params) { 420 return new File(CONFIG_ROOT.fetchFrom(params), APP_NAME.fetchFrom(params) + ".icns"); 421 } 422 423 private void prepareConfigFiles(Map<String, ? super Object> params) throws IOException { 424 File infoPlistFile = getConfig_InfoPlist(params); 425 infoPlistFile.createNewFile(); 426 writeInfoPlist(infoPlistFile, params); 427 428 // Copy icon to Resources folder 429 prepareIcon(params); 430 } 431 432 public File doBundle(Map<String, ? super Object> p, File outputDirectory, boolean dependentTask) { 433 File rootDirectory = null; 434 Map<String, ? super Object> originalParams = new HashMap<>(p); 435 436 if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) { 437 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), outputDirectory.getAbsolutePath())); 438 } 439 if (!outputDirectory.canWrite()) { 440 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), outputDirectory.getAbsolutePath())); 441 } 442 443 try { 444 final File predefinedImage = getPredefinedImage(p); 445 if (predefinedImage != null) { 446 return predefinedImage; 447 } 448 449 // side effect is temp dir is created if not specified 450 BUILD_ROOT.fetchFrom(p); 451 452 //prepare config resources (we will copy them to the bundle later) 453 // NB: explicitly saving them to simplify customization 454 prepareConfigFiles(p); 455 456 // Create directory structure 457 rootDirectory = new File(outputDirectory, APP_NAME.fetchFrom(p) + ".app"); 458 IOUtils.deleteRecursive(rootDirectory); 459 rootDirectory.mkdirs(); 460 461 if (!dependentTask) { 462 Log.info(MessageFormat.format(I18N.getString("message.creating-app-bundle"), rootDirectory.getAbsolutePath())); 463 } 464 465 File contentsDirectory = new File(rootDirectory, "Contents"); 466 contentsDirectory.mkdirs(); 467 468 File macOSDirectory = new File(contentsDirectory, "MacOS"); 469 macOSDirectory.mkdirs(); 470 471 File javaDirectory = new File(contentsDirectory, "Java"); 472 javaDirectory.mkdirs(); 473 474 File plugInsDirectory = new File(contentsDirectory, "PlugIns"); 475 476 File resourcesDirectory = new File(contentsDirectory, "Resources"); 477 resourcesDirectory.mkdirs(); 478 479 // Generate PkgInfo 480 File pkgInfoFile = new File(contentsDirectory, "PkgInfo"); 481 pkgInfoFile.createNewFile(); 482 writePkgInfo(pkgInfoFile); 483 484 // Copy executable to MacOS folder 485 File executableFile = new File(macOSDirectory, getLauncherName(p)); 486 IOUtils.copyFromURL( 487 RAW_EXECUTABLE_URL.fetchFrom(p), 488 executableFile); 489 490 // Copy library to the MacOS folder 491 IOUtils.copyFromURL( 492 MacResources.class.getResource(LIBRARY_NAME), 493 new File(macOSDirectory, LIBRARY_NAME)); 494 495 // maybe generate launcher config 496 if (!MAC_CONFIGURE_LAUNCHER_IN_PLIST.fetchFrom(p)) { 497 if (LAUNCHER_CFG_FORMAT.fetchFrom(p).equals(CFG_FORMAT_PROPERTIES)) { 498 writeCfgFile(p, rootDirectory); 499 } else { 500 writeCfgFile(p, new File(rootDirectory, getLauncherCfgName(p)), getRuntimeLocation(p)); 501 } 502 } 503 504 executableFile.setExecutable(true, false); 505 506 // Copy runtime to PlugIns folder 507 copyRuntime(plugInsDirectory, p); 508 509 // Copy class path entries to Java folder 510 copyClassPathEntries(javaDirectory, p); 511 512 //TODO: Need to support adding native libraries. 513 // Copy library path entries to MacOS folder 514 //copyLibraryPathEntries(macOSDirectory); 515 516 /*********** Take care of "config" files *******/ 517 // Copy icon to Resources folder 518 IOUtils.copyFile(getConfig_Icon(p), 519 new File(resourcesDirectory, getConfig_Icon(p).getName())); 520 521 // copy file association icons 522 for (Map<String, ? super Object> fa : FILE_ASSOCIATIONS.fetchFrom(p)) { 523 File f = FA_ICON.fetchFrom(fa); 524 if (f != null && f.exists()) { 525 IOUtils.copyFile(f, 526 new File(resourcesDirectory, f.getName())); 527 } 528 } 529 530 // Generate Info.plist 531 IOUtils.copyFile(getConfig_InfoPlist(p), 532 new File(contentsDirectory, "Info.plist")); 533 534 // create the secondary launchers, if any 535 List<Map<String, ? super Object>> entryPoints = StandardBundlerParam.SECONDARY_LAUNCHERS.fetchFrom(p); 536 for (Map<String, ? super Object> entryPoint : entryPoints) { 537 Map<String, ? super Object> tmp = new HashMap<>(originalParams); 538 tmp.putAll(entryPoint); 539 createLauncherForEntryPoint(tmp, rootDirectory); 540 } 541 542 // maybe sign 543 if (Optional.ofNullable(SIGN_BUNDLE.fetchFrom(p)).orElse(Boolean.TRUE)) { 544 String signingIdentity = DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(p); 545 if (signingIdentity != null) { 546 MacBaseInstallerBundler.signAppBundle(p, rootDirectory, signingIdentity, BUNDLE_ID_SIGNING_PREFIX.fetchFrom(p)); 547 } 548 } 549 } catch (IOException ex) { 550 Log.info(ex.toString()); 551 Log.verbose(ex); 552 return null; 553 } finally { 554 if (!VERBOSE.fetchFrom(p)) { 555 //cleanup 556 cleanupConfigFiles(p); 557 } else { 558 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), CONFIG_ROOT.fetchFrom(p).getAbsolutePath())); 559 } 560 } 561 return rootDirectory; 562 } 563 564 public void cleanupConfigFiles(Map<String, ? super Object> params) { 565 //Since building the app can be bypassed, make sure configRoot was set 566 if (CONFIG_ROOT.fetchFrom(params) != null) { 567 getConfig_Icon(params).delete(); 568 getConfig_InfoPlist(params).delete(); 569 } 570 } 571 572 private void copyClassPathEntries(File javaDirectory, Map<String, ? super Object> params) throws IOException { 573 List<RelativeFileSet> resourcesList = APP_RESOURCES_LIST.fetchFrom(params); 574 if (resourcesList == null) { 575 throw new RuntimeException(I18N.getString("message.null-classpath")); 576 } 577 578 for (RelativeFileSet classPath : resourcesList) { 579 File srcdir = classPath.getBaseDirectory(); 580 for (String fname : classPath.getIncludedFiles()) { 581 IOUtils.copyFile( 582 new File(srcdir, fname), new File(javaDirectory, fname)); 583 } 584 } 585 } 586 587 private void copyRuntime(File plugInsDirectory, Map<String, ? super Object> params) throws IOException { 588 RelativeFileSet runtime = MAC_RUNTIME.fetchFrom(params); 589 if (runtime == null) { 590 //request to use system runtime => do not bundle 591 return; 592 } 593 runtime = new RelativeFileSet(runtime); 594 if ("jre".equals(runtime.getBaseDirectory().getName())) { 595 runtime.upshift(); 596 } 597 if ("Home".equals(runtime.getBaseDirectory().getName())) { 598 runtime.upshift(); 599 } 600 if ("Contents".equals(runtime.getBaseDirectory().getName())) { 601 runtime.upshift(); 602 } 603 604 605 plugInsDirectory.mkdirs(); 606 607 File srcdir = runtime.getBaseDirectory(); 608 // the name in .../Contents/PlugIns/ must have a dot to be verified 609 // properly by the Mac App Store. 610 File destDir = new File(plugInsDirectory, "Java.runtime"); 611 Set<String> filesToCopy = runtime.getIncludedFiles(); 612 613 for (String fname : filesToCopy) { 614 IOUtils.copyFile( 615 new File(srcdir, fname), new File(destDir, fname)); 616 } 617 } 618 619 private void prepareIcon(Map<String, ? super Object> params) throws IOException { 620 File icon = ICON_ICNS.fetchFrom(params); 621 if (icon == null || !icon.exists()) { 622 fetchResource(MAC_BUNDLER_PREFIX+ APP_NAME.fetchFrom(params) +".icns", 623 "icon", 624 DEFAULT_ICNS_ICON.fetchFrom(params), 625 getConfig_Icon(params), 626 VERBOSE.fetchFrom(params), 627 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 628 } else { 629 fetchResource(MAC_BUNDLER_PREFIX+ APP_NAME.fetchFrom(params) +".icns", 630 "icon", 631 icon, 632 getConfig_Icon(params), 633 VERBOSE.fetchFrom(params), 634 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 635 } 636 } 637 638 private String getLauncherName(Map<String, ? super Object> params) { 639 if (APP_NAME.fetchFrom(params) != null) { 640 return APP_NAME.fetchFrom(params); 641 } else { 642 return MAIN_CLASS.fetchFrom(params); 643 } 644 } 645 646 private String getBundleName(Map<String, ? super Object> params) { 647 //TODO: Check to see what rules/limits are in place for CFBundleName 648 if (MAC_CF_BUNDLE_NAME.fetchFrom(params) != null) { 649 String bn = MAC_CF_BUNDLE_NAME.fetchFrom(params); 650 if (bn.length() > 16) { 651 Log.info(MessageFormat.format(I18N.getString("message.bundle-name-too-long-warning"), MAC_CF_BUNDLE_NAME.getID(), bn)); 652 } 653 return MAC_CF_BUNDLE_NAME.fetchFrom(params); 654 } else if (APP_NAME.fetchFrom(params) != null) { 655 return APP_NAME.fetchFrom(params); 656 } else { 657 String nm = MAIN_CLASS.fetchFrom(params); 658 if (nm.length() > 16) { 659 nm = nm.substring(0, 16); 660 } 661 return nm; 662 } 663 } 664 665 private String getRuntimeLocation(Map<String, ? super Object> params) { 666 if (MAC_RUNTIME.fetchFrom(params) == null) { 667 return ""; 668 } else { 669 return "$APPDIR/PlugIns/Java.runtime"; 670 } 671 } 672 673 private void writeInfoPlist(File file, Map<String, ? super Object> params) throws IOException { 674 Log.verbose(MessageFormat.format(I18N.getString("message.preparing-info-plist"), file.getAbsolutePath())); 675 676 //prepare config for exe 677 //Note: do not need CFBundleDisplayName if we do not support localization 678 Map<String, String> data = new HashMap<>(); 679 data.put("DEPLOY_ICON_FILE", getConfig_Icon(params).getName()); 680 data.put("DEPLOY_BUNDLE_IDENTIFIER", 681 MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params)); 682 data.put("DEPLOY_BUNDLE_NAME", 683 getBundleName(params)); 684 data.put("DEPLOY_BUNDLE_COPYRIGHT", 685 COPYRIGHT.fetchFrom(params) != null ? COPYRIGHT.fetchFrom(params) : "Unknown"); 686 data.put("DEPLOY_LAUNCHER_NAME", getLauncherName(params)); 687 if (MAC_RUNTIME.fetchFrom(params) != null) { 688 data.put("DEPLOY_JAVA_RUNTIME_NAME", "$APPDIR/PlugIns/Java.runtime"); 689 } else { 690 data.put("DEPLOY_JAVA_RUNTIME_NAME", ""); 691 } 692 data.put("DEPLOY_BUNDLE_SHORT_VERSION", 693 VERSION.fetchFrom(params) != null ? VERSION.fetchFrom(params) : "1.0.0"); 694 data.put("DEPLOY_BUNDLE_CFBUNDLE_VERSION", 695 MAC_CF_BUNDLE_VERSION.fetchFrom(params) != null ? MAC_CF_BUNDLE_VERSION.fetchFrom(params) : "100"); 696 data.put("DEPLOY_BUNDLE_CATEGORY", 697 //TODO parameters should provide set of values for IDEs 698 MAC_CATEGORY.validatedFetchFrom(params)); 699 700 data.put("DEPLOY_MAIN_JAR_NAME", MAIN_JAR.fetchFrom(params).getIncludedFiles().iterator().next()); 701 702 data.put("DEPLOY_PREFERENCES_ID", PREFERENCES_ID.fetchFrom(params).toLowerCase()); 703 704 StringBuilder sb = new StringBuilder(); 705 List<String> jvmOptions = JVM_OPTIONS.fetchFrom(params); 706 707 String newline = ""; //So we don't add unneccessary extra line after last append 708 for (String o : jvmOptions) { 709 sb.append(newline).append(" <string>").append(o).append("</string>"); 710 newline = "\n"; 711 } 712 713 Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params); 714 for (Map.Entry<String, String> entry : jvmProps.entrySet()) { 715 sb.append(newline) 716 .append(" <string>-D") 717 .append(entry.getKey()) 718 .append("=") 719 .append(entry.getValue()) 720 .append("</string>"); 721 newline = "\n"; 722 } 723 724 String preloader = PRELOADER_CLASS.fetchFrom(params); 725 if (preloader != null) { 726 sb.append(newline) 727 .append(" <string>-Djavafx.preloader=") 728 .append(preloader) 729 .append("</string>"); 730 //newline = "\n"; 731 } 732 733 data.put("DEPLOY_JVM_OPTIONS", sb.toString()); 734 735 sb = new StringBuilder(); 736 List<String> args = ARGUMENTS.fetchFrom(params); 737 newline = ""; //So we don't add unneccessary extra line after last append 738 for (String o : args) { 739 sb.append(newline).append(" <string>").append(o).append("</string>"); 740 newline = "\n"; 741 } 742 data.put("DEPLOY_ARGUMENTS", sb.toString()); 743 744 newline = ""; 745 sb = new StringBuilder(); 746 Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params); 747 for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) { 748 sb.append(newline) 749 .append(" <key>").append(arg.getKey()).append("</key>\n") 750 .append(" <string>").append(arg.getValue()).append("</string>"); 751 newline = "\n"; 752 } 753 data.put("DEPLOY_JVM_USER_OPTIONS", sb.toString()); 754 755 756 data.put("DEPLOY_LAUNCHER_CLASS", MAIN_CLASS.fetchFrom(params)); 757 758 StringBuilder macroedPath = new StringBuilder(); 759 for (String s : CLASSPATH.fetchFrom(params).split("[ ;:]+")) { 760 macroedPath.append(s); 761 macroedPath.append(":"); 762 } 763 macroedPath.deleteCharAt(macroedPath.length() - 1); 764 765 data.put("DEPLOY_APP_CLASSPATH", macroedPath.toString()); 766 767 //TODO: Add remainder of the classpath 768 769 StringBuilder bundleDocumentTypes = new StringBuilder(); 770 StringBuilder exportedTypes = new StringBuilder(); 771 for (Map<String, ? super Object> fileAssociation : FILE_ASSOCIATIONS.fetchFrom(params)) { 772 773 List<String> extensions = FA_EXTENSIONS.fetchFrom(fileAssociation); 774 775 if (extensions == null) { 776 Log.info(I18N.getString("message.creating-association-with-null-extension")); 777 } 778 779 List<String> mimeTypes = FA_CONTENT_TYPE.fetchFrom(fileAssociation); 780 String itemContentType = MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params) + "." + ((extensions == null || extensions.isEmpty()) 781 ? "mime" 782 : extensions.get(0)); 783 String description = FA_DESCRIPTION.fetchFrom(fileAssociation); 784 File icon = FA_ICON.fetchFrom(fileAssociation); //TODO FA_ICON_ICNS 785 786 bundleDocumentTypes.append(" <dict>\n") 787 .append(" <key>LSItemContentTypes</key>\n") 788 .append(" <array>\n") 789 .append(" <string>") 790 .append(itemContentType) 791 .append("</string>\n") 792 .append(" </array>\n") 793 .append("\n") 794 .append(" <key>CFBundleTypeName</key>\n") 795 .append(" <string>") 796 .append(description) 797 .append("</string>\n") 798 .append("\n") 799 .append(" <key>LSHandlerRank</key>\n") 800 .append(" <string>Owner</string>\n") //TODO make a bundler arg 801 .append("\n") 802 .append(" <key>CFBundleTypeRole</key>\n") 803 .append(" <string>Editor</string>\n") // TODO make a bundler arg 804 .append("\n") 805 .append(" <key>LSIsAppleDefaultForType</key>\n") 806 .append(" <true/>\n") // TODO make a bundler arg 807 .append("\n"); 808 809 if (icon != null && icon.exists()) { 810 //? 811 bundleDocumentTypes.append(" <key>CFBundleTypeIconFile</key>\n") 812 .append(" <string>") 813 .append(icon.getName()) 814 .append("</string>\n"); 815 } 816 bundleDocumentTypes.append(" </dict>\n"); 817 818 exportedTypes.append(" <dict>\n") 819 .append(" <key>UTTypeIdentifier</key>\n") 820 .append(" <string>") 821 .append(itemContentType) 822 .append("</string>\n") 823 .append("\n") 824 .append(" <key>UTTypeDescription</key>\n") 825 .append(" <string>") 826 .append(description) 827 .append("</string>\n") 828 .append(" <key>UTTypeConformsTo</key>\n") 829 .append(" <array>\n") 830 .append(" <string>public.data</string>\n") //TODO expose this? 831 .append(" </array>\n") 832 .append("\n"); 833 834 if (icon != null && icon.exists()) { 835 exportedTypes.append(" <key>UTTypeIconFile</key>\n") 836 .append(" <string>") 837 .append(icon.getName()) 838 .append("</string>\n") 839 .append("\n"); 840 } 841 842 exportedTypes.append("\n") 843 .append(" <key>UTTypeTagSpecification</key>\n") 844 .append(" <dict>\n") 845 //TODO expose via param? .append(" <key>com.apple.ostype</key>\n"); 846 //TODO expose via param? .append(" <string>ABCD</string>\n") 847 .append("\n"); 848 849 if (extensions != null && !extensions.isEmpty()) { 850 exportedTypes.append(" <key>public.filename-extension</key>\n") 851 .append(" <array>\n"); 852 853 for (String ext : extensions) { 854 exportedTypes.append(" <string>") 855 .append(ext) 856 .append("</string>\n"); 857 } 858 exportedTypes.append(" </array>\n"); 859 } 860 if (mimeTypes != null && !mimeTypes.isEmpty()) { 861 exportedTypes.append(" <key>public.mime-type</key>\n") 862 .append(" <array>\n"); 863 864 for (String mime : mimeTypes) { 865 exportedTypes.append(" <string>") 866 .append(mime) 867 .append("</string>\n"); 868 } 869 exportedTypes.append(" </array>\n"); 870 } 871 exportedTypes.append(" </dict>\n") 872 .append(" </dict>\n"); 873 } 874 String associationData; 875 if (bundleDocumentTypes.length() > 0) { 876 associationData = "\n <key>CFBundleDocumentTypes</key>\n <array>\n" 877 + bundleDocumentTypes.toString() 878 + " </array>\n\n <key>UTExportedTypeDeclarations</key>\n <array>\n" 879 + exportedTypes.toString() 880 + " </array>\n"; 881 } else { 882 associationData = ""; 883 } 884 data.put("DEPLOY_FILE_ASSOCIATIONS", associationData); 885 886 887 Writer w = new BufferedWriter(new FileWriter(file)); 888 w.write(preprocessTextResource( 889 MAC_BUNDLER_PREFIX + getConfig_InfoPlist(params).getName(), 890 I18N.getString("resource.bundle-config-file"), 891 MAC_CONFIGURE_LAUNCHER_IN_PLIST.fetchFrom(params) 892 ? TEMPLATE_INFO_PLIST_LEGACY 893 : TEMPLATE_INFO_PLIST_LITE, 894 data, VERBOSE.fetchFrom(params), 895 DROP_IN_RESOURCES_ROOT.fetchFrom(params))); 896 w.close(); 897 898 } 899 900 private void writePkgInfo(File file) throws IOException { 901 902 //hardcoded as it does not seem we need to change it ever 903 String signature = "????"; 904 905 try (Writer out = new BufferedWriter(new FileWriter(file))) { 906 out.write(OS_TYPE_CODE + signature); 907 out.flush(); 908 } 909 } 910 911 public static Rule[] createMacRuntimeRules(Map<String, ? super Object> params) { 912 if (!System.getProperty("os.name").toLowerCase().contains("os x")) { 913 // we will never get a sensible answer unless we are running on OSX, 914 // so quit now and return null indicating 'no sensible value' 915 return null; 916 } 917 918 //Subsetting of JRE is restricted. 919 //JRE README defines what is allowed to strip: 920 // http://www.oracle.com/technetwork/java/javase/jre-8-readme-2095710.html 921 // 922 923 List<Rule> rules = new ArrayList<>(); 924 925 File baseDir; 926 927 if (params.containsKey(MAC_RUNTIME.getID())) { 928 Object o = params.get(MAC_RUNTIME.getID()); 929 if (o instanceof RelativeFileSet) { 930 baseDir = ((RelativeFileSet)o).getBaseDirectory(); 931 } else { 932 baseDir = new File(o.toString()); 933 } 934 } else { 935 baseDir = new File(System.getProperty("java.home")); 936 } 937 938 // we accept either pointing at the directories typically installed at: 939 // /Libraries/Java/JavaVirtualMachine/jdk1.8.0_40/ 940 // * . 941 // * Contents/Home 942 // * Contents/Home/jre 943 // /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin/ 944 // * . 945 // * /Contents/Home 946 // version may change, and if we don't detect any Contents/Home or Contents/Home/jre we will 947 // presume we are at a root. 948 949 if (!baseDir.exists()) { 950 throw new RuntimeException(I18N.getString("error.non-existent-runtime"), 951 new ConfigException(I18N.getString("error.non-existent-runtime"), 952 I18N.getString("error.non-existent-runtime.advice"))); 953 } 954 955 boolean isJRE; 956 boolean isJDK; 957 958 try { 959 String path = baseDir.getCanonicalPath(); 960 if (path.endsWith("/Contents/Home/jre")) { 961 baseDir = baseDir.getParentFile().getParentFile().getParentFile(); 962 } else if (path.endsWith("/Contents/Home")) { 963 baseDir = baseDir.getParentFile().getParentFile(); 964 } 965 966 isJRE = new File(baseDir, "Contents/Home/lib/jli/libjli.dylib").exists(); 967 isJDK = new File(baseDir, "Contents/Home/jre/lib/jli/libjli.dylib").exists(); 968 969 } catch (IOException e) { 970 throw new RuntimeException(e); 971 } 972 973 if (!(isJRE || isJDK)) { 974 throw new RuntimeException(I18N.getString("error.cannot-detect-runtime-in-directory"), 975 new ConfigException(I18N.getString("error.cannot-detect-runtime-in-directory"), 976 I18N.getString("error.cannot-detect-runtime-in-directory.advice"))); 977 } 978 979 // we need the Info.plist for signing 980 rules.add(Rule.suffix("/contents/info.plist")); 981 982 // Strip some JRE specific stuff 983 if (isJRE) { 984 rules.add(Rule.suffixNeg("/contents/disabled.plist")); 985 rules.add(Rule.suffixNeg("/contents/enabled.plist")); 986 rules.add(Rule.substrNeg("/contents/frameworks/")); 987 } 988 989 // strip out command line tools 990 rules.add(Rule.suffixNeg("home/bin")); 991 if (isJDK) { 992 rules.add(Rule.suffixNeg("home/jre/bin")); 993 } 994 995 // strip out JRE stuff 996 if (isJRE) { 997 // update helper 998 rules.add(Rule.suffixNeg("resources")); 999 // interfacebuilder files 1000 rules.add(Rule.suffixNeg("lib/nibs")); 1001 // browser integration 1002 rules.add(Rule.suffixNeg("lib/libnpjp2.dylib")); 1003 // java webstart 1004 rules.add(Rule.suffixNeg("lib/security/javaws.policy")); 1005 rules.add(Rule.suffixNeg("lib/shortcuts")); 1006 1007 // general deploy libraries 1008 rules.add(Rule.suffixNeg("lib/deploy")); 1009 rules.add(Rule.suffixNeg("lib/deploy.jar")); 1010 rules.add(Rule.suffixNeg("lib/javaws.jar")); 1011 rules.add(Rule.suffixNeg("lib/libdeploy.dylib")); 1012 rules.add(Rule.suffixNeg("lib/plugin.jar")); 1013 } 1014 1015 // strip out man pages 1016 rules.add(Rule.suffixNeg("home/man")); 1017 1018 // this is the build hashes, strip or keep? 1019 //rules.add(Rule.suffixNeg("home/release")); 1020 1021 // strip out JDK stuff like JavaDB, JNI Headers, etc 1022 if (isJDK) { 1023 rules.add(Rule.suffixNeg("home/db")); 1024 rules.add(Rule.suffixNeg("home/demo")); 1025 rules.add(Rule.suffixNeg("home/include")); 1026 rules.add(Rule.suffixNeg("home/lib")); 1027 rules.add(Rule.suffixNeg("home/sample")); 1028 rules.add(Rule.suffixNeg("home/src.zip")); 1029 rules.add(Rule.suffixNeg("home/javafx-src.zip")); 1030 } 1031 1032 //"home/rt" is not part of the official builds 1033 // but we may be creating this symlink to make older NB projects 1034 // happy. Make sure to not include it into final artifact 1035 rules.add(Rule.suffixNeg("home/rt")); 1036 1037 //rules.add(Rule.suffixNeg("jre/lib/ext")); //need some of jars there for https to work 1038 1039 // strip out flight recorder 1040 rules.add(Rule.suffixNeg("lib/jfr.jar")); 1041 1042 return rules.toArray(new Rule[rules.size()]); 1043 } 1044 1045 ////////////////////////////////////////////////////////////////////////////////// 1046 // Implement Bundler 1047 ////////////////////////////////////////////////////////////////////////////////// 1048 1049 @Override 1050 public String getName() { 1051 return I18N.getString("bundler.name"); 1052 } 1053 1054 @Override 1055 public String getDescription() { 1056 return I18N.getString("bundler.description"); 1057 } 1058 1059 @Override 1060 public String getID() { 1061 return "mac.app"; 1062 } 1063 1064 @Override 1065 public String getBundleType() { 1066 return "IMAGE"; 1067 } 1068 1069 @Override 1070 public Collection<BundlerParamInfo<?>> getBundleParameters() { 1071 return getAppBundleParameters(); 1072 } 1073 1074 public static Collection<BundlerParamInfo<?>> getAppBundleParameters() { 1075 return Arrays.asList( 1076 APP_NAME, 1077 APP_RESOURCES, 1078 // APP_RESOURCES_LIST, // ?? 1079 ARGUMENTS, 1080 BUNDLE_ID_SIGNING_PREFIX, 1081 CLASSPATH, 1082 DEVELOPER_ID_APP_SIGNING_KEY, 1083 ICON_ICNS, 1084 JVM_OPTIONS, 1085 JVM_PROPERTIES, 1086 MAC_CATEGORY, 1087 MAC_CF_BUNDLE_IDENTIFIER, 1088 MAC_CF_BUNDLE_NAME, 1089 MAC_CF_BUNDLE_VERSION, 1090 MAC_RUNTIME, 1091 MAIN_CLASS, 1092 MAIN_JAR, 1093 PREFERENCES_ID, 1094 PRELOADER_CLASS, 1095 SIGNING_KEYCHAIN, 1096 USER_JVM_OPTIONS, 1097 VERSION 1098 ); 1099 } 1100 1101 1102 @Override 1103 public File execute(Map<String, ? super Object> params, File outputParentDir) { 1104 return doBundle(params, outputParentDir, false); 1105 } 1106 1107 private void createLauncherForEntryPoint(Map<String, ? super Object> p, File rootDirectory) throws IOException { 1108 prepareConfigFiles(p); 1109 1110 if (LAUNCHER_CFG_FORMAT.fetchFrom(p).equals(CFG_FORMAT_PROPERTIES)) { 1111 writeCfgFile(p, rootDirectory); 1112 } else { 1113 writeCfgFile(p, new File(rootDirectory, getLauncherCfgName(p)), "$APPDIR/PlugIns/Java.runtime"); 1114 } 1115 1116 // Copy executable root folder 1117 File executableFile = new File(rootDirectory, "Contents/MacOS/" + getLauncherName(p)); 1118 IOUtils.copyFromURL( 1119 RAW_EXECUTABLE_URL.fetchFrom(p), 1120 executableFile); 1121 executableFile.setExecutable(true, false); 1122 1123 } 1124 1125 public static String getLauncherCfgName(Map<String, ? super Object> p) { 1126 return "Contents/Java/" + APP_NAME.fetchFrom(p) +".cfg"; 1127 } 1128 1129 private void writeCfgFile(Map<String, ? super Object> params, File rootDir) throws FileNotFoundException { 1130 File pkgInfoFile = new File(rootDir, getLauncherCfgName(params)); 1131 1132 pkgInfoFile.delete(); 1133 1134 PrintStream out = new PrintStream(pkgInfoFile); 1135 out.println("app.runtime=" + getRuntimeLocation(params)); 1136 out.println("app.mainjar=" + MAIN_JAR.fetchFrom(params).getIncludedFiles().iterator().next()); 1137 out.println("app.version=" + VERSION.fetchFrom(params)); 1138 //for future AU support (to be able to find app in the registry) 1139 out.println("app.id=" + IDENTIFIER.fetchFrom(params)); 1140 out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params)); 1141 out.println("app.identifier=" + IDENTIFIER.fetchFrom(params)); 1142 1143 out.println("app.mainclass=" + 1144 MAIN_CLASS.fetchFrom(params).replaceAll("\\.", "/")); 1145 out.println("app.classpath=" + CLASSPATH.fetchFrom(params)); 1146 1147 List<String> jvmargs = JVM_OPTIONS.fetchFrom(params); 1148 int idx = 1; 1149 for (String a : jvmargs) { 1150 out.println("jvmarg."+idx+"="+a); 1151 idx++; 1152 } 1153 Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params); 1154 for (Map.Entry<String, String> entry : jvmProps.entrySet()) { 1155 out.println("jvmarg."+idx+"=-D"+entry.getKey()+"="+entry.getValue()); 1156 idx++; 1157 } 1158 1159 String preloader = PRELOADER_CLASS.fetchFrom(params); 1160 if (preloader != null) { 1161 out.println("jvmarg."+idx+"=-Djavafx.preloader="+preloader); 1162 } 1163 1164 Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params); 1165 idx = 1; 1166 for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) { 1167 if (arg.getKey() == null || arg.getValue() == null) { 1168 Log.info(I18N.getString("message.jvm-user-arg-is-null")); 1169 } 1170 else { 1171 out.println("jvmuserarg."+idx+".name="+arg.getKey()); 1172 out.println("jvmuserarg."+idx+".value="+arg.getValue()); 1173 } 1174 idx++; 1175 } 1176 1177 // add command line args 1178 List<String> args = ARGUMENTS.fetchFrom(params); 1179 idx = 1; 1180 for (String a : args) { 1181 out.println("arg."+idx+"="+a); 1182 idx++; 1183 } 1184 1185 out.close(); 1186 } 1187 @Override 1188 public void extractRuntimeFlags(Map<String, ? super Object> params) { 1189 if (params.containsKey(".runtime.autodetect")) return; 1190 1191 params.put(".runtime.autodetect", "attempted"); 1192 RelativeFileSet runtime = MAC_RUNTIME.fetchFrom(params); 1193 String commandline; 1194 if (runtime == null) { 1195 //System JRE, report nothing useful 1196 params.put(".runtime.autodetect", "systemjre"); 1197 } else { 1198 File workingBase = runtime.getBaseDirectory(); 1199 if (workingBase.getName().equals("jre")) { 1200 workingBase = workingBase.getParentFile(); 1201 } 1202 if (workingBase.getName().equals("Home")) { 1203 workingBase = workingBase.getParentFile(); 1204 } 1205 if (workingBase.getName().equals("Contents")) { 1206 workingBase = workingBase.getParentFile(); 1207 } 1208 1209 1210 try { 1211 byte[] infoPlistBytes = Files.readAllBytes(workingBase.toPath().resolve(Paths.get("Contents", "Info.plist"))); 1212 String infoPlist = new String(infoPlistBytes); 1213 1214 Pattern cfBundleVersionMatcher = Pattern.compile("<key>CFBundleVersion</key>\\s*<string>([^<]+)</string>"); 1215 Matcher m = cfBundleVersionMatcher.matcher(infoPlist); 1216 if (m.find()) { 1217 AbstractImageBundler.extractFlagsFromVersion(params, "java version \"" + m.group(1) + "\"\n"); 1218 params.put(".runtime.autodetect", "succeeded"); 1219 } else { 1220 params.put(".runtime.autodetect", "failed"); 1221 } 1222 } catch (IOException e) { 1223 e.printStackTrace(); 1224 params.put(".runtime.autodetect", "failed"); 1225 } 1226 } 1227 } 1228 }