1 /* 2 * Copyright (c) 2015, 2019, 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.jpackage.internal; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.StringReader; 31 import java.io.PrintWriter; 32 import java.io.StringWriter; 33 import java.nio.file.Files; 34 import java.nio.file.Path; 35 import java.text.MessageFormat; 36 import java.util.ArrayList; 37 import java.util.Collection; 38 import java.util.Collections; 39 import java.util.EnumSet; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.Iterator; 43 import java.util.LinkedHashMap; 44 import java.util.LinkedHashSet; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Properties; 48 import java.util.ResourceBundle; 49 import java.util.Set; 50 import java.util.Optional; 51 import java.util.Arrays; 52 import java.util.stream.Collectors; 53 import java.util.stream.Stream; 54 import java.util.regex.Matcher; 55 import java.util.spi.ToolProvider; 56 import java.util.jar.JarFile; 57 import java.lang.module.Configuration; 58 import java.lang.module.ResolvedModule; 59 import java.lang.module.ModuleDescriptor; 60 import java.lang.module.ModuleFinder; 61 import java.lang.module.ModuleReference; 62 import jdk.internal.module.ModulePath; 63 64 final class JLinkBundlerHelper { 65 66 private static final ResourceBundle I18N = ResourceBundle.getBundle( 67 "jdk.jpackage.internal.resources.MainResources"); 68 69 static final ToolProvider JLINK_TOOL = 70 ToolProvider.findFirst("jlink").orElseThrow(); 71 72 static File getMainJar(Map<String, ? super Object> params) { 73 File result = null; 74 RelativeFileSet fileset = 75 StandardBundlerParam.MAIN_JAR.fetchFrom(params); 76 77 if (fileset != null) { 78 String filename = fileset.getIncludedFiles().iterator().next(); 79 result = fileset.getBaseDirectory().toPath(). 80 resolve(filename).toFile(); 81 82 if (result == null || !result.exists()) { 83 String srcdir = 84 StandardBundlerParam.SOURCE_DIR.fetchFrom(params); 85 86 if (srcdir != null) { 87 result = new File(srcdir + File.separator + filename); 88 } 89 } 90 } 91 92 return result; 93 } 94 95 static String getMainClassFromModule(Map<String, ? super Object> params) { 96 String mainModule = StandardBundlerParam.MODULE.fetchFrom(params); 97 if (mainModule != null) { 98 99 int index = mainModule.indexOf("/"); 100 if (index > 0) { 101 return mainModule.substring(index + 1); 102 } else { 103 ModuleDescriptor descriptor = 104 JLinkBundlerHelper.getMainModuleDescription(params); 105 if (descriptor != null) { 106 Optional<String> mainClass = descriptor.mainClass(); 107 if (mainClass.isPresent()) { 108 Log.verbose(MessageFormat.format(I18N.getString( 109 "message.module-class"), 110 mainClass.get(), 111 JLinkBundlerHelper.getMainModule(params))); 112 return mainClass.get(); 113 } 114 } 115 } 116 } 117 return null; 118 } 119 120 static String getMainModule(Map<String, ? super Object> params) { 121 String result = null; 122 String mainModule = StandardBundlerParam.MODULE.fetchFrom(params); 123 124 if (mainModule != null) { 125 int index = mainModule.indexOf("/"); 126 127 if (index > 0) { 128 result = mainModule.substring(0, index); 129 } else { 130 result = mainModule; 131 } 132 } 133 134 return result; 135 } 136 137 static void execute(Map<String, ? super Object> params, 138 AbstractAppImageBuilder imageBuilder) 139 throws IOException, Exception { 140 141 List<Path> modulePath = 142 StandardBundlerParam.MODULE_PATH.fetchFrom(params); 143 Set<String> addModules = 144 StandardBundlerParam.ADD_MODULES.fetchFrom(params); 145 Set<String> limitModules = 146 StandardBundlerParam.LIMIT_MODULES.fetchFrom(params); 147 Path outputDir = imageBuilder.getRuntimeRoot(); 148 File mainJar = getMainJar(params); 149 ModFile.ModType mainJarType = ModFile.ModType.Unknown; 150 151 if (mainJar != null) { 152 mainJarType = new ModFile(mainJar).getModType(); 153 } else if (StandardBundlerParam.MODULE.fetchFrom(params) == null) { 154 // user specified only main class, all jars will be on the classpath 155 mainJarType = ModFile.ModType.UnnamedJar; 156 } 157 158 boolean bindServices = addModules.isEmpty(); 159 160 // Modules 161 String mainModule = getMainModule(params); 162 if (mainModule == null) { 163 if (mainJarType == ModFile.ModType.UnnamedJar) { 164 if (addModules.isEmpty()) { 165 // The default for an unnamed jar is ALL_DEFAULT 166 addModules.add(ModuleHelper.ALL_DEFAULT); 167 } 168 } else if (mainJarType == ModFile.ModType.Unknown || 169 mainJarType == ModFile.ModType.ModularJar) { 170 addModules.add(ModuleHelper.ALL_DEFAULT); 171 } 172 } 173 174 Set<String> modules = new ModuleHelper( 175 modulePath, addModules, limitModules).modules(); 176 177 if (mainModule != null) { 178 modules.add(mainModule); 179 } 180 181 runJLink(outputDir, modulePath, modules, limitModules, 182 new HashMap<String,String>(), bindServices); 183 184 imageBuilder.prepareApplicationFiles(params); 185 } 186 187 188 // Returns the path to the JDK modules in the user defined module path. 189 static Path findPathOfModule( List<Path> modulePath, String moduleName) { 190 191 for (Path path : modulePath) { 192 Path moduleNamePath = path.resolve(moduleName); 193 194 if (Files.exists(moduleNamePath)) { 195 return path; 196 } 197 } 198 199 return null; 200 } 201 202 static ModuleDescriptor getMainModuleDescription(Map<String, ? super Object> params) { 203 boolean hasModule = params.containsKey(StandardBundlerParam.MODULE.getID()); 204 if (hasModule) { 205 List<Path> modulePath = StandardBundlerParam.MODULE_PATH.fetchFrom(params); 206 if (!modulePath.isEmpty()) { 207 ModuleFinder finder = ModuleFinder.of(modulePath.toArray(new Path[0])); 208 String mainModule = JLinkBundlerHelper.getMainModule(params); 209 Optional<ModuleReference> omref = finder.find(mainModule); 210 if (omref.isPresent()) { 211 return omref.get().descriptor(); 212 } 213 } 214 } 215 216 return null; 217 } 218 219 /* 220 * Returns the set of modules that would be visible by default for 221 * a non-modular-aware application consisting of the given elements. 222 */ 223 private static Set<String> getDefaultModules( 224 Collection<Path> paths, Collection<String> addModules) { 225 226 // the modules in the run-time image that export an API 227 Stream<String> systemRoots = ModuleFinder.ofSystem().findAll().stream() 228 .map(ModuleReference::descriptor) 229 .filter(JLinkBundlerHelper::exportsAPI) 230 .map(ModuleDescriptor::name); 231 232 Set<String> roots = Stream.concat(systemRoots, 233 addModules.stream()).collect(Collectors.toSet()); 234 235 ModuleFinder finder = createModuleFinder(paths); 236 237 return Configuration.empty() 238 .resolveAndBind(finder, ModuleFinder.of(), roots) 239 .modules() 240 .stream() 241 .map(ResolvedModule::name) 242 .collect(Collectors.toSet()); 243 } 244 245 /* 246 * Returns true if the given module exports an API to all module. 247 */ 248 private static boolean exportsAPI(ModuleDescriptor descriptor) { 249 return descriptor.exports() 250 .stream() 251 .anyMatch(e -> !e.isQualified()); 252 } 253 254 private static ModuleFinder createModuleFinder(Collection<Path> modulePath) { 255 return ModuleFinder.compose( 256 ModulePath.of(JarFile.runtimeVersion(), true, 257 modulePath.toArray(Path[]::new)), 258 ModuleFinder.ofSystem()); 259 } 260 261 private static class ModuleHelper { 262 // The token for "all modules on the module path". 263 private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH"; 264 265 // The token for "all valid runtime modules". 266 static final String ALL_DEFAULT = "ALL-DEFAULT"; 267 268 private final Set<String> modules = new HashSet<>(); 269 ModuleHelper(List<Path> paths, Set<String> addModules, 270 Set<String> limitModules) { 271 boolean addAllModulePath = false; 272 boolean addDefaultMods = false; 273 274 for (Iterator<String> iterator = addModules.iterator(); 275 iterator.hasNext();) { 276 String module = iterator.next(); 277 278 switch (module) { 279 case ALL_MODULE_PATH: 280 iterator.remove(); 281 addAllModulePath = true; 282 break; 283 case ALL_DEFAULT: 284 iterator.remove(); 285 addDefaultMods = true; 286 break; 287 default: 288 this.modules.add(module); 289 } 290 } 291 292 if (addAllModulePath) { 293 this.modules.addAll(getModuleNamesFromPath(paths)); 294 } else if (addDefaultMods) { 295 this.modules.addAll(getDefaultModules( 296 paths, addModules)); 297 } 298 } 299 300 Set<String> modules() { 301 return modules; 302 } 303 304 private static Set<String> getModuleNamesFromPath(List<Path> paths) { 305 306 return createModuleFinder(paths) 307 .findAll() 308 .stream() 309 .map(ModuleReference::descriptor) 310 .map(ModuleDescriptor::name) 311 .collect(Collectors.toSet()); 312 } 313 } 314 315 private static void runJLink(Path output, List<Path> modulePath, 316 Set<String> modules, Set<String> limitModules, 317 HashMap<String, String> user, boolean bindServices) 318 throws PackagerException { 319 320 // This is just to ensure jlink is given a non-existant directory 321 // The passed in output path should be non-existant or empty directory 322 try { 323 IOUtils.deleteRecursive(output.toFile()); 324 } catch (IOException ioe) { 325 throw new PackagerException(ioe); 326 } 327 328 ArrayList<String> args = new ArrayList<String>(); 329 args.add("--output"); 330 args.add(output.toString()); 331 if (modulePath != null && !modulePath.isEmpty()) { 332 args.add("--module-path"); 333 args.add(getPathList(modulePath)); 334 } 335 if (modules != null && !modules.isEmpty()) { 336 args.add("--add-modules"); 337 args.add(getStringList(modules)); 338 } 339 if (limitModules != null && !limitModules.isEmpty()) { 340 args.add("--limit-modules"); 341 args.add(getStringList(limitModules)); 342 } 343 if (user != null && !user.isEmpty()) { 344 for (Map.Entry<String, String> entry : user.entrySet()) { 345 args.add(entry.getKey()); 346 args.add(entry.getValue()); 347 } 348 } else { 349 args.add("--strip-native-commands"); 350 args.add("--strip-debug"); 351 args.add("--no-man-pages"); 352 args.add("--no-header-files"); 353 if (bindServices) { 354 args.add("--bind-services"); 355 } 356 } 357 358 StringWriter writer = new StringWriter(); 359 PrintWriter pw = new PrintWriter(writer); 360 361 Log.verbose("jlink arguments: " + args); 362 int retVal = JLINK_TOOL.run(pw, pw, args.toArray(new String[0])); 363 String jlinkOut = writer.toString(); 364 365 if (retVal != 0) { 366 throw new PackagerException("error.jlink.failed" , jlinkOut); 367 } else if (jlinkOut.length() > 0) { 368 Log.verbose("jlink output: " + jlinkOut); 369 } 370 } 371 372 private static String getPathList(List<Path> pathList) { 373 String ret = null; 374 for (Path p : pathList) { 375 String s = Matcher.quoteReplacement(p.toString()); 376 if (ret == null) { 377 ret = s; 378 } else { 379 ret += File.pathSeparator + s; 380 } 381 } 382 return ret; 383 } 384 385 private static String getStringList(Set<String> strings) { 386 String ret = null; 387 for (String s : strings) { 388 if (ret == null) { 389 ret = s; 390 } else { 391 ret += "," + s; 392 } 393 } 394 return (ret == null) ? null : Matcher.quoteReplacement(ret); 395 } 396 }