Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incremental Java compilation on public constant change #16495

Merged
merged 13 commits into from
Apr 27, 2021
Merged
430 changes: 0 additions & 430 deletions .teamcity/performance-tests-ci.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .teamcity/subprojects.json
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@
{
"dirName": "java-compiler-plugin",
"name": "java-compiler-plugin",
"unitTests": false,
"unitTests": true,
"functionalTests": false,
"crossVersionTests": false
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public ClassPath findClassPath(String name) {
return classpath;
}
if (name.equals("JAVA-COMPILER")) {
return addJavaCompilerModules(ClassPath.EMPTY);
return addJavaCompilerModules(moduleRegistry.getExternalModule("fastutil").getClasspath());
}
if (name.equals("DEPENDENCIES-EXTENSION-COMPILER")) {
ClassPath classpath = ClassPath.EMPTY;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.gradle.integtests.fixtures

import groovy.io.FileType
import org.codehaus.groovy.control.CompilerConfiguration
import org.gradle.internal.FileUtils
import org.gradle.util.internal.TextUtil

Expand Down Expand Up @@ -150,4 +151,14 @@ class CompilationOutputsFixture {
}
changed
}

Object evaluate(@GroovyBuildScriptLanguage String script) {
CompilerConfiguration config = new CompilerConfiguration()
config.classpath.add("${targetDir}/$lang/$sourceSet".toString())
new GroovyShell(config).evaluate(script)
}

void execute(@GroovyBuildScriptLanguage String script) {
evaluate(script)
}
}
6 changes: 6 additions & 0 deletions subprojects/java-compiler-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ tasks.withType<JavaCompile>().configureEach {
sourceCompatibility = "8"
targetCompatibility = "8"
}

tasks.withType<Test>().configureEach {
if (!javaVersion.isJava9Compatible) {
classpath += javaLauncher.get().metadata.installationPath.files("lib/tools.jar")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
package org.gradle.internal.compiler.java;

import com.sun.source.util.JavacTask;
import org.gradle.internal.compiler.java.listeners.classnames.ClassNameCollector;
import org.gradle.internal.compiler.java.listeners.constants.ConstantDependentsConsumer;
import org.gradle.internal.compiler.java.listeners.constants.ConstantsCollector;

import javax.annotation.processing.Processor;
import javax.tools.JavaCompiler;
Expand All @@ -24,6 +27,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

Expand All @@ -33,20 +37,26 @@
* in its own subproject and uses as little dependencies as possible (in particular
* it only depends on JDK types).
*
* It's accessed with reflection so move it with care to other packages.
*
* This class is therefore loaded (and tested) via reflection in org.gradle.api.internal.tasks.compile.JdkTools.
*/
@SuppressWarnings("unused")
public class IncrementalCompileTask implements JavaCompiler.CompilationTask {

private final Function<File, Optional<String>> relativize;
private final Consumer<Map<String, Collection<String>>> onComplete;
private final Consumer<Map<String, Collection<String>>> classNameConsumer;
private final ConstantDependentsConsumer constantDependentsConsumer;
private final JavacTask delegate;

public IncrementalCompileTask(JavaCompiler.CompilationTask delegate,
Function<File, Optional<String>> relativize,
Consumer<Map<String, Collection<String>>> onComplete) {
Consumer<Map<String, Collection<String>>> classNamesConsumer,
BiConsumer<String, String> publicDependentDelegate,
BiConsumer<String, String> privateDependentDelegate) {
this.relativize = relativize;
this.onComplete = onComplete;
this.classNameConsumer = classNamesConsumer;
this.constantDependentsConsumer = new ConstantDependentsConsumer(publicDependentDelegate, privateDependentDelegate);
if (delegate instanceof JavacTask) {
this.delegate = (JavacTask) delegate;
} else {
Expand All @@ -71,12 +81,14 @@ public void setLocale(Locale locale) {

@Override
public Boolean call() {
ClassNameCollector collector = new ClassNameCollector(relativize, delegate.getElements());
delegate.addTaskListener(collector);
ClassNameCollector classNameCollector = new ClassNameCollector(relativize, delegate.getElements());
ConstantsCollector constantsCollector = new ConstantsCollector(delegate, constantDependentsConsumer);
delegate.addTaskListener(classNameCollector);
delegate.addTaskListener(constantsCollector);
try {
return delegate.call();
} finally {
onComplete.accept(collector.getMapping());
classNameConsumer.accept(classNameCollector.getMapping());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.internal.compiler.java;
package org.gradle.internal.compiler.java.listeners.classnames;

import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.gradle.internal.compiler.java.listeners.constants;

import java.util.function.BiConsumer;

public class ConstantDependentsConsumer {

private final BiConsumer<String, String> accessibleDependentDelegate;
private final BiConsumer<String, String> privateDependentDelegate;

public ConstantDependentsConsumer(BiConsumer<String, String> accessibleDependentConsumer, BiConsumer<String, String> privateDependentConsumer) {
this.accessibleDependentDelegate = accessibleDependentConsumer;
this.privateDependentDelegate = privateDependentConsumer;
}

/**
* Consume "accessible" dependents of a constant. Accessible dependents in this context
* are dependents that have a constant calculated from constant from origin.
*
* Example of accessible dependent:
* class A {
* public static final int CALCULATE_ACCESSIBLE_CONSTANT = CONSTANT;
* }
*/
public void consumeAccessibleDependent(String constantOrigin, String constantDependent) {
accessibleDependentDelegate.accept(constantOrigin, constantDependent);
}

/**
* Consume "private" dependents of a constant.
*
* Example of private constant dependent:
* class A {
* public static int method() {
* return CONSTANT;
* }
* }
*/
public void consumePrivateDependent(String constantOrigin, String constantDependent) {
privateDependentDelegate.accept(constantOrigin, constantDependent);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.gradle.internal.compiler.java.listeners.constants;

import com.sun.source.util.JavacTask;
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 java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class ConstantsCollector implements TaskListener {

private final JavacTask task;
private final Map<String, Collection<String>> mapping;
private final ConstantDependentsConsumer constantDependentsConsumer;

public ConstantsCollector(JavacTask task, ConstantDependentsConsumer constantDependentsConsumer) {
this.task = task;
this.mapping = new HashMap<>();
this.constantDependentsConsumer = constantDependentsConsumer;
}

public Map<String, Collection<String>> getMapping() {
return mapping;
}

@Override
public void started(TaskEvent e) {

}

@Override
public void finished(TaskEvent e) {
if (e.getKind() == Kind.ANALYZE) {
Trees trees = Trees.instance(task);
ConstantsTreeVisitor visitor = new ConstantsTreeVisitor(task.getElements(), trees, constantDependentsConsumer);
TreePath path = trees.getPath(e.getCompilationUnit(), e.getCompilationUnit());
visitor.scan(path, null);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.gradle.internal.compiler.java.listeners.constants;

import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.PackageTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;

import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import java.util.Set;

import static com.sun.source.tree.Tree.Kind.METHOD_INVOCATION;

public class ConstantsTreeVisitor extends TreePathScanner<ConstantsVisitorContext, ConstantsVisitorContext> {

private final Elements elements;
private final Trees trees;
private final ConstantDependentsConsumer consumer;

public ConstantsTreeVisitor(Elements elements, Trees trees, ConstantDependentsConsumer consumer) {
this.elements = elements;
this.trees = trees;
this.consumer = consumer;
}

@Override
public ConstantsVisitorContext visitCompilationUnit(CompilationUnitTree node, ConstantsVisitorContext constantConsumer) {
return super.visitCompilationUnit(node, constantConsumer);
}

@Override
public ConstantsVisitorContext visitAssignment(AssignmentTree node, ConstantsVisitorContext constantConsumer) {
return super.visitAssignment(node, constantConsumer);
}

@Override
@SuppressWarnings("Since15")
public ConstantsVisitorContext visitPackage(PackageTree node, ConstantsVisitorContext constantConsumer) {
Element element = trees.getElement(getCurrentPath());

// Collect classes for visited class
String visitedClass = ((PackageElement) element).getQualifiedName().toString();
// Always add self, so we know this class was visited
consumer.consumePrivateDependent(visitedClass, visitedClass);
super.visitPackage(node, new ConstantsVisitorContext(visitedClass, consumer::consumePrivateDependent));

// Return back previous collected classes
return constantConsumer;
}

@Override
public ConstantsVisitorContext visitClass(ClassTree node, ConstantsVisitorContext constantConsumer) {
Element element = trees.getElement(getCurrentPath());

// Collect classes for visited class
String visitedClass = getBinaryClassName((TypeElement) element);
// Always add self, so we know this class was visited
consumer.consumePrivateDependent(visitedClass, visitedClass);
super.visitClass(node, new ConstantsVisitorContext(visitedClass, consumer::consumePrivateDependent));

// Return back previous collected classes
return constantConsumer;
}

@Override
public ConstantsVisitorContext visitVariable(VariableTree node, ConstantsVisitorContext constantConsumer) {
if (isAccessibleConstantVariableDeclaration(node) && node.getInitializer() != null && node.getInitializer().getKind() != METHOD_INVOCATION) {
// We now just check, that constant declaration is not `static {}` or `CONSTANT = methodInvocation()`,
// but it could be further optimized to check if expression is one that can be inlined or not.
return super.visitVariable(node, new ConstantsVisitorContext(constantConsumer.getVisitedClass(), consumer::consumeAccessibleDependent));
} else {
return super.visitVariable(node, constantConsumer);
}
}

private boolean isAccessibleConstantVariableDeclaration(VariableTree node) {
Set<Modifier> modifiers = node.getModifiers().getFlags();
return modifiers.contains(Modifier.FINAL) && modifiers.contains(Modifier.STATIC) && !modifiers.contains(Modifier.PRIVATE);
}

@Override
public ConstantsVisitorContext visitMemberSelect(MemberSelectTree node, ConstantsVisitorContext context) {
Element element = trees.getElement(getCurrentPath());
if (isPrimitiveConstantVariable(element)) {
context.addConstantOrigin(getBinaryClassName((TypeElement) element.getEnclosingElement()));
}
return super.visitMemberSelect(node, context);
}

@Override
public ConstantsVisitorContext visitIdentifier(IdentifierTree node, ConstantsVisitorContext context) {
Element element = trees.getElement(getCurrentPath());

if (isPrimitiveConstantVariable(element)) {
context.addConstantOrigin(getBinaryClassName((TypeElement) element.getEnclosingElement()));
}
return super.visitIdentifier(node, context);
}

private String getBinaryClassName(TypeElement typeElement) {
if (typeElement.getNestingKind().isNested()) {
return elements.getBinaryName(typeElement).toString();
} else {
return typeElement.getQualifiedName().toString();
}
}

private boolean isPrimitiveConstantVariable(Element element) {
return element instanceof VariableElement
&& element.getEnclosingElement() instanceof TypeElement
&& ((VariableElement) element).getConstantValue() != null;
}

}
Loading