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