com.google.devtools.build.buildjar.javac.plugins.dependency.ImplicitDependencyExtractor.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.buildjar.javac.plugins.dependency.ImplicitDependencyExtractor.java

Source

// 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.common.collect.ImmutableSet;
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.file.JavacFileManager;
import com.sun.tools.javac.util.Context;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.Map;
import java.util.Set;
import javax.lang.model.util.SimpleTypeVisitor7;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;

/**
 * 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 JavaFileManager fileManager;

    /**
     * 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,
            JavaFileManager fileManager) {
        this.depsSet = depsSet;
        this.depsMap = depsMap;
        this.fileManager = fileManager;
    }

    /**
     * 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);
        if (symtab.classes == null) {
            return;
        }

        // Collect transitive references for root types
        for (ClassSymbol root : roots) {
            root.type.accept(typeVisitor, null);
        }

        Set<String> platformJars = getPlatformJars(fileManager);

        // Collect all other partially resolved types
        for (ClassSymbol cs : symtab.classes.values()) {
            // 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);
            }
        }
    }

    /** Collect the set of jars on the compilation bootclasspath. */
    public static Set<String> getPlatformJars(JavaFileManager fileManager) {

        if (fileManager instanceof JavacFileManager) {
            JavacFileManager jfm = (JavacFileManager) fileManager;
            ImmutableSet.Builder<String> result = ImmutableSet.builder();
            for (Path path : jfm.getLocationAsPaths(StandardLocation.PLATFORM_CLASS_PATH)) {
                result.add(path.toString());
            }
            return result.build();
        }

        // TODO(cushon): Assuming JavacFileManager is brittle, but in practice it is the only
        // implementation that matters.
        throw new IllegalStateException("Unsupported file manager type: " + fileManager.getClass().getName());
    }

    /**
     * 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.
    }
}