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