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.StandardBundlerParam; 32 import com.oracle.tools.packager.RelativeFileSet; 33 import com.oracle.tools.packager.Log; 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 String srcdir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params); 166 RelativeFileSet fileset = StandardBundlerParam.MAIN_JAR.fetchFrom(params); 167 168 if (fileset != null) { 169 String filename = fileset.getIncludedFiles().iterator().next(); 170 171 if (srcdir != null) { 172 filename = srcdir + File.separator + filename; 173 } 174 175 result = new File(filename); 176 } 177 178 return result; 179 } 180 181 public static String getMainClass(Map<String, ? super Object> params) { 182 String result = ""; 183 File mainJar = getMainJar(params); 184 185 if (mainJar != null) { 186 result = StandardBundlerParam.MAIN_CLASS.fetchFrom(params); 187 } 188 else { 189 String mainModule = StandardBundlerParam.MODULE.fetchFrom(params); 190 191 if (mainModule != null) { 192 int index = mainModule.indexOf("/"); 193 194 if (index > 0) { 195 result = mainModule.substring(index + 1); 196 } 197 } 198 } 199 200 return result; 201 } 202 203 public static String getMainModule(Map<String, ? super Object> params) { 204 String result = ""; 205 String mainModule = StandardBundlerParam.MODULE.fetchFrom(params); 206 207 if (mainModule != null) { 208 int index = mainModule.indexOf("/"); 209 210 if (index > 0) { 211 result = mainModule.substring(0, index); 212 } 213 else { 214 result = mainModule; 215 } 216 } 217 218 return result; 219 } 220 221 public static String getJDKVersion(Map<String, ? super Object> params) { 222 String result = ""; 223 List<Path> modulePath = StandardBundlerParam.MODULE_PATH.fetchFrom(params); 224 Path javaBasePath = findPathOfModule(modulePath, "java.base.jmod"); 225 226 if (javaBasePath != null && javaBasePath.toFile().exists()) { 227 result = RedistributableModules.getModuleVersion(javaBasePath.toFile(), 228 modulePath, StandardBundlerParam.ADD_MODULES.fetchFrom(params), 229 StandardBundlerParam.LIMIT_MODULES.fetchFrom(params)); 230 } 231 232 return result; 233 } 234 235 public static Path getJDKHome(Map<String, ? super Object> params) { 236 Path result = null; 237 List<Path> modulePath = StandardBundlerParam.MODULE_PATH.fetchFrom(params); 238 Path javaBasePath = findPathOfModule(modulePath, "java.base.jmod"); 239 240 if (javaBasePath != null && javaBasePath.toFile().exists()) { 241 result = javaBasePath.getParent(); 242 } 243 244 return result; 245 } 246 247 public static void execute(Map<String, ? super Object> params, AbstractAppImageBuilder imageBuilder) throws IOException, Exception { 248 List<Path> modulePath = StandardBundlerParam.MODULE_PATH.fetchFrom(params); 249 Set<String> addModules = StandardBundlerParam.ADD_MODULES.fetchFrom(params); 250 Set<String> limitModules = StandardBundlerParam.LIMIT_MODULES.fetchFrom(params); 251 boolean stripNativeCommands = StandardBundlerParam.STRIP_NATIVE_COMMANDS.fetchFrom(params); 252 Map<String, String> userArguments = JLINK_OPTIONS.fetchFrom(params); 253 Path outputDir = imageBuilder.getRoot(); 254 String excludeFileList = imageBuilder.getExcludeFileList(); 255 Set<String> jars = getResourceFileJarList(params, Module.JarType.UnnamedJar); 256 File mainJar = getMainJar(params); 257 Module.ModuleType mainJarType = Module.ModuleType.Unknown; 258 259 if (mainJar != null) { 260 mainJarType = new Module(mainJar).getModuleType(); 261 } 262 263 //-------------------------------------------------------------------- 264 // Modules 265 266 boolean detectModules = DETECT_MODULES.fetchFrom(params); 267 268 // The default for an unnamed jar is ALL_DEFAULT with the 269 // non-redistributable modules removed. 270 if (mainJarType == Module.ModuleType.UnnamedJar && !detectModules) { 271 addModules.add(ModuleHelper.ALL_RUNTIME); 272 } 273 else if (mainJarType == Module.ModuleType.Unknown || mainJarType == Module.ModuleType.ModularJar) { 274 String mainModule = getMainModule(params); 275 addModules.add(mainModule); 276 277 // Error if any of the srcfiles are modular jars. 278 Set<String> modularJars = getResourceFileJarList(params, Module.JarType.ModularJar); 279 280 if (!modularJars.isEmpty()) { 281 throw new Exception(MessageFormat.format(I18N.getString("error.srcfiles.contain.modules"), modularJars.toString())); 282 } 283 } 284 285 ModuleHelper moduleHelper = new ModuleHelper(modulePath, addModules, limitModules); 286 addModules.addAll(moduleHelper.modules()); 287 288 //-------------------------------------------------------------------- 289 // Jars 290 291 // Bundle with minimum dependencies that unnamed jars depend on. 292 if (detectModules && !jars.isEmpty()) { 293 Log.info(MessageFormat.format(I18N.getString("using.experimental.feature"), "--" + DETECT_MODULES.getID())); 294 Collection<String> detectedModules = JDepHelper.calculateModules(jars, modulePath); 295 296 if (!detectedModules.isEmpty()) { 297 addModules.addAll(detectedModules); 298 } 299 } 300 301 Log.info(MessageFormat.format(I18N.getString("message.modules"), addModules.toString())); 302 303 AppRuntimeImageBuilder appRuntimeBuilder = new AppRuntimeImageBuilder(); 304 appRuntimeBuilder.setOutputDir(outputDir); 305 appRuntimeBuilder.setModulePath(modulePath); 306 appRuntimeBuilder.setAddModules(addModules); 307 appRuntimeBuilder.setLimitModules(limitModules); 308 appRuntimeBuilder.setExcludeFileList(excludeFileList); 309 appRuntimeBuilder.setStripNativeCommands(stripNativeCommands); 310 appRuntimeBuilder.setUserArguments(userArguments); 311 312 appRuntimeBuilder.build(); 313 imageBuilder.prepareApplicationFiles(); 314 } 315 316 // Returns the path to the JDK modules in the user defined module path. 317 public static Path findPathOfModule(List<Path> modulePath, String moduleName) { 318 Path result = null; 319 320 for (Path path : modulePath) { 321 Path moduleNamePath = path.resolve(moduleName); 322 323 if (Files.exists(moduleNamePath)) { 324 result = path; 325 break; 326 } 327 } 328 329 return result; 330 } 331 332 private static Set<String> getResourceFileJarList(Map<String, ? super Object> params, Module.JarType Query) { 333 Set<String> files = new LinkedHashSet(); 334 335 String srcdir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params); 336 337 for (RelativeFileSet appResources : StandardBundlerParam.APP_RESOURCES_LIST.fetchFrom(params)) { 338 for (String resource : appResources.getIncludedFiles()) { 339 if (resource.endsWith(".jar")) { 340 String filename = srcdir + File.separator + resource; 341 342 switch (Query) { 343 case All: { 344 files.add(filename); 345 break; 346 } 347 case ModularJar: { 348 Module module = new Module(new File(filename)); 349 350 if (module.getModuleType() == Module.ModuleType.ModularJar) { 351 files.add(filename); 352 } 353 break; 354 } 355 case UnnamedJar: { 356 Module module = new Module(new File(filename)); 357 358 if (module.getModuleType() == Module.ModuleType.UnnamedJar) { 359 files.add(filename); 360 } 361 break; 362 } 363 } 364 } 365 } 366 } 367 368 return files; 369 } 370 371 private static class ModuleHelper { 372 // The token for "all modules on the module path". 373 private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH"; 374 375 // The token for "all redistributable runtime modules". 376 public static final String ALL_RUNTIME = "ALL-RUNTIME"; 377 378 private final Set<String> modules = new HashSet<>(); 379 380 public ModuleHelper(List<Path> paths, Set<String> roots, Set<String> limitMods) { 381 boolean found = false; 382 383 for (Iterator<String> iterator = roots.iterator(); iterator.hasNext();) { 384 String module = iterator.next(); 385 386 switch (module) { 387 case ALL_MODULE_PATH: 388 iterator.remove(); 389 390 if (!found) { 391 modules.addAll(getModuleNamesFromPath(paths)); 392 found = true; 393 } 394 break; 395 case ALL_RUNTIME: 396 iterator.remove(); 397 398 if (!found) { 399 Set<String> modules = RedistributableModules.getRedistributableModules(paths, roots, limitMods); 400 401 if (modules != null) { 402 this.modules.addAll(modules); 403 } 404 405 found = true; 406 } 407 break; 408 default: 409 modules.add(module); 410 } 411 } 412 } 413 414 public Set<String> modules() { 415 return modules; 416 } 417 418 private static Set<String> getModuleNamesFromPath(List<Path> Value) { 419 Set<String> result = new LinkedHashSet(); 420 ModuleManager mm = new ModuleManager(Value); 421 List<Module> modules = mm.getModules(EnumSet.of(ModuleManager.SearchType.ModularJar, 422 ModuleManager.SearchType.Jmod, 423 ModuleManager.SearchType.ExplodedModule)); 424 425 for (Module module : modules) { 426 result.add(module.getModuleName()); 427 } 428 429 return result; 430 } 431 } 432 }