1 /* 2 * Copyright (c) 2015, 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.text.MessageFormat; 34 import java.util.Collection; 35 import java.util.Collections; 36 import java.util.EnumSet; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.Iterator; 40 import java.util.LinkedHashMap; 41 import java.util.LinkedHashSet; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Properties; 45 import java.util.ResourceBundle; 46 import java.util.Set; 47 import java.util.Optional; 48 import java.util.Arrays; 49 import java.util.stream.Collectors; 50 import java.util.stream.Stream; 51 import java.lang.module.Configuration; 52 import java.lang.module.ResolvedModule; 53 import java.lang.module.ModuleDescriptor; 54 import java.lang.module.ModuleFinder; 55 import java.lang.module.ModuleReference; 56 57 import jdk.tools.jlink.internal.packager.AppRuntimeImageBuilder; 58 59 final class JLinkBundlerHelper { 60 61 private static final ResourceBundle I18N = ResourceBundle.getBundle( 62 "jdk.jpackage.internal.resources.MainResources"); 63 private static final String JRE_MODULES_FILENAME = 64 "jdk/jpackage/internal/resources/jre.list"; 65 private static final String SERVER_JRE_MODULES_FILENAME = 66 "jdk/jpackage/internal/resources/jre.module.list"; 67 68 private JLinkBundlerHelper() {} 69 70 @SuppressWarnings("unchecked") 71 static final BundlerParamInfo<String> JLINK_BUILDER = 72 new StandardBundlerParam<>( 73 I18N.getString("param.jlink-builder.name"), 74 I18N.getString("param.jlink-builder.description"), 75 "jlink.builder", 76 String.class, 77 null, 78 (s, p) -> s); 79 80 @SuppressWarnings("unchecked") 81 static final BundlerParamInfo<Integer> DEBUG = 82 new StandardBundlerParam<>( 83 "", 84 "", 85 "-J-Xdebug", 86 Integer.class, 87 p -> null, 88 (s, p) -> { 89 return Integer.valueOf(s); 90 }); 91 92 static String listOfPathToString(List<Path> value) { 93 String result = ""; 94 95 for (Path path : value) { 96 if (result.length() > 0) { 97 result += File.pathSeparator; 98 } 99 100 result += path.toString(); 101 } 102 103 return result; 104 } 105 106 static String setOfStringToString(Set<String> value) { 107 String result = ""; 108 109 for (String element : value) { 110 if (result.length() > 0) { 111 result += ","; 112 } 113 114 result += element; 115 } 116 117 return result; 118 } 119 120 static File getMainJar(Map<String, ? super Object> params) { 121 File result = null; 122 RelativeFileSet fileset = 123 StandardBundlerParam.MAIN_JAR.fetchFrom(params); 124 125 if (fileset != null) { 126 String filename = fileset.getIncludedFiles().iterator().next(); 127 result = fileset.getBaseDirectory().toPath(). 128 resolve(filename).toFile(); 129 130 if (result == null || !result.exists()) { 131 String srcdir = 132 StandardBundlerParam.SOURCE_DIR.fetchFrom(params); 133 134 if (srcdir != null) { 135 result = new File(srcdir + File.separator + filename); 136 } 137 } 138 } 139 140 return result; 141 } 142 143 static String getMainClass(Map<String, ? super Object> params) { 144 String result = ""; 145 String mainModule = StandardBundlerParam.MODULE.fetchFrom(params); 146 if (mainModule != null) { 147 int index = mainModule.indexOf("/"); 148 if (index > 0) { 149 result = mainModule.substring(index + 1); 150 } 151 } else { 152 RelativeFileSet fileset = 153 StandardBundlerParam.MAIN_JAR.fetchFrom(params); 154 if (fileset != null) { 155 result = StandardBundlerParam.MAIN_CLASS.fetchFrom(params); 156 } else { 157 // possibly app-image 158 } 159 } 160 161 return result; 162 } 163 164 static String getMainModule(Map<String, ? super Object> params) { 165 String result = ""; 166 String mainModule = StandardBundlerParam.MODULE.fetchFrom(params); 167 168 if (mainModule != null) { 169 int index = mainModule.indexOf("/"); 170 171 if (index > 0) { 172 result = mainModule.substring(0, index); 173 } else { 174 result = mainModule; 175 } 176 } 177 178 return result; 179 } 180 181 static String getJDKVersion(Map<String, ? super Object> params) { 182 String result = ""; 183 List<Path> modulePath = 184 StandardBundlerParam.MODULE_PATH.fetchFrom(params); 185 Set<String> limitModules = 186 StandardBundlerParam.LIMIT_MODULES.fetchFrom(params); 187 Path javaBasePath = findPathOfModule(modulePath, "java.base.jmod"); 188 Set<String> addModules = getValidModules(modulePath, 189 StandardBundlerParam.ADD_MODULES.fetchFrom(params), 190 limitModules); 191 192 193 if (javaBasePath != null && javaBasePath.toFile().exists()) { 194 result = getModuleVersion(javaBasePath.toFile(), 195 modulePath, addModules, limitModules); 196 } 197 198 return result; 199 } 200 201 static Path getJDKHome(Map<String, ? super Object> params) { 202 Path result = null; 203 List<Path> modulePath = 204 StandardBundlerParam.MODULE_PATH.fetchFrom(params); 205 Path javaBasePath = findPathOfModule(modulePath, "java.base.jmod"); 206 207 if (javaBasePath != null && javaBasePath.toFile().exists()) { 208 result = javaBasePath.getParent(); 209 210 // On a developer build the JDK Home isn't where we expect it 211 // relative to the jmods directory. Do some extra 212 // processing to find it. 213 if (result != null) { 214 boolean found = false; 215 Path bin = result.resolve("bin"); 216 217 if (Files.exists(bin)) { 218 final String exe = 219 (Platform.getPlatform() == Platform.WINDOWS) ? 220 ".exe" : ""; 221 Path javaExe = bin.resolve("java" + exe); 222 223 if (Files.exists(javaExe)) { 224 found = true; 225 } 226 } 227 228 if (!found) { 229 result = result.resolve(".." + File.separator + "jdk"); 230 } 231 } 232 } 233 234 return result; 235 } 236 237 private static Set<String> getValidModules(List<Path> modulePath, 238 Set<String> addModules, Set<String> limitModules) { 239 ModuleHelper moduleHelper = new ModuleHelper( 240 modulePath, addModules, limitModules); 241 return removeInvalidModules(modulePath, moduleHelper.modules()); 242 } 243 244 static void execute(Map<String, ? super Object> params, 245 AbstractAppImageBuilder imageBuilder) 246 throws IOException, Exception { 247 List<Path> modulePath = 248 StandardBundlerParam.MODULE_PATH.fetchFrom(params); 249 Set<String> addModules = 250 StandardBundlerParam.ADD_MODULES.fetchFrom(params); 251 Set<String> limitModules = 252 StandardBundlerParam.LIMIT_MODULES.fetchFrom(params); 253 boolean stripNativeCommands = 254 StandardBundlerParam.STRIP_NATIVE_COMMANDS.fetchFrom(params); 255 Path outputDir = imageBuilder.getRoot(); 256 String excludeFileList = imageBuilder.getExcludeFileList(); 257 File mainJar = getMainJar(params); 258 ModFile.ModType mainJarType = ModFile.ModType.Unknown; 259 260 if (mainJar != null) { 261 mainJarType = new ModFile(mainJar).getModType(); 262 } else if (StandardBundlerParam.MODULE.fetchFrom(params) == null) { 263 // user specified only main class, all jars will be on the classpath 264 mainJarType = ModFile.ModType.UnnamedJar; 265 } 266 267 // Modules 268 269 if (mainJarType == ModFile.ModType.UnnamedJar) { 270 // The default for an unnamed jar is ALL_DEFAULT 271 addModules.add(ModuleHelper.ALL_DEFAULT); 272 } if (mainJarType == ModFile.ModType.Unknown || 273 mainJarType == ModFile.ModType.ModularJar) { 274 String mainModule = getMainModule(params); 275 addModules.add(mainModule); 276 } 277 addModules.addAll(getValidModules( 278 modulePath, addModules, limitModules)); 279 280 Log.verbose(MessageFormat.format( 281 I18N.getString("message.modules"), addModules.toString())); 282 283 AppRuntimeImageBuilder appRuntimeBuilder = new AppRuntimeImageBuilder(); 284 appRuntimeBuilder.setOutputDir(outputDir); 285 appRuntimeBuilder.setModulePath(modulePath); 286 appRuntimeBuilder.setAddModules(addModules); 287 appRuntimeBuilder.setLimitModules(limitModules); 288 appRuntimeBuilder.setExcludeFileList(excludeFileList); 289 appRuntimeBuilder.setStripNativeCommands(stripNativeCommands); 290 appRuntimeBuilder.setUserArguments(new HashMap<String,String>()); 291 292 appRuntimeBuilder.build(); 293 imageBuilder.prepareApplicationFiles(); 294 } 295 296 static void generateJre(Map<String, ? super Object> params, 297 AbstractAppImageBuilder imageBuilder) 298 throws IOException, Exception { 299 List<Path> modulePath = 300 StandardBundlerParam.MODULE_PATH.fetchFrom(params); 301 Set<String> addModules = 302 StandardBundlerParam.ADD_MODULES.fetchFrom(params); 303 Set<String> limitModules = 304 StandardBundlerParam.LIMIT_MODULES.fetchFrom(params); 305 boolean stripNativeCommands = 306 StandardBundlerParam.STRIP_NATIVE_COMMANDS.fetchFrom(params); 307 Path outputDir = imageBuilder.getRoot(); 308 addModules.add(ModuleHelper.ALL_MODULE_PATH); 309 Set<String> redistModules = getValidModules(modulePath, 310 addModules, limitModules); 311 addModules.addAll(redistModules); 312 313 Log.verbose(MessageFormat.format( 314 I18N.getString("message.modules"), addModules.toString())); 315 316 AppRuntimeImageBuilder appRuntimeBuilder = new AppRuntimeImageBuilder(); 317 appRuntimeBuilder.setOutputDir(outputDir); 318 appRuntimeBuilder.setModulePath(modulePath); 319 appRuntimeBuilder.setAddModules(addModules); 320 appRuntimeBuilder.setLimitModules(limitModules); 321 appRuntimeBuilder.setStripNativeCommands(stripNativeCommands); 322 appRuntimeBuilder.setExcludeFileList(""); 323 appRuntimeBuilder.setUserArguments(new HashMap<String,String>()); 324 325 appRuntimeBuilder.build(); 326 imageBuilder.prepareJreFiles(); 327 } 328 329 // Returns the path to the JDK modules in the user defined module path. 330 static Path findPathOfModule( 331 List<Path> modulePath, String moduleName) { 332 Path result = null; 333 334 for (Path path : modulePath) { 335 Path moduleNamePath = path.resolve(moduleName); 336 337 if (Files.exists(moduleNamePath)) { 338 result = path; 339 break; 340 } 341 } 342 343 return result; 344 } 345 346 /* 347 * Returns the set of modules that would be visible by default for 348 * a non-modular-aware application consisting of the given elements. 349 */ 350 private static Set<String> getDefaultModules( 351 Path[] paths, String[] addModules) { 352 353 // the modules in the run-time image that export an API 354 Stream<String> systemRoots = ModuleFinder.ofSystem().findAll().stream() 355 .map(ModuleReference::descriptor) 356 .filter(descriptor -> exportsAPI(descriptor)) 357 .map(ModuleDescriptor::name); 358 359 Set<String> roots; 360 if (addModules == null || addModules.length == 0) { 361 roots = systemRoots.collect(Collectors.toSet()); 362 } else { 363 var extraRoots = Stream.of(addModules); 364 roots = Stream.concat(systemRoots, 365 extraRoots).collect(Collectors.toSet()); 366 } 367 368 ModuleFinder finder = ModuleFinder.ofSystem(); 369 if (paths != null && paths.length > 0) { 370 finder = ModuleFinder.compose(finder, ModuleFinder.of(paths)); 371 } 372 return Configuration.empty() 373 .resolveAndBind(finder, ModuleFinder.of(), roots) 374 .modules() 375 .stream() 376 .map(ResolvedModule::name) 377 .collect(Collectors.toSet()); 378 } 379 380 /* 381 * Returns true if the given module exports an API to all module. 382 */ 383 private static boolean exportsAPI(ModuleDescriptor descriptor) { 384 return descriptor.exports() 385 .stream() 386 .filter(e -> !e.isQualified()) 387 .findAny() 388 .isPresent(); 389 } 390 391 private static Set<String> removeInvalidModules( 392 List<Path> modulePath, Set<String> modules) { 393 Set<String> result = new LinkedHashSet<String>(); 394 ModuleManager mm = new ModuleManager(modulePath); 395 List<ModFile> lmodfiles = 396 mm.getModules(EnumSet.of(ModuleManager.SearchType.ModularJar, 397 ModuleManager.SearchType.Jmod, 398 ModuleManager.SearchType.ExplodedModule)); 399 400 HashMap<String, ModFile> validModules = new HashMap<>(); 401 402 for (ModFile modFile : lmodfiles) { 403 validModules.put(modFile.getModName(), modFile); 404 } 405 406 for (String name : modules) { 407 if (validModules.containsKey(name)) { 408 result.add(name); 409 } else { 410 Log.error(MessageFormat.format( 411 I18N.getString("warning.module.does.not.exist"), name)); 412 } 413 } 414 415 return result; 416 } 417 418 private static String getModuleVersion(File moduleFile, 419 List<Path> modulePath, Set<String> addModules, 420 Set<String> limitModules) { 421 String result = ""; 422 423 ModFile modFile = new ModFile(moduleFile); 424 ModuleFinder finder = AppRuntimeImageBuilder.moduleFinder(modulePath, 425 addModules, limitModules); 426 Optional<ModuleReference> mref = finder.find(modFile.getModName()); 427 428 if (mref.isPresent()) { 429 ModuleDescriptor descriptor = mref.get().descriptor(); 430 431 if (descriptor != null) { 432 Optional<ModuleDescriptor.Version> version = 433 descriptor.version(); 434 435 if (version.isPresent()) { 436 result = version.get().toString(); 437 } 438 } 439 } 440 441 return result; 442 } 443 444 private static class ModuleHelper { 445 // The token for "all modules on the module path". 446 private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH"; 447 448 // The token for "all valid runtime modules". 449 static final String ALL_DEFAULT = "ALL-DEFAULT"; 450 451 private final Set<String> modules = new HashSet<>(); 452 private enum Macros {None, AllModulePath, AllRuntime} 453 454 ModuleHelper(List<Path> paths, Set<String> addModules, 455 Set<String> limitModules) { 456 boolean addAllModulePath = false; 457 boolean addDefaultMods = false; 458 459 for (Iterator<String> iterator = addModules.iterator(); 460 iterator.hasNext();) { 461 String module = iterator.next(); 462 463 switch (module) { 464 case ALL_MODULE_PATH: 465 iterator.remove(); 466 addAllModulePath = true; 467 break; 468 case ALL_DEFAULT: 469 iterator.remove(); 470 addDefaultMods = true; 471 break; 472 default: 473 this.modules.add(module); 474 } 475 } 476 477 if (addAllModulePath) { 478 this.modules.addAll(getModuleNamesFromPath(paths)); 479 } else if (addDefaultMods) { 480 this.modules.addAll(getDefaultModules( 481 paths.toArray(new Path[0]), 482 addModules.toArray(new String[0]))); 483 } 484 } 485 486 Set<String> modules() { 487 return modules; 488 } 489 490 private static Set<String> getModuleNamesFromPath(List<Path> Value) { 491 Set<String> result = new LinkedHashSet<String>(); 492 ModuleManager mm = new ModuleManager(Value); 493 List<ModFile> modFiles = mm.getModules( 494 EnumSet.of(ModuleManager.SearchType.ModularJar, 495 ModuleManager.SearchType.Jmod, 496 ModuleManager.SearchType.ExplodedModule)); 497 498 for (ModFile modFile : modFiles) { 499 result.add(modFile.getModName()); 500 } 501 return result; 502 } 503 } 504 }