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 })
|