diff options
Diffstat (limited to 'src/java_tools/import_deps_checker/java')
7 files changed, 235 insertions, 98 deletions
diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/AbstractClassEntryState.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/AbstractClassEntryState.java index 209bf83fcb..eeb162f3ba 100644 --- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/AbstractClassEntryState.java +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/AbstractClassEntryState.java @@ -13,7 +13,6 @@ // limitations under the License. package com.google.devtools.build.importdeps; -import static com.google.common.base.Preconditions.checkArgument; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; @@ -100,20 +99,15 @@ public abstract class AbstractClassEntryState { public abstract static class IncompleteState extends AbstractClassEntryState { public static IncompleteState create( - ClassInfo classInfo, ImmutableList<String> resolutionFailurePath) { - checkArgument( - !resolutionFailurePath.isEmpty(), - "The resolution path should contain at least one element, the missing ancestor. %s", - resolutionFailurePath); + ClassInfo classInfo, ResolutionFailureChain resolutionFailureChain) { return new AutoValue_AbstractClassEntryState_IncompleteState( - Optional.of(classInfo), resolutionFailurePath); + Optional.of(classInfo), resolutionFailureChain); } - public abstract ImmutableList<String> getResolutionFailurePath(); + public abstract ResolutionFailureChain resolutionFailureChain(); - public String getMissingAncestor() { - ImmutableList<String> path = getResolutionFailurePath(); - return path.get(path.size() - 1); + public ImmutableList<String> missingAncestors() { + return resolutionFailureChain().missingClasses(); } @Override diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java index 232e69b936..292d5752b2 100644 --- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java @@ -37,6 +37,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.function.Predicate; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -145,43 +146,29 @@ public final class ClassCache implements Closeable { classEntry.zipFile.getEntry(entryName), "The zip entry %s is null.", entryName); try (InputStream inputStream = classEntry.zipFile.getInputStream(zipEntry)) { ClassReader classReader = new ClassReader(inputStream); - ImmutableList<String> resolutionFailurePath = null; + ImmutableList.Builder<ResolutionFailureChain> resolutionFailureChainsBuilder = + ImmutableList.builder(); for (String superName : combineWithoutNull(classReader.getSuperName(), classReader.getInterfaces())) { - LazyClassEntry superClassEntry = lazyClasspath.getLazyEntry(superName); - - if (superClassEntry == null) { - resolutionFailurePath = ImmutableList.of(superName); - break; - } else { - resolveClassEntry(superClassEntry, lazyClasspath); - AbstractClassEntryState superState = superClassEntry.state; - if (superState instanceof ExistingState) { - // Do nothing. Good to proceed. - continue; - } else if (superState instanceof IncompleteState) { - resolutionFailurePath = - ImmutableList.<String>builder() - .add(superName) - .addAll(((IncompleteState) superState).getResolutionFailurePath()) - .build(); - break; - } else { - throw new RuntimeException("Cannot reach here. superState is " + superState); - } - } + Optional<ResolutionFailureChain> failurePath = + resolveSuperClassEntry(superName, lazyClasspath); + failurePath.map(resolutionFailureChainsBuilder::add); } ClassInfoBuilder classInfoBuilder = new ClassInfoBuilder().setJarPath(classEntry.jarPath).setDirect(classEntry.direct); classReader.accept(classInfoBuilder, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - if (resolutionFailurePath == null) { + + ImmutableList<ResolutionFailureChain> resolutionFailureChains = + resolutionFailureChainsBuilder.build(); + if (resolutionFailureChains.isEmpty()) { classEntry.state = ExistingState.create(classInfoBuilder.build(lazyClasspath, /*incomplete=*/ false)); } else { + ClassInfo classInfo = classInfoBuilder.build(lazyClasspath, /*incomplete=*/ true); classEntry.state = IncompleteState.create( - classInfoBuilder.build(lazyClasspath, /*incomplete=*/ true), - resolutionFailurePath); + classInfo, + ResolutionFailureChain.createWithParent(classInfo, resolutionFailureChains)); } } catch (IOException e) { throw new RuntimeException("Error when resolving class entry " + entryName); @@ -193,6 +180,26 @@ public final class ClassCache implements Closeable { throw e; } } + + private static Optional<ResolutionFailureChain> resolveSuperClassEntry( + String superName, LazyClasspath lazyClasspath) { + LazyClassEntry superClassEntry = lazyClasspath.getLazyEntry(superName); + + if (superClassEntry == null) { + return Optional.of(ResolutionFailureChain.createMissingClass(superName)); + } else { + resolveClassEntry(superClassEntry, lazyClasspath); + AbstractClassEntryState superState = superClassEntry.state; + if (superState instanceof ExistingState) { + // Do nothing. Good to proceed. + return Optional.empty(); + } else if (superState instanceof IncompleteState) { + return Optional.of(superState.asIncompleteState().resolutionFailureChain()); + } else { + throw new RuntimeException("Cannot reach here. superState is " + superState); + } + } + } } private static ImmutableList<String> combineWithoutNull( diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java index 0a1e921ad0..3d5a5a6432 100644 --- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.base.Strings; +import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.nio.file.Path; @@ -27,7 +28,7 @@ import java.nio.file.Path; * classes. */ @AutoValue -public abstract class ClassInfo { +public abstract class ClassInfo implements Comparable<ClassInfo> { public static ClassInfo create( String internalName, @@ -64,6 +65,15 @@ public abstract class ClassInfo { return false; } + @Override + public int compareTo(ClassInfo other) { + return ComparisonChain.start() + .compare(internalName(), other.internalName()) + .compare(jarPath(), other.jarPath()) + .compareFalseFirst(directDep(), other.directDep()) + .result(); + } + /** A member is either a method or a field. */ @AutoValue public abstract static class MemberInfo { diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java index b57c273e10..b536a39633 100644 --- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java @@ -174,12 +174,19 @@ public class DepsCheckerClassVisitor extends ClassVisitor { resultCollector.addMissingOrIncompleteClass(internalName, state); } else { if (state.isIncompleteState()) { - String missingAncestor = state.asIncompleteState().getMissingAncestor(); - AbstractClassEntryState ancestorState = classCache.getClassState(missingAncestor); - checkState( - ancestorState.isMissingState(), "The ancestor should be missing. %s", ancestorState); - resultCollector.addMissingOrIncompleteClass(missingAncestor, ancestorState); - resultCollector.addMissingOrIncompleteClass(internalName, state); + state + .asIncompleteState() + .missingAncestors() + .forEach( + missingAncestor -> { + AbstractClassEntryState ancestorState = classCache.getClassState(missingAncestor); + checkState( + ancestorState.isMissingState(), + "The ancestor should be missing. %s", + ancestorState); + resultCollector.addMissingOrIncompleteClass(missingAncestor, ancestorState); + resultCollector.addMissingOrIncompleteClass(internalName, state); + }); } ClassInfo info = state.classInfo().get(); if (!info.directDep()) { diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java index 9276d92eb6..7e837a8f53 100644 --- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java @@ -13,8 +13,8 @@ // limitations under the License. package com.google.devtools.build.importdeps; -import static com.google.common.base.Preconditions.checkState; +import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState; import com.google.devtools.build.importdeps.ResultCollector.MissingMember; @@ -27,9 +27,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; 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; @@ -108,61 +109,21 @@ public final class ImportDepsChecker implements Closeable { 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'); - } + outputMissingClasses(builder, missingClasses); ImmutableList<IncompleteState> incompleteClasses = resultCollector.getSortedIncompleteClasses(); - for (IncompleteState incomplete : incompleteClasses) { - builder - .append("Incomplete ancestor classpath for ") - .append(incomplete.classInfo().get().internalName().replace('/', '.')) - .append('\n'); + outputIncompleteClasses(builder, incompleteClasses); - 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'); - } + outputMissingMembers(builder, missingMembers); + + outputStatistics(builder, missingClasses, incompleteClasses, missingMembers); + + emitAddDepCommandForIndirectJars(ruleLabel, builder); + return builder.toString(); + } + private void emitAddDepCommandForIndirectJars(String ruleLabel, StringBuilder builder) { ImmutableList<Path> indirectJars = resultCollector.getSortedIndirectDeps(); if (!indirectJars.isEmpty()) { ImmutableList<String> labels = extractLabels(indirectJars); @@ -186,7 +147,82 @@ public final class ImportDepsChecker implements Closeable { builder.append(ruleLabel).append('\n'); } } - return builder.toString(); + } + + private void outputStatistics( + StringBuilder builder, + ImmutableList<String> missingClasses, + ImmutableList<IncompleteState> incompleteClasses, + ImmutableList<MissingMember> missingMembers) { + 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'); + } + } + + private void outputMissingMembers( + StringBuilder builder, ImmutableList<MissingMember> missingMembers) { + 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'); + } + } + + private void outputIncompleteClasses( + StringBuilder builder, ImmutableList<IncompleteState> incompleteClasses) { + new LinkedHashMap<>(); + HashMultimap<String, ClassInfo> map = HashMultimap.create(); + for (IncompleteState incomplete : incompleteClasses) { + ResolutionFailureChain chain = incomplete.resolutionFailureChain(); + map.putAll(chain.getMissingClassesWithSubclasses()); + } + map.asMap() + .entrySet() + .stream() + .sorted(Map.Entry.comparingByKey()) + .forEach( + entry -> { + builder + .append("Indirectly missing class ") + .append(entry.getKey().replace('/', '.')) + .append(". Referenced by:") + .append('\n'); + entry + .getValue() + .stream() + .distinct() + .sorted() + .forEach( + reference -> { + builder + .append(INDENT) + .append(reference.internalName().replace('/', '.')) + .append('\n'); + }); + }); + } + + private void outputMissingClasses(StringBuilder builder, ImmutableList<String> missingClasses) { + for (String missing : missingClasses) { + builder.append("Missing ").append(missing.replace('/', '.')).append('\n'); + } } private static ImmutableList<String> extractLabels(ImmutableList<Path> jars) { diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResolutionFailureChain.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResolutionFailureChain.java new file mode 100644 index 0000000000..3ac67c1daf --- /dev/null +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResolutionFailureChain.java @@ -0,0 +1,82 @@ +// 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.auto.value.AutoValue; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultimap; +import javax.annotation.Nullable; + +/** The resolution failure path. */ +@AutoValue +public abstract class ResolutionFailureChain { + + public static ResolutionFailureChain createMissingClass(String missingClass) { + return new AutoValue_ResolutionFailureChain( + ImmutableList.of(missingClass), + /*resolutionStartClass=*/ null, + /*parentChains=*/ ImmutableList.of()); + } + + public static ResolutionFailureChain createWithParent( + ClassInfo resolutionStartClass, ImmutableList<ResolutionFailureChain> parentChains) { + Preconditions.checkArgument(!parentChains.isEmpty(), "The parentChains cannot be empty."); + return new AutoValue_ResolutionFailureChain( + parentChains + .stream() + .flatMap(chain -> chain.missingClasses().stream()) + .sorted() + .distinct() + .collect(ImmutableList.toImmutableList()), + resolutionStartClass, + parentChains); + } + + /** The missing class that causes the resolution failure. */ + public abstract ImmutableList<String> missingClasses(); + + /** The start of this resolution chain. */ + @Nullable + public abstract ClassInfo resolutionStartClass(); + + /** The resolution chain of the parent class. */ + public abstract ImmutableList<ResolutionFailureChain> parentChains(); + + /** For all the missing classes, represent the first chains that lead to the missing classes. */ + public ImmutableMultimap<String, ClassInfo> getMissingClassesWithSubclasses() { + ImmutableMultimap.Builder<String, ClassInfo> result = ImmutableMultimap.builder(); + getMissingClassesWithSubclasses(resolutionStartClass(), this.parentChains(), result); + return result.build(); + } + + private static void getMissingClassesWithSubclasses( + ClassInfo subclass, + ImmutableList<ResolutionFailureChain> parentChains, + ImmutableMultimap.Builder<String, ClassInfo> result) { + for (ResolutionFailureChain parentChain : parentChains) { + if (parentChain.resolutionStartClass() == null) { + checkState( + parentChain.parentChains().isEmpty() && parentChain.missingClasses().size() == 1); + result.put(parentChain.missingClasses().get(0), subclass); + } else { + checkState(!parentChain.parentChains().isEmpty()); + getMissingClassesWithSubclasses( + parentChain.resolutionStartClass(), parentChain.parentChains(), result); + } + } + } +} diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java index 5288799f07..7a1fe39b0a 100644 --- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java @@ -48,7 +48,8 @@ public class ResultCollector { "The old value and the new value are not the same object. old=%s, new=%s", oldValue, state); - missingClasss.add(state.asIncompleteState().getMissingAncestor()); // Add the real missing. + // Add the real missing. + state.asIncompleteState().missingAncestors().forEach(missingClasss::add); } else if (state.isMissingState()) { missingClasss.add(internalName); } else { |