Java tutorial
// 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(); } }