Skip to content

Commit

Permalink
Merge pull request #16495 from asodja/CONSTANTS-ANALYSIS
Browse files Browse the repository at this point in the history
Incremental Java compilation on public constant change
  • Loading branch information
melix committed Apr 27, 2021
2 parents b2fe94f + c9c39b1 commit 12254f2
Show file tree
Hide file tree
Showing 88 changed files with 3,015 additions and 296 deletions.
2 changes: 1 addition & 1 deletion .teamcity/subprojects.json
Expand Up @@ -394,7 +394,7 @@
{
"dirName": "java-compiler-plugin",
"name": "java-compiler-plugin",
"unitTests": false,
"unitTests": true,
"functionalTests": false,
"crossVersionTests": false
},
Expand Down
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
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
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")
}
}
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
@@ -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
@@ -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);
}

}
@@ -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);
}
}

}
@@ -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;
}

}

0 comments on commit 12254f2

Please sign in to comment.