1 /* 2 * Copyright (c) 2015, 2016, 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 Path javaBasePath = findPathOfModule(modulePath, "java.base.jmod"); 227 228 if (javaBasePath != null && javaBasePath.toFile().exists()) { 229 result = RedistributableModules.getModuleVersion(javaBasePath.toFile(), 230 modulePath, StandardBundlerParam.ADD_MODULES.fetchFrom(params), 231 StandardBundlerParam.LIMIT_MODULES.fetchFrom(params)); 232 } 233 234 return result; 235 } 236 237 public static Path getJDKHome(Map<String, ? super Object> params) { 238 Path result = null; 239 List<Path> modulePath = StandardBundlerParam.MODULE_PATH.fetchFrom(params); 240 Path javaBasePath = findPathOfModule(modulePath, "java.base.jmod"); 241 242 if (javaBasePath != null && javaBasePath.toFile().exists()) { 243 result = javaBasePath.getParent(); 244 245 // On a developer build the JDK Home isn't where we expect it 246 // relative to the jmods directory. Do some extra 247 // processing to find it. 248 if (result != null) { 249 boolean found = false; 250 Path bin = result.resolve("bin"); 251 252 if (Files.exists(bin)) { 253 final String exe = (Platform.getPlatform() == Platform.WINDOWS) ? ".exe" : ""; 254 Path javaExe = bin.resolve("java" + exe); 255 256 if (Files.exists(javaExe)) { 257 found = true; 258 } 259 } 260 261 if (!found) { 262 result = result.resolve(".." + File.separator + "jdk"); 263 } 264 } 265 } 266 267 return result; 268 } 269 270 public static void execute(Map<String, ? super Object> params, AbstractAppImageBuilder imageBuilder) throws IOException, Exception { 271 List<Path> modulePath = StandardBundlerParam.MODULE_PATH.fetchFrom(params); 272 Set<String> addModules = StandardBundlerParam.ADD_MODULES.fetchFrom(params); 273 Set<String> limitModules = StandardBundlerParam.LIMIT_MODULES.fetchFrom(params); 274 boolean stripNativeCommands = StandardBundlerParam.STRIP_NATIVE_COMMANDS.fetchFrom(params); 275 Map<String, String> userArguments = JLINK_OPTIONS.fetchFrom(params); 276 Path outputDir = imageBuilder.getRoot(); 277 String excludeFileList = imageBuilder.getExcludeFileList(); 278 Set<String> jars = getResourceFileJarList(params, Module.JarType.UnnamedJar); 279 File mainJar = getMainJar(params); 280 Module.ModuleType mainJarType = Module.ModuleType.Unknown; 281 282 if (mainJar != null) { 283 mainJarType = new Module(mainJar).getModuleType(); 284 } 285 286 //-------------------------------------------------------------------- 287 // Modules 288 289 boolean detectModules = DETECT_MODULES.fetchFrom(params); 290 291 // The default for an unnamed jar is ALL_DEFAULT with the 292 // non-redistributable modules removed. 293 if (mainJarType == Module.ModuleType.UnnamedJar && !detectModules) { 294 addModules.add(ModuleHelper.ALL_RUNTIME); 295 } 296 else if (mainJarType == Module.ModuleType.Unknown || mainJarType == Module.ModuleType.ModularJar) { 297 String mainModule = getMainModule(params); 298 addModules.add(mainModule); 299 300 // Error if any of the srcfiles are modular jars. 301 Set<String> modularJars = getResourceFileJarList(params, Module.JarType.ModularJar); 302 303 if (!modularJars.isEmpty()) { 304 throw new Exception(MessageFormat.format(I18N.getString("error.srcfiles.contain.modules"), modularJars.toString())); 305 } 306 } 307 308 ModuleHelper moduleHelper = new ModuleHelper(modulePath, addModules, limitModules); 309 Set<String> redistModules = removeInvalidModules(modulePath, moduleHelper.modules()); 310 addModules.addAll(redistModules); 311 312 //-------------------------------------------------------------------- 313 // Jars 314 315 // Bundle with minimum dependencies that unnamed jars depend on. 316 if (detectModules && !jars.isEmpty()) { 317 Log.info(MessageFormat.format(I18N.getString("using.experimental.feature"), "--" + DETECT_MODULES.getID())); 318 Collection<String> detectedModules = JDepHelper.calculateModules(jars, modulePath); 319 320 if (!detectedModules.isEmpty()) { 321 addModules.addAll(detectedModules); 322 } 323 } 324 325 Log.info(MessageFormat.format(I18N.getString("message.modules"), addModules.toString())); 326 327 AppRuntimeImageBuilder appRuntimeBuilder = new AppRuntimeImageBuilder(); 328 appRuntimeBuilder.setOutputDir(outputDir); 329 appRuntimeBuilder.setModulePath(modulePath); 330 appRuntimeBuilder.setAddModules(addModules); 331 appRuntimeBuilder.setLimitModules(limitModules); 332 appRuntimeBuilder.setExcludeFileList(excludeFileList); 333 appRuntimeBuilder.setStripNativeCommands(stripNativeCommands); 334 appRuntimeBuilder.setUserArguments(userArguments); 335 336 appRuntimeBuilder.build(); 337 imageBuilder.prepareApplicationFiles(); 338 } 339 340 // Returns the path to the JDK modules in the user defined module path. 341 public static Path findPathOfModule(List<Path> modulePath, String moduleName) { 342 Path result = null; 343 344 for (Path path : modulePath) { 345 Path moduleNamePath = path.resolve(moduleName); 346 347 if (Files.exists(moduleNamePath)) { 348 result = path; 349 break; 350 } 351 } 352 353 return result; 354 } 355 356 private static Set<String> getResourceFileJarList(Map<String, ? super Object> params, Module.JarType Query) { 357 Set<String> files = new LinkedHashSet(); 358 359 String srcdir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params); 360 361 for (RelativeFileSet appResources : StandardBundlerParam.APP_RESOURCES_LIST.fetchFrom(params)) { 362 for (String resource : appResources.getIncludedFiles()) { 363 if (resource.endsWith(".jar")) { 364 String filename = srcdir + File.separator + resource; 365 366 switch (Query) { 367 case All: { 368 files.add(filename); 369 break; 370 } 371 case ModularJar: { 372 Module module = new Module(new File(filename)); 373 374 if (module.getModuleType() == Module.ModuleType.ModularJar) { 375 files.add(filename); 376 } 377 break; 378 } 379 case UnnamedJar: { 380 Module module = new Module(new File(filename)); 381 382 if (module.getModuleType() == Module.ModuleType.UnnamedJar) { 383 files.add(filename); 384 } 385 break; 386 } 387 } 388 } 389 } 390 } 391 392 return files; 393 } 394 395 private static Set<String> removeInvalidModules(List<Path> modulePath, Set<String> modules) { 396 Set<String> result = new LinkedHashSet(); 397 ModuleManager mm = new ModuleManager(modulePath); 398 List<Module> lmodules = mm.getModules(EnumSet.of(ModuleManager.SearchType.ModularJar, 399 ModuleManager.SearchType.Jmod, 400 ModuleManager.SearchType.ExplodedModule)); 401 402 HashMap<String, Module> validModules = new HashMap<>(); 403 404 for (Module module : lmodules) { 405 validModules.put(module.getModuleName(), module); 406 } 407 408 for (String name : modules) { 409 if (validModules.containsKey(name)) { 410 result.add(name); 411 } 412 else { 413 Log.info(MessageFormat.format(I18N.getString("warning.module.does.not.exist"), name)); 414 } 415 } 416 417 return result; 418 } 419 420 private static class ModuleHelper { 421 // The token for "all modules on the module path". 422 private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH"; 423 424 // The token for "all redistributable runtime modules". 425 public static final String ALL_RUNTIME = "ALL-RUNTIME"; 426 427 private final Set<String> modules = new HashSet<>(); 428 private enum Macros {None, AllModulePath, AllRuntime} 429 430 public ModuleHelper(List<Path> paths, Set<String> roots, Set<String> limitMods) { 431 Set<String> lroots = new HashSet<>(); 432 Macros macro = Macros.None; 433 434 for (Iterator<String> iterator = roots.iterator(); iterator.hasNext();) { 435 String module = iterator.next(); 436 437 switch (module) { 438 case ALL_MODULE_PATH: 439 iterator.remove(); 440 macro = Macros.AllModulePath; 441 break; 442 case ALL_RUNTIME: 443 iterator.remove(); 444 macro = Macros.AllRuntime; 445 break; 446 default: 447 lroots.add(module); 448 } 449 } 450 451 switch (macro) { 452 case AllModulePath: 453 this.modules.addAll(getModuleNamesFromPath(paths)); 454 break; 455 case AllRuntime: 456 Set<String> m = RedistributableModules.getRedistributableModules(paths); 457 458 if (m != null) { 459 this.modules.addAll(m); 460 } 461 462 break; 463 } 464 465 this.modules.addAll(lroots); 466 } 467 468 public Set<String> modules() { 469 return modules; 470 } 471 472 private static Set<String> getModuleNamesFromPath(List<Path> Value) { 473 Set<String> result = new LinkedHashSet(); 474 ModuleManager mm = new ModuleManager(Value); 475 List<Module> modules = mm.getModules(EnumSet.of(ModuleManager.SearchType.ModularJar, 476 ModuleManager.SearchType.Jmod, 477 ModuleManager.SearchType.ExplodedModule)); 478 479 for (Module module : modules) { 480 result.add(module.getModuleName()); 481 } 482 483 return result; 484 } 485 } 486 }