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 jdk.jpackage.internal.BundleParams; 29 import jdk.jpackage.internal.AbstractAppImageBuilder; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.io.StringReader; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import java.nio.file.Paths; 37 import java.text.MessageFormat; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Collections; 41 import java.util.Date; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.LinkedHashSet; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Optional; 48 import java.util.Properties; 49 import java.util.ResourceBundle; 50 import java.util.Set; 51 import java.util.HashSet; 52 import java.util.function.BiFunction; 53 import java.util.function.Function; 54 import java.util.jar.Attributes; 55 import java.util.jar.JarFile; 56 import java.util.jar.Manifest; 57 import java.util.regex.Pattern; 58 import java.util.stream.Collectors; 59 60 /** 61 * StandardBundlerParam 62 * 63 * A parameter to a bundler. 64 * 65 * Also contains static definitions of all of the common bundler parameters. 66 * (additional platform specific and mode specific bundler parameters 67 * are defined in each of the specific bundlers) 68 * 69 * Also contains static methods that operate on maps of parameters. 70 */ 71 class StandardBundlerParam<T> extends BundlerParamInfo<T> { 72 73 private static final ResourceBundle I18N = ResourceBundle.getBundle( 74 "jdk.jpackage.internal.resources.MainResources"); 75 private static final String JAVABASEJMOD = "java.base.jmod"; 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<RelativeFileSet> APP_RESOURCES = 88 new StandardBundlerParam<>( 89 BundleParams.PARAM_APP_RESOURCES, 90 RelativeFileSet.class, 91 null, // no default. Required parameter 92 null // no string translation, 93 // tool must provide complex type 94 ); 95 118 } 119 else { 120 return value; 121 } 122 } 123 ); 124 125 // note that each bundler is likely to replace this one with 126 // their own converter 127 static final StandardBundlerParam<RelativeFileSet> MAIN_JAR = 128 new StandardBundlerParam<>( 129 Arguments.CLIOptions.MAIN_JAR.getId(), 130 RelativeFileSet.class, 131 params -> { 132 extractMainClassInfoFromAppResources(params); 133 return (RelativeFileSet) params.get("mainJar"); 134 }, 135 (s, p) -> getMainJar(s, p) 136 ); 137 138 // TODO: test CLASSPATH jar manifest Attributet 139 static final StandardBundlerParam<String> CLASSPATH = 140 new StandardBundlerParam<>( 141 "classpath", 142 String.class, 143 params -> { 144 extractMainClassInfoFromAppResources(params); 145 String cp = (String) params.get("classpath"); 146 return cp == null ? "" : cp; 147 }, 148 (s, p) -> s.replace(File.pathSeparator, " ") 149 ); 150 151 static final StandardBundlerParam<String> MAIN_CLASS = 152 new StandardBundlerParam<>( 153 Arguments.CLIOptions.APPCLASS.getId(), 154 String.class, 155 params -> { 156 if (isRuntimeInstaller(params)) { 157 return null; 158 } 159 extractMainClassInfoFromAppResources(params); 160 String s = (String) params.get( 161 BundleParams.PARAM_APPLICATION_CLASS); 162 if (s == null) { 163 s = JLinkBundlerHelper.getMainClass(params); 164 } 165 return s; 166 }, 167 (s, p) -> s 168 ); 169 170 static final StandardBundlerParam<File> PREDEFINED_RUNTIME_IMAGE = 171 new StandardBundlerParam<>( 172 Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), 173 File.class, 174 params -> null, 175 (s, p) -> new File(s) 176 ); 177 178 static final StandardBundlerParam<String> APP_NAME = 179 new StandardBundlerParam<>( 180 Arguments.CLIOptions.NAME.getId(), 181 String.class, 182 params -> { 183 String s = MAIN_CLASS.fetchFrom(params); 222 ? APP_NAME.fetchFrom(params) 223 : I18N.getString("param.description.default"), 224 (s, p) -> s 225 ); 226 227 static final StandardBundlerParam<String> COPYRIGHT = 228 new StandardBundlerParam<>( 229 Arguments.CLIOptions.COPYRIGHT.getId(), 230 String.class, 231 params -> MessageFormat.format(I18N.getString( 232 "param.copyright.default"), new Date()), 233 (s, p) -> s 234 ); 235 236 @SuppressWarnings("unchecked") 237 static final StandardBundlerParam<List<String>> ARGUMENTS = 238 new StandardBundlerParam<>( 239 Arguments.CLIOptions.ARGUMENTS.getId(), 240 (Class<List<String>>) (Object) List.class, 241 params -> Collections.emptyList(), 242 (s, p) -> splitStringWithEscapes(s) 243 ); 244 245 @SuppressWarnings("unchecked") 246 static final StandardBundlerParam<List<String>> JAVA_OPTIONS = 247 new StandardBundlerParam<>( 248 Arguments.CLIOptions.JAVA_OPTIONS.getId(), 249 (Class<List<String>>) (Object) List.class, 250 params -> Collections.emptyList(), 251 (s, p) -> Arrays.asList(s.split("\n\n")) 252 ); 253 254 // note that each bundler is likely to replace this one with 255 // their own converter 256 static final StandardBundlerParam<String> VERSION = 257 new StandardBundlerParam<>( 258 Arguments.CLIOptions.VERSION.getId(), 259 String.class, 260 params -> I18N.getString("param.version.default"), 261 (s, p) -> s 262 ); 263 264 @SuppressWarnings("unchecked") 265 public static final StandardBundlerParam<String> LICENSE_FILE = 266 new StandardBundlerParam<>( 267 Arguments.CLIOptions.LICENSE_FILE.getId(), 268 String.class, 269 params -> null, 270 (s, p) -> s 271 ); 272 273 static final StandardBundlerParam<File> TEMP_ROOT = 274 new StandardBundlerParam<>( 275 Arguments.CLIOptions.TEMP_ROOT.getId(), 276 File.class, 277 params -> { 278 try { 279 return Files.createTempDirectory( 280 "jdk.jpackage").toFile(); 281 } catch (IOException ioe) { 282 return null; 283 } 284 }, 285 (s, p) -> new File(s) 286 ); 287 288 public static final StandardBundlerParam<File> CONFIG_ROOT = 289 new StandardBundlerParam<>( 290 "configRoot", 291 File.class, 292 params -> { 293 File root = 294 new File(TEMP_ROOT.fetchFrom(params), "config"); 295 root.mkdirs(); 296 return root; 297 }, 298 (s, p) -> null 299 ); 300 301 static final StandardBundlerParam<String> IDENTIFIER = 302 new StandardBundlerParam<>( 303 Arguments.CLIOptions.IDENTIFIER.getId(), 304 String.class, 305 params -> { 306 String s = MAIN_CLASS.fetchFrom(params); 307 if (s == null) return null; 308 309 int idx = s.lastIndexOf("."); 310 if (idx >= 1) { 311 return s.substring(0, idx); 312 } 313 return s; 314 }, 315 (s, p) -> s 316 ); 317 318 static final StandardBundlerParam<Boolean> VERBOSE = 319 new StandardBundlerParam<>( 320 Arguments.CLIOptions.VERBOSE.getId(), 321 Boolean.class, 322 params -> false, 323 // valueOf(null) is false, and we actually do want null 324 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? 325 true : Boolean.valueOf(s) 326 ); 327 328 static final StandardBundlerParam<File> RESOURCE_DIR = 329 new StandardBundlerParam<>( 330 Arguments.CLIOptions.RESOURCE_DIR.getId(), 331 File.class, 332 params -> null, 333 (s, p) -> new File(s) 334 ); 335 336 static final BundlerParamInfo<String> INSTALL_DIR = 337 new StandardBundlerParam<>( 456 }); 457 458 @SuppressWarnings("unchecked") 459 static final BundlerParamInfo<Set<String>> ADD_MODULES = 460 new StandardBundlerParam<>( 461 Arguments.CLIOptions.ADD_MODULES.getId(), 462 (Class<Set<String>>) (Object) Set.class, 463 p -> new LinkedHashSet<String>(), 464 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 465 ); 466 467 @SuppressWarnings("unchecked") 468 static final BundlerParamInfo<Set<String>> LIMIT_MODULES = 469 new StandardBundlerParam<>( 470 "limit-modules", 471 (Class<Set<String>>) (Object) Set.class, 472 p -> new LinkedHashSet<String>(), 473 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 474 ); 475 476 static boolean isRuntimeInstaller(Map<String, ? super Object> p) { 477 if (p.containsKey(MODULE.getID()) || 478 p.containsKey(MAIN_JAR.getID()) || 479 p.containsKey(PREDEFINED_APP_IMAGE.getID())) { 480 return false; // we are building or are given an application 481 } 482 // runtime installer requires --runtime-image, if this is false 483 // here then we should have thrown error validating args. 484 return p.containsKey(PREDEFINED_RUNTIME_IMAGE.getID()); 485 } 486 487 static File getPredefinedAppImage(Map<String, ? super Object> p) { 488 File applicationImage = null; 489 if (PREDEFINED_APP_IMAGE.fetchFrom(p) != null) { 490 applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(p); 491 Log.debug("Using App Image from " + applicationImage); 492 if (!applicationImage.exists()) { 493 throw new RuntimeException( 494 MessageFormat.format(I18N.getString( 495 "message.app-image-dir-does-not-exist"), 496 PREDEFINED_APP_IMAGE.getID(), 497 applicationImage.toString())); 498 } 499 } 500 return applicationImage; 501 } 502 503 static void copyPredefinedRuntimeImage( 504 Map<String, ? super Object> p, 505 AbstractAppImageBuilder appBuilder) 506 throws IOException , ConfigException { 507 File image = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 508 if (!image.exists()) { 509 throw new ConfigException( 510 MessageFormat.format(I18N.getString( 511 "message.runtime-image-dir-does-not-exist"), 512 PREDEFINED_RUNTIME_IMAGE.getID(), 513 image.toString()), 514 MessageFormat.format(I18N.getString( 515 "message.runtime-image-dir-does-not-exist.advice"), 516 PREDEFINED_RUNTIME_IMAGE.getID())); 517 } 518 // copy whole runtime, need to skip jmods and src.zip 519 final List<String> excludes = Arrays.asList("jmods", "src.zip"); 520 IOUtils.copyRecursive(image.toPath(), appBuilder.getRoot(), excludes); 521 522 // if module-path given - copy modules to appDir/mods 523 List<Path> modulePath = 524 StandardBundlerParam.MODULE_PATH.fetchFrom(p); 525 List<Path> defaultModulePath = getDefaultModulePath(); 526 Path dest = appBuilder.getAppModsDir(); 527 528 if (dest != null) { 529 for (Path mp : modulePath) { 530 if (!defaultModulePath.contains(mp)) { 531 Files.createDirectories(dest); 532 IOUtils.copyRecursive(mp, dest); 533 } 534 } 535 } 536 537 appBuilder.prepareApplicationFiles(); 538 } 539 540 static void extractMainClassInfoFromAppResources( 541 Map<String, ? super Object> params) { 542 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 543 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 544 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 545 boolean hasModule = params.containsKey(MODULE.getID()); 546 547 if (hasMainClass && hasMainJar && hasMainJarClassPath || hasModule || 548 isRuntimeInstaller(params)) { 549 return; 550 } 551 552 // it's a pair. 553 // The [0] is the srcdir [1] is the file relative to sourcedir 554 List<String[]> filesToCheck = new ArrayList<>(); 555 556 if (hasMainJar) { 557 RelativeFileSet rfs = MAIN_JAR.fetchFrom(params); 614 cp == null ? "" : cp); 615 } 616 break; 617 } 618 } 619 } catch (IOException ignore) { 620 ignore.printStackTrace(); 621 } 622 } 623 } 624 625 static void validateMainClassInfoFromAppResources( 626 Map<String, ? super Object> params) throws ConfigException { 627 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 628 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 629 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 630 boolean hasModule = params.containsKey(MODULE.getID()); 631 boolean hasAppImage = params.containsKey(PREDEFINED_APP_IMAGE.getID()); 632 633 if (hasMainClass && hasMainJar && hasMainJarClassPath || 634 hasModule || hasAppImage || isRuntimeInstaller(params)) { 635 return; 636 } 637 638 extractMainClassInfoFromAppResources(params); 639 640 if (!params.containsKey(MAIN_CLASS.getID())) { 641 if (hasMainJar) { 642 throw new ConfigException( 643 MessageFormat.format(I18N.getString( 644 "error.no-main-class-with-main-jar"), 645 MAIN_JAR.fetchFrom(params)), 646 MessageFormat.format(I18N.getString( 647 "error.no-main-class-with-main-jar.advice"), 648 MAIN_JAR.fetchFrom(params))); 649 } else { 650 throw new ConfigException( 651 I18N.getString("error.no-main-class"), 652 I18N.getString("error.no-main-class.advice")); 653 } 654 } 655 } 656 657 private static List<String> splitStringWithEscapes(String s) { 658 List<String> l = new ArrayList<>(); 659 StringBuilder current = new StringBuilder(); 660 boolean quoted = false; 661 boolean escaped = false; 662 for (char c : s.toCharArray()) { 663 if (escaped) { 664 current.append(c); 665 } else if ('"' == c) { 666 quoted = !quoted; 667 } else if (!quoted && Character.isWhitespace(c)) { 668 l.add(current.toString()); 669 current = new StringBuilder(); 670 } else { 671 current.append(c); 672 } 673 } 674 l.add(current.toString()); 675 return l; 676 } 677 678 private static List<RelativeFileSet> 679 createAppResourcesListFromString(String s, 680 Map<String, ? super Object> objectObjectMap) { 681 List<RelativeFileSet> result = new ArrayList<>(); 682 for (String path : s.split("[:;]")) { 683 File f = new File(path); 684 if (f.getName().equals("*") || path.endsWith("/") || 685 path.endsWith("\\")) { 686 if (f.getName().equals("*")) { 687 f = f.getParentFile(); 688 } 689 Set<File> theFiles = new HashSet<>(); 690 try { 691 Files.walk(f.toPath()) 692 .filter(Files::isRegularFile) 693 .forEach(p -> theFiles.add(p.toFile())); 694 } catch (IOException e) { 695 e.printStackTrace(); 696 } 697 result.add(new RelativeFileSet(f, theFiles)); 698 } else { 699 result.add(new RelativeFileSet(f.getParentFile(), 700 Collections.singleton(f))); 701 } 702 } 703 return result; 704 } 705 706 private static RelativeFileSet getMainJar( 707 String mainJarValue, Map<String, ? super Object> params) { 708 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { 709 File appResourcesRoot = rfs.getBaseDirectory(); 710 File mainJarFile = new File(appResourcesRoot, mainJarValue); 711 712 if (mainJarFile.exists()) { 713 return new RelativeFileSet(appResourcesRoot, 750 result.add(jdkModulePath); 751 } 752 else { 753 // On a developer build the JDK Home isn't where we expect it 754 // relative to the jmods directory. Do some extra 755 // processing to find it. 756 Map<String, String> env = System.getenv(); 757 758 if (env.containsKey("JDK_HOME")) { 759 jdkModulePath = Paths.get(env.get("JDK_HOME"), 760 ".." + File.separator + "images" 761 + File.separator + "jmods").toAbsolutePath(); 762 763 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 764 result.add(jdkModulePath); 765 } 766 } 767 } 768 769 return result; 770 } 771 } | 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 ResourceBundle I18N = ResourceBundle.getBundle( 69 "jdk.incubator.jpackage.internal.resources.MainResources"); 70 private static final String JAVABASEJMOD = "java.base.jmod"; 71 private final static String DEFAULT_VERSION = "1.0"; 72 private final static String DEFAULT_RELEASE = "1"; 73 74 StandardBundlerParam(String id, Class<T> valueType, 75 Function<Map<String, ? super Object>, T> defaultValueFunction, 76 BiFunction<String, Map<String, ? super Object>, T> stringConverter) 77 { 78 this.id = id; 79 this.valueType = valueType; 80 this.defaultValueFunction = defaultValueFunction; 81 this.stringConverter = stringConverter; 82 } 83 84 static final StandardBundlerParam<RelativeFileSet> APP_RESOURCES = 85 new StandardBundlerParam<>( 86 BundleParams.PARAM_APP_RESOURCES, 87 RelativeFileSet.class, 88 null, // no default. Required parameter 89 null // no string translation, 90 // tool must provide complex type 91 ); 92 115 } 116 else { 117 return value; 118 } 119 } 120 ); 121 122 // note that each bundler is likely to replace this one with 123 // their own converter 124 static final StandardBundlerParam<RelativeFileSet> MAIN_JAR = 125 new StandardBundlerParam<>( 126 Arguments.CLIOptions.MAIN_JAR.getId(), 127 RelativeFileSet.class, 128 params -> { 129 extractMainClassInfoFromAppResources(params); 130 return (RelativeFileSet) params.get("mainJar"); 131 }, 132 (s, p) -> getMainJar(s, p) 133 ); 134 135 static final StandardBundlerParam<String> CLASSPATH = 136 new StandardBundlerParam<>( 137 "classpath", 138 String.class, 139 params -> { 140 extractMainClassInfoFromAppResources(params); 141 String cp = (String) params.get("classpath"); 142 return cp == null ? "" : cp; 143 }, 144 (s, p) -> s 145 ); 146 147 static final StandardBundlerParam<String> MAIN_CLASS = 148 new StandardBundlerParam<>( 149 Arguments.CLIOptions.APPCLASS.getId(), 150 String.class, 151 params -> { 152 if (isRuntimeInstaller(params)) { 153 return null; 154 } 155 extractMainClassInfoFromAppResources(params); 156 String s = (String) params.get( 157 BundleParams.PARAM_APPLICATION_CLASS); 158 if (s == null) { 159 s = JLinkBundlerHelper.getMainClassFromModule( 160 params); 161 } 162 return s; 163 }, 164 (s, p) -> s 165 ); 166 167 static final StandardBundlerParam<File> PREDEFINED_RUNTIME_IMAGE = 168 new StandardBundlerParam<>( 169 Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), 170 File.class, 171 params -> null, 172 (s, p) -> new File(s) 173 ); 174 175 static final StandardBundlerParam<String> APP_NAME = 176 new StandardBundlerParam<>( 177 Arguments.CLIOptions.NAME.getId(), 178 String.class, 179 params -> { 180 String s = MAIN_CLASS.fetchFrom(params); 219 ? APP_NAME.fetchFrom(params) 220 : I18N.getString("param.description.default"), 221 (s, p) -> s 222 ); 223 224 static final StandardBundlerParam<String> COPYRIGHT = 225 new StandardBundlerParam<>( 226 Arguments.CLIOptions.COPYRIGHT.getId(), 227 String.class, 228 params -> MessageFormat.format(I18N.getString( 229 "param.copyright.default"), new Date()), 230 (s, p) -> s 231 ); 232 233 @SuppressWarnings("unchecked") 234 static final StandardBundlerParam<List<String>> ARGUMENTS = 235 new StandardBundlerParam<>( 236 Arguments.CLIOptions.ARGUMENTS.getId(), 237 (Class<List<String>>) (Object) List.class, 238 params -> Collections.emptyList(), 239 (s, p) -> null 240 ); 241 242 @SuppressWarnings("unchecked") 243 static final StandardBundlerParam<List<String>> JAVA_OPTIONS = 244 new StandardBundlerParam<>( 245 Arguments.CLIOptions.JAVA_OPTIONS.getId(), 246 (Class<List<String>>) (Object) List.class, 247 params -> Collections.emptyList(), 248 (s, p) -> Arrays.asList(s.split("\n\n")) 249 ); 250 251 // note that each bundler is likely to replace this one with 252 // their own converter 253 static final StandardBundlerParam<String> VERSION = 254 new StandardBundlerParam<>( 255 Arguments.CLIOptions.VERSION.getId(), 256 String.class, 257 params -> getDefaultAppVersion(params), 258 (s, p) -> s 259 ); 260 261 static final StandardBundlerParam<String> RELEASE = 262 new StandardBundlerParam<>( 263 Arguments.CLIOptions.RELEASE.getId(), 264 String.class, 265 params -> DEFAULT_RELEASE, 266 (s, p) -> s 267 ); 268 269 @SuppressWarnings("unchecked") 270 public static final StandardBundlerParam<String> LICENSE_FILE = 271 new StandardBundlerParam<>( 272 Arguments.CLIOptions.LICENSE_FILE.getId(), 273 String.class, 274 params -> null, 275 (s, p) -> s 276 ); 277 278 static final StandardBundlerParam<File> TEMP_ROOT = 279 new StandardBundlerParam<>( 280 Arguments.CLIOptions.TEMP_ROOT.getId(), 281 File.class, 282 params -> { 283 try { 284 return Files.createTempDirectory( 285 "jdk.incubator.jpackage").toFile(); 286 } catch (IOException ioe) { 287 return null; 288 } 289 }, 290 (s, p) -> new File(s) 291 ); 292 293 public static final StandardBundlerParam<File> CONFIG_ROOT = 294 new StandardBundlerParam<>( 295 "configRoot", 296 File.class, 297 params -> { 298 File root = 299 new File(TEMP_ROOT.fetchFrom(params), "config"); 300 root.mkdirs(); 301 return root; 302 }, 303 (s, p) -> null 304 ); 305 306 static final StandardBundlerParam<String> IDENTIFIER = 307 new StandardBundlerParam<>( 308 "identifier.default", 309 String.class, 310 params -> { 311 String s = MAIN_CLASS.fetchFrom(params); 312 if (s == null) return null; 313 314 int idx = s.lastIndexOf("."); 315 if (idx >= 1) { 316 return s.substring(0, idx); 317 } 318 return s; 319 }, 320 (s, p) -> s 321 ); 322 323 static final StandardBundlerParam<Boolean> BIND_SERVICES = 324 new StandardBundlerParam<>( 325 Arguments.CLIOptions.BIND_SERVICES.getId(), 326 Boolean.class, 327 params -> false, 328 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? 329 true : Boolean.valueOf(s) 330 ); 331 332 333 static final StandardBundlerParam<Boolean> VERBOSE = 334 new StandardBundlerParam<>( 335 Arguments.CLIOptions.VERBOSE.getId(), 336 Boolean.class, 337 params -> false, 338 // valueOf(null) is false, and we actually do want null 339 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? 340 true : Boolean.valueOf(s) 341 ); 342 343 static final StandardBundlerParam<File> RESOURCE_DIR = 344 new StandardBundlerParam<>( 345 Arguments.CLIOptions.RESOURCE_DIR.getId(), 346 File.class, 347 params -> null, 348 (s, p) -> new File(s) 349 ); 350 351 static final BundlerParamInfo<String> INSTALL_DIR = 352 new StandardBundlerParam<>( 471 }); 472 473 @SuppressWarnings("unchecked") 474 static final BundlerParamInfo<Set<String>> ADD_MODULES = 475 new StandardBundlerParam<>( 476 Arguments.CLIOptions.ADD_MODULES.getId(), 477 (Class<Set<String>>) (Object) Set.class, 478 p -> new LinkedHashSet<String>(), 479 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 480 ); 481 482 @SuppressWarnings("unchecked") 483 static final BundlerParamInfo<Set<String>> LIMIT_MODULES = 484 new StandardBundlerParam<>( 485 "limit-modules", 486 (Class<Set<String>>) (Object) Set.class, 487 p -> new LinkedHashSet<String>(), 488 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 489 ); 490 491 static boolean isRuntimeInstaller(Map<String, ? super Object> params) { 492 if (params.containsKey(MODULE.getID()) || 493 params.containsKey(MAIN_JAR.getID()) || 494 params.containsKey(PREDEFINED_APP_IMAGE.getID())) { 495 return false; // we are building or are given an application 496 } 497 // runtime installer requires --runtime-image, if this is false 498 // here then we should have thrown error validating args. 499 return params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID()); 500 } 501 502 static File getPredefinedAppImage(Map<String, ? super Object> params) { 503 File applicationImage = null; 504 if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) { 505 applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params); 506 if (!applicationImage.exists()) { 507 throw new RuntimeException( 508 MessageFormat.format(I18N.getString( 509 "message.app-image-dir-does-not-exist"), 510 PREDEFINED_APP_IMAGE.getID(), 511 applicationImage.toString())); 512 } 513 } 514 return applicationImage; 515 } 516 517 static void copyPredefinedRuntimeImage( 518 Map<String, ? super Object> params, 519 AbstractAppImageBuilder appBuilder) 520 throws IOException , ConfigException { 521 File topImage = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); 522 if (!topImage.exists()) { 523 throw new ConfigException( 524 MessageFormat.format(I18N.getString( 525 "message.runtime-image-dir-does-not-exist"), 526 PREDEFINED_RUNTIME_IMAGE.getID(), 527 topImage.toString()), 528 MessageFormat.format(I18N.getString( 529 "message.runtime-image-dir-does-not-exist.advice"), 530 PREDEFINED_RUNTIME_IMAGE.getID())); 531 } 532 File image = appBuilder.getRuntimeImageDir(topImage); 533 // copy whole runtime, need to skip jmods and src.zip 534 final List<String> excludes = Arrays.asList("jmods", "src.zip"); 535 IOUtils.copyRecursive(image.toPath(), appBuilder.getRuntimeRoot(), excludes); 536 537 // if module-path given - copy modules to appDir/mods 538 List<Path> modulePath = 539 StandardBundlerParam.MODULE_PATH.fetchFrom(params); 540 List<Path> defaultModulePath = getDefaultModulePath(); 541 Path dest = appBuilder.getAppModsDir(); 542 543 if (dest != null) { 544 for (Path mp : modulePath) { 545 if (!defaultModulePath.contains(mp)) { 546 Files.createDirectories(dest); 547 IOUtils.copyRecursive(mp, dest); 548 } 549 } 550 } 551 552 appBuilder.prepareApplicationFiles(params); 553 } 554 555 static void extractMainClassInfoFromAppResources( 556 Map<String, ? super Object> params) { 557 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 558 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 559 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 560 boolean hasModule = params.containsKey(MODULE.getID()); 561 562 if (hasMainClass && hasMainJar && hasMainJarClassPath || hasModule || 563 isRuntimeInstaller(params)) { 564 return; 565 } 566 567 // it's a pair. 568 // The [0] is the srcdir [1] is the file relative to sourcedir 569 List<String[]> filesToCheck = new ArrayList<>(); 570 571 if (hasMainJar) { 572 RelativeFileSet rfs = MAIN_JAR.fetchFrom(params); 629 cp == null ? "" : cp); 630 } 631 break; 632 } 633 } 634 } catch (IOException ignore) { 635 ignore.printStackTrace(); 636 } 637 } 638 } 639 640 static void validateMainClassInfoFromAppResources( 641 Map<String, ? super Object> params) throws ConfigException { 642 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 643 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 644 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 645 boolean hasModule = params.containsKey(MODULE.getID()); 646 boolean hasAppImage = params.containsKey(PREDEFINED_APP_IMAGE.getID()); 647 648 if (hasMainClass && hasMainJar && hasMainJarClassPath || 649 hasAppImage || isRuntimeInstaller(params)) { 650 return; 651 } 652 if (hasModule) { 653 if (JLinkBundlerHelper.getMainClassFromModule(params) == null) { 654 throw new ConfigException( 655 I18N.getString("ERR_NoMainClass"), null); 656 } 657 } else { 658 extractMainClassInfoFromAppResources(params); 659 660 if (!params.containsKey(MAIN_CLASS.getID())) { 661 if (hasMainJar) { 662 throw new ConfigException( 663 MessageFormat.format(I18N.getString( 664 "error.no-main-class-with-main-jar"), 665 MAIN_JAR.fetchFrom(params)), 666 MessageFormat.format(I18N.getString( 667 "error.no-main-class-with-main-jar.advice"), 668 MAIN_JAR.fetchFrom(params))); 669 } else { 670 throw new ConfigException( 671 I18N.getString("error.no-main-class"), 672 I18N.getString("error.no-main-class.advice")); 673 } 674 } 675 } 676 } 677 678 private static List<RelativeFileSet> 679 createAppResourcesListFromString(String s, 680 Map<String, ? super Object> objectObjectMap) { 681 List<RelativeFileSet> result = new ArrayList<>(); 682 for (String path : s.split("[:;]")) { 683 File f = new File(path); 684 if (f.getName().equals("*") || path.endsWith("/") || 685 path.endsWith("\\")) { 686 if (f.getName().equals("*")) { 687 f = f.getParentFile(); 688 } 689 Set<File> theFiles = new HashSet<>(); 690 try { 691 try (Stream<Path> stream = Files.walk(f.toPath())) { 692 stream.filter(Files::isRegularFile) 693 .forEach(p -> theFiles.add(p.toFile())); 694 } 695 } catch (IOException e) { 696 e.printStackTrace(); 697 } 698 result.add(new RelativeFileSet(f, theFiles)); 699 } else { 700 result.add(new RelativeFileSet(f.getParentFile(), 701 Collections.singleton(f))); 702 } 703 } 704 return result; 705 } 706 707 private static RelativeFileSet getMainJar( 708 String mainJarValue, Map<String, ? super Object> params) { 709 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { 710 File appResourcesRoot = rfs.getBaseDirectory(); 711 File mainJarFile = new File(appResourcesRoot, mainJarValue); 712 713 if (mainJarFile.exists()) { 714 return new RelativeFileSet(appResourcesRoot, 751 result.add(jdkModulePath); 752 } 753 else { 754 // On a developer build the JDK Home isn't where we expect it 755 // relative to the jmods directory. Do some extra 756 // processing to find it. 757 Map<String, String> env = System.getenv(); 758 759 if (env.containsKey("JDK_HOME")) { 760 jdkModulePath = Paths.get(env.get("JDK_HOME"), 761 ".." + File.separator + "images" 762 + File.separator + "jmods").toAbsolutePath(); 763 764 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 765 result.add(jdkModulePath); 766 } 767 } 768 } 769 770 return result; 771 } 772 773 static String getDefaultAppVersion(Map<String, ? super Object> params) { 774 String appVersion = DEFAULT_VERSION; 775 776 ModuleDescriptor descriptor = JLinkBundlerHelper.getMainModuleDescription(params); 777 if (descriptor != null) { 778 Optional<Version> oversion = descriptor.version(); 779 if (oversion.isPresent()) { 780 Log.verbose(MessageFormat.format(I18N.getString( 781 "message.module-version"), 782 oversion.get().toString(), 783 JLinkBundlerHelper.getMainModule(params))); 784 appVersion = oversion.get().toString(); 785 } 786 } 787 788 return appVersion; 789 } 790 } |