1 /* 2 * Copyright (c) 2014, 2019, 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.jpackage.internal; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.StringReader; 31 import java.nio.file.Files; 32 import java.nio.file.Path; 33 import java.nio.file.Paths; 34 import java.text.MessageFormat; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.Collections; 38 import java.util.Date; 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.LinkedHashSet; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Optional; 45 import java.util.Properties; 46 import java.util.ResourceBundle; 47 import java.util.Set; 48 import java.util.HashSet; 49 import java.util.function.BiFunction; 50 import java.util.function.Function; 51 import java.util.jar.Attributes; 52 import java.util.jar.JarFile; 53 import java.util.jar.Manifest; 54 import java.util.regex.Pattern; 55 import java.util.stream.Collectors; 56 57 /** 58 * StandardBundlerParam 59 * 60 * A parameter to a bundler. 61 * 62 * Also contains static definitions of all of the common bundler parameters. 63 * (additional platform specific and mode specific bundler parameters 64 * are defined in each of the specific bundlers) 65 * 66 * Also contains static methods that operate on maps of parameters. 67 */ 68 class StandardBundlerParam<T> extends BundlerParamInfo<T> { 69 70 private static final ResourceBundle I18N = ResourceBundle.getBundle( 71 "jdk.jpackage.internal.resources.MainResources"); 72 private static final String JAVABASEJMOD = "java.base.jmod"; 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 93 @SuppressWarnings("unchecked") 94 static final 95 StandardBundlerParam<List<RelativeFileSet>> APP_RESOURCES_LIST = 96 new StandardBundlerParam<>( 97 BundleParams.PARAM_APP_RESOURCES + "List", 98 (Class<List<RelativeFileSet>>) (Object) List.class, 99 // Default is appResources, as a single item list 100 p -> new ArrayList<>(Collections.singletonList( 101 APP_RESOURCES.fetchFrom(p))), 102 StandardBundlerParam::createAppResourcesListFromString 103 ); 104 105 static final StandardBundlerParam<String> SOURCE_DIR = 106 new StandardBundlerParam<>( 107 Arguments.CLIOptions.INPUT.getId(), 108 String.class, 109 p -> null, 110 (s, p) -> { 111 String value = String.valueOf(s); 112 if (value.charAt(value.length() - 1) == 113 File.separatorChar) { 114 return value.substring(0, value.length() - 1); 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.getMainClass(params); 160 if (s.length() == 0) { 161 s = null; 162 } 163 } 164 return s; 165 }, 166 (s, p) -> s 167 ); 168 169 static final StandardBundlerParam<File> PREDEFINED_RUNTIME_IMAGE = 170 new StandardBundlerParam<>( 171 Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), 172 File.class, 173 params -> null, 174 (s, p) -> new File(s) 175 ); 176 177 static final StandardBundlerParam<String> APP_NAME = 178 new StandardBundlerParam<>( 179 Arguments.CLIOptions.NAME.getId(), 180 String.class, 181 params -> { 182 String s = MAIN_CLASS.fetchFrom(params); 183 if (s != null) { 184 int idx = s.lastIndexOf("."); 185 if (idx >= 0) { 186 return s.substring(idx+1); 187 } 188 return s; 189 } else if (isRuntimeInstaller(params)) { 190 File f = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); 191 if (f != null) { 192 return f.getName(); 193 } 194 } 195 return null; 196 }, 197 (s, p) -> s 198 ); 199 200 static final StandardBundlerParam<File> ICON = 201 new StandardBundlerParam<>( 202 Arguments.CLIOptions.ICON.getId(), 203 File.class, 204 params -> null, 205 (s, p) -> new File(s) 206 ); 207 208 static final StandardBundlerParam<String> VENDOR = 209 new StandardBundlerParam<>( 210 Arguments.CLIOptions.VENDOR.getId(), 211 String.class, 212 params -> I18N.getString("param.vendor.default"), 213 (s, p) -> s 214 ); 215 216 static final StandardBundlerParam<String> DESCRIPTION = 217 new StandardBundlerParam<>( 218 Arguments.CLIOptions.DESCRIPTION.getId(), 219 String.class, 220 params -> params.containsKey(APP_NAME.getID()) 221 ? APP_NAME.fetchFrom(params) 222 : I18N.getString("param.description.default"), 223 (s, p) -> s 224 ); 225 226 static final StandardBundlerParam<String> COPYRIGHT = 227 new StandardBundlerParam<>( 228 Arguments.CLIOptions.COPYRIGHT.getId(), 229 String.class, 230 params -> MessageFormat.format(I18N.getString( 231 "param.copyright.default"), new Date()), 232 (s, p) -> s 233 ); 234 235 @SuppressWarnings("unchecked") 236 static final StandardBundlerParam<List<String>> ARGUMENTS = 237 new StandardBundlerParam<>( 238 Arguments.CLIOptions.ARGUMENTS.getId(), 239 (Class<List<String>>) (Object) List.class, 240 params -> Collections.emptyList(), 241 (s, p) -> null 242 ); 243 244 @SuppressWarnings("unchecked") 245 static final StandardBundlerParam<List<String>> JAVA_OPTIONS = 246 new StandardBundlerParam<>( 247 Arguments.CLIOptions.JAVA_OPTIONS.getId(), 248 (Class<List<String>>) (Object) List.class, 249 params -> Collections.emptyList(), 250 (s, p) -> Arrays.asList(s.split("\n\n")) 251 ); 252 253 // note that each bundler is likely to replace this one with 254 // their own converter 255 static final StandardBundlerParam<String> VERSION = 256 new StandardBundlerParam<>( 257 Arguments.CLIOptions.VERSION.getId(), 258 String.class, 259 params -> I18N.getString("param.version.default"), 260 (s, p) -> s 261 ); 262 263 @SuppressWarnings("unchecked") 264 public static final StandardBundlerParam<String> LICENSE_FILE = 265 new StandardBundlerParam<>( 266 Arguments.CLIOptions.LICENSE_FILE.getId(), 267 String.class, 268 params -> null, 269 (s, p) -> s 270 ); 271 272 static final StandardBundlerParam<File> TEMP_ROOT = 273 new StandardBundlerParam<>( 274 Arguments.CLIOptions.TEMP_ROOT.getId(), 275 File.class, 276 params -> { 277 try { 278 return Files.createTempDirectory( 279 "jdk.jpackage").toFile(); 280 } catch (IOException ioe) { 281 return null; 282 } 283 }, 284 (s, p) -> new File(s) 285 ); 286 287 public static final StandardBundlerParam<File> CONFIG_ROOT = 288 new StandardBundlerParam<>( 289 "configRoot", 290 File.class, 291 params -> { 292 File root = 293 new File(TEMP_ROOT.fetchFrom(params), "config"); 294 root.mkdirs(); 295 return root; 296 }, 297 (s, p) -> null 298 ); 299 300 static final StandardBundlerParam<String> IDENTIFIER = 301 new StandardBundlerParam<>( 302 Arguments.CLIOptions.IDENTIFIER.getId(), 303 String.class, 304 params -> { 305 String s = MAIN_CLASS.fetchFrom(params); 306 if (s == null) return null; 307 308 int idx = s.lastIndexOf("."); 309 if (idx >= 1) { 310 return s.substring(0, idx); 311 } 312 return s; 313 }, 314 (s, p) -> s 315 ); 316 317 static final StandardBundlerParam<Boolean> VERBOSE = 318 new StandardBundlerParam<>( 319 Arguments.CLIOptions.VERBOSE.getId(), 320 Boolean.class, 321 params -> false, 322 // valueOf(null) is false, and we actually do want null 323 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? 324 true : Boolean.valueOf(s) 325 ); 326 327 static final StandardBundlerParam<File> RESOURCE_DIR = 328 new StandardBundlerParam<>( 329 Arguments.CLIOptions.RESOURCE_DIR.getId(), 330 File.class, 331 params -> null, 332 (s, p) -> new File(s) 333 ); 334 335 static final BundlerParamInfo<String> INSTALL_DIR = 336 new StandardBundlerParam<>( 337 Arguments.CLIOptions.INSTALL_DIR.getId(), 338 String.class, 339 params -> null, 340 (s, p) -> s 341 ); 342 343 static final StandardBundlerParam<File> PREDEFINED_APP_IMAGE = 344 new StandardBundlerParam<>( 345 Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), 346 File.class, 347 params -> null, 348 (s, p) -> new File(s)); 349 350 @SuppressWarnings("unchecked") 351 static final StandardBundlerParam<List<Map<String, ? super Object>>> ADD_LAUNCHERS = 352 new StandardBundlerParam<>( 353 Arguments.CLIOptions.ADD_LAUNCHER.getId(), 354 (Class<List<Map<String, ? super Object>>>) (Object) 355 List.class, 356 params -> new ArrayList<>(1), 357 // valueOf(null) is false, and we actually do want null 358 (s, p) -> null 359 ); 360 361 @SuppressWarnings("unchecked") 362 static final StandardBundlerParam 363 <List<Map<String, ? super Object>>> FILE_ASSOCIATIONS = 364 new StandardBundlerParam<>( 365 Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(), 366 (Class<List<Map<String, ? super Object>>>) (Object) 367 List.class, 368 params -> new ArrayList<>(1), 369 // valueOf(null) is false, and we actually do want null 370 (s, p) -> null 371 ); 372 373 @SuppressWarnings("unchecked") 374 static final StandardBundlerParam<List<String>> FA_EXTENSIONS = 375 new StandardBundlerParam<>( 376 "fileAssociation.extension", 377 (Class<List<String>>) (Object) List.class, 378 params -> null, // null means not matched to an extension 379 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 380 ); 381 382 @SuppressWarnings("unchecked") 383 static final StandardBundlerParam<List<String>> FA_CONTENT_TYPE = 384 new StandardBundlerParam<>( 385 "fileAssociation.contentType", 386 (Class<List<String>>) (Object) List.class, 387 params -> null, 388 // null means not matched to a content/mime type 389 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 390 ); 391 392 static final StandardBundlerParam<String> FA_DESCRIPTION = 393 new StandardBundlerParam<>( 394 "fileAssociation.description", 395 String.class, 396 params -> APP_NAME.fetchFrom(params) + " File", 397 null 398 ); 399 400 static final StandardBundlerParam<File> FA_ICON = 401 new StandardBundlerParam<>( 402 "fileAssociation.icon", 403 File.class, 404 ICON::fetchFrom, 405 (s, p) -> new File(s) 406 ); 407 408 @SuppressWarnings("unchecked") 409 static final BundlerParamInfo<List<Path>> MODULE_PATH = 410 new StandardBundlerParam<>( 411 Arguments.CLIOptions.MODULE_PATH.getId(), 412 (Class<List<Path>>) (Object)List.class, 413 p -> { return getDefaultModulePath(); }, 414 (s, p) -> { 415 List<Path> modulePath = Arrays.asList(s 416 .split(File.pathSeparator)).stream() 417 .map(ss -> new File(ss).toPath()) 418 .collect(Collectors.toList()); 419 Path javaBasePath = null; 420 if (modulePath != null) { 421 javaBasePath = JLinkBundlerHelper 422 .findPathOfModule(modulePath, JAVABASEJMOD); 423 } else { 424 modulePath = new ArrayList<Path>(); 425 } 426 427 // Add the default JDK module path to the module path. 428 if (javaBasePath == null) { 429 List<Path> jdkModulePath = getDefaultModulePath(); 430 431 if (jdkModulePath != null) { 432 modulePath.addAll(jdkModulePath); 433 javaBasePath = 434 JLinkBundlerHelper.findPathOfModule( 435 modulePath, JAVABASEJMOD); 436 } 437 } 438 439 if (javaBasePath == null || 440 !Files.exists(javaBasePath)) { 441 Log.error(String.format(I18N.getString( 442 "warning.no.jdk.modules.found"))); 443 } 444 445 return modulePath; 446 }); 447 448 static final BundlerParamInfo<String> MODULE = 449 new StandardBundlerParam<>( 450 Arguments.CLIOptions.MODULE.getId(), 451 String.class, 452 p -> null, 453 (s, p) -> { 454 return String.valueOf(s); 455 }); 456 457 @SuppressWarnings("unchecked") 458 static final BundlerParamInfo<Set<String>> ADD_MODULES = 459 new StandardBundlerParam<>( 460 Arguments.CLIOptions.ADD_MODULES.getId(), 461 (Class<Set<String>>) (Object) Set.class, 462 p -> new LinkedHashSet<String>(), 463 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 464 ); 465 466 @SuppressWarnings("unchecked") 467 static final BundlerParamInfo<Set<String>> LIMIT_MODULES = 468 new StandardBundlerParam<>( 469 "limit-modules", 470 (Class<Set<String>>) (Object) Set.class, 471 p -> new LinkedHashSet<String>(), 472 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 473 ); 474 475 static boolean isRuntimeInstaller(Map<String, ? super Object> p) { 476 if (p.containsKey(MODULE.getID()) || 477 p.containsKey(MAIN_JAR.getID()) || 478 p.containsKey(PREDEFINED_APP_IMAGE.getID())) { 479 return false; // we are building or are given an application 480 } 481 // runtime installer requires --runtime-image, if this is false 482 // here then we should have thrown error validating args. 483 return p.containsKey(PREDEFINED_RUNTIME_IMAGE.getID()); 484 } 485 486 static File getPredefinedAppImage(Map<String, ? super Object> p) { 487 File applicationImage = null; 488 if (PREDEFINED_APP_IMAGE.fetchFrom(p) != null) { 489 applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(p); 490 Log.debug("Using App Image from " + applicationImage); 491 if (!applicationImage.exists()) { 492 throw new RuntimeException( 493 MessageFormat.format(I18N.getString( 494 "message.app-image-dir-does-not-exist"), 495 PREDEFINED_APP_IMAGE.getID(), 496 applicationImage.toString())); 497 } 498 } 499 return applicationImage; 500 } 501 502 static void copyPredefinedRuntimeImage( 503 Map<String, ? super Object> p, 504 AbstractAppImageBuilder appBuilder) 505 throws IOException , ConfigException { 506 File image = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 507 if (!image.exists()) { 508 throw new ConfigException( 509 MessageFormat.format(I18N.getString( 510 "message.runtime-image-dir-does-not-exist"), 511 PREDEFINED_RUNTIME_IMAGE.getID(), 512 image.toString()), 513 MessageFormat.format(I18N.getString( 514 "message.runtime-image-dir-does-not-exist.advice"), 515 PREDEFINED_RUNTIME_IMAGE.getID())); 516 } 517 // copy whole runtime, need to skip jmods and src.zip 518 final List<String> excludes = Arrays.asList("jmods", "src.zip"); 519 IOUtils.copyRecursive(image.toPath(), appBuilder.getRoot(), excludes); 520 521 // if module-path given - copy modules to appDir/mods 522 List<Path> modulePath = 523 StandardBundlerParam.MODULE_PATH.fetchFrom(p); 524 List<Path> defaultModulePath = getDefaultModulePath(); 525 Path dest = appBuilder.getAppModsDir(); 526 527 if (dest != null) { 528 for (Path mp : modulePath) { 529 if (!defaultModulePath.contains(mp)) { 530 Files.createDirectories(dest); 531 IOUtils.copyRecursive(mp, dest); 532 } 533 } 534 } 535 536 appBuilder.prepareApplicationFiles(); 537 } 538 539 static void extractMainClassInfoFromAppResources( 540 Map<String, ? super Object> params) { 541 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 542 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 543 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 544 boolean hasModule = params.containsKey(MODULE.getID()); 545 546 if (hasMainClass && hasMainJar && hasMainJarClassPath || hasModule || 547 isRuntimeInstaller(params)) { 548 return; 549 } 550 551 // it's a pair. 552 // The [0] is the srcdir [1] is the file relative to sourcedir 553 List<String[]> filesToCheck = new ArrayList<>(); 554 555 if (hasMainJar) { 556 RelativeFileSet rfs = MAIN_JAR.fetchFrom(params); 557 for (String s : rfs.getIncludedFiles()) { 558 filesToCheck.add( 559 new String[] {rfs.getBaseDirectory().toString(), s}); 560 } 561 } else if (hasMainJarClassPath) { 562 for (String s : CLASSPATH.fetchFrom(params).split("\\s+")) { 563 if (APP_RESOURCES.fetchFrom(params) != null) { 564 filesToCheck.add( 565 new String[] {APP_RESOURCES.fetchFrom(params) 566 .getBaseDirectory().toString(), s}); 567 } 568 } 569 } else { 570 List<RelativeFileSet> rfsl = APP_RESOURCES_LIST.fetchFrom(params); 571 if (rfsl == null || rfsl.isEmpty()) { 572 return; 573 } 574 for (RelativeFileSet rfs : rfsl) { 575 if (rfs == null) continue; 576 577 for (String s : rfs.getIncludedFiles()) { 578 filesToCheck.add( 579 new String[]{rfs.getBaseDirectory().toString(), s}); 580 } 581 } 582 } 583 584 // presume the set iterates in-order 585 for (String[] fnames : filesToCheck) { 586 try { 587 // only sniff jars 588 if (!fnames[1].toLowerCase().endsWith(".jar")) continue; 589 590 File file = new File(fnames[0], fnames[1]); 591 // that actually exist 592 if (!file.exists()) continue; 593 594 try (JarFile jf = new JarFile(file)) { 595 Manifest m = jf.getManifest(); 596 Attributes attrs = (m != null) ? 597 m.getMainAttributes() : null; 598 599 if (attrs != null) { 600 if (!hasMainJar) { 601 if (fnames[0] == null) { 602 fnames[0] = file.getParentFile().toString(); 603 } 604 params.put(MAIN_JAR.getID(), new RelativeFileSet( 605 new File(fnames[0]), 606 new LinkedHashSet<>(Collections 607 .singletonList(file)))); 608 } 609 if (!hasMainJarClassPath) { 610 String cp = 611 attrs.getValue(Attributes.Name.CLASS_PATH); 612 params.put(CLASSPATH.getID(), 613 cp == null ? "" : cp); 614 } 615 break; 616 } 617 } 618 } catch (IOException ignore) { 619 ignore.printStackTrace(); 620 } 621 } 622 } 623 624 static void validateMainClassInfoFromAppResources( 625 Map<String, ? super Object> params) throws ConfigException { 626 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 627 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 628 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 629 boolean hasModule = params.containsKey(MODULE.getID()); 630 boolean hasAppImage = params.containsKey(PREDEFINED_APP_IMAGE.getID()); 631 632 if (hasMainClass && hasMainJar && hasMainJarClassPath || 633 hasModule || hasAppImage || isRuntimeInstaller(params)) { 634 return; 635 } 636 637 extractMainClassInfoFromAppResources(params); 638 639 if (!params.containsKey(MAIN_CLASS.getID())) { 640 if (hasMainJar) { 641 throw new ConfigException( 642 MessageFormat.format(I18N.getString( 643 "error.no-main-class-with-main-jar"), 644 MAIN_JAR.fetchFrom(params)), 645 MessageFormat.format(I18N.getString( 646 "error.no-main-class-with-main-jar.advice"), 647 MAIN_JAR.fetchFrom(params))); 648 } else { 649 throw new ConfigException( 650 I18N.getString("error.no-main-class"), 651 I18N.getString("error.no-main-class.advice")); 652 } 653 } 654 } 655 656 private static List<RelativeFileSet> 657 createAppResourcesListFromString(String s, 658 Map<String, ? super Object> objectObjectMap) { 659 List<RelativeFileSet> result = new ArrayList<>(); 660 for (String path : s.split("[:;]")) { 661 File f = new File(path); 662 if (f.getName().equals("*") || path.endsWith("/") || 663 path.endsWith("\\")) { 664 if (f.getName().equals("*")) { 665 f = f.getParentFile(); 666 } 667 Set<File> theFiles = new HashSet<>(); 668 try { 669 Files.walk(f.toPath()) 670 .filter(Files::isRegularFile) 671 .forEach(p -> theFiles.add(p.toFile())); 672 } catch (IOException e) { 673 e.printStackTrace(); 674 } 675 result.add(new RelativeFileSet(f, theFiles)); 676 } else { 677 result.add(new RelativeFileSet(f.getParentFile(), 678 Collections.singleton(f))); 679 } 680 } 681 return result; 682 } 683 684 private static RelativeFileSet getMainJar( 685 String mainJarValue, Map<String, ? super Object> params) { 686 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { 687 File appResourcesRoot = rfs.getBaseDirectory(); 688 File mainJarFile = new File(appResourcesRoot, mainJarValue); 689 690 if (mainJarFile.exists()) { 691 return new RelativeFileSet(appResourcesRoot, 692 new LinkedHashSet<>(Collections.singletonList( 693 mainJarFile))); 694 } 695 mainJarFile = new File(mainJarValue); 696 if (mainJarFile.exists()) { 697 // absolute path for main-jar may fail is not legal 698 // below contains explicit error message. 699 } else { 700 List<Path> modulePath = MODULE_PATH.fetchFrom(params); 701 modulePath.removeAll(getDefaultModulePath()); 702 if (!modulePath.isEmpty()) { 703 Path modularJarPath = JLinkBundlerHelper.findPathOfModule( 704 modulePath, mainJarValue); 705 if (modularJarPath != null && 706 Files.exists(modularJarPath)) { 707 return new RelativeFileSet(appResourcesRoot, 708 new LinkedHashSet<>(Collections.singletonList( 709 modularJarPath.toFile()))); 710 } 711 } 712 } 713 } 714 715 throw new IllegalArgumentException( 716 new ConfigException(MessageFormat.format(I18N.getString( 717 "error.main-jar-does-not-exist"), 718 mainJarValue), I18N.getString( 719 "error.main-jar-does-not-exist.advice"))); 720 } 721 722 static List<Path> getDefaultModulePath() { 723 List<Path> result = new ArrayList<Path>(); 724 Path jdkModulePath = Paths.get( 725 System.getProperty("java.home"), "jmods").toAbsolutePath(); 726 727 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 728 result.add(jdkModulePath); 729 } 730 else { 731 // On a developer build the JDK Home isn't where we expect it 732 // relative to the jmods directory. Do some extra 733 // processing to find it. 734 Map<String, String> env = System.getenv(); 735 736 if (env.containsKey("JDK_HOME")) { 737 jdkModulePath = Paths.get(env.get("JDK_HOME"), 738 ".." + File.separator + "images" 739 + File.separator + "jmods").toAbsolutePath(); 740 741 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 742 result.add(jdkModulePath); 743 } 744 } 745 } 746 747 return result; 748 } 749 }