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