8182285: Speeding up incremental build by hashing module APIs
authorerikj
Fri, 20 Oct 2017 13:33:35 +0200
changeset 47398 1fd27535bc57
parent 47397 7236260e166c
child 47399 fb677b3f0888
8182285: Speeding up incremental build by hashing module APIs Reviewed-by: ihse Contributed-by: jan.lahoda@oracle.com, erik.joelsson@oracle.com
make/BuildNashorn.gmk
make/CompileJavaModules.gmk
make/CompileToolsJdk.gmk
make/common/JavaCompilation.gmk
make/jdk/src/classes/build/tools/depend/Depend.java
make/jdk/src/classes/build/tools/depend/DependTest.java
--- a/make/BuildNashorn.gmk	Fri Oct 20 13:10:35 2017 +0200
+++ b/make/BuildNashorn.gmk	Fri Oct 20 13:33:35 2017 +0200
@@ -55,7 +55,18 @@
     MODULE := jdk.scripting.nashorn, \
     SRC := $(TOPDIR)/src/jdk.scripting.nashorn/share/classes, \
     COPY := .properties .js, \
-    BIN := $(SUPPORT_OUTPUTDIR)/special_classes))
+    BIN := $(SUPPORT_OUTPUTDIR)/special_classes, \
+    CREATE_API_DIGEST := true, \
+))
+
+# Declare dependencies between java compilations of different modules.
+# Since the other modules are declared in different invocations of this file,
+# use the macro to find the correct target file to depend on.
+# Only the javac compilation actually depends on other modules so limit
+# dependency declaration to that by using the *_COMPILE_TARGET variable.
+$(jdk.scripting.nashorn_COMPILE_TARGET): $(foreach d, $(call FindDepsForModule, jdk.scripting.nashorn), \
+    $(call SetupJavaCompilationApiTarget, $d, \
+        $(if $($d_BIN), $($d_BIN), $(JDK_OUTPUTDIR)/modules/$d)))
 
 NASGEN_SRC := $(TOPDIR)/make/nashorn/buildtools/nasgen/src
 ASM_SRC := $(TOPDIR)/src/java.base/share/classes/jdk/internal/org/objectweb/asm
--- a/make/CompileJavaModules.gmk	Fri Oct 20 13:10:35 2017 +0200
+++ b/make/CompileJavaModules.gmk	Fri Oct 20 13:33:35 2017 +0200
@@ -630,6 +630,7 @@
     FAIL_NO_SRC := $(FAIL_NO_SRC), \
     BIN := $(if $($(MODULE)_BIN), $($(MODULE)_BIN), $(JDK_OUTPUTDIR)/modules), \
     HEADERS := $(SUPPORT_OUTPUTDIR)/headers, \
+    CREATE_API_DIGEST := true, \
     ADD_JAVAC_FLAGS := \
         $($(MODULE)_ADD_JAVAC_FLAGS) \
         --module-source-path $(MODULESOURCEPATH) \
@@ -645,7 +646,7 @@
 # Only the javac compilation actually depends on other modules so limit
 # dependency declaration to that by using the *_COMPILE_TARGET variable.
 $($(MODULE)_COMPILE_TARGET): $(foreach d, $(call FindDepsForModule, $(MODULE)), \
-    $(call SetupJavaCompilationCompileTarget, $d, \
+    $(call SetupJavaCompilationApiTarget, $d, \
         $(if $($d_BIN), $($d_BIN), $(JDK_OUTPUTDIR)/modules/$d)))
 
 ################################################################################
--- a/make/CompileToolsJdk.gmk	Fri Oct 20 13:10:35 2017 +0200
+++ b/make/CompileToolsJdk.gmk	Fri Oct 20 13:33:35 2017 +0200
@@ -42,13 +42,14 @@
     $(BUILDTOOLS_OUTPUTDIR)/interim_cldrconverter_classes \
     #
 
-$(eval $(call SetupJavaCompilation, BUILD_TOOLS_JDK, \
+$(eval $(call SetupJavaCompilation,BUILD_TOOLS_JDK, \
     SETUP := GENERATE_OLDBYTECODE, \
     SRC := $(BUILD_TOOLS_SRC_DIRS), \
     EXCLUDES := \
         build/tools/deps \
         build/tools/docs \
         build/tools/jigsaw \
+        build/tools/depend \
         , \
     BIN := $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes, \
     ADD_JAVAC_FLAGS := \
@@ -70,4 +71,21 @@
 
 ################################################################################
 
+$(eval $(call SetupJavaCompilation, COMPILE_DEPEND, \
+    SETUP := GENERATE_OLDBYTECODE, \
+    SRC := $(TOPDIR)/make/jdk/src/classes, \
+    INCLUDES := build/tools/depend, \
+    BIN := $(BUILDTOOLS_OUTPUTDIR)/depend, \
+))
+
+DEPEND_SERVICE_PROVIDER := $(BUILDTOOLS_OUTPUTDIR)/depend/META-INF/services/com.sun.source.util.Plugin
+
+$(DEPEND_SERVICE_PROVIDER):
+	$(call MakeDir, $(BUILDTOOLS_OUTPUTDIR)/depend/META-INF/services)
+	$(ECHO) build.tools.depend.Depend > $@
+
+TARGETS += $(COMPILE_DEPEND) $(DEPEND_SERVICE_PROVIDER)
+
+################################################################################
+
 all: $(TARGETS)
--- a/make/common/JavaCompilation.gmk	Fri Oct 20 13:10:35 2017 +0200
+++ b/make/common/JavaCompilation.gmk	Fri Oct 20 13:33:35 2017 +0200
@@ -147,6 +147,8 @@
 # Parameter 1 is the name of the rule. This name is used as variable prefix,
 # and the targets generated are listed in a variable by that name.
 #
+# The target for public API digest is returned in $1_API_TARGET.
+#
 # Remaining parameters are named arguments. These include:
 #   SETUP:=must point to a previously setup java compiler, for example: SETUP:=BOOTJAVAC
 #   JVM:=path to ..bin/java
@@ -175,6 +177,9 @@
 #   FAIL_NO_SRC:=Set to false to not fail the build if no source files are found,
 #        default is true.
 #   DEBUG_SYMBOLS:=Set to false to disable generation of debug symbols.
+#   CREATE_API_DIGEST:=Set to true to use a javac plugin to generate a public API
+#        hash which can be used for down stream dependencies to only rebuild
+#        when the API changes. Implicitly used in sjavac.
 SetupJavaCompilation = $(NamedParamsMacroTemplate)
 define SetupJavaCompilationBody
 
@@ -327,6 +332,7 @@
         $$(SPACE),%20,$$(subst $$(COMMA),%2C,$$(strip $$($1_SERVER_JVM) $$($1_SJAVAC))))
 
     $1_COMPILE_TARGET := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_batch
+    $1_API_TARGET := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_pubapi
 
     ifeq ($$($1_DISABLE_SJAVAC)x$$(ENABLE_SJAVAC),xyes)
       # Using sjavac to compile.
@@ -386,13 +392,11 @@
                 # Create a pubapi file that only changes when the pubapi changes. Dependent
                 # compilations can use this file to only get recompiled when pubapi has changed.
                 # Grep returns 1 if no matching lines are found. Do not fail for this.
-		$(GREP) -e "^I" $$($1_BIN)$$($1_MODULE_SUBDIR)/javac_state > $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_pubapi.tmp \
-		    || test "$$$$?" = "1"
-		if [ ! -f $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_pubapi ] \
-		    || [ "`$(DIFF) $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_pubapi \
-		        $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_pubapi.tmp`" != "" ]; then \
-		  $(MV) $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_pubapi.tmp \
-		      $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_pubapi; \
+		$(GREP) -e "^I" $$($1_BIN)$$($1_MODULE_SUBDIR)/javac_state \
+		    > $$($1_API_TARGET).tmp || test "$$$$?" = "1"
+		if [ ! -f $$($1_API_TARGET) ] \
+		    || [ "`$(DIFF) $$($1_API_TARGET) $$($1_API_TARGET).tmp`" != "" ]; then \
+		  $(MV) $$($1_API_TARGET).tmp $$($1_API_TARGET); \
 		fi
 
     else
@@ -432,15 +436,26 @@
         $1_JAVAC_CMD := $$($1_JAVAC)
       endif
 
+      ifeq ($$($1_CREATE_API_DIGEST), true)
+        $1_API_DIGEST_FLAGS := \
+            -classpath $(BUILDTOOLS_OUTPUTDIR)/depend \
+            -Xplugin:"depend $$($1_API_TARGET)" \
+            #
+
+        $1_EXTRA_DEPS := $(BUILDTOOLS_OUTPUTDIR)/depend/_the.COMPILE_DEPEND_batch
+      endif
+
       # When not using sjavac, pass along all sources to javac using an @file.
-      $$($1_COMPILE_TARGET): $$($1_SRCS) $$($1_DEPENDS) $$($1_VARDEPS_FILE)
+      $$($1_COMPILE_TARGET): $$($1_SRCS) $$($1_DEPENDS) $$($1_VARDEPS_FILE) \
+          $$($1_EXTRA_DEPS)
 		$$(call MakeDir, $$(@D))
 		$$(eval $$(call ListPathsSafely,$1_SRCS, $$@.tmp))
 		$$(call LogWarn, Compiling $$(words $$($1_SRCS)) files for $1)
 		$$(call ExecuteWithLog, $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$$($1_SAFE_NAME)_batch, \
 		    $$($1_JVM) $$($1_JAVAC_CMD) $$($1_FLAGS) \
 		        -implicit:none \
-			-d $$($1_BIN) $$($1_HEADERS_ARG) @$$@.tmp) && \
+		        $$($1_API_DIGEST_FLAGS) \
+		        -d $$($1_BIN) $$($1_HEADERS_ARG) @$$@.tmp) && \
 		$(MV) $$@.tmp $$@
     endif
 
@@ -497,9 +512,10 @@
 # to declare and evaluate it again.
 # param 1 is for example BUILD_MYPACKAGE
 # param 2 is the output directory (BIN)
-define SetupJavaCompilationCompileTarget
-  $(if $(findstring yes, $(ENABLE_SJAVAC)), $(strip $2)/_the.$(strip $1)_pubapi, \
-      $(strip $2)/_the.$(strip $1)_batch)
-endef
+SetupJavaCompilationCompileTarget = \
+    $(strip $2)/_the.$(strip $1)_batch
+
+SetupJavaCompilationApiTarget = \
+    $(strip $2)/_the.$(strip $1)_pubapi
 
 endif # _JAVA_COMPILATION_GMK
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/jdk/src/classes/build/tools/depend/Depend.java	Fri Oct 20 13:33:35 2017 +0200
@@ -0,0 +1,537 @@
+/*
+ * Copyright (c) 2017, 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.depend;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Documented;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.AnnotationValueVisitor;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementVisitor;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.ModuleElement;
+import javax.lang.model.element.ModuleElement.DirectiveVisitor;
+import javax.lang.model.element.ModuleElement.ExportsDirective;
+import javax.lang.model.element.ModuleElement.OpensDirective;
+import javax.lang.model.element.ModuleElement.ProvidesDirective;
+import javax.lang.model.element.ModuleElement.RequiresDirective;
+import javax.lang.model.element.ModuleElement.UsesDirective;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.QualifiedNameable;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.NullType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.type.UnionType;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.JavaFileObject;
+
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.Plugin;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskEvent.Kind;
+import com.sun.source.util.TaskListener;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import javax.lang.model.element.ElementKind;
+
+public class Depend implements Plugin {
+
+    @Override
+    public String getName() {
+        return "depend";
+    }
+
+    @Override
+    public void init(JavacTask jt, String... args) {
+        jt.addTaskListener(new TaskListener() {
+            private final Map<ModuleElement, Set<PackageElement>> apiPackages = new HashMap<>();
+            private final MessageDigest apiHash;
+            {
+                try {
+                    apiHash = MessageDigest.getInstance("MD5");
+                } catch (NoSuchAlgorithmException ex) {
+                    throw new IllegalStateException(ex);
+                }
+            }
+            @Override
+            public void started(TaskEvent te) {
+            }
+
+            @Override
+            public void finished(TaskEvent te) {
+                if (te.getKind() == Kind.ANALYZE) {
+                    if (te.getSourceFile().isNameCompatible("module-info", JavaFileObject.Kind.SOURCE)) {
+                        ModuleElement mod = (ModuleElement) Trees.instance(jt).getElement(new TreePath(te.getCompilationUnit()));
+                        new APIVisitor(apiHash).visit(mod);
+                    } else if (te.getSourceFile().isNameCompatible("package-info", JavaFileObject.Kind.SOURCE)) {
+                        //ignore - cannot contain important changes (?)
+                    } else {
+                        TypeElement clazz = te.getTypeElement();
+                        ModuleElement mod = jt.getElements().getModuleOf(clazz);
+                        Set<PackageElement> thisModulePackages = apiPackages.computeIfAbsent(mod, m -> {
+                            return ElementFilter.exportsIn(mod.getDirectives())
+                                                .stream()
+                                                .map(ed -> ed.getPackage())
+                                                .collect(Collectors.toSet());
+                        });
+                        if (thisModulePackages.contains(jt.getElements().getPackageOf(clazz))) {
+                            new APIVisitor(apiHash).visit(clazz);
+                        }
+                    }
+                }
+                if (te.getKind() == Kind.COMPILATION) {
+                    String previousSignature = null;
+                    File digestFile = new File(args[0]);
+                    try (InputStream in = new FileInputStream(digestFile)) {
+                        previousSignature = new String(in.readAllBytes(), "UTF-8");
+                    } catch (IOException ex) {
+                        //ignore
+                    }
+                    String currentSignature = Depend.this.toString(apiHash.digest());
+                    if (!Objects.equals(previousSignature, currentSignature)) {
+                        digestFile.getParentFile().mkdirs();
+                        try (OutputStream out = new FileOutputStream(digestFile)) {
+                            out.write(currentSignature.getBytes("UTF-8"));
+                        } catch (IOException ex) {
+                            throw new IllegalStateException(ex);
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    private String toString(byte[] digest) {
+        StringBuilder result = new StringBuilder();
+
+        for (byte b : digest) {
+            result.append(String.format("%X", b));
+        }
+
+        return result.toString();
+    }
+
+    private static final class APIVisitor implements ElementVisitor<Void, Void>,
+                                                     TypeVisitor<Void, Void>,
+                                                     AnnotationValueVisitor<Void, Void>,
+                                                     DirectiveVisitor<Void, Void> {
+
+        private final MessageDigest apiHash;
+        private final Charset utf8;
+
+        public APIVisitor(MessageDigest apiHash) {
+            this.apiHash = apiHash;
+            utf8 = Charset.forName("UTF-8");
+        }
+
+        public Void visit(Iterable<? extends Element> list, Void p) {
+            list.forEach(e -> visit(e, p));
+            return null;
+        }
+
+        private void update(CharSequence data) {
+            apiHash.update(data.toString().getBytes(utf8));
+        }
+
+        private void visit(Iterable<? extends TypeMirror> types) {
+            for (TypeMirror type : types) {
+                visit(type);
+            }
+        }
+
+        private void updateAnnotation(AnnotationMirror am) {
+            update("@");
+            visit(am.getAnnotationType());
+            am.getElementValues()
+              .keySet()
+              .stream()
+              .sorted((ee1, ee2) -> ee1.getSimpleName().toString().compareTo(ee2.getSimpleName().toString()))
+              .forEach(ee -> {
+                  visit(ee);
+                  visit(am.getElementValues().get(ee));
+              });
+        }
+
+        private void updateAnnotations(Iterable<? extends AnnotationMirror> annotations) {
+            for (AnnotationMirror am : annotations) {
+                if (am.getAnnotationType().asElement().getAnnotation(Documented.class) == null)
+                    continue;
+                updateAnnotation(am);
+            }
+        }
+
+        @Override
+        public Void visit(Element e, Void p) {
+            if (e.getKind() != ElementKind.MODULE &&
+                !e.getModifiers().contains(Modifier.PUBLIC) &&
+                !e.getModifiers().contains(Modifier.PROTECTED)) {
+                return null;
+            }
+            update(e.getKind().name());
+            update(e.getModifiers().stream()
+                                   .map(mod -> mod.name())
+                                   .collect(Collectors.joining(",", "[", "]")));
+            update(e.getSimpleName());
+            updateAnnotations(e.getAnnotationMirrors());
+            return e.accept(this, p);
+        }
+
+        @Override
+        public Void visit(Element e) {
+            return visit(e, null);
+        }
+
+        @Override
+        public Void visitModule(ModuleElement e, Void p) {
+            update(String.valueOf(e.isOpen()));
+            update(e.getQualifiedName());
+            e.getDirectives()
+             .stream()
+             .forEach(d -> d.accept(this, null));
+            return null;
+        }
+
+        @Override
+        public Void visitPackage(PackageElement e, Void p) {
+            throw new UnsupportedOperationException("Should not get here.");
+        }
+
+        @Override
+        public Void visitType(TypeElement e, Void p) {
+            visit(e.getTypeParameters(), p);
+            visit(e.getSuperclass());
+            visit(e.getInterfaces());
+            visit(e.getEnclosedElements(), p);
+            return null;
+        }
+
+        @Override
+        public Void visitVariable(VariableElement e, Void p) {
+            visit(e.asType());
+            update(String.valueOf(e.getConstantValue()));
+            return null;
+        }
+
+        @Override
+        public Void visitExecutable(ExecutableElement e, Void p) {
+            update("<");
+            visit(e.getTypeParameters(), p);
+            update(">");
+            visit(e.getReturnType());
+            update("(");
+            visit(e.getParameters(), p);
+            update(")");
+            visit(e.getThrownTypes());
+            update(String.valueOf(e.getDefaultValue()));
+            update(String.valueOf(e.isVarArgs()));
+            return null;
+        }
+
+        @Override
+        public Void visitTypeParameter(TypeParameterElement e, Void p) {
+            visit(e.getBounds());
+            return null;
+        }
+
+        @Override
+        public Void visitUnknown(Element e, Void p) {
+            throw new UnsupportedOperationException("Not supported.");
+        }
+
+        @Override
+        public Void visit(TypeMirror t, Void p) {
+            if (t == null) {
+                update("null");
+                return null;
+            }
+            update(t.getKind().name());
+            updateAnnotations(t.getAnnotationMirrors());
+            t.accept(this, p);
+            return null;
+        }
+
+        @Override
+        public Void visitPrimitive(PrimitiveType t, Void p) {
+            return null; //done
+        }
+
+        @Override
+        public Void visitNull(NullType t, Void p) {
+            return null; //done
+        }
+
+        @Override
+        public Void visitArray(ArrayType t, Void p) {
+            visit(t.getComponentType());
+            update("[]");
+            return null;
+        }
+
+        @Override
+        public Void visitDeclared(DeclaredType t, Void p) {
+            update(((QualifiedNameable) t.asElement()).getQualifiedName());
+            update("<");
+            visit(t.getTypeArguments());
+            update(">");
+            return null;
+        }
+
+        @Override
+        public Void visitError(ErrorType t, Void p) {
+            return visitDeclared(t, p);
+        }
+
+        private final Set<Element> seenVariables = new HashSet<>();
+
+        @Override
+        public Void visitTypeVariable(TypeVariable t, Void p) {
+            Element decl = t.asElement();
+            if (!seenVariables.add(decl)) {
+                return null;
+            }
+            visit(decl, null);
+            visit(t.getLowerBound(), null);
+            visit(t.getUpperBound(), null);
+            seenVariables.remove(decl);
+            return null;
+        }
+
+        @Override
+        public Void visitWildcard(WildcardType t, Void p) {
+            visit(t.getSuperBound());
+            visit(t.getExtendsBound());
+            return null;
+        }
+
+        @Override
+        public Void visitExecutable(ExecutableType t, Void p) {
+            throw new UnsupportedOperationException("Not supported.");
+        }
+
+        @Override
+        public Void visitNoType(NoType t, Void p) {
+            return null;//done
+        }
+
+        @Override
+        public Void visitUnknown(TypeMirror t, Void p) {
+            throw new UnsupportedOperationException("Not supported.");
+        }
+
+        @Override
+        public Void visitUnion(UnionType t, Void p) {
+            update("(");
+            visit(t.getAlternatives());
+            update(")");
+            return null;
+        }
+
+        @Override
+        public Void visitIntersection(IntersectionType t, Void p) {
+            update("(");
+            visit(t.getBounds());
+            update(")");
+            return null;
+        }
+
+        @Override
+        public Void visit(AnnotationValue av, Void p) {
+            return av.accept(this, p);
+        }
+
+        @Override
+        public Void visitBoolean(boolean b, Void p) {
+            update(String.valueOf(b));
+            return null;
+        }
+
+        @Override
+        public Void visitByte(byte b, Void p) {
+            update(String.valueOf(b));
+            return null;
+        }
+
+        @Override
+        public Void visitChar(char c, Void p) {
+            update(String.valueOf(c));
+            return null;
+        }
+
+        @Override
+        public Void visitDouble(double d, Void p) {
+            update(String.valueOf(d));
+            return null;
+        }
+
+        @Override
+        public Void visitFloat(float f, Void p) {
+            update(String.valueOf(f));
+            return null;
+        }
+
+        @Override
+        public Void visitInt(int i, Void p) {
+            update(String.valueOf(i));
+            return null;
+        }
+
+        @Override
+        public Void visitLong(long i, Void p) {
+            update(String.valueOf(i));
+            return null;
+        }
+
+        @Override
+        public Void visitShort(short s, Void p) {
+            update(String.valueOf(s));
+            return null;
+        }
+
+        @Override
+        public Void visitString(String s, Void p) {
+            update(s);
+            return null;
+        }
+
+        @Override
+        public Void visitType(TypeMirror t, Void p) {
+            return visit(t);
+        }
+
+        @Override
+        public Void visitEnumConstant(VariableElement c, Void p) {
+            return visit(c);
+        }
+
+        @Override
+        public Void visitAnnotation(AnnotationMirror a, Void p) {
+            updateAnnotation(a);
+            return null;
+        }
+
+        @Override
+        public Void visitArray(List<? extends AnnotationValue> vals, Void p) {
+            update("(");
+            for (AnnotationValue av : vals) {
+                visit(av);
+            }
+            update(")");
+            return null;
+        }
+
+        @Override
+        public Void visitUnknown(AnnotationValue av, Void p) {
+            throw new UnsupportedOperationException("Not supported.");
+        }
+
+        @Override
+        public Void visitRequires(RequiresDirective d, Void p) {
+            update("RequiresDirective");
+            update(String.valueOf(d.isStatic()));
+            update(String.valueOf(d.isTransitive()));
+            update(d.getDependency().getQualifiedName());
+            return null;
+        }
+
+        @Override
+        public Void visitExports(ExportsDirective d, Void p) {
+            update("ExportsDirective");
+            update(d.getPackage().getQualifiedName());
+            if (d.getTargetModules() != null) {
+                for (ModuleElement me : d.getTargetModules()) {
+                    update(me.getQualifiedName());
+                }
+            } else {
+                update("<none>");
+            }
+            return null;
+        }
+
+        @Override
+        public Void visitOpens(OpensDirective d, Void p) {
+            update("OpensDirective");
+            update(d.getPackage().getQualifiedName());
+            if (d.getTargetModules() != null) {
+                for (ModuleElement me : d.getTargetModules()) {
+                    update(me.getQualifiedName());
+                }
+            } else {
+                update("<none>");
+            }
+            return null;
+        }
+
+        @Override
+        public Void visitUses(UsesDirective d, Void p) {
+            update("UsesDirective");
+            update(d.getService().getQualifiedName());
+            return null;
+        }
+
+        @Override
+        public Void visitProvides(ProvidesDirective d, Void p) {
+            update("ProvidesDirective");
+            update(d.getService().getQualifiedName());
+            update("(");
+            for (TypeElement impl : d.getImplementations()) {
+                update(impl.getQualifiedName());
+            }
+            update(")");
+            return null;
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/jdk/src/classes/build/tools/depend/DependTest.java	Fri Oct 20 13:33:35 2017 +0200
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2017, 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.depend;
+
+import com.sun.source.util.Plugin;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import javax.tools.JavaCompiler;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.ToolProvider;
+
+
+public class DependTest {
+
+    public static void main(String... args) throws Exception {
+        DependTest test = new DependTest();
+
+        test.setupClass();
+
+        test.testMethods();
+        test.testFields();
+        test.testModules();
+        test.testAnnotations();
+    }
+
+    public void testMethods() throws Exception {
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "}",
+                       "package test;" +
+                       "public class Test {\n" +
+                       "    public void test() {\n" +
+                       "    }\n" +
+                       "}",
+                       true);
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "}",
+                       "package test;" +
+                       "public class Test {\n" +
+                       "    private void test() {\n" +
+                       "    }\n" +
+                       "}",
+                       false);
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "    public void test() {\n" +
+                       "    }\n" +
+                       "}",
+                       "package test;" +
+                       "public class Test {\n" +
+                       "}",
+                       true);
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "    private void test() {\n" +
+                       "    }\n" +
+                       "}",
+                       "package test;" +
+                       "public class Test {\n" +
+                       "}",
+                       false);
+    }
+
+    public void testFields() throws Exception {
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "}",
+                       "package test;" +
+                       "public class Test {\n" +
+                       "    public int test;\n" +
+                       "}",
+                       true);
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "}",
+                       "package test;" +
+                       "public class Test {\n" +
+                       "    private int test;\n" +
+                       "}",
+                       false);
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "    public static final int test = 0;\n" +
+                       "}",
+                       "package test;" +
+                       "public class Test {\n" +
+                       "    public static final int test = 1;\n" +
+                       "}",
+                       true);
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "    public int test;\n" +
+                       "}",
+                       "package test;" +
+                       "public class Test {\n" +
+                       "}",
+                       true);
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "    private int test;\n" +
+                       "}",
+                       "package test;" +
+                       "public class Test {\n" +
+                       "}",
+                       false);
+    }
+
+    public void testAnnotations() throws Exception {
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "}",
+                       "package test;" +
+                       "@SuppressWarnings(\"any\")\n" +
+                       "public class Test {\n" +
+                       "}",
+                       false);
+        doOrdinaryTest("package test;" +
+                       "public class Test {\n" +
+                       "}",
+                       "package test;" +
+                       "@Deprecated\n" +
+                       "public class Test {\n" +
+                       "}",
+                       true);
+    }
+
+    public void testModules() throws Exception {
+        doModuleTest("module m { }",
+                     "module m { requires java.compiler; }",
+                     true);
+        doModuleTest("module m { requires java.compiler; }",
+                     "module m { requires java.compiler; }",
+                     false);
+        doModuleTest("module m { requires java.compiler; }",
+                     "module m { requires jdk.compiler; }",
+                     true);
+        doModuleTest("module m { }",
+                     "module m { exports test; }",
+                     true);
+        doModuleTest("module m { }",
+                     "module m { exports test to java.base; }",
+                     true);
+        doModuleTest("module m { }",
+                     "module m { exports test to java.compiler; }",
+                     true);
+        doModuleTest("module m { }",
+                     "module m { uses test.Test1; }",
+                     true);
+        doModuleTest("module m { uses test.Test1; }",
+                     "module m { uses test.Test2; }",
+                     true);
+        doModuleTest("module m { }",
+                     "module m { provides test.Test1 with test.TestImpl1; }",
+                     true);
+        doModuleTest("module m { provides test.Test1 with test.TestImpl1; }",
+                     "module m { provides test.Test2 with test.TestImpl1; }",
+                     true);
+        doModuleTest("module m { provides test.Test1 with test.TestImpl1; }",
+                     "module m { provides test.Test1 with test.TestImpl2; }",
+                     true);
+    }
+
+    private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+    private Path depend;
+    private Path scratchServices;
+    private Path scratchClasses;
+    private Path apiHash;
+
+    private void setupClass() throws IOException {
+        depend = Paths.get(Depend.class.getProtectionDomain().getCodeSource().getLocation().getPath());
+        Path scratch = Files.createTempDirectory("depend-test");
+        scratchServices = scratch.resolve("services");
+        Path scratchClassesServices = scratchServices.resolve("META-INF").resolve("services");
+        Files.createDirectories(scratchClassesServices);
+
+        try (OutputStream out = Files.newOutputStream(scratchClassesServices.resolve(Plugin.class.getName()))) {
+            out.write(Depend.class.getName().getBytes());
+        }
+
+        scratchClasses = scratch.resolve("classes");
+
+        Files.createDirectories(scratchClasses);
+
+        apiHash = scratch.resolve("api");
+    }
+
+    private void doOrdinaryTest(String codeBefore, String codeAfter, boolean hashChangeExpected) throws Exception {
+        List<String> options =
+                Arrays.asList("-d", scratchClasses.toString(),
+                              "-processorpath", depend.toString() + File.pathSeparator + scratchServices.toString(),
+                              "-Xplugin:depend " + apiHash.toString());
+        List<TestJavaFileObject> beforeFiles =
+                Arrays.asList(new TestJavaFileObject("module-info", "module m { exports test; }"),
+                              new TestJavaFileObject("test.Test", codeBefore));
+        compiler.getTask(null, null, null, options, null, beforeFiles).call();
+        byte[] originalHash = Files.readAllBytes(apiHash);
+        List<TestJavaFileObject> afterFiles =
+                Arrays.asList(new TestJavaFileObject("module-info", "module m { exports test; }"),
+                              new TestJavaFileObject("test.Test", codeAfter));
+        compiler.getTask(null, null, null, options, null, afterFiles).call();
+        byte[] newHash = Files.readAllBytes(apiHash);
+
+        if (Arrays.equals(originalHash, newHash) ^ !hashChangeExpected) {
+            throw new AssertionError("Unexpected hash state.");
+        }
+    }
+
+    private void doModuleTest(String codeBefore, String codeAfter, boolean hashChangeExpected) throws Exception {
+        List<String> options =
+                Arrays.asList("-d", scratchClasses.toString(),
+                              "-processorpath", depend.toString() + File.pathSeparator + scratchServices.toString(),
+                              "-Xplugin:depend " + apiHash.toString());
+        List<TestJavaFileObject> beforeFiles =
+                Arrays.asList(new TestJavaFileObject("module-info", codeBefore),
+                              new TestJavaFileObject("test.Test1", "package test; public interface Test1 {}"),
+                              new TestJavaFileObject("test.Test2", "package test; public interface Test2 {}"),
+                              new TestJavaFileObject("test.TestImpl1", "package test; public class TestImpl1 implements Test1, Test2 {}"),
+                              new TestJavaFileObject("test.TestImpl2", "package test; public class TestImpl2 implements Test1, Test2 {}"));
+        compiler.getTask(null, null, null, options, null, beforeFiles).call();
+        byte[] originalHash = Files.readAllBytes(apiHash);
+        List<TestJavaFileObject> afterFiles =
+                Arrays.asList(new TestJavaFileObject("module-info", codeAfter),
+                              new TestJavaFileObject("test.Test1", "package test; public interface Test1 {}"),
+                              new TestJavaFileObject("test.Test2", "package test; public interface Test2 {}"),
+                              new TestJavaFileObject("test.TestImpl1", "package test; public class TestImpl1 implements Test1, Test2 {}"),
+                              new TestJavaFileObject("test.TestImpl2", "package test; public class TestImpl2 implements Test1, Test2 {}"));
+        compiler.getTask(null, null, null, options, null, afterFiles).call();
+        byte[] newHash = Files.readAllBytes(apiHash);
+
+        if (Arrays.equals(originalHash, newHash) ^ !hashChangeExpected) {
+            throw new AssertionError("Unexpected hash state.");
+        }
+    }
+
+    private static final class TestJavaFileObject extends SimpleJavaFileObject {
+
+        private final String code;
+
+        public TestJavaFileObject(String className, String code) throws URISyntaxException {
+            super(new URI("mem:/" + className.replace('.', '/') + ".java"), Kind.SOURCE);
+            this.code = code;
+        }
+
+        @Override
+        public CharSequence getCharContent(boolean arg0) throws IOException {
+            return code;
+        }
+
+    }
+}