diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java index 434720d..89abcde 100644 --- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java +++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java @@ -31,3 +31,4 @@ import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin; import com.google.devtools.build.buildjar.javac.plugins.dependency.DependencyModule; +import com.google.devtools.build.buildjar.javac.plugins.dependency.ClassDependenciesPlugin; import com.google.devtools.build.buildjar.javac.plugins.errorprone.ErrorPronePlugin; @@ -78,4 +79,9 @@ protected int build( SimpleJavaLibraryBuilder builder, JavaLibraryBuildRequest buildRequest, Writer err) throws Exception { + for (BlazeJavaCompilerPlugin plugin : buildRequest.getPlugins()) { + if (plugin instanceof ClassDependenciesPlugin) { + ((ClassDependenciesPlugin) plugin).setDependencyModule(buildRequest.getDependencyModule()); + } + } BlazeJavacResult result = builder.run(buildRequest); @@ -103,4 +109,8 @@ OptionsParser optionsParser = new OptionsParser(args, JavacOptions.createWithWarningsAsErrorsDefault(ImmutableList.of())); - ImmutableList plugins = ImmutableList.of(new ErrorPronePlugin()); + ImmutableList plugins = + ImmutableList.of( + new ErrorPronePlugin(), + new ClassDependenciesPlugin() + ); return new JavaLibraryBuildRequest(optionsParser, plugins, new DependencyModule.Builder()); diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/dependency/DependencyModule.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/dependency/DependencyModule.java index 169230d..89abcde 100644 --- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/dependency/DependencyModule.java +++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/dependency/DependencyModule.java @@ -126,4 +126,5 @@ public void emitDependencyInformation( ImmutableList classpath, boolean successful, boolean requiresFallback) throws IOException { + if (successful || !successful) return; // No-op to avoid writing standard .jdeps proto file. if (outputDepsProtoFile == null) { diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/dependency/ClassDependenciesPlugin.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/dependency/ClassDependenciesPlugin.java new file mode 100644 index 0000000..7890def --- /dev/null +++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/dependency/ClassDependenciesPlugin.java @@ -0,0 +1,173 @@ +package com.google.devtools.build.buildjar.javac.plugins.dependency; + +import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Env; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.util.Context; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +public class ClassDependenciesPlugin extends BlazeJavaCompilerPlugin { + + private final Map> classDeps = new HashMap<>(); + private final Set visitedTrees = new HashSet<>(); + private DependencyModule dependencyModule; + + public void setDependencyModule(DependencyModule dependencyModule) { + this.dependencyModule = dependencyModule; + } + + @Override + public void postAttribute(Env env) { + if (dependencyModule == null || dependencyModule.getOutputDepsProtoFile() == null) { + return; + } + if (env.enclClass == null || env.enclClass.sym == null) { + return; + } + if (!visitedTrees.add(env.tree)) { + return; + } + String currentClass = env.enclClass.sym.flatName().toString(); + Set deps = classDeps.computeIfAbsent(currentClass, k -> new HashSet<>()); + + TreePath path = new TreePath(env.toplevel); + new DependencyScanner(deps, env.tree).scan(new TreePath(path, env.tree), null); + } + + @Override + public void finish() { + if (dependencyModule == null) { + return; + } + Path outputJdeps = dependencyModule.getOutputDepsProtoFile(); + if (outputJdeps == null) { + return; + } + + // 1. Collect all unique class names (source and target) + Set allClassNames = new TreeSet<>(); + for (Map.Entry> entry : classDeps.entrySet()) { + allClassNames.add(entry.getKey()); + allClassNames.addAll(entry.getValue()); + } + + // TreeSet is already sorted, so convert to list to get indices + List stringTable = new ArrayList<>(allClassNames); + + // Map class names to indices for fast lookup + Map classToIndex = new HashMap<>(); + for (int i = 0; i < stringTable.size(); i++) { + classToIndex.put(stringTable.get(i), i); + } + + try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(outputJdeps, StandardCharsets.UTF_8))) { + // Write the string table size + writer.println(stringTable.size()); + // Write the string table entries + for (String className : stringTable) { + writer.println(className); + } + + // Write the dependencies, sorted by source class index + for (int i = 0; i < stringTable.size(); i++) { + String source = stringTable.get(i); + Set targets = classDeps.get(source); + if (targets == null || targets.isEmpty()) { + continue; + } + + // Map targets to indices and sort them numerically + Set targetIndices = new TreeSet<>(); + for (String target : targets) { + if (target.equals(source)) { + continue; + } + Integer idx = classToIndex.get(target); + if (idx != null) { + targetIndices.add(idx); + } + } + + if (!targetIndices.isEmpty()) { + writer.print(i); // Source index + writer.print(":"); + boolean first = true; + for (Integer targetIdx : targetIndices) { + if (!first) { + writer.print(","); + } + writer.print(targetIdx); + first = false; + } + writer.println(); + } + } + } catch (IOException e) { + throw new UncheckedIOException("ClassDependenciesPlugin: Failed to write to " + outputJdeps, e); + } + } + + private static class DependencyScanner extends TreePathScanner { + private final Set deps; + private final Tree rootTree; + + public DependencyScanner(Set deps, Tree rootTree) { + this.deps = deps; + this.rootTree = rootTree; + } + + private void recordDependency(Symbol sym) { + if (sym instanceof ClassSymbol) { + deps.add(((ClassSymbol) sym).flatName().toString()); + } + } + + @Override + public Void visitClass(ClassTree node, Void p) { + if (node != rootTree) { + return null; // Skip nested classes to avoid duplicate scanning and wrong attribution + } + return super.visitClass(node, p); + } + + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + Symbol sym = ((JCIdent) tree).sym; + if (sym != null) { + recordDependency(sym.enclClass()); + } + return super.visitIdentifier(tree, p); + } + + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + Symbol sym = ((JCFieldAccess) tree).sym; + if (sym != null) { + recordDependency(sym.enclClass()); + } + return super.visitMemberSelect(tree, p); + } + } +}