< prev index next >

src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java

Print this page


   1 /*
   2  * Copyright (c) 2015, 2016, 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


  41 import java.lang.module.ModuleDescriptor.Exports;
  42 import java.lang.module.ModuleDescriptor.Opens;
  43 import java.lang.module.ModuleDescriptor.Provides;
  44 import java.lang.module.ModuleDescriptor.Requires;
  45 import java.lang.module.ModuleDescriptor.Version;
  46 import java.lang.module.ResolutionException;
  47 import java.lang.module.ResolvedModule;
  48 import java.net.URI;
  49 import java.nio.file.FileSystems;
  50 import java.nio.file.FileVisitOption;
  51 import java.nio.file.FileVisitResult;
  52 import java.nio.file.Files;
  53 import java.nio.file.InvalidPathException;
  54 import java.nio.file.Path;
  55 import java.nio.file.PathMatcher;
  56 import java.nio.file.Paths;
  57 import java.nio.file.SimpleFileVisitor;
  58 import java.nio.file.StandardCopyOption;
  59 import java.nio.file.attribute.BasicFileAttributes;
  60 import java.text.MessageFormat;
  61 import java.util.ArrayDeque;
  62 import java.util.ArrayList;
  63 import java.util.Collection;
  64 import java.util.Collections;
  65 import java.util.Comparator;
  66 import java.util.Deque;
  67 import java.util.HashMap;
  68 import java.util.HashSet;
  69 import java.util.LinkedHashMap;
  70 import java.util.List;
  71 import java.util.Locale;
  72 import java.util.Map;
  73 import java.util.MissingResourceException;
  74 import java.util.Optional;
  75 import java.util.ResourceBundle;
  76 import java.util.Set;
  77 import java.util.TreeSet;
  78 import java.util.function.Consumer;
  79 import java.util.function.Function;
  80 import java.util.function.Predicate;
  81 import java.util.function.Supplier;
  82 import java.util.jar.JarEntry;
  83 import java.util.jar.JarFile;
  84 import java.util.jar.JarOutputStream;
  85 import java.util.stream.Collectors;
  86 import java.util.regex.Pattern;
  87 import java.util.regex.PatternSyntaxException;
  88 import java.util.zip.ZipEntry;
  89 import java.util.zip.ZipException;
  90 import java.util.zip.ZipFile;
  91 
  92 import jdk.internal.jmod.JmodFile;
  93 import jdk.internal.jmod.JmodFile.Section;
  94 import jdk.internal.joptsimple.BuiltinHelpFormatter;
  95 import jdk.internal.joptsimple.NonOptionArgumentSpec;
  96 import jdk.internal.joptsimple.OptionDescriptor;
  97 import jdk.internal.joptsimple.OptionException;
  98 import jdk.internal.joptsimple.OptionParser;
  99 import jdk.internal.joptsimple.OptionSet;
 100 import jdk.internal.joptsimple.OptionSpec;
 101 import jdk.internal.joptsimple.ValueConverter;
 102 import jdk.internal.loader.ResourceHelper;
 103 import jdk.internal.module.ModuleHashes;

 104 import jdk.internal.module.ModuleInfo;
 105 import jdk.internal.module.ModuleInfoExtender;
 106 import jdk.internal.module.ModulePath;
 107 import jdk.internal.module.ModuleResolution;
 108 import jdk.tools.jlink.internal.Utils;
 109 
 110 import static java.util.stream.Collectors.joining;
 111 
 112 /**
 113  * Implementation for the jmod tool.
 114  */
 115 public class JmodTask {
 116 
 117     static class CommandException extends RuntimeException {
 118         private static final long serialVersionUID = 0L;
 119         boolean showUsage;
 120 
 121         CommandException(String key, Object... args) {
 122             super(getMessageOrKey(key, args));
 123         }


 269                     int index = name.lastIndexOf("/");
 270                     if (index != -1) {
 271                         Path p = dir.resolve(name.substring(0, index));
 272                         if (Files.notExists(p))
 273                             Files.createDirectories(p);
 274                     }
 275 
 276                     try (OutputStream os = Files.newOutputStream(dir.resolve(name))) {
 277                         jf.getInputStream(e).transferTo(os);
 278                     }
 279                 } catch (IOException x) {
 280                     throw new UncheckedIOException(x);
 281                 }
 282             });
 283 
 284             return true;
 285         }
 286     }
 287 
 288     private boolean hashModules() {
 289         return new Hasher(options.moduleFinder).run();




















 290     }
 291 
 292     private boolean describe() throws IOException {
 293         try (JmodFile jf = new JmodFile(options.jmodFile)) {
 294             try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
 295                 ModuleInfo.Attributes attrs = ModuleInfo.read(in, null);
 296                 printModuleDescriptor(attrs.descriptor(), attrs.recordedHashes());
 297                 return true;
 298             } catch (IOException e) {
 299                 throw new CommandException("err.module.descriptor.not.found");
 300             }
 301         }
 302     }
 303 
 304     static <T> String toString(Collection<T> c) {
 305         if (c.isEmpty()) { return ""; }
 306         return c.stream().map(e -> e.toString().toLowerCase(Locale.ROOT))
 307                   .collect(joining(" "));
 308     }
 309 


 360                              .append(toHex(hashes.hashFor(mod))));
 361         }
 362 
 363         out.println(sb.toString());
 364     }
 365 
 366     private String toHex(byte[] ba) {
 367         StringBuilder sb = new StringBuilder(ba.length);
 368         for (byte b: ba) {
 369             sb.append(String.format("%02x", b & 0xff));
 370         }
 371         return sb.toString();
 372     }
 373 
 374     private boolean create() throws IOException {
 375         JmodFileWriter jmod = new JmodFileWriter();
 376 
 377         // create jmod with temporary name to avoid it being examined
 378         // when scanning the module path
 379         Path target = options.jmodFile;
 380         Path tempTarget = target.resolveSibling(target.getFileName() + ".tmp");

 381         try {
 382             try (JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget)) {
 383                 jmod.write(jos);
 384             }
 385             Files.move(tempTarget, target);
 386         } catch (Exception e) {
 387             if (Files.exists(tempTarget)) {
 388                 try {
 389                     Files.delete(tempTarget);
 390                 } catch (IOException ioe) {
 391                     e.addSuppressed(ioe);
 392                 }
 393             }
 394             throw e;
 395         }
 396         return true;
 397     }
 398 
 399     private class JmodFileWriter {
 400         final List<Path> cmds = options.cmds;
 401         final List<Path> libs = options.libs;
 402         final List<Path> configs = options.configs;
 403         final List<Path> classpath = options.classpath;
 404         final List<Path> headerFiles = options.headerFiles;
 405         final List<Path> manPages = options.manPages;
 406         final List<Path> legalNotices = options.legalNotices;
 407 
 408         final Version moduleVersion = options.moduleVersion;
 409         final String mainClass = options.mainClass;
 410         final String osName = options.osName;
 411         final String osArch = options.osArch;
 412         final String osVersion = options.osVersion;
 413         final List<PathMatcher> excludes = options.excludes;
 414         final Hasher hasher = hasher();
 415         final ModuleResolution moduleResolution = options.moduleResolution;
 416 
 417         JmodFileWriter() { }
 418 
 419         /**
 420          * Writes the jmod to the given output stream.
 421          */
 422         void write(JmodOutputStream out) throws IOException {
 423             // module-info.class
 424             writeModuleInfo(out, findPackages(classpath));
 425 
 426             // classes
 427             processClasses(out, classpath);
 428 
 429             processSection(out, Section.CONFIG, configs);
 430             processSection(out, Section.HEADER_FILES, headerFiles);
 431             processSection(out, Section.LEGAL_NOTICES, legalNotices);
 432             processSection(out, Section.MAN_PAGES, manPages);
 433             processSection(out, Section.NATIVE_CMDS, cmds);
 434             processSection(out, Section.NATIVE_LIBS, libs);


 497                 ModuleInfoExtender extender = ModuleInfoExtender.newExtender(in);
 498 
 499                 // Add (or replace) the Packages attribute
 500                 if (packages != null) {
 501                     validatePackages(descriptor, packages);
 502                     extender.packages(packages);
 503                 }
 504 
 505                 // --main-class
 506                 if (mainClass != null)
 507                     extender.mainClass(mainClass);
 508 
 509                 // --os-name, --os-arch, --os-version
 510                 if (osName != null || osArch != null || osVersion != null)
 511                     extender.targetPlatform(osName, osArch, osVersion);
 512 
 513                 // --module-version
 514                 if (moduleVersion != null)
 515                     extender.version(moduleVersion);
 516 
 517                 if (hasher != null) {
 518                     ModuleHashes moduleHashes = hasher.computeHashes(descriptor.name());









 519                     if (moduleHashes != null) {
 520                         extender.hashes(moduleHashes);
 521                     } else {
 522                         warning("warn.no.module.hashes", descriptor.name());
 523                     }
 524                 }
 525 
 526                 if (moduleResolution != null && moduleResolution.value() != 0) {
 527                     extender.moduleResolution(moduleResolution);
 528                 }
 529 
 530                 // write the (possibly extended or modified) module-info.class
 531                 out.writeEntry(extender.toByteArray(), Section.CLASSES, MODULE_INFO);
 532             }
 533         }
 534 
 535         private void validatePackages(ModuleDescriptor descriptor, Set<String> packages) {
 536             Set<String> nonExistPackages = new TreeSet<>();
 537             descriptor.exports().stream()
 538                 .map(Exports::source)


 540                 .forEach(nonExistPackages::add);
 541 
 542             descriptor.opens().stream()
 543                 .map(Opens::source)
 544                 .filter(pn -> !packages.contains(pn))
 545                 .forEach(nonExistPackages::add);
 546 
 547             if (!nonExistPackages.isEmpty()) {
 548                 throw new CommandException("err.missing.export.or.open.packages",
 549                     descriptor.name(), nonExistPackages);
 550             }
 551         }
 552 
 553         /*
 554          * Hasher resolves a module graph using the --hash-modules PATTERN
 555          * as the roots.
 556          *
 557          * The jmod file is being created and does not exist in the
 558          * given modulepath.
 559          */
 560         private Hasher hasher() {
 561             if (options.modulesToHash == null)
 562                 return null;
 563 
 564             try {
 565                 Supplier<InputStream> miSupplier = newModuleInfoSupplier();
 566                 if (miSupplier == null) {
 567                     throw new IOException(MODULE_INFO + " not found");
 568                 }
 569 
 570                 ModuleDescriptor descriptor;
 571                 try (InputStream in = miSupplier.get()) {
 572                     descriptor = ModuleDescriptor.read(in);
 573                 }
 574 
 575                 URI uri = options.jmodFile.toUri();
 576                 ModuleReference mref = new ModuleReference(descriptor, uri) {
 577                     @Override
 578                     public ModuleReader open() {
 579                         throw new UnsupportedOperationException();
 580                     }
 581                 };
 582 
 583                 // compose a module finder with the module path and also
 584                 // a module finder that can find the jmod file being created
 585                 ModuleFinder finder = ModuleFinder.compose(options.moduleFinder,
 586                     new ModuleFinder() {
 587                         @Override
 588                         public Optional<ModuleReference> find(String name) {
 589                             if (descriptor.name().equals(name))
 590                                 return Optional.of(mref);
 591                             else return Optional.empty();
 592                         }
 593 
 594                         @Override
 595                         public Set<ModuleReference> findAll() {
 596                             return Collections.singleton(mref);
 597                         }
 598                     });
 599 
 600                 return new Hasher(finder);
 601             } catch (IOException e) {
 602                 throw new UncheckedIOException(e);
 603             }
 604         }
 605 
 606         /**
 607          * Returns the set of all packages on the given class path.
 608          */
 609         Set<String> findPackages(List<Path> classpath) {
 610             Set<String> packages = new HashSet<>();
 611             for (Path path : classpath) {
 612                 if (Files.isDirectory(path)) {
 613                     packages.addAll(findPackages(path));
 614                 } else if (Files.isRegularFile(path) && path.toString().endsWith(".jar")) {
 615                     try (JarFile jf = new JarFile(path.toString())) {
 616                         packages.addAll(findPackages(jf));
 617                     } catch (ZipException x) {
 618                         // Skip. Do nothing. No packages will be added.
 619                     } catch (IOException ioe) {
 620                         throw new UncheckedIOException(ioe);
 621                     }
 622                 }
 623             }


 772             public void accept(JarEntry je) {
 773                 try (InputStream in = jarfile.getInputStream(je)) {
 774                     out.writeEntry(in, Section.CLASSES, je.getName());
 775                 } catch (IOException e) {
 776                     throw new UncheckedIOException(e);
 777                 }
 778             }
 779             @Override
 780             public boolean test(JarEntry je) {
 781                 String name = je.getName();
 782                 // ## no support for excludes. Is it really needed?
 783                 return !name.endsWith(MODULE_INFO) && !je.isDirectory();
 784             }
 785         }
 786     }
 787 
 788     /**
 789      * Compute and record hashes
 790      */
 791     private class Hasher {
 792         final ModuleFinder moduleFinder;
 793         final Map<String, Path> moduleNameToPath;
 794         final Set<String> modules;
 795         final Configuration configuration;
 796         final boolean dryrun = options.dryrun;












 797         Hasher(ModuleFinder finder) {
 798             this.moduleFinder = finder;
















 799             // Determine the modules that matches the pattern {@code modulesToHash}
 800             this.modules = moduleFinder.findAll().stream()
 801                 .map(mref -> mref.descriptor().name())
 802                 .filter(mn -> options.modulesToHash.matcher(mn).find())
 803                 .collect(Collectors.toSet());
 804 
 805             // a map from a module name to Path of the packaged module
 806             this.moduleNameToPath = moduleFinder.findAll().stream()
 807                 .map(mref -> mref.descriptor().name())
 808                 .collect(Collectors.toMap(Function.identity(), mn -> moduleToPath(mn)));
 809 




 810             // get a resolved module graph
 811             Configuration config = null;
 812             try {
 813                 config = Configuration.empty()
 814                     .resolveRequires(ModuleFinder.ofSystem(), moduleFinder, modules);
 815             } catch (ResolutionException e) {
 816                 warning("warn.module.resolution.fail", e.getMessage());
 817             }
 818             this.configuration = config;
 819         }
 820 
 821         /**
 822          * This method is for jmod hash command.
 823          *
 824          * Identify the base modules in the module graph, i.e. no outgoing edge
 825          * to any of the modules to be hashed.
 826          *
 827          * For each base module M, compute the hashes of all modules that depend
 828          * upon M directly or indirectly.  Then update M's module-info.class
 829          * to record the hashes.
 830          */
 831         boolean run() {
 832             if (configuration == null)
 833                 return false;
 834 
 835             // transposed graph containing the the packaged modules and
 836             // its transitive dependences matching --hash-modules
 837             Map<String, Set<String>> graph = new HashMap<>();
 838             for (String root : modules) {
 839                 Deque<String> deque = new ArrayDeque<>();
 840                 deque.add(root);
 841                 Set<String> visited = new HashSet<>();
 842                 while (!deque.isEmpty()) {
 843                     String mn = deque.pop();
 844                     if (!visited.contains(mn)) {
 845                         visited.add(mn);
 846 
 847                         if (modules.contains(mn))
 848                             graph.computeIfAbsent(mn, _k -> new HashSet<>());
 849 
 850                         ResolvedModule resolvedModule = configuration.findModule(mn).get();
 851                         for (ResolvedModule dm : resolvedModule.reads()) {
 852                             String name = dm.name();
 853                             if (!visited.contains(name)) {
 854                                 deque.push(name);
 855                             }
 856 
 857                             // reverse edge
 858                             if (modules.contains(name) && modules.contains(mn)) {
 859                                 graph.computeIfAbsent(name, _k -> new HashSet<>()).add(mn);
 860                             }
 861                         }
 862                     }
 863                 }
 864             }
 865 
 866             if (dryrun)
 867                 out.println("Dry run:");
 868 
 869             // each node in a transposed graph is a matching packaged module
 870             // in which the hash of the modules that depend upon it is recorded
 871             graph.entrySet().stream()
 872                 .filter(e -> !e.getValue().isEmpty())
 873                 .forEach(e -> {
 874                     String mn = e.getKey();
 875                     Map<String, Path> modulesForHash = e.getValue().stream()
 876                             .collect(Collectors.toMap(Function.identity(),
 877                                                       moduleNameToPath::get));
 878                     ModuleHashes hashes = ModuleHashes.generate(modulesForHash, "SHA-256");
 879                     if (dryrun) {
 880                         out.format("%s%n", mn);
 881                         hashes.names().stream()
 882                               .sorted()
 883                               .forEach(name -> out.format("  hashes %s %s %s%n",
 884                                   name, hashes.algorithm(), hashes.hashFor(name)));
 885                     } else {
 886                         try {
 887                             updateModuleInfo(mn, hashes);
 888                         } catch (IOException ex) {
 889                             throw new UncheckedIOException(ex);
 890                         }
 891                     }
 892                 });
 893             return true;
 894         }
 895 
 896         /**
 897          * Compute hashes of the specified module.

 898          *
 899          * It records the hashing modules that depend upon the specified
 900          * module directly or indirectly.

 901          */
 902         ModuleHashes computeHashes(String name) {
 903             if (configuration == null)
 904                 return null;
 905 
 906             // the transposed graph includes all modules in the resolved graph
 907             Map<String, Set<String>> graph = transpose();
 908 
 909             // find the modules that transitively depend upon the specified name
 910             Deque<String> deque = new ArrayDeque<>();
 911             deque.add(name);
 912             Set<String> mods = visitNodes(graph, deque);
 913 
 914             // filter modules matching the pattern specified --hash-modules
 915             // as well as itself as the jmod file is being generated
 916             Map<String, Path> modulesForHash = mods.stream()
 917                 .filter(mn -> !mn.equals(name) && modules.contains(mn))
 918                 .collect(Collectors.toMap(Function.identity(), moduleNameToPath::get));
 919 
 920             if (modulesForHash.isEmpty())
 921                 return null;
 922 
 923            return ModuleHashes.generate(modulesForHash, "SHA-256");
 924         }
 925 
 926         /**
 927          * Returns all nodes traversed from the given roots.
 928          */
 929         private Set<String> visitNodes(Map<String, Set<String>> graph,
 930                                        Deque<String> roots) {
 931             Set<String> visited = new HashSet<>();
 932             while (!roots.isEmpty()) {
 933                 String mn = roots.pop();
 934                 if (!visited.contains(mn)) {
 935                     visited.add(mn);
 936                     // the given roots may not be part of the graph
 937                     if (graph.containsKey(mn)) {
 938                         for (String dm : graph.get(mn)) {
 939                             if (!visited.contains(dm)) {
 940                                 roots.push(dm);
 941                             }
 942                         }
 943                     }
 944                 }
 945             }
 946             return visited;
 947         }
 948 
 949         /**
 950          * Returns a transposed graph from the resolved module graph.
 951          */
 952         private Map<String, Set<String>> transpose() {
 953             Map<String, Set<String>> transposedGraph = new HashMap<>();
 954             Deque<String> deque = new ArrayDeque<>(modules);
 955 
 956             Set<String> visited = new HashSet<>();
 957             while (!deque.isEmpty()) {
 958                 String mn = deque.pop();
 959                 if (!visited.contains(mn)) {
 960                     visited.add(mn);
 961 
 962                     transposedGraph.computeIfAbsent(mn, _k -> new HashSet<>());
 963 
 964                     ResolvedModule resolvedModule = configuration.findModule(mn).get();
 965                     for (ResolvedModule dm : resolvedModule.reads()) {
 966                         String name = dm.name();
 967                         if (!visited.contains(name)) {
 968                             deque.push(name);
 969                         }
 970 
 971                         // reverse edge
 972                         transposedGraph.computeIfAbsent(name, _k -> new HashSet<>())
 973                                 .add(mn);
 974                     }
 975                 }
 976             }
 977             return transposedGraph;
 978         }
 979 
 980         /**
 981          * Reads the given input stream of module-info.class and write
 982          * the extended module-info.class with the given ModuleHashes
 983          *
 984          * @param in       InputStream of module-info.class
 985          * @param out      OutputStream to write the extended module-info.class
 986          * @param hashes   ModuleHashes
 987          */
 988         private void recordHashes(InputStream in, OutputStream out, ModuleHashes hashes)
 989             throws IOException
 990         {
 991             ModuleInfoExtender extender = ModuleInfoExtender.newExtender(in);
 992             extender.hashes(hashes);
 993             extender.write(out);
 994         }
 995 
 996         private void updateModuleInfo(String name, ModuleHashes moduleHashes)
 997             throws IOException
 998         {
 999             Path target = moduleNameToPath.get(name);
1000             Path tempTarget = target.resolveSibling(target.getFileName() + ".tmp");

1001             try {
1002                 if (target.getFileName().toString().endsWith(".jmod")) {
1003                     updateJmodFile(target, tempTarget, moduleHashes);
1004                 } else {
1005                     updateModularJar(target, tempTarget, moduleHashes);
1006                 }
1007             } catch (IOException|RuntimeException e) {
1008                 if (Files.exists(tempTarget)) {
1009                     try {
1010                         Files.delete(tempTarget);
1011                     } catch (IOException ioe) {
1012                         e.addSuppressed(ioe);
1013                     }
1014                 }
1015                 throw e;
1016             }
1017 
1018             out.println(getMessage("module.hashes.recorded", name));
1019             Files.move(tempTarget, target, StandardCopyOption.REPLACE_EXISTING);
1020         }


1058             {
1059                 jf.stream().forEach(e -> {
1060                     try (InputStream in = jf.getInputStream(e.section(), e.name())) {
1061                         if (e.name().equals(MODULE_INFO)) {
1062                             // replace module-info.class
1063                             ModuleInfoExtender extender =
1064                                 ModuleInfoExtender.newExtender(in);
1065                             extender.hashes(moduleHashes);
1066                             jos.writeEntry(extender.toByteArray(), e.section(), e.name());
1067                         } else {
1068                             jos.writeEntry(in, e);
1069                         }
1070                     } catch (IOException x) {
1071                         throw new UncheckedIOException(x);
1072                     }
1073                 });
1074             }
1075         }
1076 
1077         private Path moduleToPath(String name) {
1078             ModuleReference mref = moduleFinder.find(name).orElseThrow(
1079                 () -> new InternalError("Selected module " + name + " not on module path"));
1080 
1081             URI uri = mref.location().get();
1082             Path path = Paths.get(uri);
1083             String fn = path.getFileName().toString();
1084             if (!fn.endsWith(".jar") && !fn.endsWith(".jmod")) {
1085                 throw new InternalError(path + " is not a modular JAR or jmod file");
1086             }
1087             return path;
1088         }
1089     }
1090 
1091     /**
1092      * An abstract converter that given a string representing a list of paths,
1093      * separated by the File.pathSeparator, returns a List of java.nio.Path's.
1094      * Specific subclasses should do whatever validation is required on the
1095      * individual path elements, if any.
1096      */
1097     static abstract class AbstractPathConverter implements ValueConverter<List<Path>> {
1098         @Override
1099         public List<Path> convert(String value) {
1100             List<Path> paths = new ArrayList<>();
1101             String[] pathElements = value.split(File.pathSeparator);


   1 /*
   2  * Copyright (c) 2015, 2017, 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


  41 import java.lang.module.ModuleDescriptor.Exports;
  42 import java.lang.module.ModuleDescriptor.Opens;
  43 import java.lang.module.ModuleDescriptor.Provides;
  44 import java.lang.module.ModuleDescriptor.Requires;
  45 import java.lang.module.ModuleDescriptor.Version;
  46 import java.lang.module.ResolutionException;
  47 import java.lang.module.ResolvedModule;
  48 import java.net.URI;
  49 import java.nio.file.FileSystems;
  50 import java.nio.file.FileVisitOption;
  51 import java.nio.file.FileVisitResult;
  52 import java.nio.file.Files;
  53 import java.nio.file.InvalidPathException;
  54 import java.nio.file.Path;
  55 import java.nio.file.PathMatcher;
  56 import java.nio.file.Paths;
  57 import java.nio.file.SimpleFileVisitor;
  58 import java.nio.file.StandardCopyOption;
  59 import java.nio.file.attribute.BasicFileAttributes;
  60 import java.text.MessageFormat;

  61 import java.util.ArrayList;
  62 import java.util.Collection;
  63 import java.util.Collections;
  64 import java.util.Comparator;


  65 import java.util.HashSet;
  66 import java.util.LinkedHashMap;
  67 import java.util.List;
  68 import java.util.Locale;
  69 import java.util.Map;
  70 import java.util.MissingResourceException;
  71 import java.util.Optional;
  72 import java.util.ResourceBundle;
  73 import java.util.Set;
  74 import java.util.TreeSet;
  75 import java.util.function.Consumer;
  76 import java.util.function.Function;
  77 import java.util.function.Predicate;
  78 import java.util.function.Supplier;
  79 import java.util.jar.JarEntry;
  80 import java.util.jar.JarFile;
  81 import java.util.jar.JarOutputStream;
  82 import java.util.stream.Collectors;
  83 import java.util.regex.Pattern;
  84 import java.util.regex.PatternSyntaxException;
  85 import java.util.zip.ZipEntry;
  86 import java.util.zip.ZipException;
  87 import java.util.zip.ZipFile;
  88 
  89 import jdk.internal.jmod.JmodFile;
  90 import jdk.internal.jmod.JmodFile.Section;
  91 import jdk.internal.joptsimple.BuiltinHelpFormatter;
  92 import jdk.internal.joptsimple.NonOptionArgumentSpec;
  93 import jdk.internal.joptsimple.OptionDescriptor;
  94 import jdk.internal.joptsimple.OptionException;
  95 import jdk.internal.joptsimple.OptionParser;
  96 import jdk.internal.joptsimple.OptionSet;
  97 import jdk.internal.joptsimple.OptionSpec;
  98 import jdk.internal.joptsimple.ValueConverter;
  99 import jdk.internal.loader.ResourceHelper;
 100 import jdk.internal.module.ModuleHashes;
 101 import jdk.internal.module.ModuleHashesBuilder;
 102 import jdk.internal.module.ModuleInfo;
 103 import jdk.internal.module.ModuleInfoExtender;
 104 import jdk.internal.module.ModulePath;
 105 import jdk.internal.module.ModuleResolution;
 106 import jdk.tools.jlink.internal.Utils;
 107 
 108 import static java.util.stream.Collectors.joining;
 109 
 110 /**
 111  * Implementation for the jmod tool.
 112  */
 113 public class JmodTask {
 114 
 115     static class CommandException extends RuntimeException {
 116         private static final long serialVersionUID = 0L;
 117         boolean showUsage;
 118 
 119         CommandException(String key, Object... args) {
 120             super(getMessageOrKey(key, args));
 121         }


 267                     int index = name.lastIndexOf("/");
 268                     if (index != -1) {
 269                         Path p = dir.resolve(name.substring(0, index));
 270                         if (Files.notExists(p))
 271                             Files.createDirectories(p);
 272                     }
 273 
 274                     try (OutputStream os = Files.newOutputStream(dir.resolve(name))) {
 275                         jf.getInputStream(e).transferTo(os);
 276                     }
 277                 } catch (IOException x) {
 278                     throw new UncheckedIOException(x);
 279                 }
 280             });
 281 
 282             return true;
 283         }
 284     }
 285 
 286     private boolean hashModules() {
 287         if (options.dryrun) {
 288             out.println("Dry run:");
 289         }
 290 
 291         Hasher hasher = new Hasher(options.moduleFinder);
 292         hasher.computeHashes().forEach((mn, hashes) -> {
 293             if (options.dryrun) {
 294                 out.format("%s%n", mn);
 295                 hashes.names().stream()
 296                     .sorted()
 297                     .forEach(name -> out.format("  hashes %s %s %s%n",
 298                         name, hashes.algorithm(), toHex(hashes.hashFor(name))));
 299             } else {
 300                 try {
 301                     hasher.updateModuleInfo(mn, hashes);
 302                 } catch (IOException ex) {
 303                     throw new UncheckedIOException(ex);
 304                 }
 305             }
 306         });
 307         return true;
 308     }
 309 
 310     private boolean describe() throws IOException {
 311         try (JmodFile jf = new JmodFile(options.jmodFile)) {
 312             try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
 313                 ModuleInfo.Attributes attrs = ModuleInfo.read(in, null);
 314                 printModuleDescriptor(attrs.descriptor(), attrs.recordedHashes());
 315                 return true;
 316             } catch (IOException e) {
 317                 throw new CommandException("err.module.descriptor.not.found");
 318             }
 319         }
 320     }
 321 
 322     static <T> String toString(Collection<T> c) {
 323         if (c.isEmpty()) { return ""; }
 324         return c.stream().map(e -> e.toString().toLowerCase(Locale.ROOT))
 325                   .collect(joining(" "));
 326     }
 327 


 378                              .append(toHex(hashes.hashFor(mod))));
 379         }
 380 
 381         out.println(sb.toString());
 382     }
 383 
 384     private String toHex(byte[] ba) {
 385         StringBuilder sb = new StringBuilder(ba.length);
 386         for (byte b: ba) {
 387             sb.append(String.format("%02x", b & 0xff));
 388         }
 389         return sb.toString();
 390     }
 391 
 392     private boolean create() throws IOException {
 393         JmodFileWriter jmod = new JmodFileWriter();
 394 
 395         // create jmod with temporary name to avoid it being examined
 396         // when scanning the module path
 397         Path target = options.jmodFile;
 398         Path tmpdir = Paths.get(System.getProperty("java.io.tmpdir"));
 399         Path tempTarget = tmpdir.resolve(target.getFileName().toString() + ".tmp");
 400         try {
 401             try (JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget)) {
 402                 jmod.write(jos);
 403             }
 404             Files.move(tempTarget, target);
 405         } catch (Exception e) {
 406             if (Files.exists(tempTarget)) {
 407                 try {
 408                     Files.delete(tempTarget);
 409                 } catch (IOException ioe) {
 410                     e.addSuppressed(ioe);
 411                 }
 412             }
 413             throw e;
 414         }
 415         return true;
 416     }
 417 
 418     private class JmodFileWriter {
 419         final List<Path> cmds = options.cmds;
 420         final List<Path> libs = options.libs;
 421         final List<Path> configs = options.configs;
 422         final List<Path> classpath = options.classpath;
 423         final List<Path> headerFiles = options.headerFiles;
 424         final List<Path> manPages = options.manPages;
 425         final List<Path> legalNotices = options.legalNotices;
 426 
 427         final Version moduleVersion = options.moduleVersion;
 428         final String mainClass = options.mainClass;
 429         final String osName = options.osName;
 430         final String osArch = options.osArch;
 431         final String osVersion = options.osVersion;
 432         final List<PathMatcher> excludes = options.excludes;

 433         final ModuleResolution moduleResolution = options.moduleResolution;
 434 
 435         JmodFileWriter() { }
 436 
 437         /**
 438          * Writes the jmod to the given output stream.
 439          */
 440         void write(JmodOutputStream out) throws IOException {
 441             // module-info.class
 442             writeModuleInfo(out, findPackages(classpath));
 443 
 444             // classes
 445             processClasses(out, classpath);
 446 
 447             processSection(out, Section.CONFIG, configs);
 448             processSection(out, Section.HEADER_FILES, headerFiles);
 449             processSection(out, Section.LEGAL_NOTICES, legalNotices);
 450             processSection(out, Section.MAN_PAGES, manPages);
 451             processSection(out, Section.NATIVE_CMDS, cmds);
 452             processSection(out, Section.NATIVE_LIBS, libs);


 515                 ModuleInfoExtender extender = ModuleInfoExtender.newExtender(in);
 516 
 517                 // Add (or replace) the Packages attribute
 518                 if (packages != null) {
 519                     validatePackages(descriptor, packages);
 520                     extender.packages(packages);
 521                 }
 522 
 523                 // --main-class
 524                 if (mainClass != null)
 525                     extender.mainClass(mainClass);
 526 
 527                 // --os-name, --os-arch, --os-version
 528                 if (osName != null || osArch != null || osVersion != null)
 529                     extender.targetPlatform(osName, osArch, osVersion);
 530 
 531                 // --module-version
 532                 if (moduleVersion != null)
 533                     extender.version(moduleVersion);
 534 
 535                 // --hash-modules
 536                 if (options.modulesToHash != null) {
 537                     // To compute hashes, it creates a Configuration to resolve
 538                     // a module graph.  The post-resolution check requires
 539                     // the packages in ModuleDescriptor be available for validation.
 540                     ModuleDescriptor md;
 541                     try (InputStream is = miSupplier.get()) {
 542                         md = ModuleDescriptor.read(is, () -> packages);
 543                     }
 544 
 545                     ModuleHashes moduleHashes = computeHashes(md);
 546                     if (moduleHashes != null) {
 547                         extender.hashes(moduleHashes);
 548                     } else {
 549                         warning("warn.no.module.hashes", descriptor.name());
 550                     }
 551                 }
 552 
 553                 if (moduleResolution != null && moduleResolution.value() != 0) {
 554                     extender.moduleResolution(moduleResolution);
 555                 }
 556 
 557                 // write the (possibly extended or modified) module-info.class
 558                 out.writeEntry(extender.toByteArray(), Section.CLASSES, MODULE_INFO);
 559             }
 560         }
 561 
 562         private void validatePackages(ModuleDescriptor descriptor, Set<String> packages) {
 563             Set<String> nonExistPackages = new TreeSet<>();
 564             descriptor.exports().stream()
 565                 .map(Exports::source)


 567                 .forEach(nonExistPackages::add);
 568 
 569             descriptor.opens().stream()
 570                 .map(Opens::source)
 571                 .filter(pn -> !packages.contains(pn))
 572                 .forEach(nonExistPackages::add);
 573 
 574             if (!nonExistPackages.isEmpty()) {
 575                 throw new CommandException("err.missing.export.or.open.packages",
 576                     descriptor.name(), nonExistPackages);
 577             }
 578         }
 579 
 580         /*
 581          * Hasher resolves a module graph using the --hash-modules PATTERN
 582          * as the roots.
 583          *
 584          * The jmod file is being created and does not exist in the
 585          * given modulepath.
 586          */
 587         private ModuleHashes computeHashes(ModuleDescriptor descriptor) {
 588             String mn = descriptor.name();













 589             URI uri = options.jmodFile.toUri();
 590             ModuleReference mref = new ModuleReference(descriptor, uri) {
 591                 @Override
 592                 public ModuleReader open() {
 593                     throw new UnsupportedOperationException("opening " + mn);
 594                 }
 595             };
 596 
 597             // compose a module finder with the module path and also
 598             // a module finder that can find the jmod file being created
 599             ModuleFinder finder = ModuleFinder.compose(options.moduleFinder,
 600                 new ModuleFinder() {
 601                     @Override
 602                     public Optional<ModuleReference> find(String name) {
 603                         if (descriptor.name().equals(name))
 604                             return Optional.of(mref);
 605                         else return Optional.empty();
 606                     }
 607 
 608                     @Override
 609                     public Set<ModuleReference> findAll() {
 610                         return Collections.singleton(mref);
 611                     }
 612                 });
 613 
 614             return new Hasher(mn, finder).computeHashes().get(mn);



 615         }
 616 
 617         /**
 618          * Returns the set of all packages on the given class path.
 619          */
 620         Set<String> findPackages(List<Path> classpath) {
 621             Set<String> packages = new HashSet<>();
 622             for (Path path : classpath) {
 623                 if (Files.isDirectory(path)) {
 624                     packages.addAll(findPackages(path));
 625                 } else if (Files.isRegularFile(path) && path.toString().endsWith(".jar")) {
 626                     try (JarFile jf = new JarFile(path.toString())) {
 627                         packages.addAll(findPackages(jf));
 628                     } catch (ZipException x) {
 629                         // Skip. Do nothing. No packages will be added.
 630                     } catch (IOException ioe) {
 631                         throw new UncheckedIOException(ioe);
 632                     }
 633                 }
 634             }


 783             public void accept(JarEntry je) {
 784                 try (InputStream in = jarfile.getInputStream(je)) {
 785                     out.writeEntry(in, Section.CLASSES, je.getName());
 786                 } catch (IOException e) {
 787                     throw new UncheckedIOException(e);
 788                 }
 789             }
 790             @Override
 791             public boolean test(JarEntry je) {
 792                 String name = je.getName();
 793                 // ## no support for excludes. Is it really needed?
 794                 return !name.endsWith(MODULE_INFO) && !je.isDirectory();
 795             }
 796         }
 797     }
 798 
 799     /**
 800      * Compute and record hashes
 801      */
 802     private class Hasher {



 803         final Configuration configuration;
 804         final ModuleHashesBuilder hashesBuilder;
 805         final Set<String> modules;
 806         final Optional<String> moduleName;  // a specific module to record hashes
 807 
 808         /**
 809          * This constructor is for jmod hash command.
 810          *
 811          * This Hasher will determine which modules to record hashes, i.e.
 812          * the module in a subgraph of modules to be hashed and that
 813          * has no outgoing edges.  It will record in each of these modules,
 814          * say `M`, with the the hashes of modules that depend upon M
 815          * directly or indirectly matching the specified --hash-modules pattern.
 816          */
 817         Hasher(ModuleFinder finder) {
 818             this(null, finder);
 819         }
 820 
 821         /**
 822          * Constructs a Hasher to compute hashes.
 823          *
 824          * If a module name `M` is specified, it will compute the hashes of
 825          * modules that depend upon M directly or indirectly matching the
 826          * specified --hash-modules pattern and record in the ModuleHashes
 827          * attribute in M's module-info.class.
 828          *
 829          * @param name    name of he module to record hashes
 830          * @param finder  module finder for the specified --module-path
 831          */
 832         Hasher(String name, ModuleFinder finder) {
 833             this.moduleName = Optional.ofNullable(name);
 834 
 835             // Determine the modules that matches the pattern {@code modulesToHash}
 836             Set<String> roots = finder.findAll().stream()
 837                 .map(mref -> mref.descriptor().name())
 838                 .filter(mn -> options.modulesToHash.matcher(mn).find())
 839                 .collect(Collectors.toSet());
 840 
 841             // use system module path unless it creates a JMOD file for
 842             // a module that is present in the system image e.g. upgradeable
 843             // module
 844             ModuleFinder system;
 845             if (name != null && ModuleFinder.ofSystem().find(name).isPresent()) {
 846                 system = ModuleFinder.of();
 847             } else {
 848                 system = ModuleFinder.ofSystem();
 849             }
 850             // get a resolved module graph
 851             Configuration config = null;
 852             try {
 853                 config = Configuration.empty().resolveRequires(system, finder, roots);

 854             } catch (ResolutionException e) {
 855                 throw new CommandException("err.module.resolution.fail", e.getMessage());
 856             }
 857             this.configuration = config;






























 858 
 859             // filter modules resolved from the system module finder
 860             this.modules = config.modules().stream()
 861                 .map(ResolvedModule::name)
 862                 .filter(mn -> roots.contains(mn) && !system.find(mn).isPresent())
 863                 .collect(Collectors.toSet());













 864 
 865             this.hashesBuilder = new ModuleHashesBuilder(config, modules);
























 866         }
 867 
 868         /**
 869          * Returns a map of a module M to record hashes of the modules
 870          * that depend upon M directly or indirectly.
 871          *
 872          * For jmod hash command, each entry in the returned map is a module
 873          * in a subgraph containing the modules specified in the --hash-modules
 874          * pattern but it has no outgoing edges.
 875          */
 876         Map<String, ModuleHashes> computeHashes() {
 877             if (hashesBuilder == null)
 878                 return null;
 879 
 880             if (moduleName.isPresent()) {
 881                 return hashesBuilder.computeHashes(Set.of(moduleName.get()));
 882             } else {
 883                 return hashesBuilder.computeHashes(modules);


































































 884             }

 885         }
 886 
 887         /**
 888          * Reads the given input stream of module-info.class and write
 889          * the extended module-info.class with the given ModuleHashes
 890          *
 891          * @param in       InputStream of module-info.class
 892          * @param out      OutputStream to write the extended module-info.class
 893          * @param hashes   ModuleHashes
 894          */
 895         private void recordHashes(InputStream in, OutputStream out, ModuleHashes hashes)
 896             throws IOException
 897         {
 898             ModuleInfoExtender extender = ModuleInfoExtender.newExtender(in);
 899             extender.hashes(hashes);
 900             extender.write(out);
 901         }
 902 
 903         void updateModuleInfo(String name, ModuleHashes moduleHashes)
 904             throws IOException
 905         {
 906             Path target = moduleToPath(name);
 907             Path tmpdir = Paths.get(System.getProperty("java.io.tmpdir"));
 908             Path tempTarget = tmpdir.resolve(target.getFileName().toString() + ".tmp");
 909             try {
 910                 if (target.getFileName().toString().endsWith(".jmod")) {
 911                     updateJmodFile(target, tempTarget, moduleHashes);
 912                 } else {
 913                     updateModularJar(target, tempTarget, moduleHashes);
 914                 }
 915             } catch (IOException|RuntimeException e) {
 916                 if (Files.exists(tempTarget)) {
 917                     try {
 918                         Files.delete(tempTarget);
 919                     } catch (IOException ioe) {
 920                         e.addSuppressed(ioe);
 921                     }
 922                 }
 923                 throw e;
 924             }
 925 
 926             out.println(getMessage("module.hashes.recorded", name));
 927             Files.move(tempTarget, target, StandardCopyOption.REPLACE_EXISTING);
 928         }


 966             {
 967                 jf.stream().forEach(e -> {
 968                     try (InputStream in = jf.getInputStream(e.section(), e.name())) {
 969                         if (e.name().equals(MODULE_INFO)) {
 970                             // replace module-info.class
 971                             ModuleInfoExtender extender =
 972                                 ModuleInfoExtender.newExtender(in);
 973                             extender.hashes(moduleHashes);
 974                             jos.writeEntry(extender.toByteArray(), e.section(), e.name());
 975                         } else {
 976                             jos.writeEntry(in, e);
 977                         }
 978                     } catch (IOException x) {
 979                         throw new UncheckedIOException(x);
 980                     }
 981                 });
 982             }
 983         }
 984 
 985         private Path moduleToPath(String name) {
 986             ResolvedModule rm = configuration.findModule(name).orElseThrow(
 987                 () -> new InternalError("Selected module " + name + " not on module path"));
 988 
 989             URI uri = rm.reference().location().get();
 990             Path path = Paths.get(uri);
 991             String fn = path.getFileName().toString();
 992             if (!fn.endsWith(".jar") && !fn.endsWith(".jmod")) {
 993                 throw new InternalError(path + " is not a modular JAR or jmod file");
 994             }
 995             return path;
 996         }
 997     }
 998 
 999     /**
1000      * An abstract converter that given a string representing a list of paths,
1001      * separated by the File.pathSeparator, returns a List of java.nio.Path's.
1002      * Specific subclasses should do whatever validation is required on the
1003      * individual path elements, if any.
1004      */
1005     static abstract class AbstractPathConverter implements ValueConverter<List<Path>> {
1006         @Override
1007         public List<Path> convert(String value) {
1008             List<Path> paths = new ArrayList<>();
1009             String[] pathElements = value.split(File.pathSeparator);


< prev index next >