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 package jdk.tools.jlink.internal; 26 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.PrintWriter; 30 import java.io.UncheckedIOException; 31 import java.lang.module.Configuration; 32 import java.lang.module.ModuleFinder; 33 import java.lang.module.ModuleReference; 34 import java.lang.module.ResolutionException; 35 import java.lang.module.ResolvedModule; 36 import java.lang.reflect.InvocationTargetException; 37 import java.net.URI; 38 import java.nio.ByteOrder; 39 import java.nio.file.Files; 40 import java.nio.file.Path; 41 import java.nio.file.Paths; 42 import java.util.Date; 43 import java.util.Formatter; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Objects; 49 import java.util.Optional; 50 import java.util.Set; 51 import java.util.stream.Collectors; 52 53 import jdk.internal.module.ConfigurableModuleFinder; 54 import jdk.internal.module.ConfigurableModuleFinder.Phase; 55 import jdk.tools.jlink.internal.TaskHelper.BadArgs; 56 import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE; 57 import jdk.tools.jlink.internal.TaskHelper.Option; 58 import jdk.tools.jlink.internal.TaskHelper.OptionsHelper; 59 import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider; 60 import jdk.tools.jlink.plugin.ExecutableImage; 61 import jdk.tools.jlink.Jlink.JlinkConfiguration; 62 import jdk.tools.jlink.Jlink.PluginsConfiguration; 63 import jdk.tools.jlink.plugin.PluginException; 64 import jdk.tools.jlink.builder.DefaultImageBuilder; 65 import jdk.tools.jlink.plugin.Plugin; 66 67 /** 68 * Implementation for the jlink tool. 69 * 70 * ## Should use jdk.joptsimple some day. 71 */ 72 public class JlinkTask { 73 private static final boolean DEBUG = Boolean.getBoolean("jlink.debug"); 74 75 private static <T extends Throwable> void fail(Class<T> type, 76 String format, 77 Object... args) throws T { 78 String msg = new Formatter().format(format, args).toString(); 79 try { 80 T t = type.getConstructor(String.class).newInstance(msg); 81 throw t; 82 } catch (InstantiationException | 83 InvocationTargetException | 84 NoSuchMethodException | 85 IllegalAccessException e) { 86 throw new InternalError("Unable to create an instance of " + type, e); 87 } 88 } 89 90 private static final TaskHelper taskHelper 91 = new TaskHelper(JLINK_BUNDLE); 92 93 static Option<?>[] recognizedOptions = { 94 new Option<JlinkTask>(false, (task, opt, arg) -> { 95 task.options.help = true; 96 }, "--help"), 97 new Option<JlinkTask>(true, (task, opt, arg) -> { 98 String[] dirs = arg.split(File.pathSeparator); 99 task.options.modulePath = new Path[dirs.length]; 100 int i = 0; 101 for (String dir : dirs) { 102 task.options.modulePath[i++] = Paths.get(dir); 103 } 104 }, "--modulepath", "--mp"), 105 new Option<JlinkTask>(true, (task, opt, arg) -> { 106 for (String mn : arg.split(",")) { 107 if (mn.isEmpty()) { 108 throw taskHelper.newBadArgs("err.mods.must.be.specified", 109 "--limitmods"); 110 } 111 task.options.limitMods.add(mn); 112 } 113 }, "--limitmods"), 114 new Option<JlinkTask>(true, (task, opt, arg) -> { 115 for (String mn : arg.split(",")) { 116 if (mn.isEmpty()) { 117 throw taskHelper.newBadArgs("err.mods.must.be.specified", 118 "--addmods"); 119 } 120 task.options.addMods.add(mn); 121 } 122 }, "--addmods"), 123 new Option<JlinkTask>(true, (task, opt, arg) -> { 124 Path path = Paths.get(arg); 125 task.options.output = path; 126 }, "--output"), 127 new Option<JlinkTask>(true, (task, opt, arg) -> { 128 if ("little".equals(arg)) { 129 task.options.endian = ByteOrder.LITTLE_ENDIAN; 130 } else if ("big".equals(arg)) { 131 task.options.endian = ByteOrder.BIG_ENDIAN; 132 } else { 133 throw taskHelper.newBadArgs("err.unknown.byte.order", arg); 134 } 135 }, "--endian"), 136 new Option<JlinkTask>(false, (task, opt, arg) -> { 137 task.options.version = true; 138 }, "--version"), 139 new Option<JlinkTask>(true, (task, opt, arg) -> { 140 Path path = Paths.get(arg); 141 if (Files.exists(path)) { 142 throw taskHelper.newBadArgs("err.dir.exists", path); 143 } 144 task.options.packagedModulesPath = path; 145 }, true, "--keep-packaged-modules"), 146 new Option<JlinkTask>(true, (task, opt, arg) -> { 147 task.options.saveoptsfile = arg; 148 }, "--saveopts"), 149 new Option<JlinkTask>(false, (task, opt, arg) -> { 150 task.options.fullVersion = true; 151 }, true, "--fullversion"),}; 152 153 private static final String PROGNAME = "jlink"; 154 private final OptionsValues options = new OptionsValues(); 155 156 private static final OptionsHelper<JlinkTask> optionsHelper 157 = taskHelper.newOptionsHelper(JlinkTask.class, recognizedOptions); 158 private PrintWriter log; 159 160 void setLog(PrintWriter out) { 161 log = out; 162 taskHelper.setLog(log); 163 } 164 165 /** 166 * Result codes. 167 */ 168 static final int EXIT_OK = 0, // Completed with no errors. 169 EXIT_ERROR = 1, // Completed but reported errors. 170 EXIT_CMDERR = 2, // Bad command-line arguments 171 EXIT_SYSERR = 3, // System error or resource exhaustion. 172 EXIT_ABNORMAL = 4;// terminated abnormally 173 174 static class OptionsValues { 175 boolean help; 176 String saveoptsfile; 177 boolean version; 178 boolean fullVersion; 179 Path[] modulePath; 180 Set<String> limitMods = new HashSet<>(); 181 Set<String> addMods = new HashSet<>(); 182 Path output; 183 Path packagedModulesPath; 184 ByteOrder endian = ByteOrder.nativeOrder(); 185 } 186 187 int run(String[] args) { 188 if (log == null) { 189 setLog(new PrintWriter(System.err)); 190 } 191 try { 192 optionsHelper.handleOptions(this, args); 193 if (options.help) { 194 optionsHelper.showHelp(PROGNAME); 195 return EXIT_OK; 196 } 197 if (optionsHelper.listPlugins()) { 198 optionsHelper.listPlugins(true); 199 return EXIT_OK; 200 } 201 if (options.version || options.fullVersion) { 202 taskHelper.showVersion(options.fullVersion); 203 return EXIT_OK; 204 } 205 if (taskHelper.getExistingImage() == null) { 206 if (options.modulePath == null || options.modulePath.length == 0) { 207 Path jmodsDir = Paths.get(System.getProperty("java.home"), "jmods"); 208 if (Files.isDirectory(jmodsDir)) { 209 options.modulePath = new Path[1]; 210 options.modulePath[0] = jmodsDir; 211 } else { 212 throw taskHelper.newBadArgs("err.modulepath.must.be.specified").showUsage(true); 213 } 214 } 215 createImage(); 216 } else { 217 postProcessOnly(taskHelper.getExistingImage()); 218 } 219 220 if (options.saveoptsfile != null) { 221 Files.write(Paths.get(options.saveoptsfile), getSaveOpts().getBytes()); 222 } 223 224 return EXIT_OK; 225 } catch (UncheckedIOException | PluginException | IllegalArgumentException | 226 IOException | ResolutionException e) { 227 log.println(taskHelper.getMessage("error.prefix") + " " + e.getMessage()); 228 if (DEBUG) { 229 e.printStackTrace(log); 230 } 231 return EXIT_ERROR; 232 } catch (BadArgs e) { 233 taskHelper.reportError(e.key, e.args); 234 if (e.showUsage) { 235 log.println(taskHelper.getMessage("main.usage.summary", PROGNAME)); 236 } 237 if (DEBUG) { 238 e.printStackTrace(log); 239 } 240 return EXIT_CMDERR; 241 } catch (Throwable x) { 242 log.println(taskHelper.getMessage("error.prefix") + " " + x.getMessage()); 243 x.printStackTrace(log); 244 return EXIT_ABNORMAL; 245 } finally { 246 log.flush(); 247 } 248 } 249 250 /* 251 * Jlink API entry point. 252 */ 253 public static void createImage(JlinkConfiguration config, 254 PluginsConfiguration plugins) 255 throws Exception { 256 Objects.requireNonNull(config); 257 Objects.requireNonNull(config.getOutput()); 258 plugins = plugins == null ? new PluginsConfiguration() : plugins; 259 260 if (config.getModulepaths().isEmpty()) { 261 throw new Exception("Empty module paths"); 262 } 263 Path[] arr = new Path[config.getModulepaths().size()]; 264 arr = config.getModulepaths().toArray(arr); 265 ModuleFinder finder 266 = newModuleFinder(arr, config.getLimitmods(), config.getModules()); 267 268 // First create the image provider 269 ImageProvider imageProvider 270 = createImageProvider(finder, 271 checkAddMods(config.getModules()), 272 config.getLimitmods(), 273 config.getByteOrder(), 274 null); 275 276 // Then create the Plugin Stack 277 ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(plugins); 278 279 //Ask the stack to proceed; 280 stack.operate(imageProvider); 281 } 282 283 /* 284 * Jlink API entry point. 285 */ 286 public static void postProcessImage(ExecutableImage image, List<Plugin> postProcessorPlugins) 287 throws Exception { 288 Objects.requireNonNull(image); 289 Objects.requireNonNull(postProcessorPlugins); 290 PluginsConfiguration config = new PluginsConfiguration(postProcessorPlugins); 291 ImagePluginStack stack = ImagePluginConfiguration. 292 parseConfiguration(config); 293 294 stack.operate((ImagePluginStack stack1) -> image); 295 } 296 297 private void postProcessOnly(Path existingImage) throws Exception { 298 PluginsConfiguration config = taskHelper.getPluginsConfig(null); 299 ExecutableImage img = DefaultImageBuilder.getExecutableImage(existingImage); 300 if (img == null) { 301 throw taskHelper.newBadArgs("err.existing.image.invalid"); 302 } 303 postProcessImage(img, config.getPlugins()); 304 } 305 306 private void createImage() throws Exception { 307 if (options.output == null) { 308 throw taskHelper.newBadArgs("err.output.must.be.specified").showUsage(true); 309 } 310 ModuleFinder finder 311 = newModuleFinder(options.modulePath, options.limitMods, options.addMods); 312 try { 313 options.addMods = checkAddMods(options.addMods); 314 } catch (IllegalArgumentException ex) { 315 throw taskHelper.newBadArgs("err.mods.must.be.specified", "--addmods") 316 .showUsage(true); 317 } 318 // First create the image provider 319 ImageProvider imageProvider 320 = createImageProvider(finder, 321 options.addMods, 322 options.limitMods, 323 options.endian, 324 options.packagedModulesPath); 325 326 // Then create the Plugin Stack 327 ImagePluginStack stack = ImagePluginConfiguration. 328 parseConfiguration(taskHelper.getPluginsConfig(options.output)); 329 330 //Ask the stack to proceed 331 stack.operate(imageProvider); 332 } 333 334 private static Set<String> checkAddMods(Set<String> addMods) { 335 if (addMods.isEmpty()) { 336 throw new IllegalArgumentException("no modules to add"); 337 } 338 return addMods; 339 } 340 341 private static ModuleFinder newModuleFinder(Path[] paths, 342 Set<String> limitMods, 343 Set<String> addMods) { 344 ModuleFinder finder = ModuleFinder.of(paths); 345 346 // jmods are located at link-time 347 if (finder instanceof ConfigurableModuleFinder) { 348 ((ConfigurableModuleFinder) finder).configurePhase(Phase.LINK_TIME); 349 } 350 351 // if limitmods is specified then limit the universe 352 if (!limitMods.isEmpty()) { 353 finder = limitFinder(finder, limitMods, addMods); 354 } 355 return finder; 356 } 357 358 359 private static Path toPathLocation(ResolvedModule m) { 360 Optional<URI> ouri = m.reference().location(); 361 if (!ouri.isPresent()) 362 throw new InternalError(m + " does not have a location"); 363 URI uri = ouri.get(); 364 return Paths.get(uri); 365 } 366 367 private static ImageProvider createImageProvider(ModuleFinder finder, 368 Set<String> addMods, 369 Set<String> limitMods, 370 ByteOrder order, 371 Path retainModulesPath) 372 throws IOException 373 { 374 if (addMods.isEmpty()) { 375 throw new IllegalArgumentException("empty modules and limitmods"); 376 } 377 378 Configuration cf = Configuration.empty() 379 .resolveRequires(finder, 380 ModuleFinder.of(), 381 addMods); 382 383 Map<String, Path> mods = cf.modules().stream() 384 .collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation)); 385 return new ImageHelper(cf, mods, order, retainModulesPath); 386 } 387 388 /** 389 * Returns a ModuleFinder that limits observability to the given root 390 * modules, their transitive dependences, plus a set of other modules. 391 */ 392 private static ModuleFinder limitFinder(ModuleFinder finder, 393 Set<String> roots, 394 Set<String> otherMods) { 395 396 // resolve all root modules 397 Configuration cf = Configuration.empty() 398 .resolveRequires(finder, 399 ModuleFinder.of(), 400 roots); 401 402 // module name -> reference 403 Map<String, ModuleReference> map = new HashMap<>(); 404 cf.modules().forEach(m -> { 405 ModuleReference mref = m.reference(); 406 map.put(mref.descriptor().name(), mref); 407 }); 408 409 // add the other modules 410 otherMods.stream() 411 .map(finder::find) 412 .flatMap(Optional::stream) 413 .forEach(mref -> map.putIfAbsent(mref.descriptor().name(), mref)); 414 415 // set of modules that are observable 416 Set<ModuleReference> mrefs = new HashSet<>(map.values()); 417 418 return new ModuleFinder() { 419 @Override 420 public Optional<ModuleReference> find(String name) { 421 return Optional.ofNullable(map.get(name)); 422 } 423 424 @Override 425 public Set<ModuleReference> findAll() { 426 return mrefs; 427 } 428 }; 429 } 430 431 private String getSaveOpts() { 432 StringBuilder sb = new StringBuilder(); 433 sb.append('#').append(new Date()).append("\n"); 434 for (String c : optionsHelper.getInputCommand()) { 435 sb.append(c).append(" "); 436 } 437 438 return sb.toString(); 439 } 440 441 private static String getBomHeader() { 442 StringBuilder sb = new StringBuilder(); 443 sb.append("#").append(new Date()).append("\n"); 444 sb.append("#Please DO NOT Modify this file").append("\n"); 445 return sb.toString(); 446 } 447 448 private String genBOMContent() throws IOException { 449 StringBuilder sb = new StringBuilder(); 450 sb.append(getBomHeader()); 451 StringBuilder command = new StringBuilder(); 452 for (String c : optionsHelper.getInputCommand()) { 453 command.append(c).append(" "); 454 } 455 sb.append("command").append(" = ").append(command); 456 sb.append("\n"); 457 458 return sb.toString(); 459 } 460 461 private static String genBOMContent(JlinkConfiguration config, 462 PluginsConfiguration plugins) 463 throws IOException { 464 StringBuilder sb = new StringBuilder(); 465 sb.append(getBomHeader()); 466 sb.append(config); 467 sb.append(plugins); 468 return sb.toString(); 469 } 470 471 private static class ImageHelper implements ImageProvider { 472 473 final Set<Archive> archives; 474 final ByteOrder order; 475 final Path packagedModulesPath; 476 477 ImageHelper(Configuration cf, 478 Map<String, Path> modsPaths, 479 ByteOrder order, 480 Path packagedModulesPath) throws IOException { 481 archives = modsPaths.entrySet().stream() 482 .map(e -> newArchive(e.getKey(), e.getValue())) 483 .collect(Collectors.toSet()); 484 this.order = order; 485 this.packagedModulesPath = packagedModulesPath; 486 } 487 488 private Archive newArchive(String module, Path path) { 489 if (path.toString().endsWith(".jmod")) { 490 return new JmodArchive(module, path); 491 } else if (path.toString().endsWith(".jar")) { 492 return new ModularJarArchive(module, path); 493 } else if (Files.isDirectory(path)) { 494 return new DirArchive(path); 495 } else { 496 fail(RuntimeException.class, 497 "Selected module %s (%s) not in jmod or modular jar format", 498 module, 499 path); 500 } 501 return null; 502 } 503 504 @Override 505 public ExecutableImage retrieve(ImagePluginStack stack) throws IOException { 506 ExecutableImage image = ImageFileCreator.create(archives, order, stack); 507 if (packagedModulesPath != null) { 508 // copy the packaged modules to the given path 509 Files.createDirectories(packagedModulesPath); 510 for (Archive a : archives) { 511 Path file = a.getPath(); 512 Path dest = packagedModulesPath.resolve(file.getFileName()); 513 Files.copy(file, dest); 514 } 515 } 516 return image; 517 } 518 } 519 520 private static enum Section { 521 NATIVE_LIBS("native", nativeDir()), 522 NATIVE_CMDS("bin", "bin"), 523 CLASSES("classes", "classes"), 524 CONFIG("conf", "conf"), 525 UNKNOWN("unknown", "unknown"); 526 527 private static String nativeDir() { 528 if (System.getProperty("os.name").startsWith("Windows")) { 529 return "bin"; 530 } else { 531 return "lib"; 532 } 533 } 534 535 private final String jmodDir; 536 private final String imageDir; 537 538 Section(String jmodDir, String imageDir) { 539 this.jmodDir = jmodDir; 540 this.imageDir = imageDir; 541 } 542 543 String imageDir() { 544 return imageDir; 545 } 546 547 String jmodDir() { 548 return jmodDir; 549 } 550 551 boolean matches(String path) { 552 return path.startsWith(jmodDir); 553 } 554 555 static Section getSectionFromName(String dir) { 556 if (Section.NATIVE_LIBS.matches(dir)) { 557 return Section.NATIVE_LIBS; 558 } else if (Section.NATIVE_CMDS.matches(dir)) { 559 return Section.NATIVE_CMDS; 560 } else if (Section.CLASSES.matches(dir)) { 561 return Section.CLASSES; 562 } else if (Section.CONFIG.matches(dir)) { 563 return Section.CONFIG; 564 } else { 565 return Section.UNKNOWN; 566 } 567 } 568 } 569 }