modules/fxpackager/src/main/java/com/oracle/tools/packager/mac/MacAppBundler.java

Print this page




   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 }