blob: 9276d92eb6d291f4458b13f1a2dc443fbbb6c9a8 [file] [log] [blame]
// 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();
}
}