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
27
28 import java.io.ByteArrayInputStream;
29 import java.io.ByteArrayOutputStream;
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.io.PrintWriter;
35 import java.io.UncheckedIOException;
36 import java.lang.module.Configuration;
37 import java.lang.module.ModuleReader;
38 import java.lang.module.ModuleReference;
39 import java.lang.module.ModuleFinder;
40 import java.lang.module.ModuleDescriptor;
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 }
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
1057 JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget))
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 static class ClassPathConverter implements ValueConverter<Path> {
1092 static final ValueConverter<Path> INSTANCE = new ClassPathConverter();
1093
1094 @Override
1095 public Path convert(String value) {
1096 try {
1097 Path path = CWD.resolve(value);
|
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
27
28 import java.io.ByteArrayInputStream;
29 import java.io.ByteArrayOutputStream;
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.io.PrintWriter;
35 import java.io.UncheckedIOException;
36 import java.lang.module.Configuration;
37 import java.lang.module.ModuleReader;
38 import java.lang.module.ModuleReference;
39 import java.lang.module.ModuleFinder;
40 import java.lang.module.ModuleDescriptor;
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.net.URI;
48 import java.nio.file.FileSystems;
49 import java.nio.file.FileVisitOption;
50 import java.nio.file.FileVisitResult;
51 import java.nio.file.Files;
52 import java.nio.file.InvalidPathException;
53 import java.nio.file.Path;
54 import java.nio.file.PathMatcher;
55 import java.nio.file.Paths;
56 import java.nio.file.SimpleFileVisitor;
57 import java.nio.file.StandardCopyOption;
58 import java.nio.file.attribute.BasicFileAttributes;
59 import java.text.MessageFormat;
60 import java.util.ArrayList;
61 import java.util.Collection;
62 import java.util.Collections;
63 import java.util.Comparator;
64 import java.util.HashSet;
65 import java.util.LinkedHashMap;
66 import java.util.List;
67 import java.util.Locale;
68 import java.util.Map;
69 import java.util.MissingResourceException;
70 import java.util.Optional;
71 import java.util.ResourceBundle;
72 import java.util.Set;
73 import java.util.TreeSet;
74 import java.util.function.Consumer;
75 import java.util.function.Function;
76 import java.util.function.Predicate;
77 import java.util.function.Supplier;
78 import java.util.jar.JarEntry;
79 import java.util.jar.JarFile;
80 import java.util.jar.JarOutputStream;
81 import java.util.stream.Collectors;
82 import java.util.regex.Pattern;
83 import java.util.regex.PatternSyntaxException;
84 import java.util.zip.ZipEntry;
85 import java.util.zip.ZipException;
86 import java.util.zip.ZipFile;
87
88 import jdk.internal.jmod.JmodFile;
89 import jdk.internal.jmod.JmodFile.Section;
90 import jdk.internal.joptsimple.BuiltinHelpFormatter;
91 import jdk.internal.joptsimple.NonOptionArgumentSpec;
92 import jdk.internal.joptsimple.OptionDescriptor;
93 import jdk.internal.joptsimple.OptionException;
94 import jdk.internal.joptsimple.OptionParser;
95 import jdk.internal.joptsimple.OptionSet;
96 import jdk.internal.joptsimple.OptionSpec;
97 import jdk.internal.joptsimple.ValueConverter;
98 import jdk.internal.loader.ResourceHelper;
99 import jdk.internal.module.ModuleHashes;
100 import jdk.internal.module.ModuleHashesBuilder;
101 import jdk.internal.module.ModuleInfo;
102 import jdk.internal.module.ModuleInfoExtender;
103 import jdk.internal.module.ModulePath;
104 import jdk.internal.module.ModuleResolution;
105 import jdk.tools.jlink.internal.Utils;
106
107 import static java.util.stream.Collectors.joining;
108
109 /**
110 * Implementation for the jmod tool.
111 */
112 public class JmodTask {
113
114 static class CommandException extends RuntimeException {
115 private static final long serialVersionUID = 0L;
116 boolean showUsage;
117
118 CommandException(String key, Object... args) {
119 super(getMessageOrKey(key, args));
120 }
769 public void accept(JarEntry je) {
770 try (InputStream in = jarfile.getInputStream(je)) {
771 out.writeEntry(in, Section.CLASSES, je.getName());
772 } catch (IOException e) {
773 throw new UncheckedIOException(e);
774 }
775 }
776 @Override
777 public boolean test(JarEntry je) {
778 String name = je.getName();
779 // ## no support for excludes. Is it really needed?
780 return !name.endsWith(MODULE_INFO) && !je.isDirectory();
781 }
782 }
783 }
784
785 /**
786 * Compute and record hashes
787 */
788 private class Hasher {
789 final ModuleHashesBuilder hashesBuilder;
790 final Map<String, Path> moduleNameToPath;
791 final Set<String> modules;
792 final boolean dryrun = options.dryrun;
793 Hasher(ModuleFinder finder) {
794 // Determine the modules that matches the pattern {@code modulesToHash}
795 this.modules = finder.findAll().stream()
796 .map(mref -> mref.descriptor().name())
797 .filter(mn -> options.modulesToHash.matcher(mn).find())
798 .collect(Collectors.toSet());
799
800 // a map from a module name to Path of the packaged module
801 this.moduleNameToPath = finder.findAll().stream()
802 .map(mref -> mref.descriptor().name())
803 .filter(modules::contains)
804 .collect(Collectors.toMap(Function.identity(), mn -> moduleToPath(finder, mn)));
805
806 // get a resolved module graph
807 Configuration config = null;
808 try {
809 config = Configuration.empty()
810 .resolveRequires(ModuleFinder.ofSystem(), finder, modules);
811 } catch (ResolutionException e) {
812 warning("warn.module.resolution.fail", e.getMessage());
813 }
814
815 // ModuleHashesBuilder is constructed with the moduleNameToPath arguments
816 // so that creating java.base.jmod will compute the hash from this
817 // explicit path rather than the location from the resolved module
818 this.hashesBuilder = config != null
819 ? new ModuleHashesBuilder(config, moduleNameToPath) : null;
820 }
821
822 /**
823 * This method is for jmod hash command.
824 *
825 * Identify the base modules in the module graph, i.e. no outgoing edge
826 * to any of the modules to be hashed.
827 *
828 * For each base module M, compute the hashes of all modules that depend
829 * upon M directly or indirectly. Then update M's module-info.class
830 * to record the hashes.
831 */
832 boolean run() {
833 if (hashesBuilder == null)
834 return false;
835
836 if (dryrun) {
837 out.println("Dry run:");
838 }
839
840 hashesBuilder.computeHashes(modules).entrySet().forEach(e -> {
841 String mn = e.getKey();
842 ModuleHashes hashes = e.getValue();
843 if (dryrun) {
844 out.format("%s%n", mn);
845 hashes.names().stream()
846 .sorted()
847 .forEach(name -> out.format(" hashes %s %s %s%n",
848 name, hashes.algorithm(), toHex(hashes.hashFor(name))));
849 } else {
850 try {
851 updateModuleInfo(mn, hashes);
852 } catch (IOException ex) {
853 throw new UncheckedIOException(ex);
854 }
855 }
856 });
857 return true;
858 }
859
860 /**
861 * Compute hashes of the specified module.
862 *
863 * It records the hashing modules that depend upon the specified
864 * module directly or indirectly.
865 */
866 ModuleHashes computeHashes(String name) {
867 if (hashesBuilder == null)
868 return null;
869
870 return hashesBuilder.computeHashes(Set.of(name)).get(name);
871 }
872
873 /**
874 * Reads the given input stream of module-info.class and write
875 * the extended module-info.class with the given ModuleHashes
876 *
877 * @param in InputStream of module-info.class
878 * @param out OutputStream to write the extended module-info.class
879 * @param hashes ModuleHashes
880 */
881 private void recordHashes(InputStream in, OutputStream out, ModuleHashes hashes)
882 throws IOException
883 {
884 ModuleInfoExtender extender = ModuleInfoExtender.newExtender(in);
885 extender.hashes(hashes);
886 extender.write(out);
887 }
888
889 private void updateModuleInfo(String name, ModuleHashes moduleHashes)
890 throws IOException
950 JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget))
951 {
952 jf.stream().forEach(e -> {
953 try (InputStream in = jf.getInputStream(e.section(), e.name())) {
954 if (e.name().equals(MODULE_INFO)) {
955 // replace module-info.class
956 ModuleInfoExtender extender =
957 ModuleInfoExtender.newExtender(in);
958 extender.hashes(moduleHashes);
959 jos.writeEntry(extender.toByteArray(), e.section(), e.name());
960 } else {
961 jos.writeEntry(in, e);
962 }
963 } catch (IOException x) {
964 throw new UncheckedIOException(x);
965 }
966 });
967 }
968 }
969
970 private Path moduleToPath(ModuleFinder moduleFinder, String name) {
971 ModuleReference mref = moduleFinder.find(name).orElseThrow(
972 () -> new InternalError("Selected module " + name + " not on module path"));
973
974 URI uri = mref.location().get();
975 Path path = Paths.get(uri);
976 String fn = path.getFileName().toString();
977 if (!fn.endsWith(".jar") && !fn.endsWith(".jmod")) {
978 throw new InternalError(path + " is not a modular JAR or jmod file");
979 }
980 return path;
981 }
982 }
983
984 static class ClassPathConverter implements ValueConverter<Path> {
985 static final ValueConverter<Path> INSTANCE = new ClassPathConverter();
986
987 @Override
988 public Path convert(String value) {
989 try {
990 Path path = CWD.resolve(value);
|