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