1 /* 2 * Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 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.lang.module.ModuleDescriptor; 31 import java.lang.module.ModuleDescriptor.Version; 32 import java.nio.file.Files; 33 import java.nio.file.Path; 34 import java.nio.file.Paths; 35 import java.text.MessageFormat; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.Collections; 39 import java.util.Date; 40 import java.util.LinkedHashSet; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Optional; 44 import java.util.ResourceBundle; 45 import java.util.Set; 46 import java.util.HashSet; 47 import java.util.function.BiFunction; 48 import java.util.function.Function; 49 import java.util.jar.Attributes; 50 import java.util.jar.JarFile; 51 import java.util.jar.Manifest; 52 import java.util.stream.Collectors; 53 import java.util.stream.Stream; 54 55 /** 56 * StandardBundlerParam 57 * 58 * A parameter to a bundler. 59 * 60 * Also contains static definitions of all of the common bundler parameters. 61 * (additional platform specific and mode specific bundler parameters 62 * are defined in each of the specific bundlers) 63 * 64 * Also contains static methods that operate on maps of parameters. 65 */ 66 class StandardBundlerParam<T> extends BundlerParamInfo<T> { 67 68 private static final String JAVABASEJMOD = "java.base.jmod"; 69 private final static String DEFAULT_VERSION = "1.0"; 70 private final static String DEFAULT_RELEASE = "1"; 71 private final static String[] DEFAULT_JLINK_OPTIONS = { 72 "--strip-native-commands", 73 "--strip-debug", 74 "--no-man-pages", 75 "--no-header-files"}; 76 77 StandardBundlerParam(String id, Class<T> valueType, 78 Function<Map<String, ? super Object>, T> defaultValueFunction, 79 BiFunction<String, Map<String, ? super Object>, T> stringConverter) 80 { 81 this.id = id; 82 this.valueType = valueType; 83 this.defaultValueFunction = defaultValueFunction; 84 this.stringConverter = stringConverter; 85 } 86 87 static final StandardBundlerParam<LauncherData> LAUNCHER_DATA = 88 new StandardBundlerParam<>( 89 "launcherData", 90 LauncherData.class, 91 params -> { 92 try { 93 return LauncherData.create(params); 94 } catch (ConfigException | IOException ex) { 95 throw new RuntimeException(ex); 96 } 97 }, 98 null 99 ); 100 101 static final StandardBundlerParam<Path> SOURCE_DIR = 102 new StandardBundlerParam<>( 103 Arguments.CLIOptions.INPUT.getId(), 104 Path.class, 105 p -> null, 106 (s, p) -> Path.of(s) 107 ); 108 109 // note that each bundler is likely to replace this one with 110 // their own converter 111 static final StandardBundlerParam<Path> MAIN_JAR = 112 new StandardBundlerParam<>( 113 Arguments.CLIOptions.MAIN_JAR.getId(), 114 Path.class, 115 params -> LAUNCHER_DATA.fetchFrom(params).mainJarName(), 116 null 117 ); 118 119 static final StandardBundlerParam<String> MAIN_CLASS = 120 new StandardBundlerParam<>( 121 Arguments.CLIOptions.APPCLASS.getId(), 122 String.class, 123 params -> { 124 if (isRuntimeInstaller(params)) { 125 return null; 126 } 127 return LAUNCHER_DATA.fetchFrom(params).qualifiedClassName(); 128 }, 129 (s, p) -> s 130 ); 131 132 static final StandardBundlerParam<File> PREDEFINED_RUNTIME_IMAGE = 133 new StandardBundlerParam<>( 134 Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), 135 File.class, 136 params -> null, 137 (s, p) -> new File(s) 138 ); 139 140 static final StandardBundlerParam<String> APP_NAME = 141 new StandardBundlerParam<>( 142 Arguments.CLIOptions.NAME.getId(), 143 String.class, 144 params -> { 145 String s = MAIN_CLASS.fetchFrom(params); 146 if (s != null) { 147 int idx = s.lastIndexOf("."); 148 if (idx >= 0) { 149 return s.substring(idx+1); 150 } 151 return s; 152 } else if (isRuntimeInstaller(params)) { 153 File f = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); 154 if (f != null) { 155 return f.getName(); 156 } 157 } 158 return null; 159 }, 160 (s, p) -> s 161 ); 162 163 static final StandardBundlerParam<File> ICON = 164 new StandardBundlerParam<>( 165 Arguments.CLIOptions.ICON.getId(), 166 File.class, 167 params -> null, 168 (s, p) -> new File(s) 169 ); 170 171 static final StandardBundlerParam<String> VENDOR = 172 new StandardBundlerParam<>( 173 Arguments.CLIOptions.VENDOR.getId(), 174 String.class, 175 params -> I18N.getString("param.vendor.default"), 176 (s, p) -> s 177 ); 178 179 static final StandardBundlerParam<String> DESCRIPTION = 180 new StandardBundlerParam<>( 181 Arguments.CLIOptions.DESCRIPTION.getId(), 182 String.class, 183 params -> params.containsKey(APP_NAME.getID()) 184 ? APP_NAME.fetchFrom(params) 185 : I18N.getString("param.description.default"), 186 (s, p) -> s 187 ); 188 189 static final StandardBundlerParam<String> COPYRIGHT = 190 new StandardBundlerParam<>( 191 Arguments.CLIOptions.COPYRIGHT.getId(), 192 String.class, 193 params -> MessageFormat.format(I18N.getString( 194 "param.copyright.default"), new Date()), 195 (s, p) -> s 196 ); 197 198 @SuppressWarnings("unchecked") 199 static final StandardBundlerParam<List<String>> ARGUMENTS = 200 new StandardBundlerParam<>( 201 Arguments.CLIOptions.ARGUMENTS.getId(), 202 (Class<List<String>>) (Object) List.class, 203 params -> Collections.emptyList(), 204 (s, p) -> null 205 ); 206 207 @SuppressWarnings("unchecked") 208 static final StandardBundlerParam<List<String>> JAVA_OPTIONS = 209 new StandardBundlerParam<>( 210 Arguments.CLIOptions.JAVA_OPTIONS.getId(), 211 (Class<List<String>>) (Object) List.class, 212 params -> Collections.emptyList(), 213 (s, p) -> Arrays.asList(s.split("\n\n")) 214 ); 215 216 static final StandardBundlerParam<String> VERSION = 217 new StandardBundlerParam<>( 218 Arguments.CLIOptions.VERSION.getId(), 219 String.class, 220 StandardBundlerParam::getDefaultAppVersion, 221 (s, p) -> s 222 ); 223 224 static final StandardBundlerParam<String> RELEASE = 225 new StandardBundlerParam<>( 226 Arguments.CLIOptions.RELEASE.getId(), 227 String.class, 228 params -> DEFAULT_RELEASE, 229 (s, p) -> s 230 ); 231 232 @SuppressWarnings("unchecked") 233 public static final StandardBundlerParam<String> LICENSE_FILE = 234 new StandardBundlerParam<>( 235 Arguments.CLIOptions.LICENSE_FILE.getId(), 236 String.class, 237 params -> null, 238 (s, p) -> s 239 ); 240 241 static final StandardBundlerParam<File> TEMP_ROOT = 242 new StandardBundlerParam<>( 243 Arguments.CLIOptions.TEMP_ROOT.getId(), 244 File.class, 245 params -> { 246 try { 247 return Files.createTempDirectory( 248 "jdk.incubator.jpackage").toFile(); 249 } catch (IOException ioe) { 250 return null; 251 } 252 }, 253 (s, p) -> new File(s) 254 ); 255 256 public static final StandardBundlerParam<File> CONFIG_ROOT = 257 new StandardBundlerParam<>( 258 "configRoot", 259 File.class, 260 params -> { 261 File root = 262 new File(TEMP_ROOT.fetchFrom(params), "config"); 263 root.mkdirs(); 264 return root; 265 }, 266 (s, p) -> null 267 ); 268 269 static final StandardBundlerParam<Boolean> BIND_SERVICES = 270 new StandardBundlerParam<>( 271 Arguments.CLIOptions.BIND_SERVICES.getId(), 272 Boolean.class, 273 params -> false, 274 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? 275 true : Boolean.valueOf(s) 276 ); 277 278 279 static final StandardBundlerParam<Boolean> VERBOSE = 280 new StandardBundlerParam<>( 281 Arguments.CLIOptions.VERBOSE.getId(), 282 Boolean.class, 283 params -> false, 284 // valueOf(null) is false, and we actually do want null 285 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? 286 true : Boolean.valueOf(s) 287 ); 288 289 static final StandardBundlerParam<File> RESOURCE_DIR = 290 new StandardBundlerParam<>( 291 Arguments.CLIOptions.RESOURCE_DIR.getId(), 292 File.class, 293 params -> null, 294 (s, p) -> new File(s) 295 ); 296 297 static final BundlerParamInfo<String> INSTALL_DIR = 298 new StandardBundlerParam<>( 299 Arguments.CLIOptions.INSTALL_DIR.getId(), 300 String.class, 301 params -> null, 302 (s, p) -> s 303 ); 304 305 static final StandardBundlerParam<File> PREDEFINED_APP_IMAGE = 306 new StandardBundlerParam<>( 307 Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), 308 File.class, 309 params -> null, 310 (s, p) -> new File(s)); 311 312 @SuppressWarnings("unchecked") 313 static final StandardBundlerParam<List<Map<String, ? super Object>>> ADD_LAUNCHERS = 314 new StandardBundlerParam<>( 315 Arguments.CLIOptions.ADD_LAUNCHER.getId(), 316 (Class<List<Map<String, ? super Object>>>) (Object) 317 List.class, 318 params -> new ArrayList<>(1), 319 // valueOf(null) is false, and we actually do want null 320 (s, p) -> null 321 ); 322 323 @SuppressWarnings("unchecked") 324 static final StandardBundlerParam 325 <List<Map<String, ? super Object>>> FILE_ASSOCIATIONS = 326 new StandardBundlerParam<>( 327 Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(), 328 (Class<List<Map<String, ? super Object>>>) (Object) 329 List.class, 330 params -> new ArrayList<>(1), 331 // valueOf(null) is false, and we actually do want null 332 (s, p) -> null 333 ); 334 335 @SuppressWarnings("unchecked") 336 static final StandardBundlerParam<List<String>> FA_EXTENSIONS = 337 new StandardBundlerParam<>( 338 "fileAssociation.extension", 339 (Class<List<String>>) (Object) List.class, 340 params -> null, // null means not matched to an extension 341 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 342 ); 343 344 @SuppressWarnings("unchecked") 345 static final StandardBundlerParam<List<String>> FA_CONTENT_TYPE = 346 new StandardBundlerParam<>( 347 "fileAssociation.contentType", 348 (Class<List<String>>) (Object) List.class, 349 params -> null, 350 // null means not matched to a content/mime type 351 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 352 ); 353 354 static final StandardBundlerParam<String> FA_DESCRIPTION = 355 new StandardBundlerParam<>( 356 "fileAssociation.description", 357 String.class, 358 params -> APP_NAME.fetchFrom(params) + " File", 359 null 360 ); 361 362 static final StandardBundlerParam<File> FA_ICON = 363 new StandardBundlerParam<>( 364 "fileAssociation.icon", 365 File.class, 366 ICON::fetchFrom, 367 (s, p) -> new File(s) 368 ); 369 370 @SuppressWarnings("unchecked") 371 static final BundlerParamInfo<List<Path>> MODULE_PATH = 372 new StandardBundlerParam<>( 373 Arguments.CLIOptions.MODULE_PATH.getId(), 374 (Class<List<Path>>) (Object)List.class, 375 p -> getDefaultModulePath(), 376 (s, p) -> { 377 List<Path> modulePath = Stream.of(s.split(File.pathSeparator)) 378 .map(Path::of) 379 .collect(Collectors.toList()); 380 Path javaBasePath = findPathOfModule(modulePath, JAVABASEJMOD); 381 382 // Add the default JDK module path to the module path. 383 if (javaBasePath == null) { 384 List<Path> jdkModulePath = getDefaultModulePath(); 385 386 if (jdkModulePath != null) { 387 modulePath.addAll(jdkModulePath); 388 javaBasePath = findPathOfModule(modulePath, JAVABASEJMOD); 389 } 390 } 391 392 if (javaBasePath == null || 393 !Files.exists(javaBasePath)) { 394 Log.error(String.format(I18N.getString( 395 "warning.no.jdk.modules.found"))); 396 } 397 398 return modulePath; 399 }); 400 401 // Returns the path to the JDK modules in the user defined module path. 402 private static Path findPathOfModule( List<Path> modulePath, String moduleName) { 403 404 for (Path path : modulePath) { 405 Path moduleNamePath = path.resolve(moduleName); 406 407 if (Files.exists(moduleNamePath)) { 408 return path; 409 } 410 } 411 412 return null; 413 } 414 415 static final BundlerParamInfo<String> MODULE = 416 new StandardBundlerParam<>( 417 Arguments.CLIOptions.MODULE.getId(), 418 String.class, 419 p -> null, 420 (s, p) -> { 421 return String.valueOf(s); 422 }); 423 424 @SuppressWarnings("unchecked") 425 static final BundlerParamInfo<Set<String>> ADD_MODULES = 426 new StandardBundlerParam<>( 427 Arguments.CLIOptions.ADD_MODULES.getId(), 428 (Class<Set<String>>) (Object) Set.class, 429 p -> new LinkedHashSet<String>(), 430 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 431 ); 432 433 @SuppressWarnings("unchecked") 434 static final StandardBundlerParam<List<String>> JLINK_OPTIONS = 435 new StandardBundlerParam<>( 436 Arguments.CLIOptions.JLINK_OPTIONS.getId(), 437 (Class<List<String>>) (Object) List.class, 438 p -> Arrays.asList(DEFAULT_JLINK_OPTIONS), 439 (s, p) -> null); 440 441 @SuppressWarnings("unchecked") 442 static final BundlerParamInfo<Set<String>> LIMIT_MODULES = 443 new StandardBundlerParam<>( 444 "limit-modules", 445 (Class<Set<String>>) (Object) Set.class, 446 p -> new LinkedHashSet<String>(), 447 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 448 ); 449 450 static boolean isRuntimeInstaller(Map<String, ? super Object> params) { 451 if (params.containsKey(MODULE.getID()) || 452 params.containsKey(MAIN_JAR.getID()) || 453 params.containsKey(PREDEFINED_APP_IMAGE.getID())) { 454 return false; // we are building or are given an application 455 } 456 // runtime installer requires --runtime-image, if this is false 457 // here then we should have thrown error validating args. 458 return params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID()); 459 } 460 461 static File getPredefinedAppImage(Map<String, ? super Object> params) { 462 File applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params); 463 if (applicationImage != null && !applicationImage.exists()) { 464 throw new RuntimeException( 465 MessageFormat.format(I18N.getString( 466 "message.app-image-dir-does-not-exist"), 467 PREDEFINED_APP_IMAGE.getID(), 468 applicationImage.toString())); 469 } 470 return applicationImage; 471 } 472 473 static void copyPredefinedRuntimeImage(Map<String, ? super Object> params, 474 ApplicationLayout appLayout) throws IOException, ConfigException { 475 File topImage = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); 476 if (!topImage.exists()) { 477 throw new ConfigException( 478 MessageFormat.format(I18N.getString( 479 "message.runtime-image-dir-does-not-exist"), 480 PREDEFINED_RUNTIME_IMAGE.getID(), 481 topImage.toString()), 482 MessageFormat.format(I18N.getString( 483 "message.runtime-image-dir-does-not-exist.advice"), 484 PREDEFINED_RUNTIME_IMAGE.getID())); 485 } 486 487 if (Platform.isMac()) { 488 // On Mac topImage can be runtime root or runtime home. 489 Path runtimeHome = topImage.toPath().resolve("Contents/Home"); 490 if (Files.isDirectory(runtimeHome)) { 491 // topImage references runtime root, adjust it to pick data from 492 // runtime home 493 topImage = runtimeHome.toFile(); 494 } 495 } 496 497 // copy whole runtime, need to skip jmods and src.zip 498 final List<String> excludes = Arrays.asList("jmods", "src.zip"); 499 IOUtils.copyRecursive(topImage.toPath(), 500 appLayout.runtimeHomeDirectory(), excludes); 501 502 // if module-path given - copy modules to appDir/mods 503 List<Path> modulePath = 504 StandardBundlerParam.MODULE_PATH.fetchFrom(params); 505 List<Path> defaultModulePath = getDefaultModulePath(); 506 Path dest = appLayout.appModsDirectory(); 507 508 if (dest != null) { 509 for (Path mp : modulePath) { 510 if (!defaultModulePath.contains(mp)) { 511 Files.createDirectories(dest); 512 IOUtils.copyRecursive(mp, dest); 513 } 514 } 515 } 516 } 517 518 private static List<Path> getDefaultModulePath() { 519 return List.of( 520 Path.of(System.getProperty("java.home"), "jmods").toAbsolutePath()); 521 } 522 523 private static String getDefaultAppVersion(Map<String, ? super Object> params) { 524 String appVersion = DEFAULT_VERSION; 525 526 if (isRuntimeInstaller(params)) { 527 return appVersion; 528 } 529 530 LauncherData launcherData = null; 531 try { 532 launcherData = LAUNCHER_DATA.fetchFrom(params); 533 } catch (RuntimeException ex) { 534 if (ex.getCause() instanceof ConfigException) { 535 return appVersion; 536 } 537 throw ex; 538 } 539 540 if (launcherData.isModular()) { 541 String moduleVersion = launcherData.getAppVersion(); 542 if (moduleVersion != null) { 543 Log.verbose(MessageFormat.format(I18N.getString( 544 "message.module-version"), 545 moduleVersion, 546 launcherData.moduleName())); 547 appVersion = moduleVersion; 548 } 549 } 550 551 return appVersion; 552 } 553 }