com.google.devtools.build.importdeps.ImportDepsChecker.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.importdeps.ImportDepsChecker.java

Source

// Copyright 2018 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.importdeps;

import static com.google.common.base.Preconditions.checkState;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState;
import com.google.devtools.build.importdeps.ResultCollector.MissingMember;
import com.google.devtools.build.lib.view.proto.Deps.Dependencies;
import com.google.devtools.build.lib.view.proto.Deps.Dependency;
import com.google.devtools.build.lib.view.proto.Deps.Dependency.Kind;
import java.io.Closeable;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.Objects;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipFile;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassReader;

/**
 * Checker that checks the classes in the input jars have complete dependencies. If not, output the
 * missing dependencies to a file.
 */
public final class ImportDepsChecker implements Closeable {

    private final ClassCache classCache;
    private final ResultCollector resultCollector;
    private final ImmutableList<Path> inputJars;

    public ImportDepsChecker(ImmutableList<Path> bootclasspath, ImmutableList<Path> directClasspath,
            ImmutableList<Path> classpath, ImmutableList<Path> inputJars) throws IOException {
        this.classCache = new ClassCache(bootclasspath, directClasspath, classpath, inputJars);
        this.resultCollector = new ResultCollector();
        this.inputJars = inputJars;
    }

    /**
     * Checks for dependency problems in the given input jars agains the classpath.
     *
     * @return {@literal true} for no problems, {@literal false} otherwise.
     */
    public boolean check() throws IOException {
        for (Path path : inputJars) {
            try (ZipFile jarFile = new ZipFile(path.toFile())) {
                jarFile.stream().forEach(entry -> {
                    String name = entry.getName();
                    if (!name.endsWith(".class")) {
                        return;
                    }
                    try (InputStream inputStream = jarFile.getInputStream(entry)) {
                        ClassReader reader = new ClassReader(inputStream);
                        DepsCheckerClassVisitor checker = new DepsCheckerClassVisitor(classCache, resultCollector);
                        reader.accept(checker, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
                    } catch (IOException e) {
                        throw new IOError(e);
                    } catch (RuntimeException e) {
                        System.err.printf("A runtime exception occurred when processing the class %s "
                                + "in the zip file %s\n", name, path);
                        throw e;
                    }
                });
            }
        }
        return resultCollector.isEmpty();
    }

    /** Emit the jdeps proto. The parameter ruleLabel is optional, indicated with the empty string. */
    public Dependencies emitJdepsProto(String ruleLabel) {
        Dependencies.Builder builder = Dependencies.newBuilder();
        ImmutableList<Path> paths = classCache.collectUsedJarsInRegularClasspath();
        // TODO(b/77723273): Consider "implicit" for Jars only needed to resolve supertypes
        paths.forEach(path -> builder
                .addDependency(Dependency.newBuilder().setKind(Kind.EXPLICIT).setPath(path.toString()).build()));
        return builder.setRuleLabel(ruleLabel).setSuccess(true).build();
    }

    private static final String INDENT = "    ";

    public String computeResultOutput(String ruleLabel) {
        StringBuilder builder = new StringBuilder();
        ImmutableList<String> missingClasses = resultCollector.getSortedMissingClassInternalNames();
        for (String missing : missingClasses) {
            builder.append("Missing ").append(missing.replace('/', '.')).append('\n');
        }

        ImmutableList<IncompleteState> incompleteClasses = resultCollector.getSortedIncompleteClasses();
        for (IncompleteState incomplete : incompleteClasses) {
            builder.append("Incomplete ancestor classpath for ")
                    .append(incomplete.classInfo().get().internalName().replace('/', '.')).append('\n');

            ImmutableList<String> failurePath = incomplete.getResolutionFailurePath();
            checkState(!failurePath.isEmpty(), "The resolution failure path is empty. %s", failurePath);
            builder.append(INDENT).append("missing ancestor: ")
                    .append(failurePath.get(failurePath.size() - 1).replace('/', '.')).append('\n');
            builder.append(INDENT).append("resolution failure path: ").append(failurePath.stream()
                    .map(internalName -> internalName.replace('/', '.')).collect(Collectors.joining(" -> ")))
                    .append('\n');
        }
        ImmutableList<MissingMember> missingMembers = resultCollector.getSortedMissingMembers();
        for (MissingMember missing : missingMembers) {
            builder.append("Missing member '").append(missing.memberName()).append("' in class ")
                    .append(missing.owner().replace('/', '.')).append(" : name=").append(missing.memberName())
                    .append(", descriptor=").append(missing.descriptor()).append('\n');
        }
        if (missingClasses.size() + incompleteClasses.size() + missingMembers.size() != 0) {
            builder.append("===Total===\n").append("missing=").append(missingClasses.size()).append('\n')
                    .append("incomplete=").append(incompleteClasses.size()).append('\n').append("missing_members=")
                    .append(missingMembers.size()).append('\n');
        }

        ImmutableList<Path> indirectJars = resultCollector.getSortedIndirectDeps();
        if (!indirectJars.isEmpty()) {
            ImmutableList<String> labels = extractLabels(indirectJars);
            if (ruleLabel.isEmpty() || labels.isEmpty()) {
                builder.append("*** Missing strict dependencies on the following Jars which don't carry "
                        + "rule labels.\nPlease determine the originating rules, e.g., using Bazel's "
                        + "'query' command, and add them to the dependencies of ")
                        .append(ruleLabel.isEmpty() ? inputJars : ruleLabel).append('\n');
                for (Path jar : indirectJars) {
                    builder.append(jar).append('\n');
                }
            } else {
                builder.append("*** Missing strict dependencies. Run the following command to fix ***\n\n");
                builder.append("    add_dep ");
                for (String indirectLabel : labels) {
                    builder.append(indirectLabel).append(" ");
                }
                builder.append(ruleLabel).append('\n');
            }
        }
        return builder.toString();
    }

    private static ImmutableList<String> extractLabels(ImmutableList<Path> jars) {
        return jars.parallelStream().map(ImportDepsChecker::extractLabel).filter(Objects::nonNull).distinct()
                .sorted().collect(ImmutableList.toImmutableList());
    }

    @Nullable
    private static String extractLabel(Path jarPath) {
        try (JarFile jar = new JarFile(jarPath.toFile())) {
            return jar.getManifest().getMainAttributes().getValue("Target-Label");
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    @Override
    public void close() throws IOException {
        classCache.close();
    }
}