modules/jdk.packager/src/main/java/jdk/packager/internal/legacy/builders/mac/MacAppImageBuilder.java

Print this page




  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 jdk.packager.internal.legacy.builders.mac;
  26 
  27 
  28 import com.oracle.tools.packager.BundlerParamInfo;
  29 import com.oracle.tools.packager.IOUtils;
  30 import com.oracle.tools.packager.Log;

  31 import com.oracle.tools.packager.RelativeFileSet;
  32 import com.oracle.tools.packager.StandardBundlerParam;
  33 import com.oracle.tools.packager.mac.MacResources;
  34 
  35 import jdk.packager.internal.legacy.builders.AbstractAppImageBuilder;
  36 import jdk.packager.internal.legacy.JLinkBundlerHelper;
  37 
  38 import java.io.BufferedWriter;
  39 import java.io.File;
  40 import java.io.FileInputStream;
  41 import java.io.FileOutputStream;
  42 import java.io.FileWriter;
  43 import java.io.IOException;
  44 import java.io.InputStream;
  45 import java.io.OutputStream;
  46 import java.io.OutputStreamWriter;
  47 import java.io.UncheckedIOException;
  48 import java.io.Writer;
  49 import java.math.BigInteger;
  50 import java.nio.file.Files;


  75             ResourceBundle.getBundle(MacAppImageBuilder.class.getName());
  76 
  77     private static final String EXECUTABLE_NAME = "JavaAppLauncher";
  78     private static final String LIBRARY_NAME = "libpackager.dylib";
  79     private static final String TEMPLATE_BUNDLE_ICON = "GenericApp.icns";
  80     private static final String OS_TYPE_CODE = "APPL";
  81     private static final String TEMPLATE_INFO_PLIST_LITE = "Info-lite.plist.template";
  82     private static final String TEMPLATE_RUNTIME_INFO_PLIST = "Runtime-Info.plist.template";
  83 
  84     private final Path root;
  85     private final Path contentsDir;
  86     private final Path javaDir;
  87     private final Path resourcesDir;
  88     private final Path macOSDir;
  89     private final Path runtimeDir;
  90     private final Path runtimeRoot;
  91     private final Path mdir;
  92 
  93     private final Map<String, ? super Object> params;
  94 


  95     private static Map<String, String> getMacCategories() {
  96         Map<String, String> map = new HashMap<>();
  97         map.put("Business", "public.app-category.business");
  98         map.put("Developer Tools", "public.app-category.developer-tools");
  99         map.put("Education", "public.app-category.education");
 100         map.put("Entertainment", "public.app-category.entertainment");
 101         map.put("Finance", "public.app-category.finance");
 102         map.put("Games", "public.app-category.games");
 103         map.put("Graphics & Design", "public.app-category.graphics-design");
 104         map.put("Healthcare & Fitness", "public.app-category.healthcare-fitness");
 105         map.put("Lifestyle", "public.app-category.lifestyle");
 106         map.put("Medical", "public.app-category.medical");
 107         map.put("Music", "public.app-category.music");
 108         map.put("News", "public.app-category.news");
 109         map.put("Photography", "public.app-category.photography");
 110         map.put("Productivity", "public.app-category.productivity");
 111         map.put("Reference", "public.app-category.reference");
 112         map.put("Social Networking", "public.app-category.social-networking");
 113         map.put("Sports", "public.app-category.sports");
 114         map.put("Travel", "public.app-category.travel");


 398             if (f != null && f.exists()) {
 399                 try (InputStream in2 = new FileInputStream(f)) {
 400                     Files.copy(in2, resourcesDir.resolve(f.getName()));
 401                 }
 402 
 403             }
 404         }
 405 
 406         // Generate Info.plist
 407         writeInfoPlist(contentsDir.resolve("Info.plist").toFile());
 408 
 409         // generate java runtime info.plist
 410         writeRuntimeInfoPlist(runtimeDir.resolve("Contents/Info.plist").toFile());
 411 
 412         // copy library
 413         Path runtimeMacOSDir = Files.createDirectories(runtimeDir.resolve("Contents/MacOS"));
 414         Files.copy(runtimeRoot.resolve("lib/jli/libjli.dylib"), runtimeMacOSDir.resolve("libjli.dylib"));
 415 
 416         // maybe sign
 417         if (Optional.ofNullable(SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) {





 418             String signingIdentity = DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(params);
 419             if (signingIdentity != null) {
 420                 signAppBundle(params, root, signingIdentity, BUNDLE_ID_SIGNING_PREFIX.fetchFrom(params), null, null);
 421             }

 422         }
 423     }
 424 
 425 
 426     private String getLauncherName(Map<String, ? super Object> params) {
 427         if (APP_NAME.fetchFrom(params) != null) {
 428             return APP_NAME.fetchFrom(params);
 429         } else {
 430             return MAIN_CLASS.fetchFrom(params);
 431         }
 432     }
 433 
 434     public static String getLauncherCfgName(Map<String, ? super Object> p) {
 435         return "Contents/Java/" + APP_NAME.fetchFrom(p) + ".cfg";
 436     }
 437 
 438     private void copyClassPathEntries(Path javaDirectory) throws IOException {
 439         List<RelativeFileSet> resourcesList = APP_RESOURCES_LIST.fetchFrom(params);
 440         if (resourcesList == null) {
 441             throw new RuntimeException(I18N.getString("message.null-classpath"));


 702         w.write(preprocessTextResource(
 703                 //MAC_BUNDLER_PREFIX + getConfig_InfoPlist(params).getName(),
 704                 "package/macosx/Info.plist",
 705                 I18N.getString("resource.app-info-plist"),
 706                 TEMPLATE_INFO_PLIST_LITE,
 707                 data, VERBOSE.fetchFrom(params),
 708                 DROP_IN_RESOURCES_ROOT.fetchFrom(params)));
 709         w.close();
 710     }
 711 
 712     private void writePkgInfo(File file) throws IOException {
 713         //hardcoded as it does not seem we need to change it ever
 714         String signature = "????";
 715 
 716         try (Writer out = new BufferedWriter(new FileWriter(file))) {
 717             out.write(OS_TYPE_CODE + signature);
 718             out.flush();
 719         }
 720     }
 721 









































































 722     public static void signAppBundle(Map<String, ? super Object> params, Path appLocation, String signingIdentity, String identifierPrefix, String entitlementsFile, String inheritedEntitlements) throws IOException {
 723         AtomicReference<IOException> toThrow = new AtomicReference<>();
 724         String appExecutable = "/Contents/MacOS/" + APP_NAME.fetchFrom(params);
 725         String keyChain = SIGNING_KEYCHAIN.fetchFrom(params);
 726 
 727         // sign all dylibs and jars
 728         Files.walk(appLocation)
 729                 // fix permissions
 730                 .peek(path -> {
 731                     try {
 732                         Set<PosixFilePermission> pfp = Files.getPosixFilePermissions(path);
 733                         if (!pfp.contains(PosixFilePermission.OWNER_WRITE)) {
 734                             pfp = EnumSet.copyOf(pfp);
 735                             pfp.add(PosixFilePermission.OWNER_WRITE);
 736                             Files.setPosixFilePermissions(path, pfp);
 737                         }
 738                     } catch (IOException e) {
 739                         Log.debug(e);
 740                     }
 741                 })




  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 jdk.packager.internal.legacy.builders.mac;
  26 
  27 
  28 import com.oracle.tools.packager.BundlerParamInfo;
  29 import com.oracle.tools.packager.IOUtils;
  30 import com.oracle.tools.packager.Log;
  31 import com.oracle.tools.packager.Platform;
  32 import com.oracle.tools.packager.RelativeFileSet;
  33 import com.oracle.tools.packager.StandardBundlerParam;
  34 import com.oracle.tools.packager.mac.MacResources;
  35 
  36 import jdk.packager.internal.legacy.builders.AbstractAppImageBuilder;
  37 import jdk.packager.internal.legacy.JLinkBundlerHelper;
  38 
  39 import java.io.BufferedWriter;
  40 import java.io.File;
  41 import java.io.FileInputStream;
  42 import java.io.FileOutputStream;
  43 import java.io.FileWriter;
  44 import java.io.IOException;
  45 import java.io.InputStream;
  46 import java.io.OutputStream;
  47 import java.io.OutputStreamWriter;
  48 import java.io.UncheckedIOException;
  49 import java.io.Writer;
  50 import java.math.BigInteger;
  51 import java.nio.file.Files;


  76             ResourceBundle.getBundle(MacAppImageBuilder.class.getName());
  77 
  78     private static final String EXECUTABLE_NAME = "JavaAppLauncher";
  79     private static final String LIBRARY_NAME = "libpackager.dylib";
  80     private static final String TEMPLATE_BUNDLE_ICON = "GenericApp.icns";
  81     private static final String OS_TYPE_CODE = "APPL";
  82     private static final String TEMPLATE_INFO_PLIST_LITE = "Info-lite.plist.template";
  83     private static final String TEMPLATE_RUNTIME_INFO_PLIST = "Runtime-Info.plist.template";
  84 
  85     private final Path root;
  86     private final Path contentsDir;
  87     private final Path javaDir;
  88     private final Path resourcesDir;
  89     private final Path macOSDir;
  90     private final Path runtimeDir;
  91     private final Path runtimeRoot;
  92     private final Path mdir;
  93 
  94     private final Map<String, ? super Object> params;
  95 
  96     private static List<String> keyChains;
  97 
  98     private static Map<String, String> getMacCategories() {
  99         Map<String, String> map = new HashMap<>();
 100         map.put("Business", "public.app-category.business");
 101         map.put("Developer Tools", "public.app-category.developer-tools");
 102         map.put("Education", "public.app-category.education");
 103         map.put("Entertainment", "public.app-category.entertainment");
 104         map.put("Finance", "public.app-category.finance");
 105         map.put("Games", "public.app-category.games");
 106         map.put("Graphics & Design", "public.app-category.graphics-design");
 107         map.put("Healthcare & Fitness", "public.app-category.healthcare-fitness");
 108         map.put("Lifestyle", "public.app-category.lifestyle");
 109         map.put("Medical", "public.app-category.medical");
 110         map.put("Music", "public.app-category.music");
 111         map.put("News", "public.app-category.news");
 112         map.put("Photography", "public.app-category.photography");
 113         map.put("Productivity", "public.app-category.productivity");
 114         map.put("Reference", "public.app-category.reference");
 115         map.put("Social Networking", "public.app-category.social-networking");
 116         map.put("Sports", "public.app-category.sports");
 117         map.put("Travel", "public.app-category.travel");


 401             if (f != null && f.exists()) {
 402                 try (InputStream in2 = new FileInputStream(f)) {
 403                     Files.copy(in2, resourcesDir.resolve(f.getName()));
 404                 }
 405 
 406             }
 407         }
 408 
 409         // Generate Info.plist
 410         writeInfoPlist(contentsDir.resolve("Info.plist").toFile());
 411 
 412         // generate java runtime info.plist
 413         writeRuntimeInfoPlist(runtimeDir.resolve("Contents/Info.plist").toFile());
 414 
 415         // copy library
 416         Path runtimeMacOSDir = Files.createDirectories(runtimeDir.resolve("Contents/MacOS"));
 417         Files.copy(runtimeRoot.resolve("lib/jli/libjli.dylib"), runtimeMacOSDir.resolve("libjli.dylib"));
 418 
 419         // maybe sign
 420         if (Optional.ofNullable(SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) {
 421             try {
 422                 addNewKeychain(params);
 423             } catch (InterruptedException e) {
 424                 Log.error(e.getMessage());
 425             }
 426             String signingIdentity = DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(params);
 427             if (signingIdentity != null) {
 428                 signAppBundle(params, root, signingIdentity, BUNDLE_ID_SIGNING_PREFIX.fetchFrom(params), null, null);
 429             }
 430             restoreKeychainList(params);
 431         }
 432     }
 433 
 434 
 435     private String getLauncherName(Map<String, ? super Object> params) {
 436         if (APP_NAME.fetchFrom(params) != null) {
 437             return APP_NAME.fetchFrom(params);
 438         } else {
 439             return MAIN_CLASS.fetchFrom(params);
 440         }
 441     }
 442 
 443     public static String getLauncherCfgName(Map<String, ? super Object> p) {
 444         return "Contents/Java/" + APP_NAME.fetchFrom(p) + ".cfg";
 445     }
 446 
 447     private void copyClassPathEntries(Path javaDirectory) throws IOException {
 448         List<RelativeFileSet> resourcesList = APP_RESOURCES_LIST.fetchFrom(params);
 449         if (resourcesList == null) {
 450             throw new RuntimeException(I18N.getString("message.null-classpath"));


 711         w.write(preprocessTextResource(
 712                 //MAC_BUNDLER_PREFIX + getConfig_InfoPlist(params).getName(),
 713                 "package/macosx/Info.plist",
 714                 I18N.getString("resource.app-info-plist"),
 715                 TEMPLATE_INFO_PLIST_LITE,
 716                 data, VERBOSE.fetchFrom(params),
 717                 DROP_IN_RESOURCES_ROOT.fetchFrom(params)));
 718         w.close();
 719     }
 720 
 721     private void writePkgInfo(File file) throws IOException {
 722         //hardcoded as it does not seem we need to change it ever
 723         String signature = "????";
 724 
 725         try (Writer out = new BufferedWriter(new FileWriter(file))) {
 726             out.write(OS_TYPE_CODE + signature);
 727             out.flush();
 728         }
 729     }
 730 
 731     public static void addNewKeychain(Map<String, ? super Object> params) 
 732                                     throws IOException, InterruptedException {
 733         if (Platform.getMajorVersion() < 10 || 
 734             (Platform.getMajorVersion() == 10 && Platform.getMinorVersion() < 12)) {
 735             // we need this for OS X 10.12+
 736             return;
 737         }
 738 
 739         String keyChain = SIGNING_KEYCHAIN.fetchFrom(params);
 740         if (keyChain == null || keyChain.isEmpty()) {
 741             return;
 742         }
 743 
 744         // get current keychain list
 745         String keyChainPath = new File (keyChain).getAbsolutePath().toString();
 746         List<String> keychainList = new ArrayList<>();
 747         int ret = IOUtils.getProcessOutput(keychainList, "security", "list-keychains");
 748         if (ret != 0) {
 749             Log.error(I18N.getString("message.keychain.error"));
 750             return;
 751         }
 752 
 753         boolean contains = keychainList.stream().anyMatch(
 754                     str -> str.trim().equals("\""+keyChainPath.trim()+"\""));
 755         if (contains) {
 756             // keychain is already added in the search list
 757             return;
 758         }
 759 
 760         keyChains = new ArrayList<>();
 761         // remove "
 762         keychainList.forEach((String s) -> {
 763             String path = s.trim();
 764             if (path.startsWith("\"") && path.endsWith("\"")) {
 765                 path = path.substring(1, path.length()-1);
 766             }
 767             keyChains.add(path);
 768         });
 769 
 770         List<String> args = new ArrayList<>();
 771         args.add("security");
 772         args.add("list-keychains");
 773         args.add("-s");
 774 
 775         args.addAll(keyChains);
 776         args.add(keyChain);
 777 
 778         ProcessBuilder  pb = new ProcessBuilder(args);
 779         IOUtils.exec(pb, VERBOSE.fetchFrom(params));
 780     }
 781 
 782     public static void restoreKeychainList(Map<String, ? super Object> params) throws IOException{
 783         if (Platform.getMajorVersion() < 10 || 
 784             (Platform.getMajorVersion() == 10 && Platform.getMinorVersion() < 12)) {
 785             // we need this for OS X 10.12+
 786             return;
 787         }
 788 
 789         if (keyChains == null || keyChains.isEmpty()) {
 790             return;
 791         }
 792 
 793         List<String> args = new ArrayList<>();
 794         args.add("security");
 795         args.add("list-keychains");
 796         args.add("-s");
 797 
 798         args.addAll(keyChains);
 799 
 800         ProcessBuilder  pb = new ProcessBuilder(args);
 801         IOUtils.exec(pb, VERBOSE.fetchFrom(params));
 802     }
 803 
 804     public static void signAppBundle(Map<String, ? super Object> params, Path appLocation, String signingIdentity, String identifierPrefix, String entitlementsFile, String inheritedEntitlements) throws IOException {
 805         AtomicReference<IOException> toThrow = new AtomicReference<>();
 806         String appExecutable = "/Contents/MacOS/" + APP_NAME.fetchFrom(params);
 807         String keyChain = SIGNING_KEYCHAIN.fetchFrom(params);
 808 
 809         // sign all dylibs and jars
 810         Files.walk(appLocation)
 811                 // fix permissions
 812                 .peek(path -> {
 813                     try {
 814                         Set<PosixFilePermission> pfp = Files.getPosixFilePermissions(path);
 815                         if (!pfp.contains(PosixFilePermission.OWNER_WRITE)) {
 816                             pfp = EnumSet.copyOf(pfp);
 817                             pfp.add(PosixFilePermission.OWNER_WRITE);
 818                             Files.setPosixFilePermissions(path, pfp);
 819                         }
 820                     } catch (IOException e) {
 821                         Log.debug(e);
 822                     }
 823                 })