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