Update Import Deps tool to label some dependencies (e.g. superclasses) as IMPLICIT instead of EXPLICIT.
RELNOTES: None.
PiperOrigin-RevId: 280772732
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 eeb162f..2659562 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
@@ -55,12 +55,13 @@
public abstract Optional<ClassInfo> classInfo();
+ public abstract boolean direct();
+
/** A state to indicate that a class exists. */
@AutoValue
public abstract static class ExistingState extends AbstractClassEntryState {
-
- public static ExistingState create(ClassInfo classInfo) {
- return new AutoValue_AbstractClassEntryState_ExistingState(Optional.of(classInfo));
+ public static ExistingState create(ClassInfo classInfo, boolean direct) {
+ return new AutoValue_AbstractClassEntryState_ExistingState(Optional.of(classInfo), direct);
}
@Override
@@ -89,6 +90,11 @@
public Optional<ClassInfo> classInfo() {
return Optional.empty();
}
+
+ @Override
+ public boolean direct() {
+ return true;
+ }
}
/**
@@ -101,7 +107,7 @@
public static IncompleteState create(
ClassInfo classInfo, ResolutionFailureChain resolutionFailureChain) {
return new AutoValue_AbstractClassEntryState_IncompleteState(
- Optional.of(classInfo), resolutionFailureChain);
+ Optional.of(classInfo), /* direct= */ true, resolutionFailureChain);
}
public abstract ResolutionFailureChain resolutionFailureChain();
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 e7e335e..a632d2a 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
@@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMap;
import com.google.common.io.Closer;
import com.google.devtools.build.importdeps.AbstractClassEntryState.ExistingState;
import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState;
@@ -33,7 +34,6 @@
import java.io.PrintStream;
import java.nio.file.Path;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -74,7 +74,7 @@
return entry.getState(lazyClasspath);
}
- public ImmutableList<Path> collectUsedJarsInRegularClasspath() {
+ public ImmutableMap<Path, Boolean> collectUsedJarsInRegularClasspath() {
return lazyClasspath.collectUsedJarsInRegularClasspath();
}
@@ -88,18 +88,19 @@
private final String internalName;
private final ZipFile zipFile;
private final Path jarPath;
- private final boolean direct;
+ private final boolean isDirectDep;
/**
* The state of this class entry. If {@literal null}, then this class has not been resolved yet.
*/
@Nullable private AbstractClassEntryState state = null;
- private LazyClassEntry(String internalName, ZipFile zipFile, Path jarPath, boolean direct) {
+ private LazyClassEntry(
+ String internalName, ZipFile zipFile, Path jarPath, boolean isDirectDep) {
this.internalName = internalName;
this.zipFile = zipFile;
this.jarPath = jarPath;
- this.direct = direct;
+ this.isDirectDep = isDirectDep;
}
ZipFile getZipFile() {
@@ -132,13 +133,21 @@
if (isResolved()) {
return;
}
- resolveClassEntry(this, lazyClasspath);
+ resolveClassEntry(this, lazyClasspath, /* explicitUse= */ true);
checkNotNull(state, "After resolution, the state cannot be null");
}
- private static void resolveClassEntry(LazyClassEntry classEntry, LazyClasspath lazyClasspath) {
+ private static void resolveClassEntry(
+ LazyClassEntry classEntry, LazyClasspath lazyClasspath, boolean explicitUse) {
if (classEntry.state != null) {
// Already resolved. See if it is the existing state.
+ if (classEntry.state instanceof ExistingState) {
+ ExistingState state = (ExistingState) classEntry.state;
+ if (!state.direct() && explicitUse) {
+ // If the state was previously indirect, update now for direct dep
+ classEntry.state = ExistingState.create(state.classInfo().get(), explicitUse);
+ }
+ }
return;
}
@@ -157,7 +166,7 @@
failurePath.map(resolutionFailureChainsBuilder::add);
}
ClassInfoBuilder classInfoBuilder =
- new ClassInfoBuilder().setJarPath(classEntry.jarPath).setDirect(classEntry.direct);
+ new ClassInfoBuilder().setJarPath(classEntry.jarPath).setDirect(classEntry.isDirectDep);
// Only visit the class if we need to extract its list of members. If we do visit, skip
// code and debug attributes since we just care about finding declarations here.
if (lazyClasspath.populateMembers) {
@@ -173,7 +182,8 @@
resolutionFailureChainsBuilder.build();
if (resolutionFailureChains.isEmpty()) {
classEntry.state =
- ExistingState.create(classInfoBuilder.build(lazyClasspath, /*incomplete=*/ false));
+ ExistingState.create(
+ classInfoBuilder.build(lazyClasspath, /*incomplete=*/ false), explicitUse);
} else {
ClassInfo classInfo = classInfoBuilder.build(lazyClasspath, /*incomplete=*/ true);
classEntry.state =
@@ -199,7 +209,7 @@
if (superClassEntry == null) {
return Optional.of(ResolutionFailureChain.createMissingClass(superName));
} else {
- resolveClassEntry(superClassEntry, lazyClasspath);
+ resolveClassEntry(superClassEntry, lazyClasspath, /* explicitUse= */ false);
AbstractClassEntryState superState = superClassEntry.state;
if (superState instanceof ExistingState) {
// Do nothing. Good to proceed.
@@ -268,7 +278,7 @@
.orElse(null);
}
- public ImmutableList<Path> collectUsedJarsInRegularClasspath() {
+ public ImmutableMap<Path, Boolean> collectUsedJarsInRegularClasspath() {
return regularClasspath.collectUsedJarFiles();
}
@@ -308,15 +318,18 @@
return classIndex.get(internalName);
}
- public ImmutableList<Path> collectUsedJarFiles() {
- HashSet<Path> usedJars = new HashSet<>();
+ /** Second argument in the Map is if the jar is used directly (at least once). */
+ public ImmutableMap<Path, Boolean> collectUsedJarFiles() {
+ Map<Path, Boolean> usedJars = new HashMap<>();
for (Map.Entry<String, LazyClassEntry> entry : classIndex.entrySet()) {
LazyClassEntry clazz = entry.getValue();
if (clazz.isResolved()) {
- usedJars.add(clazz.jarPath);
+ if (!usedJars.containsKey(clazz.jarPath) || clazz.state.direct()) {
+ usedJars.put(clazz.jarPath, clazz.state.direct());
+ }
}
}
- return ImmutableList.sortedCopyOf(usedJars);
+ return ImmutableSortedMap.copyOf(usedJars);
}
private void printClasspath(PrintStream stream) {
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 91b706c..40b7491 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
@@ -15,6 +15,7 @@
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState;
import com.google.devtools.build.importdeps.ResultCollector.MissingMember;
@@ -105,12 +106,14 @@
/** 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
+ ImmutableMap<Path, Boolean> paths = classCache.collectUsedJarsInRegularClasspath();
paths.forEach(
- path ->
+ (path, explicit) ->
builder.addDependency(
- Dependency.newBuilder().setKind(Kind.EXPLICIT).setPath(path.toString()).build()));
+ Dependency.newBuilder()
+ .setKind(explicit ? Kind.EXPLICIT : Kind.IMPLICIT)
+ .setPath(path.toString())
+ .build()));
return builder.setRuleLabel(ruleLabel).setSuccess(true).build();
}
diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassCacheTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassCacheTest.java
index af6a52e..6ac5a33 100644
--- a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassCacheTest.java
+++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassCacheTest.java
@@ -192,7 +192,7 @@
.getClassState("com/google/devtools/build/importdeps/testdata/Library$Class9")
.isIncompleteState())
.isTrue();
- assertThat(cache.collectUsedJarsInRegularClasspath()).containsExactly(libraryJar);
+ assertThat(cache.collectUsedJarsInRegularClasspath()).containsExactly(libraryJar, true);
assertThat(
cache
@@ -200,7 +200,7 @@
.isIncompleteState())
.isTrue();
assertThat(cache.collectUsedJarsInRegularClasspath())
- .containsExactly(libraryJar, libraryAnnotationsJar);
+ .containsExactly(libraryJar, true, libraryAnnotationsJar, true);
assertThat(
cache
@@ -208,7 +208,28 @@
.isIncompleteState())
.isTrue();
assertThat(cache.collectUsedJarsInRegularClasspath())
- .containsExactly(libraryJar, libraryAnnotationsJar, libraryInterfaceJar);
+ .containsExactly(
+ libraryJar, true, libraryAnnotationsJar, true, libraryInterfaceJar, true);
+ }
+ }
+
+ @Test
+ public void testJdepsOutput_withSuperclasses_hasImplicitDeps() throws IOException {
+ try (ClassCache cache =
+ new ClassCache(
+ ImmutableSet.of(bootclasspath),
+ ImmutableSet.of(libraryJar, libraryInterfaceJar),
+ ImmutableSet.of(libraryJar, libraryInterfaceJar),
+ ImmutableSet.of(clientJar),
+ /*populateMembers=*/ true)) {
+
+ assertThat(
+ cache
+ .getClassState("com/google/devtools/build/importdeps/testdata/Client")
+ .isExistingState())
+ .isTrue();
+ assertThat(cache.collectUsedJarsInRegularClasspath())
+ .containsExactly(libraryJar, false, libraryInterfaceJar, false);
}
}
diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java
index 9cf9e35..08f559d 100644
--- a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java
+++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java
@@ -39,11 +39,12 @@
@Test
public void testExistingState() {
- ExistingState state = ExistingState.create(LIST_CLASS_INFO);
+ ExistingState state = ExistingState.create(LIST_CLASS_INFO, /* direct= */ true);
assertThat(state.isExistingState()).isTrue();
assertThat(state.isIncompleteState()).isFalse();
assertThat(state.isMissingState()).isFalse();
+ assertThat(state.direct()).isTrue();
assertThat(state.asExistingState()).isSameInstanceAs(state);
assertThrows(IllegalStateException.class, () -> state.asIncompleteState());