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