1 /* 2 * Copyright (c) 2009, 2010, 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 org.openjdk.jigsaw.cli; 27 28 import java.lang.module.*; 29 import java.io.*; 30 import java.net.*; 31 import java.nio.file.Files; 32 import java.nio.file.Path; 33 import java.nio.file.Paths; 34 import java.security.*; 35 import java.util.*; 36 37 import static java.lang.System.out; 38 import static java.lang.System.err; 39 40 import org.openjdk.jigsaw.*; 41 import org.openjdk.jigsaw.SimpleLibrary.StorageOption; 42 import org.openjdk.internal.joptsimple.*; 43 44 45 public class Librarian { 46 47 private static final JigsawModuleSystem jms 48 = JigsawModuleSystem.instance(); 49 50 private static final File homeLibrary = Library.systemLibraryPath(); 51 52 static class Create extends Command<SimpleLibrary> { 53 protected void go(SimpleLibrary lib) 54 throws Command.Exception 55 { 56 noDry(); 57 finishArgs(); 58 File lp = libPath(opts); 59 File pp = null; 60 if (opts.has(parentPath)) 61 pp = opts.valueOf(parentPath); 62 else if (!opts.has("N")) 63 pp = homeLibrary; 64 65 File natlibs = null; 66 if (opts.has(nativeLibs)) 67 natlibs = opts.valueOf(nativeLibs); 68 File natcmds = null; 69 if (opts.has(nativeCmds)) 70 natcmds = opts.valueOf(nativeCmds); 71 File configs = null; 72 if (opts.has(configFiles)) 73 configs = opts.valueOf(configFiles); 74 75 Set<StorageOption> createOpts = new HashSet<>(); 76 if (opts.has("z")) 77 createOpts.add(StorageOption.DEFLATED); 78 79 try { 80 lib = SimpleLibrary.create(lp, pp, natlibs, natcmds, 81 configs, createOpts); 82 } catch (IOException x) { 83 throw new Command.Exception(x); 84 } 85 } 86 } 87 88 static class DumpClass extends Command<SimpleLibrary> { 89 protected void go(SimpleLibrary lib) 90 throws Command.Exception 91 { 92 noDry(); 93 String mids = takeArg(); 94 ModuleId mid = null; 95 try { 96 mid = jms.parseModuleId(mids); 97 } catch (IllegalArgumentException x) { 98 throw new Command.Exception(x.getMessage()); 99 } 100 String cn = takeArg(); 101 String ops = takeArg(); 102 finishArgs(); 103 byte[] bs = null; 104 try { 105 bs = lib.readClass(mid, cn); 106 if (bs == null) 107 throw new Command.Exception("%s: No such class in module %s", 108 cn, mid); 109 OutputStream fout = null; 110 try { 111 fout = (ops.equals("-") 112 ? System.out 113 : new FileOutputStream(ops)); 114 fout.write(bs); 115 } finally { 116 if (fout != null) 117 fout.close(); 118 } 119 } catch (IllegalArgumentException x) { 120 throw new Command.Exception(x.getMessage()); 121 } catch (IOException x) { 122 throw new Command.Exception(x); 123 } 124 } 125 } 126 127 static class Identify extends Command<SimpleLibrary> { 128 protected void go(SimpleLibrary lib) 129 throws Command.Exception 130 { 131 noDry(); 132 finishArgs(); 133 out.format("path %s%n", lib.root()); 134 out.format("version %d.%d%n", 135 lib.majorVersion(), lib.minorVersion()); 136 if (lib.natlibs() != null) 137 out.format("natlibs %s%n", lib.natlibs()); 138 if (lib.natcmds() != null) 139 out.format("natcmds %s%n", lib.natcmds()); 140 if (lib.configs() != null) 141 out.format("configs %s%n", lib.configs()); 142 SimpleLibrary plib = lib.parent(); 143 while (plib != null) { 144 out.format("parent %s%n", plib.root()); 145 plib = plib.parent(); 146 } 147 } 148 } 149 150 static class Extract extends Command<SimpleLibrary> { 151 protected void go(SimpleLibrary lib) 152 throws Command.Exception 153 { 154 noDry(); 155 while (hasArg()) { 156 File module = new File(takeArg()); 157 File destination = null; 158 try (FileInputStream fis = new FileInputStream(module)) { 159 ModuleFile.Reader reader = new ModuleFile.Reader(fis); 160 161 ModuleInfo mi = jms.parseModuleInfo(reader.getModuleInfoBytes()); 162 destination = new File(mi.id().name()); 163 Path path = destination.toPath(); 164 Files.deleteIfExists(path); 165 Files.createDirectory(path); 166 reader.extractTo(destination, false); 167 } catch (IOException | ModuleFileParserException x) { 168 // Try to cleanup if an exception is thrown 169 if (destination != null && destination.exists()) 170 try { 171 FilePaths.deleteTree(destination.toPath()); 172 } catch (IOException y) { 173 throw (Command.Exception) 174 new Command.Exception(y).initCause(x); 175 } 176 throw new Command.Exception(x); 177 } 178 } 179 finishArgs(); 180 } 181 } 182 183 static class Install extends Command<SimpleLibrary> { 184 protected void go(SimpleLibrary lib) 185 throws Command.Exception 186 { 187 String key = takeArg(); 188 File kf = new File(key); 189 boolean verifySignature = !opts.has("noverify"); 190 boolean strip = opts.has("G"); 191 192 // Old form: install <classes-dir> <module-name> ... 193 // 194 if (kf.exists() && kf.isDirectory()) { 195 noDry(); 196 List<Manifest> mfs = new ArrayList<>(); 197 while (hasArg()) 198 mfs.add(Manifest.create(takeArg(), kf)); 199 finishArgs(); 200 if (mfs.isEmpty()) 201 throw new Command.Exception("%s: no module-name specified", 202 command); 203 try { 204 lib.installFromManifests(mfs, strip); 205 } catch (ConfigurationException | IOException x) { 206 throw new Command.Exception(x); 207 } 208 return; 209 } 210 211 // Install one or more module file(s) 212 // 213 if (kf.exists() && kf.isFile()) { 214 noDry(); 215 List<File> fs = new ArrayList<>(); 216 fs.add(kf); 217 while (hasArg()) 218 fs.add(new File(takeArg())); 219 finishArgs(); 220 try { 221 lib.install(fs, verifySignature, strip); 222 } catch (ConfigurationException | IOException | 223 SignatureException | ModuleFileParserException x) { 224 throw new Command.Exception(x); 225 } 226 return; 227 } 228 229 // Otherwise treat args as module-id queries 230 List<ModuleIdQuery> midqs = new ArrayList<>(); 231 String s = key; 232 for (;;) { 233 ModuleIdQuery mq = null; 234 try { 235 mq = jms.parseModuleIdQuery(s); 236 } catch (IllegalArgumentException x) { 237 throw new Command.Exception(x); 238 } 239 midqs.add(mq); 240 if (!hasArg()) 241 break; 242 s = takeArg(); 243 } 244 try { 245 boolean quiet = false; // ## Need -q 246 Resolution res = lib.resolve(midqs); 247 if (res.modulesNeeded().isEmpty()) { 248 if (!quiet) 249 out.format("Nothing to install%n"); 250 return; 251 } 252 if (!quiet) { 253 out.format("To install: %s%n", 254 res.modulesNeeded() 255 .toString().replaceAll("^\\[|\\]$", "")); 256 out.format("%d bytes to download/transfer%n", 257 res.downloadRequired()); 258 out.format("%d bytes to store%n", 259 res.spaceRequired()); 260 } 261 if (dry) 262 return; 263 lib.install(res, verifySignature, strip); 264 } catch (ConfigurationException | IOException | SignatureException | 265 ModuleFileParserException x) { 266 throw new Command.Exception(x); 267 } 268 269 } 270 } 271 272 // ## preinstall is used by jpkg for creating debian package. 273 // ## need to revisit this 274 static class PreInstall extends Command<SimpleLibrary> { 275 protected void go(SimpleLibrary lib) 276 throws Command.Exception 277 { 278 noDry(); 279 File classes = new File(takeArg()); 280 File dst = new File(takeArg()); 281 List<Manifest> mfs = new ArrayList<Manifest>(); 282 while (hasArg()) 283 mfs.add(Manifest.create(takeArg(), classes)); 284 finishArgs(); 285 try { 286 lib.preInstall(mfs, dst); 287 } catch (IOException x) { 288 throw new Command.Exception(x); 289 } 290 } 291 } 292 293 static class Remove extends Command<SimpleLibrary> { 294 protected void go(SimpleLibrary lib) 295 throws Command.Exception 296 { 297 if (dry && force) 298 throw new Command.Exception("%s: specify only one of " 299 + "-n (--dry-run) or -f (--force)", 300 command); 301 List<ModuleId> mids = new ArrayList<ModuleId>(); 302 try { 303 while (hasArg()) 304 mids.add(jms.parseModuleId(takeArg())); 305 } catch (IllegalArgumentException x) { 306 throw new Command.Exception(x.getMessage()); 307 } 308 boolean quiet = false; // ## Need -q 309 try { 310 if (force) 311 lib.removeForcibly(mids); 312 else 313 lib.remove(mids, dry); 314 } catch (ConfigurationException x) { 315 throw new Command.Exception(x); 316 } catch (IOException x) { 317 if (!quiet) { 318 for (Throwable t : x.getSuppressed()) 319 err.format("Warning: %s%n", t.getMessage()); 320 } 321 throw new Command.Exception(x); 322 } 323 } 324 } 325 326 static class DumpConfig extends Command<SimpleLibrary> { 327 protected void go(SimpleLibrary lib) 328 throws Command.Exception 329 { 330 noDry(); 331 String midqs = takeArg(); 332 ModuleIdQuery midq = null; 333 try { 334 midq = jms.parseModuleIdQuery(midqs); 335 } catch (IllegalArgumentException x) { 336 throw new Command.Exception(x.getMessage()); 337 } 338 finishArgs(); 339 try { 340 ModuleId mid = lib.findLatestModuleId(midq); 341 if (mid == null) 342 throw new Command.Exception(midq + ": No such module"); 343 Configuration<Context> cf = lib.readConfiguration(mid); 344 if (cf == null) 345 throw new Command.Exception(mid + ": Not a root module"); 346 cf.dump(out, verbose); 347 } catch (IOException x) { 348 throw new Command.Exception(x); 349 } 350 } 351 } 352 353 static class Config extends Command<SimpleLibrary> { 354 protected void go(SimpleLibrary lib) 355 throws Command.Exception 356 { 357 noDry(); 358 List<ModuleId> mids = new ArrayList<ModuleId>(); 359 try { 360 while (hasArg()) 361 mids.add(jms.parseModuleId(takeArg())); 362 } catch (IllegalArgumentException x) { 363 throw new Command.Exception(x.getMessage()); 364 } 365 try { 366 lib.configure(mids); 367 } catch (ConfigurationException x) { 368 throw new Command.Exception(x); 369 } catch (IOException x) { 370 throw new Command.Exception(x); 371 } 372 } 373 } 374 375 static class ReIndex extends Command<SimpleLibrary> { 376 protected void go(SimpleLibrary lib) 377 throws Command.Exception 378 { 379 noDry(); 380 List<ModuleId> mids = new ArrayList<ModuleId>(); 381 try { 382 while (hasArg()) 383 mids.add(jms.parseModuleId(takeArg())); 384 } catch (IllegalArgumentException x) { 385 throw new Command.Exception(x.getMessage()); 386 } 387 try { 388 lib.reIndex(mids); 389 } catch (ConfigurationException x) { 390 throw new Command.Exception(x); 391 } catch (IOException x) { 392 throw new Command.Exception(x); 393 } 394 } 395 } 396 397 static class Repos extends Command<SimpleLibrary> { 398 protected void go(SimpleLibrary lib) 399 throws Command.Exception 400 { 401 noDry(); 402 finishArgs(); 403 try { 404 RemoteRepositoryList rl = lib.repositoryList(); 405 int i = 0; 406 for (RemoteRepository rr : rl.repositories()) 407 out.format("%d %s%n", i++, rr.location()); 408 } catch (IOException x) { 409 throw new Command.Exception(x); 410 } 411 } 412 } 413 414 static URI parseURI(String s) 415 throws Command.Exception 416 { 417 try { 418 // if a scheme isn't specified then assume that a file path 419 if (s.indexOf(':') == -1) 420 return Paths.get(s).toUri(); 421 return new URI(s); 422 } catch (URISyntaxException x) { 423 throw new Command.Exception("URI syntax error: " 424 + x.getMessage()); 425 } 426 } 427 428 static class AddRepo extends Command<SimpleLibrary> { 429 protected void go(SimpleLibrary lib) 430 throws Command.Exception 431 { 432 noDry(); 433 URI u = parseURI(takeArg()); 434 int i = (opts.has(repoIndex) 435 ? opts.valueOf(repoIndex) 436 : Integer.MAX_VALUE); 437 finishArgs(); 438 try { 439 RemoteRepositoryList rl = lib.repositoryList(); 440 rl.add(u, i); 441 } catch (IOException x) { 442 throw new Command.Exception(x); 443 } 444 } 445 } 446 447 static class DelRepo extends Command<SimpleLibrary> { 448 protected void go(SimpleLibrary lib) 449 throws Command.Exception 450 { 451 noDry(); 452 URI u = null; 453 int i = -1; 454 if (hasArg()) { 455 String s = takeArg(); 456 if (!s.endsWith("/")) 457 s += "/"; 458 u = parseURI(s); 459 finishArgs(); 460 } 461 if (opts.has(repoIndex)) 462 i = opts.valueOf(repoIndex); 463 if (u != null && i != -1) { 464 throw new Command.Exception("del-repo: Cannot specify" 465 + " both -i and a URL"); 466 } 467 if (u == null && i == -1) { 468 throw new Command.Exception("del-repo: One of -i <index>" 469 + " or a URL required"); 470 } 471 try { 472 RemoteRepositoryList rl = lib.repositoryList(); 473 if (i != -1) { 474 rl.remove(rl.repositories().get(i)); 475 return; 476 } 477 for (RemoteRepository rr : rl.repositories()) { 478 if (rr.location().equals(u)) { 479 rl.remove(rr); 480 return; 481 } 482 } 483 throw new Command.Exception("No repository found for deletion"); 484 } catch (IOException x) { 485 throw new Command.Exception(x); 486 } 487 } 488 } 489 490 static class Refresh extends Command<SimpleLibrary> { 491 protected void go(SimpleLibrary lib) 492 throws Command.Exception 493 { 494 finishArgs(); 495 try { 496 // refresh the module directory 497 lib.refresh(); 498 499 // refresh the repository catalog 500 RemoteRepositoryList rl = lib.repositoryList(); 501 int n = 0; 502 for (RemoteRepository rr : rl.repositories()) { 503 out.format("%s - ", rr.location()); 504 out.flush(); 505 boolean stale 506 = dry ? rr.isCatalogStale() : rr.updateCatalog(force); 507 if (stale) { 508 n++; 509 out.format(dry ? "out of date%n" : "updated%n"); 510 } else { 511 out.format("up to date%n"); 512 } 513 } 514 } catch (IOException x) { 515 throw new Command.Exception(x); 516 } 517 } 518 } 519 520 private static final Map<String,Class<? extends Command<SimpleLibrary>>> commands 521 = new HashMap<>(); 522 523 static { 524 commands.put("add-repo", AddRepo.class); 525 commands.put("config", Config.class); 526 commands.put("create", Create.class); 527 commands.put("del-repo", DelRepo.class); 528 commands.put("dump-class", DumpClass.class); 529 commands.put("dump-config", DumpConfig.class); 530 commands.put("extract", Extract.class); 531 commands.put("id", Identify.class); 532 commands.put("identify", Identify.class); 533 commands.put("install", Install.class); 534 commands.put("list", Commands.ListLibrary.class); 535 commands.put("ls", Commands.ListLibrary.class); 536 commands.put("preinstall", PreInstall.class); 537 commands.put("refresh", Refresh.class); 538 commands.put("reindex", ReIndex.class); 539 commands.put("remove", Remove.class); 540 commands.put("rm", Remove.class); 541 commands.put("repos", Repos.class); 542 } 543 544 private OptionParser parser; 545 546 private static OptionSpec<Integer> repoIndex; // ## 547 private static OptionSpec<File> libPath; 548 private static OptionSpec<File> parentPath; 549 private static OptionSpec<File> nativeLibs; 550 private static OptionSpec<File> nativeCmds; 551 private static OptionSpec<File> configFiles; 552 553 private void usage() { 554 out.format("%n"); 555 out.format("usage: jmod add-repo [-i <index>] URL%n"); 556 out.format(" jmod config [<module-id> ...]%n"); 557 out.format(" jmod create [-L <library>] [-P <parent>]" + 558 " [--natlib <natlib>] [--natcmd <natcmd>] [--config <config>]%n"); 559 out.format(" jmod del-repo URL%n"); 560 out.format(" jmod dump-class <module-id> <class-name> <output-file>%n"); 561 out.format(" jmod dump-config <module-id>%n"); 562 out.format(" jmod extract <module-file> ...%n"); 563 out.format(" jmod identify%n"); 564 out.format(" jmod install [--noverify] [-n] <module-id-query> ...%n"); 565 out.format(" jmod install [--noverify] <module-file> ...%n"); 566 out.format(" jmod install <classes-dir> <module-name> ...%n"); 567 out.format(" jmod list [-v] [-p] [-R] [<module-id-query>]%n"); 568 out.format(" jmod preinstall <classes-dir> <dst-dir> <module-name> ...%n"); 569 out.format(" jmod refresh [-f] [-n] [-v]%n"); 570 out.format(" jmod reindex [<module-id> ...]%n"); 571 out.format(" jmod remove [-f] [-n] [<module-id> ...]%n"); 572 out.format(" jmod repos [-v]%n"); 573 out.format("%n"); 574 try { 575 parser.printHelpOn(out); 576 } catch (IOException x) { 577 throw new AssertionError(x); 578 } 579 out.format("%n"); 580 System.exit(0); 581 } 582 583 public static void run(String [] args) throws OptionException, Command.Exception { 584 new Librarian().exec(args); 585 } 586 587 private void exec(String[] args) throws OptionException, Command.Exception { 588 parser = new OptionParser(); 589 590 // ## Need subcommand-specific option parsing 591 libPath 592 = (parser.acceptsAll(Arrays.asList("L", "library"), 593 "Module-library location" 594 + " (default $JAVA_MODULES)") 595 .withRequiredArg() 596 .describedAs("path") 597 .ofType(File.class)); 598 parentPath 599 = (parser.acceptsAll(Arrays.asList("P", "parent-path"), 600 "Parent module-library location") 601 .withRequiredArg() 602 .describedAs("path") 603 .ofType(File.class)); 604 nativeLibs 605 = (parser.accepts("natlib", "Directory to store native libs") 606 .withRequiredArg() 607 .describedAs("dir") 608 .ofType(File.class)); 609 nativeCmds 610 = (parser.accepts("natcmd", "Directory to store native launchers") 611 .withRequiredArg() 612 .describedAs("dir") 613 .ofType(File.class)); 614 configFiles 615 = (parser.accepts("config", "Directory to store config files") 616 .withRequiredArg() 617 .describedAs("dir") 618 .ofType(File.class)); 619 parser.acceptsAll(Arrays.asList("N", "no-parent"), 620 "Use no parent library when creating"); 621 parser.acceptsAll(Arrays.asList("v", "verbose"), 622 "Enable verbose output"); 623 parser.acceptsAll(Arrays.asList("h", "?", "help"), 624 "Show this help message"); 625 parser.acceptsAll(Arrays.asList("p", "parent"), 626 "Apply operation to parent library, if any"); 627 parser.acceptsAll(Arrays.asList("z", "enable-compression"), 628 "Enable compression of module contents"); 629 repoIndex 630 = (parser.acceptsAll(Arrays.asList("i"), 631 "Repository-list index") 632 .withRequiredArg() 633 .describedAs("index") 634 .ofType(Integer.class)); 635 parser.acceptsAll(Arrays.asList("f", "force"), 636 "Force the requested operation"); 637 parser.acceptsAll(Arrays.asList("n", "dry-run"), 638 "Dry-run the requested operation"); 639 parser.acceptsAll(Arrays.asList("R", "repos"), 640 "List contents of associated repositories"); 641 parser.acceptsAll(Arrays.asList("noverify"), 642 "Do not verify module signatures. " 643 + "Treat as unsigned."); 644 parser.acceptsAll(Arrays.asList("G", "strip-debug"), 645 "Strip debug attributes during installation"); 646 647 if (args.length == 0) 648 usage(); 649 650 OptionSet opts = parser.parse(args); 651 if (opts.has("h")) 652 usage(); 653 List<String> words = opts.nonOptionArguments(); 654 if (words.isEmpty()) 655 usage(); 656 String verb = words.get(0); 657 Class<? extends Command<SimpleLibrary>> cmd = commands.get(verb); 658 if (cmd == null) 659 throw new Command.Exception("%s: unknown command", verb); 660 661 // Every command, except 'create' and 'extract', needs 662 // to have a valid reference to the library 663 SimpleLibrary lib = null; 664 if (!(verb.equals("create") || verb.equals("extract"))) { 665 File lp = libPath(opts); 666 try { 667 lib = SimpleLibrary.open(lp); 668 } catch (FileNotFoundException x) { 669 String msg = null; 670 File f = new File(x.getMessage()); 671 try { 672 f = f.getCanonicalFile(); 673 if (lp.getCanonicalFile().equals(f)) 674 msg = "No such library"; 675 else 676 msg = "Cannot open parent library " + f; 677 } catch (IOException y) { 678 throw new Command.Exception(y); 679 } 680 throw new Command.Exception("%s: %s", lp, msg); 681 } catch (IOException x) { 682 throw new Command.Exception(x); 683 } 684 } 685 try { 686 cmd.newInstance().run(lib, opts); 687 } catch (InstantiationException x) { 688 throw new AssertionError(x); 689 } catch (IllegalAccessException x) { 690 throw new AssertionError(x); 691 } 692 } 693 694 private static File libPath(OptionSet opts) { 695 if (opts.has(libPath)) { 696 return opts.valueOf(libPath); 697 } else { 698 String jm = System.getenv("JAVA_MODULES"); 699 if (jm != null) 700 return new File(jm); 701 else 702 return homeLibrary; 703 } 704 } 705 706 private Librarian() { } 707 708 public static void main(String[] args) { 709 try { 710 run(args); 711 } catch (OptionException x) { 712 err.println(x.getMessage()); 713 System.exit(1); 714 } catch (Command.Exception x) { 715 err.println(x.getMessage()); 716 x.printStackTrace(); 717 System.exit(1); 718 } 719 } 720 721 }