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