Skip to content

Commit

Permalink
Populate build state for each Javac build to support incremental build
Browse files Browse the repository at this point in the history
  • Loading branch information
testforstephen committed Jul 2, 2024
1 parent 206803d commit 22ef37e
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public void compile(ICompilationUnit[] sourceUnits) {
}
}
});

IJavaProject javaProject = Stream.of(sourceUnits).filter(SourceFile.class::isInstance).map(
SourceFile.class::cast).map(source -> source.resource).map(IResource::getProject).filter(
JavaProject::hasJavaNature).map(JavaCore::create).findFirst().orElse(null);
Expand All @@ -103,9 +104,9 @@ public void compile(ICompilationUnit[] sourceUnits) {
.collect(Collectors.groupingBy(this::computeOutputDirectory));

// Register listener to intercept intermediate results from Javac task.
JavacTaskListener resultListener = new JavacTaskListener(this.compilerConfig, outputSourceMapping);
JavacTaskListener javacListener = new JavacTaskListener(this.compilerConfig, outputSourceMapping);
MultiTaskListener mtl = MultiTaskListener.instance(javacContext);
mtl.add(resultListener);
mtl.add(javacListener);

for (Entry<File, List<ICompilationUnit>> outputSourceSet : outputSourceMapping.entrySet()) {
var outputFile = outputSourceSet.getKey();
Expand Down Expand Up @@ -164,6 +165,12 @@ public int errorCount() {
for (int i = 0; i < sourceUnits.length; i++) {
ICompilationUnit in = sourceUnits[i];
CompilationResult result = new CompilationResult(in, i, sourceUnits.length, Integer.MAX_VALUE);
if (javacListener.getResults().containsKey(in)) {
result = javacListener.getResults().get(in);
result.unitIndex = i;
result.totalUnitsKnown = sourceUnits.length;
}

if (javacProblems.containsKey(in)) {
JavacProblem[] problems = javacProblems.get(in).toArray(new JavacProblem[0]);
result.problems = problems; // JavaBuilder is responsible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,49 @@
package org.eclipse.jdt.internal.javac;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;

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.Tree;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;

public class JavacTaskListener implements TaskListener {
private Map<ICompilationUnit, IContainer> sourceOutputMapping = new HashMap<>();
private Map<ICompilationUnit, CompilationResult> results = new HashMap<>();

public JavacTaskListener(JavacConfig config, Map<File, List<ICompilationUnit>> outputSourceMapping) {
Map<File, IContainer> outputs = config.originalConfig().sourceOutputMapping().values().stream()
Expand Down Expand Up @@ -71,6 +91,87 @@ public void finished(TaskEvent e) {
}
}
}
} else if (e.getKind() == TaskEvent.Kind.ANALYZE) {
CompilationUnitTree unit = e.getCompilationUnit();
JavaFileObject file = e.getSourceFile();
if (!(file instanceof JavacFileObject)) {
return;
}

ICompilationUnit cu = ((JavacFileObject) file).getOriginalUnit();
final CompilationResult result = this.results.computeIfAbsent(cu, (cu1) ->
new CompilationResult(cu1, 0, 0, Integer.MAX_VALUE));
final Map<Symbol, ClassFile> symbolToClassFiles = new HashMap<Symbol, ClassFile>();
Set<String> qualifiedReferences = new TreeSet<>();
Set<String> simpleNameReferences = new TreeSet<>();
Set<String> rootReferences = new TreeSet<>();

TreeScanner scanner = new TreeScanner() {
@Override
public Object visitClass(ClassTree node, Object p) {
if (node instanceof JCClassDecl classDecl) {
String fullName = classDecl.sym.flatName().toString();
String compoundName = fullName.replace('.', '/');
ClassFile parentClass = classDecl.sym.owner == null ? null : symbolToClassFiles.get(classDecl.sym.owner);
ClassFile currentClass = new JavacVirtualClassFile(classDecl, parentClass);
symbolToClassFiles.put(classDecl.sym, currentClass);
result.compiledTypes.put(compoundName.toCharArray(), currentClass);
}

return super.visitClass(node, p);
}

@Override
public Object visitIdentifier(IdentifierTree node, Object p) {
if (node instanceof JCIdent id) {
if ((id.sym instanceof MethodSymbol method && method.isConstructor())
|| id.sym instanceof VarSymbol) {
return super.visitIdentifier(node, p);
}
simpleNameReferences.add(id.name.toString());
if (id.sym instanceof ClassSymbol clazz) {
recordQualifiedReference(clazz.className());
}
}
return super.visitIdentifier(node, p);
}

@Override
public Object visitMemberSelect(MemberSelectTree node, Object p) {
if (node instanceof JCFieldAccess field) {
if (field.sym != null) {
simpleNameReferences.add(field.name.toString());
recordQualifiedReference(node.toString());
}
}
return super.visitMemberSelect(node, p);
}

private void recordQualifiedReference(String qualifiedName) {
int firstDot = qualifiedName.indexOf('.');
if (firstDot > -1) {
rootReferences.add(qualifiedName.substring(0, firstDot));
}

qualifiedReferences.add(qualifiedName);
}
};

scanner.scan(unit, null);

result.simpleNameReferences = simpleNameReferences.stream().map(String::toCharArray).toArray(char[][]::new);
result.rootReferences = rootReferences.stream().map(String::toCharArray).toArray(char[][]::new);
List<char[][]> names = new ArrayList<>();
for (String qualifiedName : qualifiedReferences) {
String[] qualifiedNames = qualifiedName.split("\\.");
char[][] nameChars = Stream.of(qualifiedNames).map(String::toCharArray).toArray(char[][]::new);
names.add(nameChars);
}
result.qualifiedReferences = names.stream().toArray(char[][][]::new);
}
}

public Map<ICompilationUnit, CompilationResult> getResults() {
return this.results;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*******************************************************************************
* Copyright (c) 2024 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/

package org.eclipse.jdt.internal.javac;

import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.VirtualClassFile;

import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;

public class JavacVirtualClassFile extends VirtualClassFile {
JCClassDecl jcClass;

public JavacVirtualClassFile(JCClassDecl jcClass, ClassFile parentFile) {
this.jcClass = jcClass;
this.isNestedType = !(jcClass.sym.owner instanceof PackageSymbol);
this.enclosingClassFile = parentFile;
}

@Override
public char[][] getCompoundName() {
String fullName = this.jcClass.sym.flatName().toString();
String[] names = fullName.split("\\.");
char[][] compoundNames = new char[names.length][];
for (int i = 0; i < names.length; i++) {
compoundNames[i] = names[i].toCharArray();
}

return compoundNames;
}

@Override
public char[] fileName() {
String fullName = this.jcClass.sym.flatName().toString();
String compoundName = fullName.replace('.', '/');
return compoundName.toCharArray();
}

@Override
public boolean hasStructuralChanges() {
// TODO keep it as structural changes for each class update,
// and might consider to optimize it later.
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2024 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/

package org.eclipse.jdt.internal.compiler;

/**
* Represents a virtual class file that may not contain any actual bytes
* but still encapsulates certain class-related properties such as the
* file name and nested type status.
*
* Clients typically use this class when opting for an alternative compiler
* like Javac to return the generated class files state to the project builder.
*
* @since 3.38
*/
public class VirtualClassFile extends ClassFile {

/**
* Return whether the class file contains structural changes. This flag
* is typically used by the incremental builder to decide whether
* dependent classes need recompilation when the class file is modified.
* Only structural changes will require recompilation of dependent classes.
*
* Structural changes are:
* - modifiers changes for the class, the this.fields or the this.methods
* - signature changes for this.fields or this.methods.
* - changes in the number of this.fields or this.methods
* - changes for field constants
* - changes for thrown exceptions
* - change for the super class or any super interfaces
* - changes for member types name or modifiers
*/
public boolean hasStructuralChanges() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,11 @@ protected char[] writeClassFile(ClassFile classFile, SourceFile compilationUnit,
}

protected void writeClassFileContents(ClassFile classFile, IFile file, String qualifiedFileName, boolean isTopLevelType, SourceFile compilationUnit) throws CoreException {
if (classFile instanceof VirtualClassFile) {
// Skip writing class file contents if it's a virtual class file.
return;
}

if (JavaBuilder.DEBUG) {
trace("Writing changed class file " + file.getName());//$NON-NLS-1$
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,15 @@ protected void updateTasksFor(SourceFile sourceFile, CompilationResult result) t
*/
@Override
protected void writeClassFileContents(ClassFile classfile, IFile file, String qualifiedFileName, boolean isTopLevelType, SourceFile compilationUnit) throws CoreException {
if (classfile instanceof VirtualClassFile vclass) {
// Only log state for virtual class file, but not writing out the contents.
if (vclass.hasStructuralChanges()) {
addDependentsOf(new Path(qualifiedFileName), true);
this.newState.wasStructurallyChanged(qualifiedFileName);
}
return;
}

// Before writing out the class file, compare it to the previous file
// If structural changes occurred then add dependent source files
byte[] bytes = classfile.getBytes();
Expand Down

0 comments on commit 22ef37e

Please sign in to comment.