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