8293116: Incremental JDK build could be sped up

Reviewed-by: erikj, vromero, ihse
This commit is contained in:
Jan Lahoda 2022-09-21 10:27:58 +00:00
parent e9401e67b3
commit cd1cdcdb0d
7 changed files with 590 additions and 24 deletions
make
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);
}
}
}