// Copyright 2014 The Bazel Authors. All rights reserved. // // 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 com.google.devtools.build.buildjar.javac.plugins.dependency; import com.google.devtools.build.lib.view.proto.Deps; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.util.Context; import java.lang.reflect.Field; import java.util.Map; import java.util.Set; import javax.lang.model.util.SimpleTypeVisitor7; import javax.tools.JavaFileObject; /** * A lightweight mechanism for extracting compile-time dependencies from javac, by performing a scan * of the symbol table after compilation finishes. It only includes dependencies from jar files, * which can be interface jars or regular third_party jars, matching the compilation model of Blaze. * Note that JDK8 may provide support for extracting per-class, finer-grained dependencies, and if * that implementation has reasonable overhead it may be a future option. */ public class ImplicitDependencyExtractor { /** Set collecting dependencies names, used for the text output (soon to be removed) */ private final Set<String> depsSet; /** Map collecting dependency information, used for the proto output */ private final Map<String, Deps.Dependency> depsMap; private final TypeVisitor typeVisitor = new TypeVisitor(); private final Set<String> platformJars; /** * ImplicitDependencyExtractor does not guarantee any ordering of the reported dependencies. * Clients should preserve the original classpath ordering if trying to minimize their classpaths * using this information. */ public ImplicitDependencyExtractor( Set<String> depsSet, Map<String, Deps.Dependency> depsMap, Set<String> platformJars) { this.depsSet = depsSet; this.depsMap = depsMap; this.platformJars = platformJars; } /** * Collects the implicit dependencies of the given set of ClassSymbol roots. As we're interested * in differentiating between symbols that were just resolved vs. symbols that were fully * completed by the compiler, we start the analysis by finding all the implicit dependencies * reachable from the given set of roots. For completeness, we then walk the symbol table * associated with the given context and collect the jar files of the remaining class symbols * found there. * * @param context compilation context * @param roots root classes in the implicit dependency collection */ public void accumulate(Context context, Set<ClassSymbol> roots) { Symtab symtab = Symtab.instance(context); // Collect transitive references for root types for (ClassSymbol root : roots) { root.type.accept(typeVisitor, null); } // Collect all other partially resolved types for (ClassSymbol cs : symtab.getAllClasses()) { // When recording we want to differentiate between jar references through completed symbols // and incomplete symbols boolean completed = cs.isCompleted(); if (cs.classfile != null) { collectJarOf(cs.classfile, platformJars, completed); } else if (cs.sourcefile != null) { collectJarOf(cs.sourcefile, platformJars, completed); } } } /** * Attempts to add the jar associated with the given JavaFileObject, if any, to the collection, * filtering out jars on the compilation bootclasspath. * * @param reference JavaFileObject representing a class or source file * @param platformJars classes on javac's bootclasspath * @param completed whether the jar was referenced through a completed symbol */ private void collectJarOf(JavaFileObject reference, Set<String> platformJars, boolean completed) { String name = getJarName(reference); if (name == null) { return; } // Filter out classes in rt.jar if (platformJars.contains(name)) { return; } depsSet.add(name); Deps.Dependency currentDep = depsMap.get(name); // If the dep hasn't been recorded we add it to the map // If it's been recorded as INCOMPLETE but is now complete we upgrade the dependency if (currentDep == null || (completed && currentDep.getKind() == Deps.Dependency.Kind.INCOMPLETE)) { depsMap.put( name, Deps.Dependency.newBuilder() .setKind(completed ? Deps.Dependency.Kind.IMPLICIT : Deps.Dependency.Kind.INCOMPLETE) .setPath(name) .build()); } } public static String getJarName(JavaFileObject file) { if (file == null) { return null; } try { Field field = file.getClass().getDeclaredField("userJarPath"); field.setAccessible(true); return field.get(file).toString(); } catch (NoSuchFieldException e) { return null; } catch (ReflectiveOperationException e) { throw new LinkageError(e.getMessage(), e); } } private static class TypeVisitor extends SimpleTypeVisitor7<Void, Void> { // TODO(bazel-team): Override the visitor methods we're interested in. } }