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