8055856: checkdeps build target doesn't work for cross-compilation builds
8056113: [build] tools.jar missing modules.xml
Reviewed-by: ihse, erikj
--- a/jdk/make/ModulesXml.gmk Fri Aug 29 18:11:04 2014 +0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-#
-# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
-# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
-#
-# This code is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 2 only, as
-# published by the Free Software Foundation. Oracle designates this
-# particular file as subject to the "Classpath" exception as provided
-# by Oracle in the LICENSE file that accompanied this code.
-#
-# This code is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
-# version 2 for more details (a copy is included in the LICENSE file that
-# accompanied this code).
-#
-# You should have received a copy of the GNU General Public License version
-# 2 along with this work; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
-# or visit www.oracle.com if you need additional information or have any
-# questions.
-#
-
-# Default target declared first
-default: all
-
-include $(SPEC)
-include MakeBase.gmk
-include Tools.gmk
-
-#
-# Generate modules.xml for jdeps to use
-# It augments $(TOPDIR)/modules.xml to include module membership
-#
-JDEPS_MODULES_XML := $(JDK_OUTPUTDIR)/modules/jdk.dev/com/sun/tools/jdeps/resources/modules.xml
-
-METADATA := $(JDK_OUTPUTDIR)/btclasses/build/tools/module/modules.xml
-$(METADATA): $(TOPDIR)/modules.xml
- $(call install-file)
-
-METADATA_FILES := $(METADATA)
-
-ifndef OPENJDK
- CLOSED_METADATA := $(JDK_OUTPUTDIR)/btclasses/build/tools/module/closed/modules.xml
- $(CLOSED_METADATA): $(TOPDIR)/closed/modules.xml
- $(call install-file)
-
- METADATA_FILES += $(CLOSED_METADATA)
-endif
-
-$(JDEPS_MODULES_XML): $(BUILD_TOOLS_JDK) $(METADATA_FILES)
- $(MKDIR) -p $(@D)
- $(RM) $@
- $(TOOL_GENMODULESXML) $@ $(JDK_OUTPUTDIR)/modules
-
-#
-# Verify access across module boundaries
-#
-checkdeps: $(JDEPS_MODULES_XML)
- $(ECHO) "Checking dependencies across JDK modules"
- $(FIXPATH) $(JDK_OUTPUTDIR)/bin/jdeps -verify:access -mp $(JDK_OUTPUTDIR)/modules
-
-gen-modules-xml: $(JDEPS_MODULES_XML)
-
-all: checkdeps
-
-.PHONY: all
--- a/jdk/make/Tools.gmk Fri Aug 29 18:11:04 2014 +0400
+++ b/jdk/make/Tools.gmk Fri Aug 29 10:47:32 2014 -0700
@@ -134,7 +134,7 @@
TOOL_GENMODULESXML = $(JAVA_SMALL) -Xbootclasspath/p:$(INTERIM_LANGTOOLS_JAR) \
-cp "$(JDK_OUTPUTDIR)/btclasses$(PATH_SEP)$(JDK_OUTPUTDIR)" \
- build.tools.module.GenerateModulesXml
+ build.tools.module.GenJdepsModulesXml
##########################################################################################
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/make/gendata/Gendata-jdk.dev.gmk Fri Aug 29 10:47:32 2014 -0700
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+include GendataCommon.gmk
+
+$(eval $(call IncludeCustomExtension, jdk, gendata/Gendata-jdk.dev.gmk))
+
+GENDATA := $(JDK_OUTPUTDIR)/modules/jdk.dev/com/sun/tools/jdeps/resources/jdeps-modules.xml
+METADATA_FILES += $(TOPDIR)/modules.xml
+
+#
+# Generate modules.xml for jdeps to use
+# It augments $(TOPDIR)/modules.xml to include module membership
+#
+$(GENDATA): $(BUILD_TOOLS_JDK) $(METADATA_FILES)
+ $(MKDIR) -p $(@D)
+ $(RM) $@
+ $(TOOL_GENMODULESXML) -o $@ -mp $(JDK_OUTPUTDIR)/modules $(METADATA_FILES)
+
+jdk.dev: $(GENDATA)
+
+all: $(GENDATA)
+
+.PHONY: all jdk.dev
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/make/src/classes/build/tools/module/GenJdepsModulesXml.java Fri Aug 29 10:47:32 2014 -0700
@@ -0,0 +1,485 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package build.tools.module;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import javax.xml.namespace.QName;
+import javax.xml.stream.*;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.XMLEvent;
+
+/**
+ * GenJdepsModulesXml augments the input modules.xml file(s)
+ * to include the module membership from the given path to
+ * the JDK exploded image. The output file is used by jdeps
+ * to analyze dependencies and enforce module boundaries.
+ *
+ * The input modules.xml file defines the modular structure of
+ * the JDK as described in JEP 200: The Modular JDK
+ * (http://openjdk.java.net/jeps/200).
+ *
+ * $ java build.tools.module.GenJdepsModulesXml \
+ * -o com/sun/tools/jdeps/resources/modules.xml \
+ * -mp $OUTPUTDIR/modules \
+ * top/modules.xml
+ */
+public final class GenJdepsModulesXml {
+ private final static String USAGE =
+ "Usage: GenJdepsModulesXml -o <output file> -mp build/modules path-to-modules-xml";
+
+ public static void main(String[] args) throws Exception {
+ Path outfile = null;
+ Path modulepath = null;
+ int i = 0;
+ while (i < args.length) {
+ String arg = args[i];
+ if (arg.equals("-o")) {
+ outfile = Paths.get(args[i+1]);
+ i = i+2;
+ } else if (arg.equals("-mp")) {
+ modulepath = Paths.get(args[i+1]);
+ i = i+2;
+ if (!Files.isDirectory(modulepath)) {
+ System.err.println(modulepath + " is not a directory");
+ System.exit(1);
+ }
+ } else {
+ break;
+ }
+ }
+ if (outfile == null || modulepath == null || i >= args.length) {
+ System.err.println(USAGE);
+ System.exit(-1);
+ }
+
+ GenJdepsModulesXml gentool = new GenJdepsModulesXml(modulepath);
+ Set<Module> modules = new HashSet<>();
+ for (; i < args.length; i++) {
+ Path p = Paths.get(args[i]);
+ try (InputStream in = new BufferedInputStream(Files.newInputStream(p))) {
+ Set<Module> mods = gentool.load(in);
+ modules.addAll(mods);
+ }
+ }
+
+ Files.createDirectories(outfile.getParent());
+ gentool.writeXML(modules, outfile);
+ }
+
+ final Path modulepath;
+ public GenJdepsModulesXml(Path modulepath) {
+ this.modulepath = modulepath;
+ }
+
+ private static final String MODULES = "modules";
+ private static final String MODULE = "module";
+ private static final String NAME = "name";
+ private static final String DEPEND = "depend";
+ private static final String EXPORT = "export";
+ private static final String TO = "to";
+ private static final String INCLUDE = "include";
+ private static final QName REEXPORTS = new QName("re-exports");
+ private Set<Module> load(InputStream in) throws XMLStreamException, IOException {
+ Set<Module> modules = new HashSet<>();
+ XMLInputFactory factory = XMLInputFactory.newInstance();
+ XMLEventReader stream = factory.createXMLEventReader(in);
+ Module.Builder mb = null;
+ String modulename = null;
+ String pkg = null;
+ Set<String> permits = new HashSet<>();
+ while (stream.hasNext()) {
+ XMLEvent event = stream.nextEvent();
+ if (event.isStartElement()) {
+ String startTag = event.asStartElement().getName().getLocalPart();
+ switch (startTag) {
+ case MODULES:
+ break;
+ case MODULE:
+ if (mb != null) {
+ throw new RuntimeException("end tag for module is missing");
+ }
+ modulename = getNextTag(stream, NAME);
+ mb = new Module.Builder();
+ mb.name(modulename);
+ break;
+ case NAME:
+ throw new RuntimeException(event.toString());
+ case DEPEND:
+ boolean reexports = false;
+ Attribute attr = event.asStartElement().getAttributeByName(REEXPORTS);
+ if (attr != null) {
+ String value = attr.getValue();
+ if (value.equals("true") || value.equals("false")) {
+ reexports = Boolean.parseBoolean(value);
+ } else {
+ throw new RuntimeException("unexpected attribute " + attr.toString());
+ }
+ }
+ mb.require(getData(stream), reexports);
+ break;
+ case INCLUDE:
+ throw new RuntimeException("unexpected " + event);
+ case EXPORT:
+ pkg = getNextTag(stream, NAME);
+ break;
+ case TO:
+ permits.add(getData(stream));
+ break;
+ default:
+ }
+ } else if (event.isEndElement()) {
+ String endTag = event.asEndElement().getName().getLocalPart();
+ switch (endTag) {
+ case MODULE:
+ buildIncludes(mb, modulename);
+ modules.add(mb.build());
+ mb = null;
+ break;
+ case EXPORT:
+ if (pkg == null) {
+ throw new RuntimeException("export-to is malformed");
+ }
+ mb.exportTo(pkg, permits);
+ pkg = null;
+ permits.clear();
+ break;
+ default:
+ }
+ } else if (event.isCharacters()) {
+ String s = event.asCharacters().getData();
+ if (!s.trim().isEmpty()) {
+ throw new RuntimeException("export-to is malformed");
+ }
+ }
+ }
+ return modules;
+ }
+
+ private String getData(XMLEventReader reader) throws XMLStreamException {
+ XMLEvent e = reader.nextEvent();
+ if (e.isCharacters()) {
+ return e.asCharacters().getData();
+ }
+ throw new RuntimeException(e.toString());
+ }
+
+ private String getNextTag(XMLEventReader reader, String tag) throws XMLStreamException {
+ XMLEvent e = reader.nextTag();
+ if (e.isStartElement()) {
+ String t = e.asStartElement().getName().getLocalPart();
+ if (!tag.equals(t)) {
+ throw new RuntimeException(e + " expected: " + tag);
+ }
+ return getData(reader);
+ }
+ throw new RuntimeException("export-to name is missing:" + e);
+ }
+ private void writeXML(Set<Module> modules, Path path)
+ throws IOException, XMLStreamException
+ {
+ XMLOutputFactory xof = XMLOutputFactory.newInstance();
+ try (OutputStream out = Files.newOutputStream(path)) {
+ int depth = 0;
+ XMLStreamWriter xtw = xof.createXMLStreamWriter(out, "UTF-8");
+ xtw.writeStartDocument("utf-8","1.0");
+ writeStartElement(xtw, MODULES, depth);
+ modules.stream()
+ .sorted(Comparator.comparing(Module::name))
+ .forEach(m -> writeModuleElement(xtw, m, depth+1));
+ writeEndElement(xtw, depth);
+ xtw.writeCharacters("\n");
+ xtw.writeEndDocument();
+ xtw.flush();
+ xtw.close();
+ }
+ }
+
+ private void writeElement(XMLStreamWriter xtw, String element, String value, int depth) {
+ try {
+ writeStartElement(xtw, element, depth);
+ xtw.writeCharacters(value);
+ xtw.writeEndElement();
+ } catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void writeDependElement(XMLStreamWriter xtw, Module.Dependence d, int depth) {
+ try {
+ writeStartElement(xtw, DEPEND, depth);
+ if (d.reexport) {
+ xtw.writeAttribute("re-exports", "true");
+ }
+ xtw.writeCharacters(d.name);
+ xtw.writeEndElement();
+ } catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void writeExportElement(XMLStreamWriter xtw, String pkg, int depth) {
+ writeExportElement(xtw, pkg, Collections.emptySet(), depth);
+ }
+
+ private void writeExportElement(XMLStreamWriter xtw, String pkg,
+ Set<String> permits, int depth) {
+ try {
+ writeStartElement(xtw, EXPORT, depth);
+ writeElement(xtw, NAME, pkg, depth+1);
+ if (!permits.isEmpty()) {
+ permits.stream().sorted()
+ .forEach(m -> writeElement(xtw, TO, m, depth + 1));
+ }
+ writeEndElement(xtw, depth);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ private void writeModuleElement(XMLStreamWriter xtw, Module m, int depth) {
+ try {
+ writeStartElement(xtw, MODULE, depth);
+ writeElement(xtw, NAME, m.name(), depth+1);
+ m.requires().stream().sorted(Comparator.comparing(d -> d.name))
+ .forEach(d -> writeDependElement(xtw, d, depth+1));
+ m.exports().keySet().stream()
+ .filter(pn -> m.exports().get(pn).isEmpty())
+ .sorted()
+ .forEach(pn -> writeExportElement(xtw, pn, depth+1));
+ m.exports().entrySet().stream()
+ .filter(e -> !e.getValue().isEmpty())
+ .sorted(Map.Entry.comparingByKey())
+ .forEach(e -> writeExportElement(xtw, e.getKey(), e.getValue(), depth+1));
+ m.packages().stream().sorted()
+ .forEach(p -> writeElement(xtw, INCLUDE, p, depth+1));
+ writeEndElement(xtw, depth);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+
+ }
+ }
+
+ /** Two spaces; the default indentation. */
+ public static final String DEFAULT_INDENT = " ";
+
+ /** stack[depth] indicates what's been written into the current scope. */
+ private static String[] stack = new String[] { "\n",
+ "\n" + DEFAULT_INDENT,
+ "\n" + DEFAULT_INDENT + DEFAULT_INDENT,
+ "\n" + DEFAULT_INDENT + DEFAULT_INDENT + DEFAULT_INDENT};
+
+ private void writeStartElement(XMLStreamWriter xtw, String name, int depth)
+ throws XMLStreamException
+ {
+ xtw.writeCharacters(stack[depth]);
+ xtw.writeStartElement(name);
+ }
+
+ private void writeEndElement(XMLStreamWriter xtw, int depth) throws XMLStreamException {
+ xtw.writeCharacters(stack[depth]);
+ xtw.writeEndElement();
+ }
+
+ private String packageName(Path p) {
+ return packageName(p.toString().replace(File.separatorChar, '/'));
+ }
+ private String packageName(String name) {
+ int i = name.lastIndexOf('/');
+ return (i > 0) ? name.substring(0, i).replace('/', '.') : "";
+ }
+
+ private boolean includes(String name) {
+ return name.endsWith(".class") && !name.equals("module-info.class");
+ }
+
+ public void buildIncludes(Module.Builder mb, String modulename) throws IOException {
+ Path mclasses = modulepath.resolve(modulename);
+ try {
+ Files.find(mclasses, Integer.MAX_VALUE, (Path p, BasicFileAttributes attr)
+ -> includes(p.getFileName().toString()))
+ .map(p -> packageName(mclasses.relativize(p)))
+ .forEach(mb::include);
+ } catch (NoSuchFileException e) {
+ // aggregate module may not have class
+ }
+ }
+
+ static class Module {
+ static class Dependence {
+ final String name;
+ final boolean reexport;
+ Dependence(String name) {
+ this(name, false);
+ }
+ Dependence(String name, boolean reexport) {
+ this.name = name;
+ this.reexport = reexport;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 11 * hash + Objects.hashCode(this.name);
+ hash = 11 * hash + (this.reexport ? 1 : 0);
+ return hash;
+ }
+
+ public boolean equals(Object o) {
+ Dependence d = (Dependence)o;
+ return this.name.equals(d.name) && this.reexport == d.reexport;
+ }
+ }
+ private final String moduleName;
+ private final Set<Dependence> requires;
+ private final Map<String, Set<String>> exports;
+ private final Set<String> packages;
+
+ private Module(String name,
+ Set<Dependence> requires,
+ Map<String, Set<String>> exports,
+ Set<String> packages) {
+ this.moduleName = name;
+ this.requires = Collections.unmodifiableSet(requires);
+ this.exports = Collections.unmodifiableMap(exports);
+ this.packages = Collections.unmodifiableSet(packages);
+ }
+
+ public String name() {
+ return moduleName;
+ }
+
+ public Set<Dependence> requires() {
+ return requires;
+ }
+
+ public Map<String, Set<String>> exports() {
+ return exports;
+ }
+
+ public Set<String> packages() {
+ return packages;
+ }
+
+ @Override
+ public boolean equals(Object ob) {
+ if (!(ob instanceof Module)) {
+ return false;
+ }
+ Module that = (Module) ob;
+ return (moduleName.equals(that.moduleName)
+ && requires.equals(that.requires)
+ && exports.equals(that.exports)
+ && packages.equals(that.packages));
+ }
+
+ @Override
+ public int hashCode() {
+ int hc = moduleName.hashCode();
+ hc = hc * 43 + requires.hashCode();
+ hc = hc * 43 + exports.hashCode();
+ hc = hc * 43 + packages.hashCode();
+ return hc;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("module ").append(moduleName).append(" {").append("\n");
+ requires.stream().sorted().forEach(d ->
+ sb.append(String.format(" requires %s%s%n", d.reexport ? "public " : "", d.name)));
+ exports.entrySet().stream().filter(e -> e.getValue().isEmpty())
+ .sorted(Map.Entry.comparingByKey())
+ .forEach(e -> sb.append(String.format(" exports %s%n", e.getKey())));
+ exports.entrySet().stream().filter(e -> !e.getValue().isEmpty())
+ .sorted(Map.Entry.comparingByKey())
+ .forEach(e -> sb.append(String.format(" exports %s to %s%n", e.getKey(), e.getValue())));
+ packages.stream().sorted().forEach(pn -> sb.append(String.format(" includes %s%n", pn)));
+ sb.append("}");
+ return sb.toString();
+ }
+
+ static class Builder {
+ private String name;
+ private final Set<Dependence> requires = new HashSet<>();
+ private final Map<String, Set<String>> exports = new HashMap<>();
+ private final Set<String> packages = new HashSet<>();
+
+ public Builder() {
+ }
+
+ public Builder name(String n) {
+ name = n;
+ return this;
+ }
+
+ public Builder require(String d, boolean reexport) {
+ requires.add(new Dependence(d, reexport));
+ return this;
+ }
+
+ public Builder include(String p) {
+ packages.add(p);
+ return this;
+ }
+
+ public Builder export(String p) {
+ return exportTo(p, Collections.emptySet());
+ }
+
+ public Builder exportTo(String p, Set<String> ms) {
+ Objects.requireNonNull(p);
+ Objects.requireNonNull(ms);
+ if (exports.containsKey(p)) {
+ throw new RuntimeException(name + " already exports " + p);
+ }
+ exports.put(p, new HashSet<>(ms));
+ return this;
+ }
+
+ public Module build() {
+ Module m = new Module(name, requires, exports, packages);
+ return m;
+ }
+ }
+ }
+}
--- a/jdk/make/src/classes/build/tools/module/GenerateModulesXml.java Fri Aug 29 18:11:04 2014 +0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,475 +0,0 @@
-/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package build.tools.module;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import javax.xml.namespace.QName;
-import javax.xml.stream.*;
-import javax.xml.stream.events.Attribute;
-import javax.xml.stream.events.XMLEvent;
-
-/**
- * This tool is used to generate com/sun/tools/jdeps/resources/modules.xml
- * for jdeps to analyze dependencies and enforce module boundaries.
- *
- * $ java build.tools.module.GenerateModulesXml \
- * com/sun/tools/jdeps/resources/modules.xml $OUTPUTDIR/modules
- *
- * This will generate modules.xml as jdeps resources that extend
- * the metadata to include module membership (jdeps needs the
- * membership information to determine which module a type belongs to.)
- */
-public final class GenerateModulesXml {
- private final static String USAGE =
- "Usage: GenerateModulesXml <output file> build/modules";
-
- public static void main(String[] args) throws Exception {
- if (args.length < 2) {
- System.err.println(USAGE);
- System.exit(-1);
- }
-
- Path outfile = Paths.get(args[0]);
- Path modulepath = Paths.get(args[1]);
-
- if (!Files.isDirectory(modulepath)) {
- System.err.println(modulepath + " is not a directory");
- System.exit(1);
- }
- GenerateModulesXml gentool =
- new GenerateModulesXml(modulepath);
- Set<Module> modules;
- try (InputStream in = GenerateModulesXml.class.getResourceAsStream("modules.xml")) {
- modules = gentool.load(in);
- }
-
- InputStream in = GenerateModulesXml.class.getResourceAsStream("closed/modules.xml");
- if (in != null) {
- try {
- Set<Module> mods = gentool.load(in);
- modules.addAll(mods);
- } finally {
- in.close();
- }
- }
-
- Files.createDirectories(outfile.getParent());
- gentool.writeXML(modules, outfile);
- }
-
- final Path modulepath;
- public GenerateModulesXml(Path modulepath) {
- this.modulepath = modulepath;
- }
-
- private static final String MODULES = "modules";
- private static final String MODULE = "module";
- private static final String NAME = "name";
- private static final String DEPEND = "depend";
- private static final String EXPORT = "export";
- private static final String TO = "to";
- private static final String INCLUDE = "include";
- private static final QName REEXPORTS = new QName("re-exports");
- private Set<Module> load(InputStream in) throws XMLStreamException, IOException {
- Set<Module> modules = new HashSet<>();
- XMLInputFactory factory = XMLInputFactory.newInstance();
- XMLEventReader stream = factory.createXMLEventReader(in);
- Module.Builder mb = null;
- String modulename = null;
- String pkg = null;
- Set<String> permits = new HashSet<>();
- while (stream.hasNext()) {
- XMLEvent event = stream.nextEvent();
- if (event.isStartElement()) {
- String startTag = event.asStartElement().getName().getLocalPart();
- switch (startTag) {
- case MODULES:
- break;
- case MODULE:
- if (mb != null) {
- throw new RuntimeException("end tag for module is missing");
- }
- modulename = getNextTag(stream, NAME);
- mb = new Module.Builder();
- mb.name(modulename);
- break;
- case NAME:
- throw new RuntimeException(event.toString());
- case DEPEND:
- boolean reexports = false;
- Attribute attr = event.asStartElement().getAttributeByName(REEXPORTS);
- if (attr != null) {
- String value = attr.getValue();
- if (value.equals("true") || value.equals("false")) {
- reexports = Boolean.parseBoolean(value);
- } else {
- throw new RuntimeException("unexpected attribute " + attr.toString());
- }
- }
- mb.require(getData(stream), reexports);
- break;
- case INCLUDE:
- throw new RuntimeException("unexpected " + event);
- case EXPORT:
- pkg = getNextTag(stream, NAME);
- break;
- case TO:
- permits.add(getData(stream));
- break;
- default:
- }
- } else if (event.isEndElement()) {
- String endTag = event.asEndElement().getName().getLocalPart();
- switch (endTag) {
- case MODULE:
- buildIncludes(mb, modulename);
- modules.add(mb.build());
- mb = null;
- break;
- case EXPORT:
- if (pkg == null) {
- throw new RuntimeException("export-to is malformed");
- }
- mb.exportTo(pkg, permits);
- pkg = null;
- permits.clear();
- break;
- default:
- }
- } else if (event.isCharacters()) {
- String s = event.asCharacters().getData();
- if (!s.trim().isEmpty()) {
- throw new RuntimeException("export-to is malformed");
- }
- }
- }
- return modules;
- }
-
- private String getData(XMLEventReader reader) throws XMLStreamException {
- XMLEvent e = reader.nextEvent();
- if (e.isCharacters()) {
- return e.asCharacters().getData();
- }
- throw new RuntimeException(e.toString());
- }
-
- private String getNextTag(XMLEventReader reader, String tag) throws XMLStreamException {
- XMLEvent e = reader.nextTag();
- if (e.isStartElement()) {
- String t = e.asStartElement().getName().getLocalPart();
- if (!tag.equals(t)) {
- throw new RuntimeException(e + " expected: " + tag);
- }
- return getData(reader);
- }
- throw new RuntimeException("export-to name is missing:" + e);
- }
- private void writeXML(Set<Module> modules, Path path)
- throws IOException, XMLStreamException
- {
- XMLOutputFactory xof = XMLOutputFactory.newInstance();
- try (OutputStream out = Files.newOutputStream(path)) {
- int depth = 0;
- XMLStreamWriter xtw = xof.createXMLStreamWriter(out, "UTF-8");
- xtw.writeStartDocument("utf-8","1.0");
- writeStartElement(xtw, MODULES, depth);
- modules.stream()
- .sorted(Comparator.comparing(Module::name))
- .forEach(m -> writeModuleElement(xtw, m, depth+1));
- writeEndElement(xtw, depth);
- xtw.writeCharacters("\n");
- xtw.writeEndDocument();
- xtw.flush();
- xtw.close();
- }
- }
-
- private void writeElement(XMLStreamWriter xtw, String element, String value, int depth) {
- try {
- writeStartElement(xtw, element, depth);
- xtw.writeCharacters(value);
- xtw.writeEndElement();
- } catch (XMLStreamException e) {
- throw new RuntimeException(e);
- }
- }
-
- private void writeDependElement(XMLStreamWriter xtw, Module.Dependence d, int depth) {
- try {
- writeStartElement(xtw, DEPEND, depth);
- if (d.reexport) {
- xtw.writeAttribute("re-exports", "true");
- }
- xtw.writeCharacters(d.name);
- xtw.writeEndElement();
- } catch (XMLStreamException e) {
- throw new RuntimeException(e);
- }
- }
-
- private void writeExportElement(XMLStreamWriter xtw, String pkg, int depth) {
- writeExportElement(xtw, pkg, Collections.emptySet(), depth);
- }
-
- private void writeExportElement(XMLStreamWriter xtw, String pkg,
- Set<String> permits, int depth) {
- try {
- writeStartElement(xtw, EXPORT, depth);
- writeElement(xtw, NAME, pkg, depth+1);
- if (!permits.isEmpty()) {
- permits.stream().sorted()
- .forEach(m -> writeElement(xtw, TO, m, depth + 1));
- }
- writeEndElement(xtw, depth);
- } catch (XMLStreamException e) {
- throw new RuntimeException(e);
- }
- }
- private void writeModuleElement(XMLStreamWriter xtw, Module m, int depth) {
- try {
- writeStartElement(xtw, MODULE, depth);
- writeElement(xtw, NAME, m.name(), depth+1);
- m.requires().stream().sorted(Comparator.comparing(d -> d.name))
- .forEach(d -> writeDependElement(xtw, d, depth+1));
- m.exports().keySet().stream()
- .filter(pn -> m.exports().get(pn).isEmpty())
- .sorted()
- .forEach(pn -> GenerateModulesXml.this.writeExportElement(xtw, pn, depth+1));
- m.exports().entrySet().stream()
- .filter(e -> !e.getValue().isEmpty())
- .sorted(Map.Entry.comparingByKey())
- .forEach(e -> writeExportElement(xtw, e.getKey(), e.getValue(), depth+1));
- m.packages().stream().sorted()
- .forEach(p -> writeElement(xtw, INCLUDE, p, depth+1));
- writeEndElement(xtw, depth);
- } catch (XMLStreamException e) {
- throw new RuntimeException(e);
-
- }
- }
-
- /** Two spaces; the default indentation. */
- public static final String DEFAULT_INDENT = " ";
-
- /** stack[depth] indicates what's been written into the current scope. */
- private static String[] stack = new String[] { "\n",
- "\n" + DEFAULT_INDENT,
- "\n" + DEFAULT_INDENT + DEFAULT_INDENT,
- "\n" + DEFAULT_INDENT + DEFAULT_INDENT + DEFAULT_INDENT};
-
- private void writeStartElement(XMLStreamWriter xtw, String name, int depth)
- throws XMLStreamException
- {
- xtw.writeCharacters(stack[depth]);
- xtw.writeStartElement(name);
- }
-
- private void writeEndElement(XMLStreamWriter xtw, int depth) throws XMLStreamException {
- xtw.writeCharacters(stack[depth]);
- xtw.writeEndElement();
- }
-
- private String packageName(Path p) {
- return packageName(p.toString().replace(File.separatorChar, '/'));
- }
- private String packageName(String name) {
- int i = name.lastIndexOf('/');
- return (i > 0) ? name.substring(0, i).replace('/', '.') : "";
- }
-
- private boolean includes(String name) {
- return name.endsWith(".class") && !name.equals("module-info.class");
- }
-
- public void buildIncludes(Module.Builder mb, String modulename) throws IOException {
- Path mclasses = modulepath.resolve(modulename);
- try {
- Files.find(mclasses, Integer.MAX_VALUE, (Path p, BasicFileAttributes attr)
- -> includes(p.getFileName().toString()))
- .map(p -> packageName(mclasses.relativize(p)))
- .forEach(mb::include);
- } catch (NoSuchFileException e) {
- // aggregate module may not have class
- }
- }
-
- static class Module {
- static class Dependence {
- final String name;
- final boolean reexport;
- Dependence(String name) {
- this(name, false);
- }
- Dependence(String name, boolean reexport) {
- this.name = name;
- this.reexport = reexport;
- }
-
- @Override
- public int hashCode() {
- int hash = 5;
- hash = 11 * hash + Objects.hashCode(this.name);
- hash = 11 * hash + (this.reexport ? 1 : 0);
- return hash;
- }
-
- public boolean equals(Object o) {
- Dependence d = (Dependence)o;
- return this.name.equals(d.name) && this.reexport == d.reexport;
- }
- }
- private final String moduleName;
- private final Set<Dependence> requires;
- private final Map<String, Set<String>> exports;
- private final Set<String> packages;
-
- private Module(String name,
- Set<Dependence> requires,
- Map<String, Set<String>> exports,
- Set<String> packages) {
- this.moduleName = name;
- this.requires = Collections.unmodifiableSet(requires);
- this.exports = Collections.unmodifiableMap(exports);
- this.packages = Collections.unmodifiableSet(packages);
- }
-
- public String name() {
- return moduleName;
- }
-
- public Set<Dependence> requires() {
- return requires;
- }
-
- public Map<String, Set<String>> exports() {
- return exports;
- }
-
- public Set<String> packages() {
- return packages;
- }
-
- @Override
- public boolean equals(Object ob) {
- if (!(ob instanceof Module)) {
- return false;
- }
- Module that = (Module) ob;
- return (moduleName.equals(that.moduleName)
- && requires.equals(that.requires)
- && exports.equals(that.exports)
- && packages.equals(that.packages));
- }
-
- @Override
- public int hashCode() {
- int hc = moduleName.hashCode();
- hc = hc * 43 + requires.hashCode();
- hc = hc * 43 + exports.hashCode();
- hc = hc * 43 + packages.hashCode();
- return hc;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("module ").append(moduleName).append(" {").append("\n");
- requires.stream().sorted().forEach(d ->
- sb.append(String.format(" requires %s%s%n", d.reexport ? "public " : "", d.name)));
- exports.entrySet().stream().filter(e -> e.getValue().isEmpty())
- .sorted(Map.Entry.comparingByKey())
- .forEach(e -> sb.append(String.format(" exports %s%n", e.getKey())));
- exports.entrySet().stream().filter(e -> !e.getValue().isEmpty())
- .sorted(Map.Entry.comparingByKey())
- .forEach(e -> sb.append(String.format(" exports %s to %s%n", e.getKey(), e.getValue())));
- packages.stream().sorted().forEach(pn -> sb.append(String.format(" includes %s%n", pn)));
- sb.append("}");
- return sb.toString();
- }
-
- static class Builder {
- private String name;
- private final Set<Dependence> requires = new HashSet<>();
- private final Map<String, Set<String>> exports = new HashMap<>();
- private final Set<String> packages = new HashSet<>();
-
- public Builder() {
- }
-
- public Builder name(String n) {
- name = n;
- return this;
- }
-
- public Builder require(String d, boolean reexport) {
- requires.add(new Dependence(d, reexport));
- return this;
- }
-
- public Builder include(String p) {
- packages.add(p);
- return this;
- }
-
- public Builder export(String p) {
- return exportTo(p, Collections.emptySet());
- }
-
- public Builder exportTo(String p, Set<String> ms) {
- Objects.requireNonNull(p);
- Objects.requireNonNull(ms);
- if (exports.containsKey(p)) {
- throw new RuntimeException(name + " already exports " + p);
- }
- exports.put(p, new HashSet<>(ms));
- return this;
- }
-
- public Module build() {
- Module m = new Module(name, requires, exports, packages);
- return m;
- }
- }
- }
-}