8293116: Incremental JDK build could be sped up
Reviewed-by: erikj, vromero, ihse
This commit is contained in:
parent
e9401e67b3
commit
cd1cdcdb0d
make
CompileInterimLangtools.gmkCompileJavaModules.gmkCompileToolsJdk.gmk
common
jdk/src/classes/build/tools/depend
src/jdk.compiler/share/classes/com/sun/tools/javac/main
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2014, 2022, 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
|
||||
@ -48,6 +48,30 @@ $(BUILDTOOLS_OUTPUTDIR)/gensrc/%.interim/module-info.java: \
|
||||
TARGETS += $(patsubst %, $(BUILDTOOLS_OUTPUTDIR)/gensrc/%/module-info.java, \
|
||||
$(INTERIM_LANGTOOLS_MODULES))
|
||||
|
||||
################################################################################
|
||||
# Generate interim versions of the ToolProvider.java files for the interim
|
||||
# langtools modules, which will allow to load javac from the interim
|
||||
# jdk.compiler.
|
||||
|
||||
INTERIM_TOOL_PROVIDER_PATTERN := \
|
||||
$(foreach m, $(INTERIM_LANGTOOLS_BASE_MODULES), -e 's/"$m"/"$m.interim"/g')
|
||||
|
||||
$(BUILDTOOLS_OUTPUTDIR)/gensrc/java.compiler.interim/javax/tools/ToolProvider.java: \
|
||||
$(TOPDIR)/src/java.compiler/share/classes/javax/tools/ToolProvider.java
|
||||
$(call LogInfo, Generating ToolProvider.java for java.compiler.interim)
|
||||
$(call MakeDir, $(@D))
|
||||
$(SED) $(INTERIM_TOOL_PROVIDER_PATTERN) $< > $@
|
||||
|
||||
java.compiler.interim_EXTRA_FILES := \
|
||||
$(BUILDTOOLS_OUTPUTDIR)/gensrc/java.compiler.interim/javax/tools/ToolProvider.java
|
||||
|
||||
TARGETS += $(BUILDTOOLS_OUTPUTDIR)/gensrc/java.compiler.interim/javax/tools/ToolProvider.java
|
||||
|
||||
################################################################################
|
||||
# Use the up-to-date PreviewFeature.java and NoPreview.java from the current
|
||||
# sources, instead of the versions from the boot JDK, as javac may be referring
|
||||
# to constants from the up-to-date versions.
|
||||
|
||||
$(eval $(call SetupCopyFiles, COPY_PREVIEW_FEATURES, \
|
||||
FILES := $(TOPDIR)/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java \
|
||||
$(TOPDIR)/src/java.base/share/classes/jdk/internal/javac/NoPreview.java, \
|
||||
@ -73,8 +97,10 @@ define SetupInterimModule
|
||||
$(TOPDIR)/src/$1/share/classes, \
|
||||
EXCLUDES := sun javax/tools/snippet-files, \
|
||||
EXCLUDE_FILES := $(TOPDIR)/src/$1/share/classes/module-info.java \
|
||||
$(TOPDIR)/src/$1/share/classes/javax/tools/ToolProvider.java \
|
||||
Standard.java, \
|
||||
EXTRA_FILES := $(BUILDTOOLS_OUTPUTDIR)/gensrc/$1.interim/module-info.java, \
|
||||
EXTRA_FILES := $(BUILDTOOLS_OUTPUTDIR)/gensrc/$1.interim/module-info.java \
|
||||
$($1.interim_EXTRA_FILES), \
|
||||
COPY := .gif .png .xml .css .svg .js .js.template .txt javax.tools.JavaCompilerTool, \
|
||||
BIN := $(BUILDTOOLS_OUTPUTDIR)/interim_langtools_modules/$1.interim, \
|
||||
DISABLED_WARNINGS := module options, \
|
||||
|
@ -121,8 +121,8 @@ TARGETS += $($(MODULE))
|
||||
# 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.
|
||||
$($(MODULE)_COMPILE_TARGET): $(foreach d, $(call FindDepsForModule, $(MODULE)), \
|
||||
# dependency declaration to that by using the *_MODFILELIST variable.
|
||||
$($(MODULE)_MODFILELIST): $(foreach d, $(call FindDepsForModule, $(MODULE)), \
|
||||
$(call SetupJavaCompilationApiTarget, $d, \
|
||||
$(if $($d_BIN), $($d_BIN), $(JDK_OUTPUTDIR)/modules/$d)))
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2011, 2022, 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
|
||||
@ -77,6 +77,13 @@ $(eval $(call SetupJavaCompilation, COMPILE_DEPEND, \
|
||||
INCLUDES := build/tools/depend, \
|
||||
BIN := $(BUILDTOOLS_OUTPUTDIR)/depend, \
|
||||
DISABLED_WARNINGS := options, \
|
||||
JAVAC_FLAGS := \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED, \
|
||||
))
|
||||
|
||||
DEPEND_SERVICE_PROVIDER := $(BUILDTOOLS_OUTPUTDIR)/depend/META-INF/services/com.sun.source.util.Plugin
|
||||
|
@ -402,8 +402,10 @@ define SetupJavaCompilationBody
|
||||
|
||||
$1_COMPILE_TARGET := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_batch
|
||||
$1_FILELIST := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_batch.filelist
|
||||
$1_MODFILELIST := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_batch.modfiles
|
||||
|
||||
$1_API_TARGET := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_pubapi
|
||||
$1_API_INTERNAL := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1_internalapi
|
||||
|
||||
# Put headers in a temp dir to filter out those that actually
|
||||
# changed before copying them to the real header dir.
|
||||
@ -437,6 +439,8 @@ define SetupJavaCompilationBody
|
||||
$1_API_DIGEST_FLAGS := \
|
||||
-classpath $$(BUILDTOOLS_OUTPUTDIR)/depend \
|
||||
-Xplugin:"depend $$($1_API_TARGET)" \
|
||||
"-XDinternalAPIPath=$$($1_API_INTERNAL)" \
|
||||
"-XDLOG_LEVEL=$(LOG_LEVEL)" \
|
||||
#
|
||||
|
||||
$1_EXTRA_DEPS := $$(BUILDTOOLS_OUTPUTDIR)/depend/_the.COMPILE_DEPEND_batch
|
||||
@ -447,16 +451,33 @@ define SetupJavaCompilationBody
|
||||
# list of files.
|
||||
$$($1_FILELIST): $$($1_SRCS) $$($1_VARDEPS_FILE)
|
||||
$$(call MakeDir, $$(@D))
|
||||
$$(call LogWarn, Compiling $$(words $$($1_SRCS)) files for $1)
|
||||
$$(call LogWarn, Compiling up to $$(words $$($1_SRCS)) files for $1)
|
||||
$$(eval $$(call ListPathsSafely, $1_SRCS, $$($1_FILELIST)))
|
||||
|
||||
# Create a $$($1_MODFILELIST) file with significant modified dependencies
|
||||
# (either sources files or the other mark dependencies).
|
||||
# It is then sent using a side-channel
|
||||
# to the custom Depend plugin. The Depend plugin will check the provided list
|
||||
# of modified files, and if none of the Java source files is changed in a way
|
||||
# observable from outside of the file, and the list of modified files does
|
||||
# not include a non-Java source file, it will only compile the modified files.
|
||||
# Otherwise, all module's sources will be compiled. If a non-Java file is included,
|
||||
# it will be considered to be a significant change, and all module source will
|
||||
# be recompiled
|
||||
$$($1_MODFILELIST): $$($1_SRCS) $$($1_DEPENDS) \
|
||||
$$($1_VARDEPS_FILE) $$($1_EXTRA_DEPS) $$($1_JAVAC_SERVER_CONFIG)
|
||||
$$(eval $1_MODFILES := $$?)
|
||||
$$(eval $$(call ListPathsSafely, $1_MODFILES, $$($1_MODFILELIST)))
|
||||
|
||||
# Do the actual compilation
|
||||
$$($1_COMPILE_TARGET): $$($1_SRCS) $$($1_FILELIST) $$($1_DEPENDS) \
|
||||
$$($1_VARDEPS_FILE) $$($1_EXTRA_DEPS) $$($1_JAVAC_SERVER_CONFIG)
|
||||
$$($1_VARDEPS_FILE) $$($1_EXTRA_DEPS) $$($1_JAVAC_SERVER_CONFIG) \
|
||||
$$($1_MODFILELIST)
|
||||
$$(call MakeDir, $$(@D))
|
||||
$$(call ExecuteWithLog, $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$$($1_SAFE_NAME)_batch, \
|
||||
$$($1_JAVAC_CMD) $$($1_FLAGS) \
|
||||
$$($1_API_DIGEST_FLAGS) \
|
||||
-XDmodifiedInputs=$$($1_MODFILELIST) \
|
||||
-d $$($1_BIN) $$($1_HEADERS_ARG) @$$($1_FILELIST)) && \
|
||||
$(TOUCH) $$@
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2022, 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
|
||||
@ -24,6 +24,17 @@
|
||||
*/
|
||||
package build.tools.depend;
|
||||
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.ImportTree;
|
||||
import com.sun.source.tree.LiteralTree;
|
||||
import com.sun.source.tree.MemberReferenceTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.ModifiersTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
@ -86,8 +97,37 @@ 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.TreeScanner;
|
||||
import com.sun.source.util.Trees;
|
||||
import com.sun.tools.javac.api.BasicJavacTask;
|
||||
import com.sun.tools.javac.code.Flags;
|
||||
import com.sun.tools.javac.main.JavaCompiler;
|
||||
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
|
||||
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import com.sun.tools.javac.util.Context.Key;
|
||||
import com.sun.tools.javac.util.ListBuffer;
|
||||
import com.sun.tools.javac.util.Options;
|
||||
import com.sun.tools.javac.util.StringUtils;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.StreamSupport;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.Name;
|
||||
import javax.tools.ForwardingJavaFileManager;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.StandardLocation;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
public class Depend implements Plugin {
|
||||
|
||||
@ -98,6 +138,43 @@ public class Depend implements Plugin {
|
||||
|
||||
@Override
|
||||
public void init(JavacTask jt, String... args) {
|
||||
addExports();
|
||||
|
||||
AtomicBoolean noApiChange = new AtomicBoolean();
|
||||
try {
|
||||
Context context = ((BasicJavacTask) jt).getContext();
|
||||
Options options = Options.instance(context);
|
||||
String modifiedInputs = options.get("modifiedInputs");
|
||||
if (modifiedInputs == null) {
|
||||
throw new IllegalStateException("Expected modifiedInputs to be set using -XDmodifiedInputs=<list-of-files>");
|
||||
}
|
||||
String logLevel = options.get("LOG_LEVEL");
|
||||
boolean debug = "trace".equals(logLevel) || "debug".equals(logLevel);
|
||||
String internalAPIPath = options.get("internalAPIPath");
|
||||
if (internalAPIPath == null) {
|
||||
throw new IllegalStateException("Expected internalAPIPath to be set using -XDinternalAPIPath=<internal-API-path>");
|
||||
}
|
||||
Set<String> modified = new HashSet<>(Files.readAllLines(Paths.get(modifiedInputs)));
|
||||
Path internalAPIDigestFile = Paths.get(internalAPIPath);
|
||||
JavaCompiler compiler = JavaCompiler.instance(context);
|
||||
Class<?> initialFileParserIntf = Class.forName("com.sun.tools.javac.main.JavaCompiler$InitialFileParserIntf");
|
||||
Class<?> initialFileParser = Class.forName("com.sun.tools.javac.main.JavaCompiler$InitialFileParser");
|
||||
Field initialParserKeyField = initialFileParser.getDeclaredField("initialParserKey");
|
||||
@SuppressWarnings("unchecked")
|
||||
Key<Object> key = (Key<Object>) initialParserKeyField.get(null);
|
||||
Object initialParserInstance =
|
||||
Proxy.newProxyInstance(Depend.class.getClassLoader(),
|
||||
new Class<?>[] {initialFileParserIntf},
|
||||
new FilteredInitialFileParser(compiler,
|
||||
modified,
|
||||
internalAPIDigestFile,
|
||||
noApiChange,
|
||||
debug));
|
||||
context.<Object>put(key, initialParserInstance);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
|
||||
jt.addTaskListener(new TaskListener() {
|
||||
private final Map<ModuleElement, Set<PackageElement>> apiPackages = new HashMap<>();
|
||||
private final MessageDigest apiHash;
|
||||
@ -134,7 +211,7 @@ public class Depend implements Plugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (te.getKind() == Kind.COMPILATION) {
|
||||
if (te.getKind() == Kind.COMPILATION && !noApiChange.get()) {
|
||||
String previousSignature = null;
|
||||
File digestFile = new File(args[0]);
|
||||
try (InputStream in = new FileInputStream(digestFile)) {
|
||||
@ -156,6 +233,114 @@ public class Depend implements Plugin {
|
||||
});
|
||||
}
|
||||
|
||||
private void addExports() {
|
||||
var systemCompiler = ToolProvider.getSystemJavaCompiler();
|
||||
try (JavaFileManager jfm = systemCompiler.getStandardFileManager(null, null, null)) {
|
||||
JavaFileManager fm = new ForwardingJavaFileManager<JavaFileManager>(jfm) {
|
||||
@Override
|
||||
public ClassLoader getClassLoader(JavaFileManager.Location location) {
|
||||
if (location == StandardLocation.CLASS_PATH) {
|
||||
return Depend.class.getClassLoader();
|
||||
}
|
||||
return super.getClassLoader(location);
|
||||
}
|
||||
};
|
||||
((JavacTask) systemCompiler.getTask(null, fm, null,
|
||||
List.of("-proc:only", "-XDaccessInternalAPI=true"),
|
||||
List.of("java.lang.Object"), null))
|
||||
.analyze();
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private com.sun.tools.javac.util.List<JCCompilationUnit> doFilteredParse(
|
||||
JavaCompiler compiler, Iterable<JavaFileObject> fileObjects, Set<String> modified,
|
||||
Path internalAPIDigestFile, AtomicBoolean noApiChange,
|
||||
boolean debug) {
|
||||
Map<String, String> internalAPI = new LinkedHashMap<>();
|
||||
if (Files.isReadable(internalAPIDigestFile)) {
|
||||
try {
|
||||
Files.readAllLines(internalAPIDigestFile, StandardCharsets.UTF_8)
|
||||
.forEach(line -> {
|
||||
String[] keyAndValue = line.split("=");
|
||||
internalAPI.put(keyAndValue[0], keyAndValue[1]);
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
Map<JavaFileObject, JCCompilationUnit> files2CUT = new IdentityHashMap<>();
|
||||
boolean fullRecompile = modified.stream()
|
||||
.anyMatch(f -> !StringUtils.toLowerCase(f).endsWith(".java"));
|
||||
ListBuffer<JCCompilationUnit> result = new ListBuffer<>();
|
||||
for (JavaFileObject jfo : fileObjects) {
|
||||
if (modified.contains(jfo.getName())) {
|
||||
JCCompilationUnit parsed = compiler.parse(jfo);
|
||||
files2CUT.put(jfo, parsed);
|
||||
String currentSignature = treeDigest(parsed);
|
||||
if (!currentSignature.equals(internalAPI.get(jfo.getName()))) {
|
||||
fullRecompile |= true;
|
||||
internalAPI.put(jfo.getName(), currentSignature);
|
||||
}
|
||||
result.add(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
if (fullRecompile) {
|
||||
for (JavaFileObject jfo : fileObjects) {
|
||||
if (!modified.contains(jfo.getName())) {
|
||||
JCCompilationUnit parsed = files2CUT.get(jfo);
|
||||
if (parsed == null) {
|
||||
parsed = compiler.parse(jfo);
|
||||
internalAPI.put(jfo.getName(), treeDigest(parsed));
|
||||
}
|
||||
result.add(parsed);
|
||||
}
|
||||
}
|
||||
try (OutputStream out = Files.newOutputStream(internalAPIDigestFile)) {
|
||||
String hashes = internalAPI.entrySet()
|
||||
.stream()
|
||||
.map(e -> e.getKey() + "=" + e.getValue())
|
||||
.collect(Collectors.joining("\n"));
|
||||
out.write(hashes.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
} else {
|
||||
noApiChange.set(true);
|
||||
}
|
||||
if (debug) {
|
||||
long allJavaInputs = StreamSupport.stream(fileObjects.spliterator(), false).count();
|
||||
String module = StreamSupport.stream(fileObjects.spliterator(), false)
|
||||
.map(fo -> fo.toUri().toString())
|
||||
.filter(path -> path.contains("/share/classes/"))
|
||||
.map(path -> path.substring(0, path.indexOf("/share/classes/")))
|
||||
.map(path -> path.substring(path.lastIndexOf("/") + 1))
|
||||
.findAny()
|
||||
.orElseGet(() -> "unknown");
|
||||
String nonJavaModifiedFiles = modified.stream()
|
||||
.filter(f -> !StringUtils.toLowerCase(f)
|
||||
.endsWith(".java"))
|
||||
.collect(Collectors.joining(", "));
|
||||
System.err.println("compiling module: " + module +
|
||||
", all Java inputs: " + allJavaInputs +
|
||||
", modified files (Java or non-Java): " + modified.size() +
|
||||
", full recompile: " + fullRecompile +
|
||||
", non-Java modified files: " + nonJavaModifiedFiles);
|
||||
}
|
||||
return result.toList();
|
||||
}
|
||||
|
||||
private String treeDigest(JCCompilationUnit cu) {
|
||||
try {
|
||||
TreeVisitor v = new TreeVisitor(MessageDigest.getInstance("MD5"));
|
||||
v.scan(cu, null);
|
||||
return Depend.this.toString(v.apiHash.digest());
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
private String toString(byte[] digest) {
|
||||
return HexFormat.of().withUpperCase().formatHex(digest);
|
||||
}
|
||||
@ -537,4 +722,192 @@ public class Depend implements Plugin {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class TreeVisitor extends TreeScanner<Void, Void> {
|
||||
|
||||
private final Set<Name> seenIdentifiers = new HashSet<>();
|
||||
private final MessageDigest apiHash;
|
||||
private final Charset utf8;
|
||||
|
||||
public TreeVisitor(MessageDigest apiHash) {
|
||||
this.apiHash = apiHash;
|
||||
utf8 = Charset.forName("UTF-8");
|
||||
}
|
||||
|
||||
private void update(CharSequence data) {
|
||||
apiHash.update(data.toString().getBytes(utf8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void scan(Tree tree, Void p) {
|
||||
update("(");
|
||||
if (tree != null) {
|
||||
update(tree.getKind().name());
|
||||
};
|
||||
super.scan(tree, p);
|
||||
update(")");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
|
||||
seenIdentifiers.clear();
|
||||
scan(node.getPackage(), p);
|
||||
scan(node.getTypeDecls(), p);
|
||||
scan(((JCCompilationUnit) node).getModuleDecl(), p);
|
||||
List<ImportTree> importantImports = new ArrayList<>();
|
||||
for (ImportTree imp : node.getImports()) {
|
||||
Tree t = imp.getQualifiedIdentifier();
|
||||
if (t.getKind() == Tree.Kind.MEMBER_SELECT) {
|
||||
Name member = ((MemberSelectTree) t).getIdentifier();
|
||||
if (member.contentEquals("*") || seenIdentifiers.contains(member)) {
|
||||
importantImports.add(imp);
|
||||
}
|
||||
} else {
|
||||
//should not happen, possibly erroneous source?
|
||||
importantImports.add(imp);
|
||||
}
|
||||
}
|
||||
importantImports.sort((imp1, imp2) -> {
|
||||
if (imp1.isStatic() ^ imp2.isStatic()) {
|
||||
return imp1.isStatic() ? -1 : 1;
|
||||
} else {
|
||||
return imp1.getQualifiedIdentifier().toString().compareTo(imp2.getQualifiedIdentifier().toString());
|
||||
}
|
||||
});
|
||||
scan(importantImports, p);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitIdentifier(IdentifierTree node, Void p) {
|
||||
update(node.getName());
|
||||
seenIdentifiers.add(node.getName());
|
||||
return super.visitIdentifier(node, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitMemberSelect(MemberSelectTree node, Void p) {
|
||||
update(node.getIdentifier());
|
||||
return super.visitMemberSelect(node, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitMemberReference(MemberReferenceTree node, Void p) {
|
||||
update(node.getName());
|
||||
return super.visitMemberReference(node, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void scan(Iterable<? extends Tree> nodes, Void p) {
|
||||
update("(");
|
||||
super.scan(nodes, p);
|
||||
update(")");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitClass(ClassTree node, Void p) {
|
||||
update(node.getSimpleName());
|
||||
scan(node.getModifiers(), p);
|
||||
scan(node.getTypeParameters(), p);
|
||||
scan(node.getExtendsClause(), p);
|
||||
scan(node.getImplementsClause(), p);
|
||||
scan(node.getMembers()
|
||||
.stream()
|
||||
.filter(this::importantMember)
|
||||
.collect(Collectors.toList()),
|
||||
p);
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean importantMember(Tree m) {
|
||||
return switch (m.getKind()) {
|
||||
case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, RECORD ->
|
||||
!isPrivate(((ClassTree) m).getModifiers());
|
||||
case METHOD ->
|
||||
!isPrivate(((MethodTree) m).getModifiers());
|
||||
case VARIABLE ->
|
||||
!isPrivate(((VariableTree) m).getModifiers()) ||
|
||||
isRecordComponent((VariableTree) m);
|
||||
case BLOCK -> false;
|
||||
default -> throw new IllegalStateException("Unexpected tree kind: " + m.getKind());
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isPrivate(ModifiersTree mt) {
|
||||
return mt.getFlags().contains(Modifier.PRIVATE);
|
||||
}
|
||||
|
||||
private boolean isRecordComponent(VariableTree vt) {
|
||||
return (((JCVariableDecl) vt).mods.flags & Flags.RECORD) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVariable(VariableTree node, Void p) {
|
||||
update(node.getName());
|
||||
return super.visitVariable(node, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitMethod(MethodTree node, Void p) {
|
||||
update(node.getName());
|
||||
scan(node.getModifiers(), p);
|
||||
scan(node.getReturnType(), p);
|
||||
scan(node.getTypeParameters(), p);
|
||||
scan(node.getParameters(), p);
|
||||
scan(node.getReceiverParameter(), p);
|
||||
scan(node.getThrows(), p);
|
||||
scan(node.getDefaultValue(), p);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitLiteral(LiteralTree node, Void p) {
|
||||
update(String.valueOf(node.getValue()));
|
||||
return super.visitLiteral(node, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitModifiers(ModifiersTree node, Void p) {
|
||||
update(node.getFlags().toString());
|
||||
return super.visitModifiers(node, p);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class FilteredInitialFileParser implements InvocationHandler {
|
||||
|
||||
private final JavaCompiler compiler;
|
||||
private final Set<String> modified;
|
||||
private final Path internalAPIDigestFile;
|
||||
private final AtomicBoolean noApiChange;
|
||||
private final boolean debug;
|
||||
|
||||
public FilteredInitialFileParser(JavaCompiler compiler,
|
||||
Set<String> modified,
|
||||
Path internalAPIDigestFile,
|
||||
AtomicBoolean noApiChange,
|
||||
boolean debug) {
|
||||
this.compiler = compiler;
|
||||
this.modified = modified;
|
||||
this.internalAPIDigestFile = internalAPIDigestFile;
|
||||
this.noApiChange = noApiChange;
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
return switch (method.getName()) {
|
||||
case "parse" -> doFilteredParse(compiler,
|
||||
(Iterable<JavaFileObject>) args[0],
|
||||
modified,
|
||||
internalAPIDigestFile,
|
||||
noApiChange,
|
||||
debug);
|
||||
default -> throw new UnsupportedOperationException();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2022, 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
|
||||
@ -52,6 +52,8 @@ public class DependTest {
|
||||
test.testModules();
|
||||
test.testAnnotations();
|
||||
test.testRecords();
|
||||
test.testImports();
|
||||
test.testModifiers();
|
||||
}
|
||||
|
||||
public void testMethods() throws Exception {
|
||||
@ -145,7 +147,8 @@ public class DependTest {
|
||||
"@SuppressWarnings(\"any\")\n" +
|
||||
"public class Test {\n" +
|
||||
"}",
|
||||
false);
|
||||
false,
|
||||
true); //Tree hash does not tolerate undocumented annotations
|
||||
doOrdinaryTest("package test;" +
|
||||
"public class Test {\n" +
|
||||
"}",
|
||||
@ -156,6 +159,83 @@ public class DependTest {
|
||||
true);
|
||||
}
|
||||
|
||||
public void testImports() throws Exception {
|
||||
doOrdinaryTest("package test;" +
|
||||
"public class Test {\n" +
|
||||
"}",
|
||||
"package test;" +
|
||||
"import java.util.List;\n" +
|
||||
"public class Test {\n" +
|
||||
" private List l;\n" +
|
||||
"}",
|
||||
false);
|
||||
doOrdinaryTest("package test;" +
|
||||
"public class Test {\n" +
|
||||
"}",
|
||||
"package test;" +
|
||||
"import java.util.List;\n" +
|
||||
"public class Test {\n" +
|
||||
" public List l;\n" +
|
||||
"}",
|
||||
true);
|
||||
doOrdinaryTest("package test;" +
|
||||
"public class Test {\n" +
|
||||
"}",
|
||||
"package test;" +
|
||||
"import java.util.List;\n" +
|
||||
"public class Test {\n" +
|
||||
" List l;\n" +
|
||||
"}",
|
||||
false,
|
||||
true);
|
||||
doOrdinaryTest("package test;" +
|
||||
"import java.util.*;\n" +
|
||||
"public abstract class Test implements List {\n" +
|
||||
"}\n" +
|
||||
"class H {\n" +
|
||||
" public interface List {}\n" +
|
||||
"}",
|
||||
"package test;" +
|
||||
"import java.util.*;\n" +
|
||||
"import test.H.List;\n" +
|
||||
"public abstract class Test implements List {\n" +
|
||||
"}\n" +
|
||||
"class H {\n" +
|
||||
" public interface List {}\n" +
|
||||
"}",
|
||||
true);
|
||||
doOrdinaryTest("package test;" +
|
||||
"public class Test {\n" +
|
||||
"}",
|
||||
"package test;" +
|
||||
"import java.util.*;\n" +
|
||||
"public class Test {\n" +
|
||||
"}",
|
||||
false,
|
||||
true);
|
||||
doOrdinaryTest("package test;" +
|
||||
"import java.util.*;\n" +
|
||||
"public class Test {\n" +
|
||||
"}",
|
||||
"package test;" +
|
||||
"public class Test {\n" +
|
||||
"}",
|
||||
false,
|
||||
true);
|
||||
}
|
||||
|
||||
public void testModifiers() throws Exception {
|
||||
doOrdinaryTest("package test;" +
|
||||
"public class Test {\n" +
|
||||
" String l;\n" +
|
||||
"}",
|
||||
"package test;" +
|
||||
"public class Test {\n" +
|
||||
" public String l;\n" +
|
||||
"}",
|
||||
true);
|
||||
}
|
||||
|
||||
public void testModules() throws Exception {
|
||||
doModuleTest("module m { }",
|
||||
"module m { requires java.compiler; }",
|
||||
@ -199,11 +279,13 @@ public class DependTest {
|
||||
doOrdinaryTest("package test; public record Test (int x, int y) { }",
|
||||
"package test; public record Test (int x, int y) {" +
|
||||
"public Test { } }", // compact ctr
|
||||
false);
|
||||
false,
|
||||
true);
|
||||
doOrdinaryTest("package test; public record Test (int x, int y) { }",
|
||||
"package test; public record Test (int x, int y) {" +
|
||||
"public Test (int x, int y) { this.x=x; this.y=y;} }", // canonical ctr
|
||||
false);
|
||||
false,
|
||||
true);
|
||||
doOrdinaryTest("package test; public record Test (int x, int y) { }",
|
||||
"package test; public record Test (int y, int x) { }", // reverse
|
||||
true);
|
||||
@ -227,6 +309,7 @@ public class DependTest {
|
||||
private Path scratchServices;
|
||||
private Path scratchClasses;
|
||||
private Path apiHash;
|
||||
private Path treeHash;
|
||||
|
||||
private void setupClass() throws IOException {
|
||||
depend = Paths.get(Depend.class.getProtectionDomain().getCodeSource().getLocation().getPath());
|
||||
@ -244,34 +327,53 @@ public class DependTest {
|
||||
Files.createDirectories(scratchClasses);
|
||||
|
||||
apiHash = scratch.resolve("api");
|
||||
treeHash = scratch.resolve("tree");
|
||||
}
|
||||
|
||||
private void doOrdinaryTest(String codeBefore, String codeAfter, boolean hashChangeExpected) throws Exception {
|
||||
doOrdinaryTest(codeBefore, codeAfter, hashChangeExpected, hashChangeExpected);
|
||||
}
|
||||
|
||||
private void doOrdinaryTest(String codeBefore, String codeAfter, boolean apiHashChangeExpected, boolean treeHashChangeExpected) throws Exception {
|
||||
List<String> options =
|
||||
Arrays.asList("-d", scratchClasses.toString(),
|
||||
"-processorpath", depend.toString() + File.pathSeparator + scratchServices.toString(),
|
||||
"-Xplugin:depend " + apiHash.toString());
|
||||
"-Xplugin:depend " + apiHash.toString() + " " + treeHash.toString(),
|
||||
"-XDmodifiedInputs=build-all");
|
||||
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);
|
||||
byte[] originalApiHash = Files.readAllBytes(apiHash);
|
||||
byte[] originalTreeHash = Files.readAllBytes(treeHash);
|
||||
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);
|
||||
byte[] newApiHash = Files.readAllBytes(apiHash);
|
||||
byte[] newTreeHash = Files.readAllBytes(treeHash);
|
||||
|
||||
if (Arrays.equals(originalHash, newHash) ^ !hashChangeExpected) {
|
||||
throw new AssertionError("Unexpected hash state.");
|
||||
if (Arrays.equals(originalApiHash, newApiHash) ^ !apiHashChangeExpected) {
|
||||
throw new AssertionError("Unexpected API hash state.");
|
||||
}
|
||||
|
||||
if (Arrays.equals(originalTreeHash, newTreeHash) ^ !treeHashChangeExpected) {
|
||||
throw new AssertionError("Unexpected Tree hash state, " +
|
||||
"original: " + new String(originalTreeHash) +
|
||||
", new: " + new String(newTreeHash));
|
||||
}
|
||||
}
|
||||
|
||||
private void doModuleTest(String codeBefore, String codeAfter, boolean hashChangeExpected) throws Exception {
|
||||
doModuleTest(codeBefore, codeAfter, hashChangeExpected, hashChangeExpected);
|
||||
}
|
||||
|
||||
private void doModuleTest(String codeBefore, String codeAfter, boolean apiHashChangeExpected, boolean treeHashChangeExpected) throws Exception {
|
||||
List<String> options =
|
||||
Arrays.asList("-d", scratchClasses.toString(),
|
||||
"-processorpath", depend.toString() + File.pathSeparator + scratchServices.toString(),
|
||||
"-Xplugin:depend " + apiHash.toString());
|
||||
"-Xplugin:depend " + apiHash.toString() + " " + treeHash.toString(),
|
||||
"-XDmodifiedInputs=build-all");
|
||||
List<TestJavaFileObject> beforeFiles =
|
||||
Arrays.asList(new TestJavaFileObject("module-info", codeBefore),
|
||||
new TestJavaFileObject("test.Test1", "package test; public interface Test1 {}"),
|
||||
@ -279,7 +381,8 @@ public class DependTest {
|
||||
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);
|
||||
byte[] originalApiHash = Files.readAllBytes(apiHash);
|
||||
byte[] originalTreeHash = Files.readAllBytes(treeHash);
|
||||
List<TestJavaFileObject> afterFiles =
|
||||
Arrays.asList(new TestJavaFileObject("module-info", codeAfter),
|
||||
new TestJavaFileObject("test.Test1", "package test; public interface Test1 {}"),
|
||||
@ -287,10 +390,17 @@ public class DependTest {
|
||||
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);
|
||||
byte[] newApiHash = Files.readAllBytes(apiHash);
|
||||
byte[] newTreeHash = Files.readAllBytes(treeHash);
|
||||
|
||||
if (Arrays.equals(originalHash, newHash) ^ !hashChangeExpected) {
|
||||
throw new AssertionError("Unexpected hash state.");
|
||||
if (Arrays.equals(originalApiHash, newApiHash) ^ !apiHashChangeExpected) {
|
||||
throw new AssertionError("Unexpected API hash state.");
|
||||
}
|
||||
|
||||
if (Arrays.equals(originalTreeHash, newTreeHash) ^ !treeHashChangeExpected) {
|
||||
throw new AssertionError("Unexpected Tree hash state, " +
|
||||
"original: " + new String(originalTreeHash) +
|
||||
", new: " + new String(newTreeHash));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,7 @@ import com.sun.tools.javac.tree.JCTree.JCMemberReference;
|
||||
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
|
||||
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
import com.sun.tools.javac.util.*;
|
||||
import com.sun.tools.javac.util.Context.Key;
|
||||
import com.sun.tools.javac.util.DefinedBy.Api;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.Factory;
|
||||
import com.sun.tools.javac.util.Log.DiagnosticHandler;
|
||||
@ -990,7 +991,7 @@ public class JavaCompiler {
|
||||
* Parses a list of files.
|
||||
*/
|
||||
public List<JCCompilationUnit> parseFiles(Iterable<JavaFileObject> fileObjects) {
|
||||
return parseFiles(fileObjects, false);
|
||||
return InitialFileParser.instance(context).parse(fileObjects);
|
||||
}
|
||||
|
||||
public List<JCCompilationUnit> parseFiles(Iterable<JavaFileObject> fileObjects, boolean force) {
|
||||
@ -1866,4 +1867,32 @@ public class JavaCompiler {
|
||||
inputFiles.clear();
|
||||
todo.clear();
|
||||
}
|
||||
|
||||
public interface InitialFileParserIntf {
|
||||
public List<JCCompilationUnit> parse(Iterable<JavaFileObject> files);
|
||||
}
|
||||
|
||||
public static class InitialFileParser implements InitialFileParserIntf {
|
||||
|
||||
public static final Key<InitialFileParserIntf> initialParserKey = new Key<>();
|
||||
|
||||
public static InitialFileParserIntf instance(Context context) {
|
||||
InitialFileParserIntf instance = context.get(initialParserKey);
|
||||
if (instance == null)
|
||||
instance = new InitialFileParser(context);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final JavaCompiler compiler;
|
||||
|
||||
private InitialFileParser(Context context) {
|
||||
context.put(initialParserKey, this);
|
||||
this.compiler = JavaCompiler.instance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JCCompilationUnit> parse(Iterable<JavaFileObject> fileObjects) {
|
||||
return compiler.parseFiles(fileObjects, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user