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