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