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