6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jpackage.internal; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.math.BigInteger; 31 import java.text.MessageFormat; 32 import java.util.Arrays; 33 import java.util.Collection; 34 import java.util.HashMap; 35 import java.util.Map; 36 import java.util.Optional; 37 import java.util.ResourceBundle; 38 39 import static jdk.jpackage.internal.StandardBundlerParam.*; 40 import static jdk.jpackage.internal.MacBaseInstallerBundler.*; 41 import jdk.jpackage.internal.AbstractAppImageBuilder; 42 43 public class MacAppBundler extends AbstractImageBundler { 44 45 private static final ResourceBundle I18N = ResourceBundle.getBundle( 46 "jdk.jpackage.internal.resources.MacResources"); 47 48 private static final String TEMPLATE_BUNDLE_ICON = "GenericApp.icns"; 49 50 public static Map<String, String> getMacCategories() { 51 Map<String, String> map = new HashMap<>(); 52 map.put("Business", "public.app-category.business"); 53 map.put("Developer Tools", "public.app-category.developer-tools"); 54 map.put("Education", "public.app-category.education"); 55 map.put("Entertainment", "public.app-category.entertainment"); 56 map.put("Finance", "public.app-category.finance"); 57 map.put("Games", "public.app-category.games"); 58 map.put("Graphics & Design", "public.app-category.graphics-design"); 59 map.put("Healthcare & Fitness", 60 "public.app-category.healthcare-fitness"); 61 map.put("Lifestyle", "public.app-category.lifestyle"); 62 map.put("Medical", "public.app-category.medical"); 63 map.put("Music", "public.app-category.music"); 64 map.put("News", "public.app-category.news"); 65 map.put("Photography", "public.app-category.photography"); 66 map.put("Productivity", "public.app-category.productivity"); 67 map.put("Reference", "public.app-category.reference"); 68 map.put("Social Networking", "public.app-category.social-networking"); 69 map.put("Sports", "public.app-category.sports"); 70 map.put("Travel", "public.app-category.travel"); 71 map.put("Utilities", "public.app-category.utilities"); 72 map.put("Video", "public.app-category.video"); 73 map.put("Weather", "public.app-category.weather"); 74 75 map.put("Action Games", "public.app-category.action-games"); 76 map.put("Adventure Games", "public.app-category.adventure-games"); 77 map.put("Arcade Games", "public.app-category.arcade-games"); 78 map.put("Board Games", "public.app-category.board-games"); 79 map.put("Card Games", "public.app-category.card-games"); 80 map.put("Casino Games", "public.app-category.casino-games"); 81 map.put("Dice Games", "public.app-category.dice-games"); 82 map.put("Educational Games", "public.app-category.educational-games"); 83 map.put("Family Games", "public.app-category.family-games"); 84 map.put("Kids Games", "public.app-category.kids-games"); 85 map.put("Music Games", "public.app-category.music-games"); 86 map.put("Puzzle Games", "public.app-category.puzzle-games"); 87 map.put("Racing Games", "public.app-category.racing-games"); 88 map.put("Role Playing Games", "public.app-category.role-playing-games"); 89 map.put("Simulation Games", "public.app-category.simulation-games"); 90 map.put("Sports Games", "public.app-category.sports-games"); 91 map.put("Strategy Games", "public.app-category.strategy-games"); 92 map.put("Trivia Games", "public.app-category.trivia-games"); 93 map.put("Word Games", "public.app-category.word-games"); 94 95 return map; 96 } 97 98 public static final EnumeratedBundlerParam<String> MAC_CATEGORY = 99 new EnumeratedBundlerParam<>( 100 Arguments.CLIOptions.MAC_APP_STORE_CATEGORY.getId(), 101 String.class, 102 params -> "Unknown", 103 (s, p) -> s, 104 getMacCategories(), 105 false //strict - for MacStoreBundler this should be strict 106 ); 107 108 public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME = 109 new StandardBundlerParam<>( 110 Arguments.CLIOptions.MAC_BUNDLE_NAME.getId(), 111 String.class, 112 params -> null, 113 (s, p) -> s); 114 115 public static final BundlerParamInfo<String> MAC_CF_BUNDLE_IDENTIFIER = 116 new StandardBundlerParam<>( 117 Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(), 118 String.class, 119 IDENTIFIER::fetchFrom, 120 (s, p) -> s); 121 122 public static final BundlerParamInfo<String> MAC_CF_BUNDLE_VERSION = 123 new StandardBundlerParam<>( 124 "mac.CFBundleVersion", 125 String.class, 126 p -> { 127 String s = VERSION.fetchFrom(p); 128 if (validCFBundleVersion(s)) { 129 return s; 130 } else { 131 return "100"; 132 } 133 }, 134 (s, p) -> s); 135 136 public static final BundlerParamInfo<String> DEFAULT_ICNS_ICON = 137 new StandardBundlerParam<>( 138 ".mac.default.icns", 139 String.class, 140 params -> TEMPLATE_BUNDLE_ICON, 141 (s, p) -> s); 142 143 public static final BundlerParamInfo<String> DEVELOPER_ID_APP_SIGNING_KEY = 144 new StandardBundlerParam<>( 145 "mac.signing-key-developer-id-app", 146 String.class, 147 params -> { 148 String result = MacBaseInstallerBundler.findKey( 149 "Developer ID Application: " 150 + SIGNING_KEY_USER.fetchFrom(params), 151 SIGNING_KEYCHAIN.fetchFrom(params), 152 VERBOSE.fetchFrom(params)); 153 if (result != null) { 154 MacCertificate certificate = new MacCertificate(result, 155 VERBOSE.fetchFrom(params)); 156 157 if (!certificate.isValid()) { 158 Log.error(MessageFormat.format(I18N.getString( 159 "error.certificate.expired"), result)); 160 } 161 } 162 163 return result; 164 }, 165 (s, p) -> s); 166 167 public static final BundlerParamInfo<String> BUNDLE_ID_SIGNING_PREFIX = 168 new StandardBundlerParam<>( 169 Arguments.CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId(), 170 String.class, 171 params -> IDENTIFIER.fetchFrom(params) + ".", 172 (s, p) -> s); 173 174 public static final BundlerParamInfo<File> ICON_ICNS = 175 new StandardBundlerParam<>( 224 } 225 if (p.length > 2) { 226 n = new BigInteger(p[2]); 227 if (BigInteger.ZERO.compareTo(n) > 0) { 228 Log.verbose(I18N.getString( 229 "message.version-string-no-negative-numbers")); 230 return false; 231 } 232 } 233 } catch (NumberFormatException ne) { 234 Log.verbose(I18N.getString("message.version-string-numbers-only")); 235 Log.verbose(ne); 236 return false; 237 } 238 239 return true; 240 } 241 242 @Override 243 public boolean validate(Map<String, ? super Object> params) 244 throws UnsupportedPlatformException, ConfigException { 245 try { 246 return doValidate(params); 247 } catch (RuntimeException re) { 248 if (re.getCause() instanceof ConfigException) { 249 throw (ConfigException) re.getCause(); 250 } else { 251 throw new ConfigException(re); 252 } 253 } 254 } 255 256 private boolean doValidate(Map<String, ? super Object> p) 257 throws UnsupportedPlatformException, ConfigException { 258 if (Platform.getPlatform() != Platform.MAC) { 259 throw new UnsupportedPlatformException(); 260 } 261 262 imageBundleValidation(p); 263 264 if (StandardBundlerParam.getPredefinedAppImage(p) != null) { 265 return true; 266 } 267 268 // validate short version 269 if (!validCFBundleVersion(MAC_CF_BUNDLE_VERSION.fetchFrom(p))) { 270 throw new ConfigException( 271 I18N.getString("error.invalid-cfbundle-version"), 272 I18N.getString("error.invalid-cfbundle-version.advice")); 273 } 274 275 // reject explicitly set sign to true and no valid signature key 276 if (Optional.ofNullable(MacAppImageBuilder. 277 SIGN_BUNDLE.fetchFrom(p)).orElse(Boolean.FALSE)) { 278 String signingIdentity = DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(p); 279 if (signingIdentity == null) { 280 throw new ConfigException( 281 I18N.getString("error.explicit-sign-no-cert"), 282 I18N.getString("error.explicit-sign-no-cert.advice")); 283 } 284 } 285 286 return true; 287 } 288 289 File doBundle(Map<String, ? super Object> p, File outputDirectory, 290 boolean dependentTask) throws PackagerException { 291 if (StandardBundlerParam.isRuntimeInstaller(p)) { 292 return PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 293 } else { 294 return doAppBundle(p, outputDirectory, dependentTask); 295 } 296 } 297 298 File doAppBundle(Map<String, ? super Object> p, File outputDirectory, 299 boolean dependentTask) throws PackagerException { 300 try { 301 File rootDirectory = createRoot(p, outputDirectory, dependentTask, 302 APP_NAME.fetchFrom(p) + ".app"); 303 AbstractAppImageBuilder appBuilder = 304 new MacAppImageBuilder(p, outputDirectory.toPath()); 305 if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) { 306 JLinkBundlerHelper.execute(p, appBuilder); 307 } else { 308 StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder); 309 } 310 return rootDirectory; 311 } catch (PackagerException pe) { 312 throw pe; 313 } catch (Exception ex) { 314 Log.verbose(ex); 315 throw new PackagerException(ex); 316 } 317 } 318 319 ///////////////////////////////////////////////////////////////////////// 320 // Implement Bundler 321 ///////////////////////////////////////////////////////////////////////// 322 323 @Override 324 public String getName() { 325 return I18N.getString("app.bundler.name"); 326 } 327 328 @Override 329 public String getDescription() { 330 return I18N.getString("app.bundler.description"); 331 } 332 333 @Override 334 public String getID() { 335 return "mac.app"; 336 } 337 338 @Override 339 public String getBundleType() { 340 return "IMAGE"; 341 } 342 343 @Override 344 public Collection<BundlerParamInfo<?>> getBundleParameters() { 345 return getAppBundleParameters(); 346 } 347 348 public static Collection<BundlerParamInfo<?>> getAppBundleParameters() { 349 return Arrays.asList( 350 APP_NAME, 351 APP_RESOURCES, 352 ARGUMENTS, 353 BUNDLE_ID_SIGNING_PREFIX, 354 CLASSPATH, 355 DEVELOPER_ID_APP_SIGNING_KEY, 356 ICON_ICNS, 357 JAVA_OPTIONS, 358 MAC_CATEGORY, 359 MAC_CF_BUNDLE_IDENTIFIER, 360 MAC_CF_BUNDLE_NAME, 361 MAC_CF_BUNDLE_VERSION, 362 MAIN_CLASS, 363 MAIN_JAR, 364 SIGNING_KEYCHAIN, 365 VERSION, 366 VERBOSE 367 ); 368 } 369 370 371 @Override 372 public File execute(Map<String, ? super Object> params, 373 File outputParentDir) throws PackagerException { 374 return doBundle(params, outputParentDir, false); 375 } 376 377 @Override 378 public boolean supported(boolean runtimeInstaller) { 379 return Platform.getPlatform() == Platform.MAC; 380 } 381 382 } | 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.incubator.jpackage.internal; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.math.BigInteger; 31 import java.text.MessageFormat; 32 import java.util.HashMap; 33 import java.util.Map; 34 import java.util.Optional; 35 import java.util.ResourceBundle; 36 37 import static jdk.incubator.jpackage.internal.StandardBundlerParam.*; 38 import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.*; 39 40 public class MacAppBundler extends AbstractImageBundler { 41 42 private static final ResourceBundle I18N = ResourceBundle.getBundle( 43 "jdk.incubator.jpackage.internal.resources.MacResources"); 44 45 private static final String TEMPLATE_BUNDLE_ICON = "java.icns"; 46 47 public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME = 48 new StandardBundlerParam<>( 49 Arguments.CLIOptions.MAC_BUNDLE_NAME.getId(), 50 String.class, 51 params -> null, 52 (s, p) -> s); 53 54 public static final BundlerParamInfo<String> MAC_CF_BUNDLE_VERSION = 55 new StandardBundlerParam<>( 56 "mac.CFBundleVersion", 57 String.class, 58 p -> { 59 String s = VERSION.fetchFrom(p); 60 if (validCFBundleVersion(s)) { 61 return s; 62 } else { 63 return "100"; 64 } 65 }, 66 (s, p) -> s); 67 68 public static final BundlerParamInfo<String> DEFAULT_ICNS_ICON = 69 new StandardBundlerParam<>( 70 ".mac.default.icns", 71 String.class, 72 params -> TEMPLATE_BUNDLE_ICON, 73 (s, p) -> s); 74 75 public static final BundlerParamInfo<String> DEVELOPER_ID_APP_SIGNING_KEY = 76 new StandardBundlerParam<>( 77 "mac.signing-key-developer-id-app", 78 String.class, 79 params -> { 80 String result = MacBaseInstallerBundler.findKey( 81 "Developer ID Application: " 82 + SIGNING_KEY_USER.fetchFrom(params), 83 SIGNING_KEYCHAIN.fetchFrom(params), 84 VERBOSE.fetchFrom(params)); 85 if (result != null) { 86 MacCertificate certificate = new MacCertificate(result); 87 88 if (!certificate.isValid()) { 89 Log.error(MessageFormat.format(I18N.getString( 90 "error.certificate.expired"), result)); 91 } 92 } 93 94 return result; 95 }, 96 (s, p) -> s); 97 98 public static final BundlerParamInfo<String> BUNDLE_ID_SIGNING_PREFIX = 99 new StandardBundlerParam<>( 100 Arguments.CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId(), 101 String.class, 102 params -> IDENTIFIER.fetchFrom(params) + ".", 103 (s, p) -> s); 104 105 public static final BundlerParamInfo<File> ICON_ICNS = 106 new StandardBundlerParam<>( 155 } 156 if (p.length > 2) { 157 n = new BigInteger(p[2]); 158 if (BigInteger.ZERO.compareTo(n) > 0) { 159 Log.verbose(I18N.getString( 160 "message.version-string-no-negative-numbers")); 161 return false; 162 } 163 } 164 } catch (NumberFormatException ne) { 165 Log.verbose(I18N.getString("message.version-string-numbers-only")); 166 Log.verbose(ne); 167 return false; 168 } 169 170 return true; 171 } 172 173 @Override 174 public boolean validate(Map<String, ? super Object> params) 175 throws ConfigException { 176 try { 177 return doValidate(params); 178 } catch (RuntimeException re) { 179 if (re.getCause() instanceof ConfigException) { 180 throw (ConfigException) re.getCause(); 181 } else { 182 throw new ConfigException(re); 183 } 184 } 185 } 186 187 private boolean doValidate(Map<String, ? super Object> params) 188 throws ConfigException { 189 190 imageBundleValidation(params); 191 192 if (StandardBundlerParam.getPredefinedAppImage(params) != null) { 193 return true; 194 } 195 196 // validate short version 197 if (!validCFBundleVersion(MAC_CF_BUNDLE_VERSION.fetchFrom(params))) { 198 throw new ConfigException( 199 I18N.getString("error.invalid-cfbundle-version"), 200 I18N.getString("error.invalid-cfbundle-version.advice")); 201 } 202 203 // reject explicitly set sign to true and no valid signature key 204 if (Optional.ofNullable(MacAppImageBuilder. 205 SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) { 206 String signingIdentity = 207 DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(params); 208 if (signingIdentity == null) { 209 throw new ConfigException( 210 I18N.getString("error.explicit-sign-no-cert"), 211 I18N.getString("error.explicit-sign-no-cert.advice")); 212 } 213 214 // Signing will not work without Xcode with command line developer tools 215 try { 216 ProcessBuilder pb = new ProcessBuilder("xcrun", "--help"); 217 Process p = pb.start(); 218 int code = p.waitFor(); 219 if (code != 0) { 220 throw new ConfigException( 221 I18N.getString("error.no.xcode.signing"), 222 I18N.getString("error.no.xcode.signing.advice")); 223 } 224 } catch (IOException | InterruptedException ex) { 225 throw new ConfigException(ex); 226 } 227 } 228 229 return true; 230 } 231 232 File doBundle(Map<String, ? super Object> params, File outputDirectory, 233 boolean dependentTask) throws PackagerException { 234 if (StandardBundlerParam.isRuntimeInstaller(params)) { 235 return PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); 236 } else { 237 return doAppBundle(params, outputDirectory, dependentTask); 238 } 239 } 240 241 File doAppBundle(Map<String, ? super Object> params, File outputDirectory, 242 boolean dependentTask) throws PackagerException { 243 try { 244 File rootDirectory = createRoot(params, outputDirectory, 245 dependentTask, APP_NAME.fetchFrom(params) + ".app"); 246 AbstractAppImageBuilder appBuilder = 247 new MacAppImageBuilder(params, outputDirectory.toPath()); 248 if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ) { 249 JLinkBundlerHelper.execute(params, appBuilder); 250 } else { 251 StandardBundlerParam.copyPredefinedRuntimeImage( 252 params, appBuilder); 253 } 254 return rootDirectory; 255 } catch (PackagerException pe) { 256 throw pe; 257 } catch (Exception ex) { 258 Log.verbose(ex); 259 throw new PackagerException(ex); 260 } 261 } 262 263 ///////////////////////////////////////////////////////////////////////// 264 // Implement Bundler 265 ///////////////////////////////////////////////////////////////////////// 266 267 @Override 268 public String getName() { 269 return I18N.getString("app.bundler.name"); 270 } 271 272 @Override 273 public String getID() { 274 return "mac.app"; 275 } 276 277 @Override 278 public String getBundleType() { 279 return "IMAGE"; 280 } 281 282 @Override 283 public File execute(Map<String, ? super Object> params, 284 File outputParentDir) throws PackagerException { 285 return doBundle(params, outputParentDir, false); 286 } 287 288 @Override 289 public boolean supported(boolean runtimeInstaller) { 290 return true; 291 } 292 293 @Override 294 public boolean isDefault() { 295 return false; 296 } 297 298 } |