1 /* 2 * Copyright (c) 2009, 2011, 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.util.*; 31 32 import static java.lang.System.out; 33 import static java.lang.System.err; 34 35 import org.openjdk.jigsaw.*; 36 import org.openjdk.jigsaw.SimpleLibrary.StorageOption; 37 import org.openjdk.internal.joptsimple.*; 38 39 /* Interface: 40 41 jpkg [-v] [-L <library>] [-r <resource-dir>] [-i include-dir] \ 42 [-m <module_dir>] [-d <output_dir>] [-c <command>] [-n <name>] \ 43 [-e <e-mail@address>] [-s <short description>] [-l <long description>] \ 44 [-x <extra metadata>] [deb|jmod] <module_name>* 45 46 -v : verbose output 47 -L : library the modules are installed to 48 -i : directory with files to include as part of the package 49 -m : directory with modules to package 50 -d : destination directory to put the package in 51 -c : command name for launcher invoking main class 52 -n : maintainer name 53 -e : maintainer e-mail address 54 -s : short description 55 -l : long description 56 -x : additional metadata - for Debian packages, a file whose 57 contents gets appended to DEBIAN/control 58 --fast : use fastest, rather then best compression 59 */ 60 61 public class Packager { 62 63 private static final JigsawModuleSystem jms 64 = JigsawModuleSystem.instance(); 65 66 private static final boolean jigsawDevMode 67 = System.getenv("JIGSAW_DEV_MODE") != null; 68 69 /** Temp dir for modules to be pre-installed into */ 70 private static File tmp_dst; 71 72 /** Directory where the classes to package are stored on disk. */ 73 private File classes = new File(System.getProperty("user.dir")); 74 75 /** Default destination dir is current directory */ 76 private File destination = new File(System.getProperty("user.dir")); 77 78 /** The default location to install modules to */ 79 private File library = new File("/usr/lib/java/modules"); 80 81 /** The JRE to use in control scripts */ 82 private File javaHome = new File(System.getProperty("java.home")); 83 84 private boolean verbose; 85 86 private boolean fast; 87 88 /** Command launcher for main class */ 89 private String bincmd; 90 91 /** Directory with optional files to include in package */ 92 private File includes; 93 94 /** Name of the maintainer or creator of the package */ 95 private String maintainer_name; 96 97 /** Default name of the maintainer or creator of the package */ 98 private String default_maintainer_name = System.getProperty("user.name"); 99 100 /** E-mail address of the maintainer or creator of the package */ 101 private String maintainer_email; 102 103 /** Default E-mail address of the maintainer or creator of the package */ 104 private String default_maintainer_email = "<generated@by.jpkg>"; 105 106 /** Short description of the package */ 107 private String short_description; 108 109 /** Default short description of the package */ 110 private static String default_short_description = "Generated by jpkg"; 111 112 /** Long description of the package */ 113 private String long_description; 114 115 /** Default long description of the package */ 116 private static String default_long_description 117 = " This package was automatically generated from the corresponding Jigsaw module.\n" 118 + " Information on Jigsaw is available at http://openjdk.java.net/projects/jigsaw."; 119 120 /** Packaging-system dependent additional metadata */ 121 private File extra_metadata; 122 123 private File natlibs; 124 private File natcmds; 125 private File config_dir; 126 127 // Installed size 128 private Integer installedSize = null; 129 130 // Platform boot module 131 private static final String BOOT_MODULE = "jdk.base"; 132 133 private static void createTempWorkDir() 134 throws Command.Exception 135 { 136 try { 137 tmp_dst = File.createTempFile("jigsaw", null); 138 Files.delete(tmp_dst); 139 Files.mkdirs(tmp_dst, "jigsaw temp directory"); 140 } catch (IOException x) { 141 throw new Command.Exception(x); 142 } 143 } 144 145 class Contents extends Command<SimpleLibrary> { 146 protected void go(SimpleLibrary lib) 147 throws Command.Exception 148 { 149 String jmodName = takeArg(); 150 finishArgs(); 151 try (FileInputStream fis = new FileInputStream(jmodName); 152 DataInputStream dis = new DataInputStream(fis); 153 ModuleFile.Reader reader = new ModuleFile.Reader(dis)) { 154 List<String> contents = reader.getContents(); 155 for (String fn : contents) 156 out.format(" %s%n", fn); 157 } catch (IOException x) { 158 throw new Command.Exception(x); 159 } 160 } 161 } 162 163 class Show extends Command<SimpleLibrary> { 164 protected void go(SimpleLibrary lib) 165 throws Command.Exception 166 { 167 String jmodName = takeArg(); 168 finishArgs(); 169 try (FileInputStream fis = new FileInputStream(jmodName); 170 DataInputStream dis = new DataInputStream(fis); 171 ModuleFile.Reader reader = new ModuleFile.Reader(dis)) { 172 ModuleInfo mi = jms.parseModuleInfo(reader.readStart()); 173 Commands.formatModule(out, mi, verbose); 174 } catch (IOException x) { 175 throw new Command.Exception(x); 176 } 177 } 178 } 179 180 class Jmod extends Command<SimpleLibrary> { 181 private String getModuleVersion(String modulename) 182 throws Command.Exception 183 { 184 Manifest mf = Manifest.create(modulename, classes); 185 ModuleInfo info = getModuleInfo(mf); 186 return info.id().version().toString(); 187 } 188 189 protected void go(SimpleLibrary lib) 190 throws Command.Exception 191 { 192 while (hasArg()) { 193 File outputfile = null; 194 try { 195 String modulename = takeArg(); 196 String version = getModuleVersion(modulename); 197 String outputfilename = modulename + '@'+ version + ".jmod"; 198 if (verbose) 199 System.out.println("Creating module file " 200 + outputfilename + " for " 201 + modulename); 202 outputfile = new File(destination, outputfilename); 203 ModuleFileWriter writer 204 = new ModuleFileWriter(outputfile, (fast || jigsawDevMode)); 205 writer.writeModule(classes, natlibs, natcmds, config_dir); 206 } 207 catch (IOException x) { 208 if (outputfile != null && !outputfile.delete()) { 209 Throwable t 210 = new IOException(outputfile + 211 ": Cannot delete").initCause(x); 212 throw new Command.Exception((IOException)t); 213 } 214 throw new Command.Exception(x); 215 } 216 } 217 finishArgs(); 218 } 219 } 220 221 static private ModuleInfo getModuleInfo(Manifest mf) 222 throws Command.Exception 223 { 224 try { 225 final String MINFO_CLASS = "module-info.class"; 226 File classdir = mf.classes().get(0); 227 File mif = new File(classdir , MINFO_CLASS); 228 if (!mif.exists()) 229 mif = new File(classdir, 230 mf.module() + File.separator + MINFO_CLASS); 231 byte[] bs = Files.load(mif); 232 return jms.parseModuleInfo(bs); 233 } catch (IOException x) { 234 throw new Command.Exception(x); 235 } 236 } 237 238 class Deb extends Command<SimpleLibrary> { 239 private File tmp_module_dst; 240 private File tmp_metadata_dst; 241 242 private void createMetaDataDir() 243 throws Command.Exception 244 { 245 tmp_metadata_dst = new File(tmp_dst, "DEBIAN"); 246 if (!tmp_metadata_dst.mkdirs()) 247 throw new Command.Exception("Couldn't create meta data directory " 248 + tmp_metadata_dst); 249 } 250 251 private void createModuleLibraryWorkDir() 252 throws Command.Exception 253 { 254 tmp_module_dst = new File(tmp_dst, library.toString()); 255 256 if (!tmp_module_dst.mkdirs()) 257 throw new Command.Exception("Couldn't create module destination directory " 258 + tmp_module_dst); 259 260 // Delete the modules dir to make SimpleLibrary happy, 261 // it wants to create the jigsaw metadata and the directory 262 // along with it. 263 if (!tmp_module_dst.delete()) 264 throw new Command.Exception("Can't delete " + tmp_module_dst); 265 } 266 267 private void preinstallModule(Manifest manifest) 268 throws Command.Exception 269 { 270 try { 271 272 createModuleLibraryWorkDir(); 273 Set<StorageOption> opts = Collections.emptySet(); 274 if (BOOT_MODULE.equals(manifest.module())) { 275 // Create a module library to the boot module package 276 SimpleLibrary.create(tmp_module_dst, opts).installFromManifests(Collections.singleton(manifest)); 277 } else { 278 // We need to create a throwaway SimpleLibrary to work with it, 279 // As there is no static preInstall method 280 File scratchlib_dst = new File(tmp_dst, "scratchlib"); 281 SimpleLibrary.create(scratchlib_dst, opts).preInstall(manifest, tmp_module_dst); 282 Files.deleteTree(scratchlib_dst); 283 } 284 } catch (IOException | ConfigurationException x) { 285 throw new Command.Exception(x); 286 } 287 } 288 289 private String translateVersion(String v) { 290 // Debian version format: [epoch:]upstream_version[-debian_revision] 291 // upstream_version may contain only alphanumerics and '.', '+', '-', '~' 292 // There is no epoch, ':' not allowed. 293 // 294 if (!v.matches("[A-Za-z0-9\\+-~\\.]+")) 295 throw new AssertionError("Invalid debian version format: " + v); 296 return v; 297 } 298 299 private String computeDependencies(ModuleInfo info) 300 { 301 StringBuilder deps = new StringBuilder(); 302 303 for (ViewDependence d : info.requiresModules()) { 304 if (d.modifiers().contains(ViewDependence.Modifier.OPTIONAL)) 305 continue; // skip optional dependency 306 307 deps.append(", ") 308 .append(d.query().name()) 309 .append(' ') 310 .append(d.query().versionQuery() != null ? 311 "(" + translateVersion(d.query().versionQuery().toString()) + ")" : 312 ""); 313 } 314 315 return deps.length() > 0 ? 316 deps.substring(2) : 317 ""; 318 } 319 320 private String computeProvides(ModuleInfo info) 321 { 322 StringBuilder deps = new StringBuilder(); 323 324 for (ModuleId id : info.defaultView().aliases()) 325 deps.append(", ") 326 .append(id.name()); 327 328 return deps.length() > 0 ? 329 deps.substring(2) : 330 ""; 331 } 332 333 /** Long descriptions in Debian control files must start the line with a space. */ 334 private String formatDescription(String description) 335 { 336 return " " + description.replace("\n", "\n "); 337 } 338 339 /** 340 * The ModuleInfo metadata gets translated into the package in the following way: 341 * 342 * package name is module's name 343 * package version is module's version 344 * package dependecies are module's required dependencies 345 * 346 * @param manifest The module's manifest 347 */ 348 private void writeMetaData(Manifest manifest) 349 throws Command.Exception 350 { 351 boolean bootmodule = BOOT_MODULE.equals(manifest.module()); 352 createMetaDataDir(); 353 ModuleInfo info = getModuleInfo(manifest); 354 355 // Create the control file, and fill in dependency and provides info 356 try (PrintStream control 357 = new PrintStream(new File(tmp_metadata_dst, "control"))) { 358 control.format("Package: %s%n" 359 + "Version: %s%n" 360 + "Section: misc%n" 361 + "Priority: optional%n" 362 + "Architecture: " 363 + System.getProperty("os.arch") + "%n", 364 info.id().name(), 365 translateVersion(info.id().version().toString())); 366 367 // If either maintainer name or e-mail is declared as parameter, use it 368 if (null != maintainer_name || null != maintainer_email) { 369 control.format("Maintainer: %s %s%n", 370 maintainer_name == null? default_maintainer_name : maintainer_name, 371 maintainer_email == null? default_maintainer_email : maintainer_email); 372 } 373 // Otherwise, if there is no extra metadata, use defaults 374 else if (null == extra_metadata) { 375 control.format("Maintainer: %s %s%n", 376 default_maintainer_name, 377 default_maintainer_email); 378 } 379 380 // If either short or long description is declared as parameter, use it 381 if (null != short_description || null != long_description) { 382 control.format("Description: %s%n" 383 + "%s%n", 384 short_description == null? default_short_description : short_description, 385 long_description == null? default_long_description : formatDescription(long_description)); 386 } 387 // Otherwise, if there is no extra metadata, use defaults 388 else if (null == extra_metadata) { 389 control.format("Description: %s%n" 390 + "%s%n", 391 default_short_description, 392 default_long_description); 393 } 394 395 if (!bootmodule && !info.requiresModules().isEmpty()) 396 control.format("Depends: %s\n", computeDependencies(info)); 397 if (!info.defaultView().aliases().isEmpty()) 398 control.format("Provides: %s\n", computeProvides(info)); 399 if (null != extra_metadata) 400 control.format("%s\n", new String(Files.load(extra_metadata))); 401 if (installedSize != null) 402 control.format("Installed-Size: %d\n", installedSize); 403 404 // All jpkg generated packages (except boot) depend on a boot 405 // package being installed at time of installation to be able to run jmod. 406 if (!bootmodule) 407 control.format("Pre-Depends: %s\n", BOOT_MODULE); 408 409 410 // Generate the launcher script, if a main class exists 411 if (!bootmodule && info.defaultView().mainClass() != null) { 412 // If no command name is given, use module name 413 if (null == bincmd) 414 bincmd = info.id().name(); 415 416 String BINDIR = "/usr/bin"; 417 File bin = new File(tmp_dst + BINDIR); 418 if (!bin.mkdirs()) 419 throw new IOException("Couldn't create " + tmp_dst + BINDIR); 420 421 File cmd = new File(bin, bincmd); 422 try (PrintStream launcher = new PrintStream(cmd)) { 423 String java_launcher = System.getProperty("java.home") 424 + "/bin/java"; 425 if (! (new File(java_launcher)).exists()) 426 throw new IOException("Couldn't find java launcher " 427 + "at " + java_launcher); 428 429 launcher.format("#!/bin/sh\n" + 430 "set -e\n" + 431 "exec %s -ea -L %s -m %s \"$@\"\n", 432 java_launcher, library, 433 info.id().name()); 434 } 435 cmd.setExecutable(true, false); 436 } 437 438 String installedJMod = javaHome.getPath() + "/bin/jmod"; 439 440 // Before a package is installed, 441 // check if the jigsaw module library needs to be created first 442 if (!bootmodule) { 443 File preinst = new File(tmp_metadata_dst, "preinst"); 444 try (PrintStream pis = new PrintStream(preinst)) { 445 pis.format("#!/bin/sh\n" + 446 "set -e\n" + 447 "if [ ! -f %1$s/%%jigsaw-library ]\n" + 448 "then\n" + 449 " %2$s -L %1$s create\n" + 450 "fi\n", 451 library, installedJMod); 452 } 453 preinst.setExecutable(true, false); 454 455 // After a package is installed, 456 // reconfigure the jigsaw module library for the module 457 File postinst = new File(tmp_metadata_dst, "postinst"); 458 try (PrintStream pis = new PrintStream(postinst)) { 459 pis.format("#!/bin/sh\n" + 460 "set -e\n" + 461 "if [ -f %s/%s.pack ] ; then\n" + 462 " for i in %s/*.pack ; do\n" + 463 " %s/bin/unpack200 -r $i %s/tmp.jar\n" + 464 " unzip -o -q -d / %s/tmp.jar\n" + 465 " rm %s/tmp.jar\n" + 466 " done\n" + 467 "fi\n" + 468 "%s -L %s config %s\n", 469 library, manifest.module(), 470 library, 471 javaHome, library, 472 library, 473 library, 474 installedJMod, library, info.id()); 475 } 476 postinst.setExecutable(true, false); 477 } 478 479 // Before a package is removed, 480 // remove the generated jigsaw module configuration 481 File prerm = new File(tmp_metadata_dst, "prerm"); 482 try (PrintStream pis = new PrintStream(prerm)) { 483 pis.format("#!/bin/sh\n" + 484 "set -e\n" + 485 // Delete unpacked class files. 486 "if [ -e %1$s/%2$s/%3$s ]\n" + 487 "then\n" + 488 " rm -rf %1$s/%2$s/%3$s\n" + 489 "fi\n" + 490 // Delete module library directory if it's empty. 491 "find %1$s/%2$s/ -maxdepth 0 -type d -empty -delete\n", 492 library, info.id().name(), info.id().version()); 493 } 494 prerm.setExecutable(true, false); 495 } catch (IOException x) { 496 throw new Command.Exception(x); 497 } 498 } 499 500 private void buildPackage() 501 throws Command.Exception 502 { 503 String dashz = "-z" + ((fast || jigsawDevMode) ? 1 : 9); 504 try { 505 Process build 506 = (new ProcessBuilder("fakeroot", "dpkg-deb", dashz, "-Zlzma", "--build", 507 tmp_dst.toString(), destination.toString())).start(); 508 509 try (BufferedReader br = new BufferedReader( 510 new InputStreamReader(build.getErrorStream()))) { 511 512 if (0 != build.waitFor()) 513 throw new Command.Exception("Failed to create package " 514 + br.readLine()); 515 } 516 } catch (IOException | InterruptedException x) { 517 throw new Command.Exception(x); 518 } 519 } 520 521 private void cleanup() 522 throws Command.Exception 523 { 524 try { 525 Files.deleteTree(tmp_dst); 526 } catch (IOException x) { 527 throw new Command.Exception(x); 528 } 529 } 530 531 private File getPackFile(Manifest manifest) 532 { 533 return new File(tmp_module_dst, manifest.module() + ".pack"); 534 } 535 536 private void packModule(Manifest manifest) 537 throws Command.Exception 538 { 539 // Can't pack the boot module since unpack200 tool may not exist 540 if (BOOT_MODULE.equals(manifest.module())) 541 return; 542 543 try { 544 File tmp_jar = new File(tmp_dst, "module.jar"); 545 546 // Create temporary jar file with module classes and ressources 547 // Store entries, as we'll just need it for pack200 to read from 548 // and then delete it. No point in wasting time. 549 Process jar 550 = (new ProcessBuilder("jar", "c0f", tmp_jar.toString(), 551 "-C", tmp_dst.toString(), 552 library.toString())).start(); 553 try (BufferedReader br = new BufferedReader( 554 new InputStreamReader(jar.getErrorStream()))) { 555 if (0 != jar.waitFor()) 556 throw new Command.Exception("Failed to jar module " 557 + br.readLine()); 558 } 559 560 // Remove redundant META-INF directory from jar file, 561 // so that it doesn't pollute the filesystem hierarchy 562 // when we unpack the files again. 563 Process zip 564 = (new ProcessBuilder("zip", tmp_jar.toString(), 565 "-d", "META-INF/*")).start(); 566 try (BufferedReader br = new BufferedReader( 567 new InputStreamReader(zip.getErrorStream()))) { 568 if (0 != zip.waitFor()) 569 throw new Command.Exception("Failed to remove META-INF " 570 + "directory from jar " 571 + "module " 572 + br.readLine()); 573 } 574 // Compress the jar file with pack200. 575 String dashE = "-E" + ((fast || jigsawDevMode) ? "0" : "9"); 576 Process pack200 577 = (new ProcessBuilder("pack200", dashE, "-S-1", "--no-gzip", 578 getPackFile(manifest).toString(), 579 tmp_jar.toString())).start(); 580 try (BufferedReader br = new BufferedReader( 581 new InputStreamReader(pack200.getErrorStream()))) { 582 if (0 != pack200.waitFor()) 583 throw new Command.Exception("Failed to pack200 module " 584 + br.readLine()); 585 } 586 587 if (! tmp_jar.delete()) 588 throw new Command.Exception("Failed to delete temporary file " + tmp_jar); 589 Files.deleteTree(new File(tmp_module_dst, manifest.module().toString())); 590 } catch (IOException | InterruptedException x) { 591 throw new Command.Exception(x); 592 } 593 } 594 595 protected void go(SimpleLibrary lib) 596 throws Command.Exception 597 { 598 List<Manifest> mfs = new ArrayList<>(); 599 while (hasArg()) { 600 mfs.add(Manifest.create(takeArg(), classes)); 601 } 602 finishArgs(); 603 604 for (Manifest manifest : mfs) { 605 606 if (verbose) 607 System.out.println("Creating binary Debian package for " + manifest.module()); 608 609 createTempWorkDir(); 610 if (null != includes) { 611 try { 612 Files.copyTree(includes, tmp_dst); 613 } catch (IOException x) { 614 throw new Command.Exception(x); 615 } 616 } 617 preinstallModule(manifest); 618 packModule(manifest); 619 writeMetaData(manifest); 620 buildPackage(); 621 cleanup(); 622 } 623 } 624 } 625 626 private static final Map<String,Class<? extends Command<SimpleLibrary>>> commands 627 = new HashMap<>(); 628 629 static { 630 commands.put("contents", Contents.class); 631 commands.put("deb", Deb.class); 632 commands.put("jmod", Jmod.class); 633 commands.put("show", Show.class); 634 } 635 636 private OptionParser parser; 637 638 private static OptionSpec<File> resourcePath; // ## 639 640 private void usage() { 641 out.format("%n"); 642 out.format("usage: jpkg contents <module-file>%n"); 643 out.format(" jpkg show [-v] <module-file>%n"); 644 out.format(" jpkg [-v] [-L <library>] [-r <resource-dir>] "); 645 out.format( "[-i <include-dir>] [-m <module-dir>] "); 646 out.format( "[-d <output-dir>] [-c <command>] [-n <name>] "); 647 out.format( "[-e <e-mail@address>] [-s <short description>] "); 648 out.format( "[-l <long description>] [-x <extra metadata>] "); 649 out.format( "[deb|jmod] <module-name>%n"); 650 out.format("%n"); 651 try { 652 parser.printHelpOn(out); 653 } catch (IOException x) { 654 throw new AssertionError(x); 655 } 656 out.format("%n"); 657 } 658 659 public static void run(String[] args) 660 throws OptionException, Command.Exception { 661 662 new Packager().exec(args); 663 } 664 665 private void exec(String[] args) 666 throws OptionException, Command.Exception { 667 668 parser = new OptionParser(); 669 670 OptionSpec<File> libPath 671 = (parser.acceptsAll(Arrays.asList("L", "library"), 672 "Module-library location" 673 + " (default $JAVA_MODULES)") 674 .withRequiredArg() 675 .describedAs("path") 676 .ofType(File.class)); 677 678 OptionSpec<File> launcherPath 679 = (parser.acceptsAll(Arrays.asList("c", "command"), 680 "Launcher command name") 681 .withRequiredArg() 682 .describedAs("path") 683 .ofType(File.class)); 684 685 parser.acceptsAll(Arrays.asList("v", "verbose"), 686 "Enable verbose output"); 687 parser.acceptsAll(Arrays.asList("h", "?", "help"), 688 "Show this help message"); 689 OptionSpec<File> destinationPath 690 = (parser.acceptsAll(Arrays.asList("d", "dest-dir"), 691 "Destination directory for packages") 692 .withRequiredArg() 693 .describedAs("path") 694 .ofType(File.class)); 695 696 OptionSpec<File> modulePath 697 = (parser.acceptsAll(Arrays.asList("m", "module-dir"), 698 "Source directory for modules") 699 .withRequiredArg() 700 .describedAs("path") 701 .ofType(File.class)); 702 703 OptionSpec<File> includePath 704 = (parser.acceptsAll(Arrays.asList("i", "include"), 705 "Directory of files to be included") 706 .withRequiredArg() 707 .describedAs("path") 708 .ofType(File.class)); 709 710 OptionSpec<String> maintainerName 711 = (parser.acceptsAll(Arrays.asList("n", "name"), 712 "Package maintainer's name") 713 .withRequiredArg() 714 .describedAs("name") 715 .ofType(String.class)); 716 717 OptionSpec<String> maintainerEmail 718 = (parser.acceptsAll(Arrays.asList("e", "email"), 719 "Package maintainer's e-mail address") 720 .withRequiredArg() 721 .describedAs("e-mail@address") 722 .ofType(String.class)); 723 724 OptionSpec<String> shortDescription 725 = (parser.acceptsAll(Arrays.asList("s", "short"), 726 "Short description of the package") 727 .withRequiredArg() 728 .describedAs("description") 729 .ofType(String.class)); 730 731 OptionSpec<String> longDescription 732 = (parser.acceptsAll(Arrays.asList("l", "long"), 733 "Long description of the package") 734 .withRequiredArg() 735 .describedAs("description") 736 .ofType(String.class)); 737 738 parser.acceptsAll(Arrays.asList("fast"), 739 "Use fastest compression"); 740 741 OptionSpec<Integer> isize 742 = (parser.acceptsAll(Arrays.asList("installed-size"), 743 "Installed size in kilobytes") 744 .withRequiredArg() 745 .describedAs("size") 746 .ofType(Integer.class)); 747 748 OptionSpec<File> javaHomePath 749 = (parser.acceptsAll(Arrays.asList("java-home"), 750 "Alternate $JAVA_HOME location") 751 .withRequiredArg() 752 .describedAs("dir") 753 .ofType(File.class)); 754 755 OptionSpec<File> extraMetadata 756 = (parser.acceptsAll(Arrays.asList("x", "extra"), 757 "File or directory with additional metadata," 758 + " depending on the packaging system") 759 .withRequiredArg() 760 .describedAs("dir") 761 .ofType(File.class)); 762 763 OptionSpec<File> nativeLibs 764 = (parser.accepts("natlib", "Directory with native libs") 765 .withRequiredArg() 766 .describedAs("dir") 767 .ofType(File.class)); 768 769 OptionSpec<File> nativeCmds 770 = (parser.accepts("natcmd", "Directory with native launchers") 771 .withRequiredArg() 772 .describedAs("dir") 773 .ofType(File.class)); 774 775 OptionSpec<File> config 776 = (parser.accepts("config", "Directory with configuration") 777 .withRequiredArg() 778 .describedAs("dir") 779 .ofType(File.class)); 780 781 if (args.length == 0) { 782 usage(); 783 return; 784 } 785 786 OptionSet opts = parser.parse(args); 787 if (opts.has("h")) { 788 usage(); 789 return; 790 } 791 if (opts.has("v")) 792 verbose = true; 793 if (opts.has("fast")) 794 fast = true; 795 List<String> words = opts.nonOptionArguments(); 796 if (words.isEmpty()) { 797 usage(); 798 return; 799 } 800 String verb = words.get(0); 801 Class<? extends Command<SimpleLibrary>> cmd = commands.get(verb); 802 if (cmd == null) 803 throw new Command.Exception("%s: unknown command", verb); 804 if (opts.has(launcherPath)) 805 bincmd = opts.valueOf(launcherPath).toString(); 806 if (opts.has(destinationPath)) 807 destination = opts.valueOf(destinationPath); 808 if (opts.has(modulePath)) { 809 classes = opts.valueOf(modulePath); 810 checkPathArgument(classes, "Module"); 811 } 812 if (opts.has(libPath)) { 813 library = opts.valueOf(libPath); 814 } else { 815 String jm = System.getenv("JAVA_MODULES"); 816 if (jm != null) 817 library = new File(jm); 818 } 819 if (opts.has(includePath)) 820 includes = opts.valueOf(includePath); 821 if (opts.has(javaHomePath)) 822 javaHome = opts.valueOf(javaHomePath); 823 if (opts.has(maintainerName)) 824 maintainer_name = opts.valueOf(maintainerName); 825 if (opts.has(maintainerEmail)) { 826 maintainer_email = opts.valueOf(maintainerEmail); 827 // Add missing e-mail quotes if necessary 828 maintainer_email 829 = (maintainer_email.startsWith("<") ? "" : "<") 830 + maintainer_email 831 + (maintainer_email.endsWith(">") ? "" : ">") ; 832 } 833 if (opts.has(shortDescription)) 834 short_description = opts.valueOf(shortDescription); 835 if (opts.has(longDescription)) 836 long_description = opts.valueOf(longDescription); 837 if (opts.has(extraMetadata)) 838 extra_metadata = opts.valueOf(extraMetadata); 839 if (opts.has(nativeLibs)) { 840 natlibs = opts.valueOf(nativeLibs); 841 checkPathArgument(natlibs, "Native library"); 842 } 843 if (opts.has(nativeCmds)) { 844 natcmds = opts.valueOf(nativeCmds); 845 checkPathArgument(natcmds, "Native command"); 846 } 847 if (opts.has(config)) { 848 config_dir = opts.valueOf(config); 849 checkPathArgument(config_dir, "Config"); 850 } 851 if (opts.has(isize)) 852 installedSize = opts.valueOf(isize); 853 854 if (cmd == Deb.class) 855 (new Deb()).run(null, opts); 856 else if (cmd == Jmod.class) 857 (new Jmod()).run(null, opts); 858 else if (cmd == Contents.class) 859 (new Contents()).run(null, opts); 860 else if (cmd == Show.class) 861 (new Show()).run(null, opts); 862 } 863 864 /** 865 * Helper method to check if a path exists before using it further. 866 * 867 * @param path to check 868 * @param type of path being checked 869 * 870 * @throws Command.Exception if path doesn't exist 871 */ 872 private static void checkIfPathExists(File path, String type) 873 throws Command.Exception { 874 875 if (!path.exists()) 876 throw new Command.Exception("%s path doesn't exist: %s", 877 type, path); 878 } 879 880 /** 881 * Helper method to check if a path is readable before using it further. 882 * 883 * @param path to check 884 * @param type of path being checked 885 * 886 * @throws Command.Exception if path isn't readable 887 */ 888 private static void checkIfPathIsReadable(File path, String type) 889 throws Command.Exception { 890 891 if (!path.canRead()) 892 throw new Command.Exception("%s path isn't readable: %s", 893 type, path); 894 } 895 896 /** 897 * Helper method to check if a path is a directory before using it further. 898 * 899 * @param path to check 900 * @param type of path being checked 901 * 902 * @throws Command.Exception if path is not a directory 903 */ 904 private static void checkIfPathIsDirectory(File path, String type) 905 throws Command.Exception { 906 907 if (!path.isDirectory()) 908 throw new Command.Exception("%s path is not a directory: %s", 909 type, path); 910 } 911 912 /** 913 * Helper method to check if a path argument is valid. 914 * 915 * @param path to check 916 * @param type of path being checked 917 * 918 * @throws Command.Exception if path is not a directory 919 */ 920 private static void checkPathArgument(File path, String type) 921 throws Command.Exception { 922 923 checkIfPathExists(path, type); 924 checkIfPathIsReadable(path, type); 925 checkIfPathIsDirectory(path, type); 926 } 927 928 private Packager() { } 929 930 public static void main(String[] args) throws Exception { 931 try { 932 run(args); 933 } catch (OptionException | Command.Exception x) { 934 err.println(x.getMessage()); 935 System.exit(1); 936 } 937 } 938 939 } --- EOF ---