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.AbstractBundler; 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.text.MessageFormat; 45 import java.util.*; 46 47 import static com.oracle.tools.packager.StandardBundlerParam.*; 48 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.SIGNING_KEYCHAIN; 49 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.SIGNING_KEY_USER; 50 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.getPredefinedImage; 51 52 public class MacAppBundler extends AbstractBundler { 53 54 private static final ResourceBundle I18N = 55 ResourceBundle.getBundle(MacAppBundler.class.getName()); 56 57 public final static String MAC_BUNDLER_PREFIX = 58 BUNDLER_PREFIX + "macosx" + File.separator; 59 60 private static final String EXECUTABLE_NAME = "JavaAppLauncher"; 61 private final static String LIBRARY_NAME = "libpackager.dylib"; 62 private static final String TEMPLATE_BUNDLE_ICON = "GenericApp.icns"; 63 private static final String OS_TYPE_CODE = "APPL"; 64 private static final String TEMPLATE_INFO_PLIST = "Info.plist.template"; 65 66 private static Map<String, String> getMacCategories() { 67 Map<String, String> map = new HashMap<>(); 68 map.put("Business", "public.app-category.business"); 69 map.put("Developer Tools", "public.app-category.developer-tools"); 70 map.put("Education", "public.app-category.education"); 71 map.put("Entertainment", "public.app-category.entertainment"); 72 map.put("Finance", "public.app-category.finance"); 73 map.put("Games", "public.app-category.games"); 74 map.put("Graphics & Design", "public.app-category.graphics-design"); 75 map.put("Healthcare & Fitness", "public.app-category.healthcare-fitness"); 76 map.put("Lifestyle", "public.app-category.lifestyle"); 77 map.put("Medical", "public.app-category.medical"); 78 map.put("Music", "public.app-category.music"); 79 map.put("News", "public.app-category.news"); 80 map.put("Photography", "public.app-category.photography"); 81 map.put("Productivity", "public.app-category.productivity"); 82 map.put("Reference", "public.app-category.reference"); 83 map.put("Social Networking", "public.app-category.social-networking"); 84 map.put("Sports", "public.app-category.sports"); 93 map.put("Board Games", "public.app-category.board-games"); 94 map.put("Card Games", "public.app-category.card-games"); 95 map.put("Casino Games", "public.app-category.casino-games"); 96 map.put("Dice Games", "public.app-category.dice-games"); 97 map.put("Educational Games", "public.app-category.educational-games"); 98 map.put("Family Games", "public.app-category.family-games"); 99 map.put("Kids Games", "public.app-category.kids-games"); 100 map.put("Music Games", "public.app-category.music-games"); 101 map.put("Puzzle Games", "public.app-category.puzzle-games"); 102 map.put("Racing Games", "public.app-category.racing-games"); 103 map.put("Role Playing Games", "public.app-category.role-playing-games"); 104 map.put("Simulation Games", "public.app-category.simulation-games"); 105 map.put("Sports Games", "public.app-category.sports-games"); 106 map.put("Strategy Games", "public.app-category.strategy-games"); 107 map.put("Trivia Games", "public.app-category.trivia-games"); 108 map.put("Word Games", "public.app-category.word-games"); 109 110 return map; 111 } 112 113 public static final EnumeratedBundlerParam<String> MAC_CATEGORY = 114 new EnumeratedBundlerParam<>( 115 I18N.getString("param.category-name"), 116 I18N.getString("param.category-name.description"), 117 "mac.category", 118 String.class, 119 params -> params.containsKey(CATEGORY.getID()) 120 ? CATEGORY.fetchFrom(params) 121 : "Unknown", 122 (s, p) -> s, 123 getMacCategories(), 124 false //strict - for MacStoreBundler this should be strict 125 ); 126 127 public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME = 128 new StandardBundlerParam<>( 129 I18N.getString("param.cfbundle-name.name"), 130 I18N.getString("param.cfbundle-name.description"), 131 "mac.CFBundleName", 132 String.class, 256 } 257 258 if (workingBase.getName().equals("jre")) { 259 workingBase = workingBase.getParentFile(); 260 } 261 if (workingBase.getName().equals("Home")) { 262 workingBase = workingBase.getParentFile(); 263 } 264 if (workingBase.getName().equals("Contents")) { 265 workingBase = workingBase.getParentFile(); 266 } 267 return JreUtils.extractJreAsRelativeFileSet(workingBase.toString(), 268 MAC_RULES.fetchFrom(params), true); 269 } 270 271 public MacAppBundler() { 272 super(); 273 baseResourceLoader = MacResources.class; 274 } 275 276 public static boolean validCFBundleVersion(String v) { 277 // CFBundleVersion (String - iOS, OS X) specifies the build version 278 // number of the bundle, which identifies an iteration (released or 279 // unreleased) of the bundle. The build version number should be a 280 // string comprised of three non-negative, period-separated integers 281 // with the first integer being greater than zero. The string should 282 // only contain numeric (0-9) and period (.) characters. Leading zeros 283 // are truncated from each integer and will be ignored (that is, 284 // 1.02.3 is equivalent to 1.2.3). This key is not localizable. 285 286 if (v == null) { 287 return false; 288 } 289 290 String p[] = v.split("\\."); 291 if (p.length > 3 || p.length < 1) { 292 Log.verbose(I18N.getString("message.version-string-too-many-components")); 293 return false; 294 } 295 396 397 private File getConfig_InfoPlist(Map<String, ? super Object> params) { 398 return new File(CONFIG_ROOT.fetchFrom(params), "Info.plist"); 399 } 400 401 private File getConfig_Icon(Map<String, ? super Object> params) { 402 return new File(CONFIG_ROOT.fetchFrom(params), APP_NAME.fetchFrom(params) + ".icns"); 403 } 404 405 private void prepareConfigFiles(Map<String, ? super Object> params) throws IOException { 406 File infoPlistFile = getConfig_InfoPlist(params); 407 infoPlistFile.createNewFile(); 408 writeInfoPlist(infoPlistFile, params); 409 410 // Copy icon to Resources folder 411 prepareIcon(params); 412 } 413 414 public File doBundle(Map<String, ? super Object> p, File outputDirectory, boolean dependentTask) { 415 File rootDirectory = null; 416 if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) { 417 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), outputDirectory.getAbsolutePath())); 418 } 419 if (!outputDirectory.canWrite()) { 420 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), outputDirectory.getAbsolutePath())); 421 } 422 423 try { 424 final File predefinedImage = getPredefinedImage(p); 425 if (predefinedImage != null) { 426 return predefinedImage; 427 } 428 429 // side effect is temp dir is created if not specified 430 BUILD_ROOT.fetchFrom(p); 431 432 //prepare config resources (we will copy them to the bundle later) 433 // NB: explicitly saving them to simplify customization 434 prepareConfigFiles(p); 435 455 456 File resourcesDirectory = new File(contentsDirectory, "Resources"); 457 resourcesDirectory.mkdirs(); 458 459 // Generate PkgInfo 460 File pkgInfoFile = new File(contentsDirectory, "PkgInfo"); 461 pkgInfoFile.createNewFile(); 462 writePkgInfo(pkgInfoFile); 463 464 // Copy executable to MacOS folder 465 File executableFile = new File(macOSDirectory, getLauncherName(p)); 466 IOUtils.copyFromURL( 467 RAW_EXECUTABLE_URL.fetchFrom(p), 468 executableFile); 469 470 // Copy library to the MacOS folder 471 IOUtils.copyFromURL( 472 MacResources.class.getResource(LIBRARY_NAME), 473 new File(macOSDirectory, LIBRARY_NAME)); 474 475 executableFile.setExecutable(true, false); 476 477 // Copy runtime to PlugIns folder 478 copyRuntime(plugInsDirectory, p); 479 480 // Copy class path entries to Java folder 481 copyClassPathEntries(javaDirectory, p); 482 483 //TODO: Need to support adding native libraries. 484 // Copy library path entries to MacOS folder 485 //copyLibraryPathEntries(macOSDirectory); 486 487 /*********** Take care of "config" files *******/ 488 // Copy icon to Resources folder 489 IOUtils.copyFile(getConfig_Icon(p), 490 new File(resourcesDirectory, getConfig_Icon(p).getName())); 491 492 // copy file association icons 493 for (Map<String, ? super Object> fa : FILE_ASSOCIATIONS.fetchFrom(p)) { 494 File f = FA_ICON.fetchFrom(fa); 495 if (f != null && f.exists()) { 496 IOUtils.copyFile(f, 497 new File(resourcesDirectory, f.getName())); 498 } 499 } 500 501 502 // Generate Info.plist 503 IOUtils.copyFile(getConfig_InfoPlist(p), 504 new File(contentsDirectory, "Info.plist")); 505 506 // maybe sign 507 if (Optional.ofNullable(SIGN_BUNDLE.fetchFrom(p)).orElse(Boolean.TRUE)) { 508 String signingIdentity = DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(p); 509 if (signingIdentity != null) { 510 MacBaseInstallerBundler.signAppBundle(p, rootDirectory, signingIdentity, BUNDLE_ID_SIGNING_PREFIX.fetchFrom(p)); 511 } 512 } 513 } catch (IOException ex) { 514 Log.info(ex.toString()); 515 Log.verbose(ex); 516 return null; 517 } finally { 518 if (!VERBOSE.fetchFrom(p)) { 519 //cleanup 520 cleanupConfigFiles(p); 521 } else { 522 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), CONFIG_ROOT.fetchFrom(p).getAbsolutePath())); 523 } 524 } 525 return rootDirectory; 526 } 527 528 public void cleanupConfigFiles(Map<String, ? super Object> params) { 529 //Since building the app can be bypassed, make sure configRoot was set 530 if (CONFIG_ROOT.fetchFrom(params) != null) { 531 if (getConfig_Icon(params) != null) { 532 getConfig_Icon(params).delete(); 533 } 534 if (getConfig_InfoPlist(params) != null) { 535 getConfig_InfoPlist(params).delete(); 536 } 537 } 538 } 539 540 private void copyClassPathEntries(File javaDirectory, Map<String, ? super Object> params) throws IOException { 541 List<RelativeFileSet> resourcesList = APP_RESOURCES_LIST.fetchFrom(params); 542 if (resourcesList == null) { 543 throw new RuntimeException(I18N.getString("message.null-classpath")); 544 } 545 546 for (RelativeFileSet classPath : resourcesList) { 547 File srcdir = classPath.getBaseDirectory(); 548 for (String fname : classPath.getIncludedFiles()) { 549 IOUtils.copyFile( 550 new File(srcdir, fname), new File(javaDirectory, fname)); 551 } 552 } 553 } 554 555 private void copyRuntime(File plugInsDirectory, Map<String, ? super Object> params) throws IOException { 556 RelativeFileSet runTime = MAC_RUNTIME.fetchFrom(params); 557 if (runTime == null) { 558 //request to use system runtime => do not bundle 818 } 819 exportedTypes.append(" </dict>\n") 820 .append(" </dict>\n"); 821 } 822 String associationData; 823 if (bundleDocumentTypes.length() > 0) { 824 associationData = "\n <key>CFBundleDocumentTypes</key>\n <array>\n" 825 + bundleDocumentTypes.toString() 826 + " </array>\n\n <key>UTExportedTypeDeclarations</key>\n <array>\n" 827 + exportedTypes.toString() 828 + " </array>\n"; 829 } else { 830 associationData = ""; 831 } 832 data.put("DEPLOY_FILE_ASSOCIATIONS", associationData); 833 834 835 Writer w = new BufferedWriter(new FileWriter(file)); 836 w.write(preprocessTextResource( 837 MAC_BUNDLER_PREFIX + getConfig_InfoPlist(params).getName(), 838 I18N.getString("resource.bundle-config-file"), TEMPLATE_INFO_PLIST, data, 839 VERBOSE.fetchFrom(params), 840 DROP_IN_RESOURCES_ROOT.fetchFrom(params))); 841 w.close(); 842 843 } 844 845 private void writePkgInfo(File file) throws IOException { 846 847 //hardcoded as it does not seem we need to change it ever 848 String signature = "????"; 849 850 try (Writer out = new BufferedWriter(new FileWriter(file))) { 851 out.write(OS_TYPE_CODE + signature); 852 out.flush(); 853 } 854 } 855 856 public static Rule[] createMacRuntimeRules(Map<String, ? super Object> params) { 857 if (!System.getProperty("os.name").toLowerCase().contains("os x")) { 858 // we will never get a sensible answer unless we are running on OSX, 859 // so quit now and return null indicating 'no sensible value' 1032 MAC_CATEGORY, 1033 MAC_CF_BUNDLE_IDENTIFIER, 1034 MAC_CF_BUNDLE_NAME, 1035 MAC_CF_BUNDLE_VERSION, 1036 MAC_RUNTIME, 1037 MAIN_CLASS, 1038 MAIN_JAR, 1039 PREFERENCES_ID, 1040 PRELOADER_CLASS, 1041 SIGNING_KEYCHAIN, 1042 USER_JVM_OPTIONS, 1043 VERSION 1044 ); 1045 } 1046 1047 1048 @Override 1049 public File execute(Map<String, ? super Object> params, File outputParentDir) { 1050 return doBundle(params, outputParentDir, false); 1051 } 1052 } | 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.text.MessageFormat; 45 import java.util.*; 46 47 import static com.oracle.tools.packager.StandardBundlerParam.*; 48 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.SIGNING_KEYCHAIN; 49 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.SIGNING_KEY_USER; 50 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.getPredefinedImage; 51 52 public class MacAppBundler extends AbstractImageBundler { 53 54 private static final ResourceBundle I18N = 55 ResourceBundle.getBundle(MacAppBundler.class.getName()); 56 57 public final static String MAC_BUNDLER_PREFIX = 58 BUNDLER_PREFIX + "macosx" + File.separator; 59 60 private static final String EXECUTABLE_NAME = "JavaAppLauncher"; 61 private final static String LIBRARY_NAME = "libpackager.dylib"; 62 private static final String TEMPLATE_BUNDLE_ICON = "GenericApp.icns"; 63 private static final String OS_TYPE_CODE = "APPL"; 64 private static final String TEMPLATE_INFO_PLIST_LEGACY = "Info.plist.template"; 65 private static final String TEMPLATE_INFO_PLIST_LITE = "Info-lite.plist.template"; 66 67 private static Map<String, String> getMacCategories() { 68 Map<String, String> map = new HashMap<>(); 69 map.put("Business", "public.app-category.business"); 70 map.put("Developer Tools", "public.app-category.developer-tools"); 71 map.put("Education", "public.app-category.education"); 72 map.put("Entertainment", "public.app-category.entertainment"); 73 map.put("Finance", "public.app-category.finance"); 74 map.put("Games", "public.app-category.games"); 75 map.put("Graphics & Design", "public.app-category.graphics-design"); 76 map.put("Healthcare & Fitness", "public.app-category.healthcare-fitness"); 77 map.put("Lifestyle", "public.app-category.lifestyle"); 78 map.put("Medical", "public.app-category.medical"); 79 map.put("Music", "public.app-category.music"); 80 map.put("News", "public.app-category.news"); 81 map.put("Photography", "public.app-category.photography"); 82 map.put("Productivity", "public.app-category.productivity"); 83 map.put("Reference", "public.app-category.reference"); 84 map.put("Social Networking", "public.app-category.social-networking"); 85 map.put("Sports", "public.app-category.sports"); 94 map.put("Board Games", "public.app-category.board-games"); 95 map.put("Card Games", "public.app-category.card-games"); 96 map.put("Casino Games", "public.app-category.casino-games"); 97 map.put("Dice Games", "public.app-category.dice-games"); 98 map.put("Educational Games", "public.app-category.educational-games"); 99 map.put("Family Games", "public.app-category.family-games"); 100 map.put("Kids Games", "public.app-category.kids-games"); 101 map.put("Music Games", "public.app-category.music-games"); 102 map.put("Puzzle Games", "public.app-category.puzzle-games"); 103 map.put("Racing Games", "public.app-category.racing-games"); 104 map.put("Role Playing Games", "public.app-category.role-playing-games"); 105 map.put("Simulation Games", "public.app-category.simulation-games"); 106 map.put("Sports Games", "public.app-category.sports-games"); 107 map.put("Strategy Games", "public.app-category.strategy-games"); 108 map.put("Trivia Games", "public.app-category.trivia-games"); 109 map.put("Word Games", "public.app-category.word-games"); 110 111 return map; 112 } 113 114 public static final BundlerParamInfo<Boolean> MAC_CONFIGURE_LAUNCHER_IN_PLIST = 115 new StandardBundlerParam<>( 116 I18N.getString("param.configure-launcher-in-plist"), 117 I18N.getString("param.configure-launcher-in-plist.description"), 118 "mac.configure-launcher-in-plist", 119 Boolean.class, 120 params -> Boolean.FALSE, 121 (s, p) -> Boolean.valueOf(s)); 122 123 public static final EnumeratedBundlerParam<String> MAC_CATEGORY = 124 new EnumeratedBundlerParam<>( 125 I18N.getString("param.category-name"), 126 I18N.getString("param.category-name.description"), 127 "mac.category", 128 String.class, 129 params -> params.containsKey(CATEGORY.getID()) 130 ? CATEGORY.fetchFrom(params) 131 : "Unknown", 132 (s, p) -> s, 133 getMacCategories(), 134 false //strict - for MacStoreBundler this should be strict 135 ); 136 137 public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME = 138 new StandardBundlerParam<>( 139 I18N.getString("param.cfbundle-name.name"), 140 I18N.getString("param.cfbundle-name.description"), 141 "mac.CFBundleName", 142 String.class, 266 } 267 268 if (workingBase.getName().equals("jre")) { 269 workingBase = workingBase.getParentFile(); 270 } 271 if (workingBase.getName().equals("Home")) { 272 workingBase = workingBase.getParentFile(); 273 } 274 if (workingBase.getName().equals("Contents")) { 275 workingBase = workingBase.getParentFile(); 276 } 277 return JreUtils.extractJreAsRelativeFileSet(workingBase.toString(), 278 MAC_RULES.fetchFrom(params), true); 279 } 280 281 public MacAppBundler() { 282 super(); 283 baseResourceLoader = MacResources.class; 284 } 285 286 @Override 287 protected String getCacheLocation(Map<String, ? super Object> params) { 288 Boolean systemWide = SYSTEM_WIDE.fetchFrom(params); 289 if (systemWide == null || systemWide) { 290 return "/Library/Application Support/" + IDENTIFIER.fetchFrom(params) + "/cache/"; 291 } else { 292 return "$CACHEDIR/"; 293 } 294 } 295 296 297 public static boolean validCFBundleVersion(String v) { 298 // CFBundleVersion (String - iOS, OS X) specifies the build version 299 // number of the bundle, which identifies an iteration (released or 300 // unreleased) of the bundle. The build version number should be a 301 // string comprised of three non-negative, period-separated integers 302 // with the first integer being greater than zero. The string should 303 // only contain numeric (0-9) and period (.) characters. Leading zeros 304 // are truncated from each integer and will be ignored (that is, 305 // 1.02.3 is equivalent to 1.2.3). This key is not localizable. 306 307 if (v == null) { 308 return false; 309 } 310 311 String p[] = v.split("\\."); 312 if (p.length > 3 || p.length < 1) { 313 Log.verbose(I18N.getString("message.version-string-too-many-components")); 314 return false; 315 } 316 417 418 private File getConfig_InfoPlist(Map<String, ? super Object> params) { 419 return new File(CONFIG_ROOT.fetchFrom(params), "Info.plist"); 420 } 421 422 private File getConfig_Icon(Map<String, ? super Object> params) { 423 return new File(CONFIG_ROOT.fetchFrom(params), APP_NAME.fetchFrom(params) + ".icns"); 424 } 425 426 private void prepareConfigFiles(Map<String, ? super Object> params) throws IOException { 427 File infoPlistFile = getConfig_InfoPlist(params); 428 infoPlistFile.createNewFile(); 429 writeInfoPlist(infoPlistFile, params); 430 431 // Copy icon to Resources folder 432 prepareIcon(params); 433 } 434 435 public File doBundle(Map<String, ? super Object> p, File outputDirectory, boolean dependentTask) { 436 File rootDirectory = null; 437 Map<String, ? super Object> originalParams = new HashMap<>(p); 438 439 if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) { 440 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), outputDirectory.getAbsolutePath())); 441 } 442 if (!outputDirectory.canWrite()) { 443 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), outputDirectory.getAbsolutePath())); 444 } 445 446 try { 447 final File predefinedImage = getPredefinedImage(p); 448 if (predefinedImage != null) { 449 return predefinedImage; 450 } 451 452 // side effect is temp dir is created if not specified 453 BUILD_ROOT.fetchFrom(p); 454 455 //prepare config resources (we will copy them to the bundle later) 456 // NB: explicitly saving them to simplify customization 457 prepareConfigFiles(p); 458 478 479 File resourcesDirectory = new File(contentsDirectory, "Resources"); 480 resourcesDirectory.mkdirs(); 481 482 // Generate PkgInfo 483 File pkgInfoFile = new File(contentsDirectory, "PkgInfo"); 484 pkgInfoFile.createNewFile(); 485 writePkgInfo(pkgInfoFile); 486 487 // Copy executable to MacOS folder 488 File executableFile = new File(macOSDirectory, getLauncherName(p)); 489 IOUtils.copyFromURL( 490 RAW_EXECUTABLE_URL.fetchFrom(p), 491 executableFile); 492 493 // Copy library to the MacOS folder 494 IOUtils.copyFromURL( 495 MacResources.class.getResource(LIBRARY_NAME), 496 new File(macOSDirectory, LIBRARY_NAME)); 497 498 // maybe generate launcher config 499 if (!MAC_CONFIGURE_LAUNCHER_IN_PLIST.fetchFrom(p)) { 500 if (LAUNCHER_CFG_FORMAT.fetchFrom(p).equals(CFG_FORMAT_PROPERTIES)) { 501 writeCfgFile(p, rootDirectory); 502 } else { 503 writeCfgFile(p, new File(rootDirectory, getLauncherCfgName(p)), "$APPDIR/PlugIns/Java.runtime"); 504 } 505 } 506 507 executableFile.setExecutable(true, false); 508 509 // Copy runtime to PlugIns folder 510 copyRuntime(plugInsDirectory, p); 511 512 // Copy class path entries to Java folder 513 copyClassPathEntries(javaDirectory, p); 514 515 //TODO: Need to support adding native libraries. 516 // Copy library path entries to MacOS folder 517 //copyLibraryPathEntries(macOSDirectory); 518 519 /*********** Take care of "config" files *******/ 520 // Copy icon to Resources folder 521 IOUtils.copyFile(getConfig_Icon(p), 522 new File(resourcesDirectory, getConfig_Icon(p).getName())); 523 524 // copy file association icons 525 for (Map<String, ? super Object> fa : FILE_ASSOCIATIONS.fetchFrom(p)) { 526 File f = FA_ICON.fetchFrom(fa); 527 if (f != null && f.exists()) { 528 IOUtils.copyFile(f, 529 new File(resourcesDirectory, f.getName())); 530 } 531 } 532 533 // Generate Info.plist 534 IOUtils.copyFile(getConfig_InfoPlist(p), 535 new File(contentsDirectory, "Info.plist")); 536 537 // create the secondary launchers, if any 538 List<Map<String, ? super Object>> entryPoints = StandardBundlerParam.SECONDARY_LAUNCHERS.fetchFrom(p); 539 for (Map<String, ? super Object> entryPoint : entryPoints) { 540 Map<String, ? super Object> tmp = new HashMap<>(originalParams); 541 tmp.putAll(entryPoint); 542 createLauncherForEntryPoint(tmp, rootDirectory); 543 } 544 545 // maybe sign 546 if (Optional.ofNullable(SIGN_BUNDLE.fetchFrom(p)).orElse(Boolean.TRUE)) { 547 String signingIdentity = DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(p); 548 if (signingIdentity != null) { 549 MacBaseInstallerBundler.signAppBundle(p, rootDirectory, signingIdentity, BUNDLE_ID_SIGNING_PREFIX.fetchFrom(p)); 550 } 551 } 552 } catch (IOException ex) { 553 Log.info(ex.toString()); 554 Log.verbose(ex); 555 return null; 556 } finally { 557 if (!VERBOSE.fetchFrom(p)) { 558 //cleanup 559 cleanupConfigFiles(p); 560 } else { 561 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), CONFIG_ROOT.fetchFrom(p).getAbsolutePath())); 562 } 563 } 564 return rootDirectory; 565 } 566 567 public void cleanupConfigFiles(Map<String, ? super Object> params) { 568 //Since building the app can be bypassed, make sure configRoot was set 569 if (CONFIG_ROOT.fetchFrom(params) != null) { 570 getConfig_Icon(params).delete(); 571 getConfig_InfoPlist(params).delete(); 572 } 573 } 574 575 private void copyClassPathEntries(File javaDirectory, Map<String, ? super Object> params) throws IOException { 576 List<RelativeFileSet> resourcesList = APP_RESOURCES_LIST.fetchFrom(params); 577 if (resourcesList == null) { 578 throw new RuntimeException(I18N.getString("message.null-classpath")); 579 } 580 581 for (RelativeFileSet classPath : resourcesList) { 582 File srcdir = classPath.getBaseDirectory(); 583 for (String fname : classPath.getIncludedFiles()) { 584 IOUtils.copyFile( 585 new File(srcdir, fname), new File(javaDirectory, fname)); 586 } 587 } 588 } 589 590 private void copyRuntime(File plugInsDirectory, Map<String, ? super Object> params) throws IOException { 591 RelativeFileSet runTime = MAC_RUNTIME.fetchFrom(params); 592 if (runTime == null) { 593 //request to use system runtime => do not bundle 853 } 854 exportedTypes.append(" </dict>\n") 855 .append(" </dict>\n"); 856 } 857 String associationData; 858 if (bundleDocumentTypes.length() > 0) { 859 associationData = "\n <key>CFBundleDocumentTypes</key>\n <array>\n" 860 + bundleDocumentTypes.toString() 861 + " </array>\n\n <key>UTExportedTypeDeclarations</key>\n <array>\n" 862 + exportedTypes.toString() 863 + " </array>\n"; 864 } else { 865 associationData = ""; 866 } 867 data.put("DEPLOY_FILE_ASSOCIATIONS", associationData); 868 869 870 Writer w = new BufferedWriter(new FileWriter(file)); 871 w.write(preprocessTextResource( 872 MAC_BUNDLER_PREFIX + getConfig_InfoPlist(params).getName(), 873 I18N.getString("resource.bundle-config-file"), 874 MAC_CONFIGURE_LAUNCHER_IN_PLIST.fetchFrom(params) 875 ? TEMPLATE_INFO_PLIST_LEGACY 876 : TEMPLATE_INFO_PLIST_LITE, 877 data, VERBOSE.fetchFrom(params), 878 DROP_IN_RESOURCES_ROOT.fetchFrom(params))); 879 w.close(); 880 881 } 882 883 private void writePkgInfo(File file) throws IOException { 884 885 //hardcoded as it does not seem we need to change it ever 886 String signature = "????"; 887 888 try (Writer out = new BufferedWriter(new FileWriter(file))) { 889 out.write(OS_TYPE_CODE + signature); 890 out.flush(); 891 } 892 } 893 894 public static Rule[] createMacRuntimeRules(Map<String, ? super Object> params) { 895 if (!System.getProperty("os.name").toLowerCase().contains("os x")) { 896 // we will never get a sensible answer unless we are running on OSX, 897 // so quit now and return null indicating 'no sensible value' 1070 MAC_CATEGORY, 1071 MAC_CF_BUNDLE_IDENTIFIER, 1072 MAC_CF_BUNDLE_NAME, 1073 MAC_CF_BUNDLE_VERSION, 1074 MAC_RUNTIME, 1075 MAIN_CLASS, 1076 MAIN_JAR, 1077 PREFERENCES_ID, 1078 PRELOADER_CLASS, 1079 SIGNING_KEYCHAIN, 1080 USER_JVM_OPTIONS, 1081 VERSION 1082 ); 1083 } 1084 1085 1086 @Override 1087 public File execute(Map<String, ? super Object> params, File outputParentDir) { 1088 return doBundle(params, outputParentDir, false); 1089 } 1090 1091 private void createLauncherForEntryPoint(Map<String, ? super Object> p, File rootDirectory) throws IOException { 1092 prepareConfigFiles(p); 1093 1094 if (LAUNCHER_CFG_FORMAT.fetchFrom(p).equals(CFG_FORMAT_PROPERTIES)) { 1095 writeCfgFile(p, rootDirectory); 1096 } else { 1097 writeCfgFile(p, new File(rootDirectory, getLauncherCfgName(p)), "$APPDIR/PlugIns/Java.runtime"); 1098 } 1099 1100 // Copy executable root folder 1101 File executableFile = new File(rootDirectory, "Contents/MacOS/" + getLauncherName(p)); 1102 IOUtils.copyFromURL( 1103 RAW_EXECUTABLE_URL.fetchFrom(p), 1104 executableFile); 1105 executableFile.setExecutable(true, false); 1106 1107 } 1108 1109 public static String getLauncherCfgName(Map<String, ? super Object> p) { 1110 return "Contents/Java/" + APP_NAME.fetchFrom(p) +".cfg"; 1111 } 1112 1113 private void writeCfgFile(Map<String, ? super Object> params, File rootDir) throws FileNotFoundException { 1114 File pkgInfoFile = new File(rootDir, getLauncherCfgName(params)); 1115 1116 pkgInfoFile.delete(); 1117 1118 PrintStream out = new PrintStream(pkgInfoFile); 1119 if (MAC_RUNTIME.fetchFrom(params) == null) { 1120 out.println("app.runtime="); 1121 } else { 1122 out.println("app.runtime=$APPDIR/PlugIns/Java.runtime"); 1123 } 1124 out.println("app.mainjar=" + MAIN_JAR.fetchFrom(params).getIncludedFiles().iterator().next()); 1125 out.println("app.version=" + VERSION.fetchFrom(params)); 1126 //for future AU support (to be able to find app in the registry) 1127 out.println("app.id=" + IDENTIFIER.fetchFrom(params)); 1128 out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params)); 1129 out.println("app.identifier=" + IDENTIFIER.fetchFrom(params)); 1130 1131 out.println("app.mainclass=" + 1132 MAIN_CLASS.fetchFrom(params).replaceAll("\\.", "/")); 1133 out.println("app.classpath=" + CLASSPATH.fetchFrom(params)); 1134 1135 List<String> jvmargs = JVM_OPTIONS.fetchFrom(params); 1136 int idx = 1; 1137 for (String a : jvmargs) { 1138 out.println("jvmarg."+idx+"="+a); 1139 idx++; 1140 } 1141 Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params); 1142 for (Map.Entry<String, String> entry : jvmProps.entrySet()) { 1143 out.println("jvmarg."+idx+"=-D"+entry.getKey()+"="+entry.getValue()); 1144 idx++; 1145 } 1146 1147 String preloader = PRELOADER_CLASS.fetchFrom(params); 1148 if (preloader != null) { 1149 out.println("jvmarg."+idx+"=-Djavafx.preloader="+preloader); 1150 } 1151 1152 Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params); 1153 idx = 1; 1154 for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) { 1155 if (arg.getKey() == null || arg.getValue() == null) { 1156 Log.info(I18N.getString("message.jvm-user-arg-is-null")); 1157 } 1158 else { 1159 out.println("jvmuserarg."+idx+".name="+arg.getKey()); 1160 out.println("jvmuserarg."+idx+".value="+arg.getValue()); 1161 } 1162 idx++; 1163 } 1164 1165 // add command line args 1166 List<String> args = ARGUMENTS.fetchFrom(params); 1167 idx = 1; 1168 for (String a : args) { 1169 out.println("arg."+idx+"="+a); 1170 idx++; 1171 } 1172 1173 out.close(); 1174 } 1175 1176 } |