diff --git a/CHANGELOG.md b/CHANGELOG.md index 7051375b1..32469fd56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ ### Fixes 🛠️ +- Now sources of thrift dependencies are included as dependencies. + | [#202](https://github.com/JetBrains/bazel-bsp/pull/202) +- Handle the case when there is no JDK in the project. + | [#200](https://github.com/JetBrains/bazel-bsp/pull/200) - Fixed extraction of java version and java home for bazel `5.0.0`. | [#165](https://github.com/JetBrains/bazel-bsp/pull/165) - Log messages are no longer trimmed. diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java index d8f6b256b..8dd2a85f9 100644 --- a/server/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java @@ -33,6 +33,7 @@ import org.jetbrains.bsp.bazel.server.sync.languages.cpp.CppLanguagePlugin; import org.jetbrains.bsp.bazel.server.sync.languages.java.JavaLanguagePlugin; import org.jetbrains.bsp.bazel.server.sync.languages.scala.ScalaLanguagePlugin; +import org.jetbrains.bsp.bazel.server.sync.languages.thrift.ThriftLanguagePlugin; public class BazelBspServer { @@ -66,8 +67,10 @@ public void startServer(BspIntegrationData bspIntegrationData) { var javaLanguagePlugin = new JavaLanguagePlugin(bazelPathsResolver, bazelInfo); var scalaLanguagePlugin = new ScalaLanguagePlugin(javaLanguagePlugin, bazelPathsResolver); var cppLanguagePlugin = new CppLanguagePlugin(); + var thriftLanguagePlugin = new ThriftLanguagePlugin(bazelPathsResolver); var languagePluginsService = - new LanguagePluginsService(scalaLanguagePlugin, javaLanguagePlugin, cppLanguagePlugin); + new LanguagePluginsService( + scalaLanguagePlugin, javaLanguagePlugin, cppLanguagePlugin, thriftLanguagePlugin); var targetKindResolver = new TargetKindResolver(); var bazelProjectMapper = new BazelProjectMapper(languagePluginsService, bazelPathsResolver, targetKindResolver); diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/BUILD b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/BUILD index 3936c5a5f..438ac3fec 100644 --- a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/BUILD +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/BUILD @@ -14,6 +14,7 @@ java_library( "//server/src/main/java/org/jetbrains/bsp/bazel/server/bsp/info", "//server/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers", "//server/src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils", + "//server/src/main/java/org/jetbrains/bsp/bazel/server/sync/dependencytree", "//server/src/main/java/org/jetbrains/bsp/bazel/server/sync/proto:bsp_target_info_java_proto", "@com_google_protobuf//:protobuf_java", "@maven//:ch_epfl_scala_bsp4j", diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/BazelProjectMapper.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/BazelProjectMapper.java index 0870f8605..3086c34a4 100644 --- a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/BazelProjectMapper.java +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/BazelProjectMapper.java @@ -12,6 +12,7 @@ import org.jetbrains.bsp.bazel.info.BspTargetInfo.TargetInfo; import org.jetbrains.bsp.bazel.projectview.model.ProjectView; import org.jetbrains.bsp.bazel.server.bsp.utils.SourceRootGuesser; +import org.jetbrains.bsp.bazel.server.sync.dependencytree.DependencyTree; import org.jetbrains.bsp.bazel.server.sync.languages.LanguageData; import org.jetbrains.bsp.bazel.server.sync.languages.LanguagePluginsService; import org.jetbrains.bsp.bazel.server.sync.model.Label; @@ -22,7 +23,6 @@ import org.jetbrains.bsp.bazel.server.sync.model.Tag; public class BazelProjectMapper { - private final LanguagePluginsService languagePluginsService; private final BazelPathsResolver bazelPathsResolver; private final TargetKindResolver targetKindResolver; @@ -39,8 +39,9 @@ public BazelProjectMapper( public Project createProject( Map targets, Set rootTargets, ProjectView projectView) { languagePluginsService.prepareSync(targets.values()); + var dependencyTree = new DependencyTree(targets, rootTargets); var targetsToImport = selectTargetsToImport(rootTargets, targets); - var modulesFromBazel = createModules(targetsToImport); + var modulesFromBazel = createModules(targetsToImport, dependencyTree); var workspaceRoot = bazelPathsResolver.workspaceRoot(); var syntheticModules = createSyntheticModules(modulesFromBazel, workspaceRoot, projectView); var allModules = modulesFromBazel.appendAll(syntheticModules); @@ -55,13 +56,14 @@ private List selectTargetsToImport( return List.ofAll(rootTargets).flatMap(targets::get); } - private List createModules(List targetsToImport) { + private List createModules( + List targetsToImport, DependencyTree dependencyTree) { return targetsToImport - .map(this::createModule) + .map(target -> createModule(target, dependencyTree)) .filter(module -> !module.tags().contains(Tag.NO_IDE)); } - private Module createModule(TargetInfo target) { + private Module createModule(TargetInfo target, DependencyTree dependencyTree) { var label = Label.from(target.getId()); var directDependencies = resolveDirectDependencies(target); var languages = inferLanguages(target); @@ -73,7 +75,7 @@ private Module createModule(TargetInfo target) { var languagePlugin = languagePluginsService.getPlugin(languages); var languageData = (Option) languagePlugin.resolveModule(target); - var sourceDependencies = languagePlugin.dependencySources(target); + var sourceDependencies = languagePlugin.dependencySources(target, dependencyTree); return new Module( label, diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/BUILD b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/BUILD new file mode 100644 index 000000000..8bbea30f8 --- /dev/null +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/BUILD @@ -0,0 +1,17 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "dependencytree", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + exports = [ + "//server/src/main/java/org/jetbrains/bsp/bazel/server/sync/proto:bsp_target_info_java_proto", + "@com_google_protobuf//:protobuf_java", + "@maven//:io_vavr_vavr", + ], + deps = [ + "//server/src/main/java/org/jetbrains/bsp/bazel/server/sync/proto:bsp_target_info_java_proto", + "@com_google_protobuf//:protobuf_java", + "@maven//:io_vavr_vavr", + ], +) diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/DependencyTree.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/DependencyTree.java new file mode 100644 index 000000000..d3423d5d5 --- /dev/null +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/DependencyTree.java @@ -0,0 +1,71 @@ +package org.jetbrains.bsp.bazel.server.sync.dependencytree; + +import io.vavr.Lazy; +import io.vavr.collection.HashSet; +import io.vavr.collection.Map; +import io.vavr.collection.Set; +import org.jetbrains.bsp.bazel.info.BspTargetInfo.Dependency; +import org.jetbrains.bsp.bazel.info.BspTargetInfo.TargetInfo; + +public class DependencyTree { + private final Set rootTargets; + private final Map idToTargetInfo; + private final Map>> idToLazyTransitiveDependencies; + + public DependencyTree(Map idToTargetInfo, Set rootTargets) { + this.rootTargets = rootTargets; + this.idToTargetInfo = idToTargetInfo; + this.idToLazyTransitiveDependencies = createIdToLazyTransitiveDependenciesMap(idToTargetInfo); + } + + private Map>> createIdToLazyTransitiveDependenciesMap( + Map idToTargetInfo) { + return idToTargetInfo.mapValues(this::calculateLazyTransitiveDependenciesForTarget); + } + + private Lazy> calculateLazyTransitiveDependenciesForTarget( + TargetInfo targetInfo) { + return Lazy.of(() -> calculateTransitiveDependenciesForTarget(targetInfo)); + } + + private Set calculateTransitiveDependenciesForTarget(TargetInfo targetInfo) { + var dependencies = getDependencies(targetInfo); + var strictlyTransitiveDependencies = calculateStrictlyTransitiveDependencies(dependencies); + var directDependencies = calculateDirectDependencies(dependencies); + + return strictlyTransitiveDependencies.addAll(directDependencies); + } + + private Set calculateStrictlyTransitiveDependencies(Set dependencies) { + return dependencies.flatMap(idToLazyTransitiveDependencies::get).flatMap(Lazy::get); + } + + private Set calculateDirectDependencies(Set dependencies) { + return dependencies.flatMap(idToTargetInfo::get); + } + + public Set transitiveDependenciesWithoutRootTargets(String targetId) { + var target = idToTargetInfo.get(targetId); + + return target + .toSet() + .flatMap(this::getDependencies) + .filter(this::isNotARootTarget) + .flatMap(this::collectTransitiveDependenciesAndAddTarget); + } + + private Set getDependencies(TargetInfo target) { + return HashSet.ofAll(target.getDependenciesList()).map(Dependency::getId); + } + + private boolean isNotARootTarget(String targetId) { + return !rootTargets.contains(targetId); + } + + private Set collectTransitiveDependenciesAndAddTarget(String targetId) { + var target = idToTargetInfo.get(targetId).toSet(); + var dependencies = idToLazyTransitiveDependencies.get(targetId).toSet().flatMap(Lazy::get); + + return dependencies.addAll(target); + } +} diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePlugin.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePlugin.java index 892fed257..5565633ac 100644 --- a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePlugin.java +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePlugin.java @@ -7,6 +7,7 @@ import io.vavr.control.Option; import java.net.URI; import org.jetbrains.bsp.bazel.info.BspTargetInfo.TargetInfo; +import org.jetbrains.bsp.bazel.server.sync.dependencytree.DependencyTree; public abstract class LanguagePlugin { public void prepareSync(Seq targets) {} @@ -15,7 +16,7 @@ public Option resolveModule(TargetInfo targetInfo) { return Option.none(); } - public Set dependencySources(TargetInfo targetInfo) { + public Set dependencySources(TargetInfo targetInfo, DependencyTree dependencyTree) { return HashSet.empty(); } diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePluginsService.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePluginsService.java index bc8adb1c0..a72e7312d 100644 --- a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePluginsService.java +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePluginsService.java @@ -9,6 +9,7 @@ import org.jetbrains.bsp.bazel.server.sync.languages.java.JavaModule; import org.jetbrains.bsp.bazel.server.sync.languages.scala.ScalaLanguagePlugin; import org.jetbrains.bsp.bazel.server.sync.languages.scala.ScalaModule; +import org.jetbrains.bsp.bazel.server.sync.languages.thrift.ThriftLanguagePlugin; import org.jetbrains.bsp.bazel.server.sync.model.Language; import org.jetbrains.bsp.bazel.server.sync.model.Module; @@ -16,15 +17,18 @@ public class LanguagePluginsService { private final ScalaLanguagePlugin scalaLanguagePlugin; private final JavaLanguagePlugin javaLanguagePlugin; private final CppLanguagePlugin cppLanguagePlugin; + private final ThriftLanguagePlugin thriftLanguagePlugin; private final EmptyLanguagePlugin emptyLanguagePlugin; public LanguagePluginsService( ScalaLanguagePlugin scalaLanguagePlugin, JavaLanguagePlugin javaLanguagePlugin, - CppLanguagePlugin cppLanguagePlugin) { + CppLanguagePlugin cppLanguagePlugin, + ThriftLanguagePlugin thriftLanguagePlugin) { this.scalaLanguagePlugin = scalaLanguagePlugin; this.javaLanguagePlugin = javaLanguagePlugin; this.cppLanguagePlugin = cppLanguagePlugin; + this.thriftLanguagePlugin = thriftLanguagePlugin; this.emptyLanguagePlugin = new EmptyLanguagePlugin(); } @@ -32,6 +36,7 @@ public void prepareSync(Seq targetInfos) { scalaLanguagePlugin.prepareSync(targetInfos); javaLanguagePlugin.prepareSync(targetInfos); cppLanguagePlugin.prepareSync(targetInfos); + thriftLanguagePlugin.prepareSync(targetInfos); } public LanguagePlugin getPlugin(Set languages) { @@ -41,6 +46,8 @@ public LanguagePlugin getPlugin(Set languages) { return javaLanguagePlugin; } else if (languages.contains(Language.CPP)) { return cppLanguagePlugin; + } else if (languages.contains(Language.THRIFT)) { + return thriftLanguagePlugin; } else { return emptyLanguagePlugin; } diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/java/JavaLanguagePlugin.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/java/JavaLanguagePlugin.java index c1da803c7..ca1854eec 100644 --- a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/java/JavaLanguagePlugin.java +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/java/JavaLanguagePlugin.java @@ -17,6 +17,7 @@ import org.jetbrains.bsp.bazel.info.BspTargetInfo.JavaTargetInfo; import org.jetbrains.bsp.bazel.info.BspTargetInfo.TargetInfo; import org.jetbrains.bsp.bazel.server.sync.BazelPathsResolver; +import org.jetbrains.bsp.bazel.server.sync.dependencytree.DependencyTree; import org.jetbrains.bsp.bazel.server.sync.languages.LanguagePlugin; import org.jetbrains.bsp.bazel.server.sync.model.Module; @@ -82,7 +83,7 @@ private List resolveIdeClasspath(List runtimeClasspath, List comp } @Override - public Set dependencySources(TargetInfo targetInfo) { + public Set dependencySources(TargetInfo targetInfo, DependencyTree dependencyTree) { if (!targetInfo.hasJavaTargetInfo()) { return HashSet.empty(); } diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/scala/ScalaLanguagePlugin.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/scala/ScalaLanguagePlugin.java index 263d2880b..3076e9499 100644 --- a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/scala/ScalaLanguagePlugin.java +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/scala/ScalaLanguagePlugin.java @@ -19,6 +19,7 @@ import java.util.function.BiFunction; import org.jetbrains.bsp.bazel.info.BspTargetInfo.TargetInfo; import org.jetbrains.bsp.bazel.server.sync.BazelPathsResolver; +import org.jetbrains.bsp.bazel.server.sync.dependencytree.DependencyTree; import org.jetbrains.bsp.bazel.server.sync.languages.LanguagePlugin; import org.jetbrains.bsp.bazel.server.sync.languages.java.JavaLanguagePlugin; import org.jetbrains.bsp.bazel.server.sync.languages.java.JavaModule; @@ -61,8 +62,8 @@ private ScalaSdk getScalaSdk() { } @Override - public Set dependencySources(TargetInfo targetInfo) { - return javaLanguagePlugin.dependencySources(targetInfo); + public Set dependencySources(TargetInfo targetInfo, DependencyTree dependencyTree) { + return javaLanguagePlugin.dependencySources(targetInfo, dependencyTree); } @Override diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/thrift/ThriftLanguagePlugin.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/thrift/ThriftLanguagePlugin.java new file mode 100644 index 000000000..0d8d1ea5a --- /dev/null +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/thrift/ThriftLanguagePlugin.java @@ -0,0 +1,37 @@ +package org.jetbrains.bsp.bazel.server.sync.languages.thrift; + +import ch.epfl.scala.bsp4j.BuildTarget; +import io.vavr.collection.Set; +import java.net.URI; +import org.jetbrains.bsp.bazel.info.BspTargetInfo.TargetInfo; +import org.jetbrains.bsp.bazel.server.sync.BazelPathsResolver; +import org.jetbrains.bsp.bazel.server.sync.dependencytree.DependencyTree; +import org.jetbrains.bsp.bazel.server.sync.languages.LanguagePlugin; + +public class ThriftLanguagePlugin extends LanguagePlugin { + private static final String THRIFT_LIBRARY_RULE_NAME = "thrift_library"; + + private final BazelPathsResolver bazelPathsResolver; + + public ThriftLanguagePlugin(BazelPathsResolver bazelPathsResolver) { + this.bazelPathsResolver = bazelPathsResolver; + } + + @Override + public Set dependencySources(TargetInfo targetInfo, DependencyTree dependencyTree) { + return dependencyTree + .transitiveDependenciesWithoutRootTargets(targetInfo.getId()) + .filter(this::isThriftLibrary) + .flatMap(TargetInfo::getSourcesList) + .map(bazelPathsResolver::resolveUri); + } + + private boolean isThriftLibrary(TargetInfo target) { + return target.getKind().equals(THRIFT_LIBRARY_RULE_NAME); + } + + @Override + protected void applyModuleData(ThriftModule moduleData, BuildTarget buildTarget) { + // no actions needed + } +} diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/thrift/ThriftModule.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/thrift/ThriftModule.java new file mode 100644 index 000000000..12f5a3641 --- /dev/null +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/languages/thrift/ThriftModule.java @@ -0,0 +1,5 @@ +package org.jetbrains.bsp.bazel.server.sync.languages.thrift; + +import org.jetbrains.bsp.bazel.server.sync.languages.LanguageData; + +public class ThriftModule implements LanguageData {} diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/model/Language.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/model/Language.java index 26da36e78..2cf6df872 100644 --- a/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/model/Language.java +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/sync/model/Language.java @@ -8,7 +8,8 @@ public enum Language { SCALA("scala", HashSet.of(".scala")), JAVA("java", HashSet.of(".java")), KOTLIN("kotlin", HashSet.of(".kt"), HashSet.of(Language.JAVA.name)), - CPP("cpp", HashSet.of(".C", ".cc", ".cpp", ".CPP", ".c++", ".cp", "cxx", ".h", ".hpp")); + CPP("cpp", HashSet.of(".C", ".cc", ".cpp", ".CPP", ".c++", ".cp", "cxx", ".h", ".hpp")), + THRIFT("thrift", HashSet.of(".thrift")); private final String name; private final Set extensions; diff --git a/server/src/test/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/BUILD b/server/src/test/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/BUILD new file mode 100644 index 000000000..85f2a2c4d --- /dev/null +++ b/server/src/test/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/BUILD @@ -0,0 +1,11 @@ +load("//:junit5.bzl", "java_junit5_test") + +java_junit5_test( + name = "DependencyTreeTest", + size = "small", + srcs = ["DependencyTreeTest.java"], + test_package = "org.jetbrains.bsp.bazel.server.sync.dependencytree", + deps = [ + "//server/src/main/java/org/jetbrains/bsp/bazel/server/sync/dependencytree", + ], +) diff --git a/server/src/test/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/DependencyTreeTest.java b/server/src/test/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/DependencyTreeTest.java new file mode 100644 index 000000000..10527d7e2 --- /dev/null +++ b/server/src/test/java/org/jetbrains/bsp/bazel/server/sync/dependencytree/DependencyTreeTest.java @@ -0,0 +1,303 @@ +package org.jetbrains.bsp.bazel.server.sync.dependencytree; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.vavr.collection.HashMap; +import io.vavr.collection.HashSet; +import io.vavr.collection.List; +import org.jetbrains.bsp.bazel.info.BspTargetInfo.Dependency; +import org.jetbrains.bsp.bazel.info.BspTargetInfo.TargetInfo; +import org.junit.jupiter.api.Test; + +// graphs generated using: https://arthursonzogni.com/Diagon/#GraphDAG +public class DependencyTreeTest { + + @Test + public void shouldReturnEmptyListForNotExistingTarget() { + // given + var dependencyTree = new DependencyTree(HashMap.empty(), HashSet.empty()); + + // when + var dependencies = dependencyTree.transitiveDependenciesWithoutRootTargets("//does/not/exist"); + + // then + assertThat(dependencies).isEmpty(); + } + + @Test + public void shouldReturnNoDependenciesForTargetWithoutDependencies() { + // tree: + // '?' - queried target + // '+' - should be returned + // '-' - shouldn't be returned + // capital letter - root target + // ┌─┐ + // │A│ + // └┬┘ + // ┌▽┐ + // │B│ + // │?│ + // └─┘ + + // given + var a = targetInfo("//A", List.of("//B")); + var b = targetInfo("//B", List.of()); + + var idToTargetInfo = HashSet.of(a, b).toMap(TargetInfo::getId, x -> x); + var rootTargets = HashSet.of("//A", "//B"); + var dependencyTree = new DependencyTree(idToTargetInfo, rootTargets); + + // when + var dependencies = dependencyTree.transitiveDependenciesWithoutRootTargets("//B"); + + // then + assertThat(dependencies).isEmpty(); + } + + @Test + public void shouldReturnOnlyDirectDependenciesForTargetWithoutTransitiveDependencies() { + // tree: + // '?' - queried target + // '+' - should be returned + // '-' - shouldn't be returned + // capital letter - root target + // ┌───────┐ + // │ A │ + // │ ? │ + // └┬──┬──┬┘ + // ┌▽┐┌▽┐┌▽┐ + // │b││c││d│ + // │+││+││+│ + // └─┘└─┘└─┘ + + // given + var a = targetInfo("//A", List.of("//b", "//c", "//d")); + var b = targetInfo("//b", List.of()); + var c = targetInfo("//c", List.of()); + var d = targetInfo("//d", List.of()); + + var idToTargetInfo = HashSet.of(a, b, c, d).toMap(TargetInfo::getId, x -> x); + var rootTargets = HashSet.of("//A"); + var dependencyTree = new DependencyTree(idToTargetInfo, rootTargets); + + // when + var dependencies = dependencyTree.transitiveDependenciesWithoutRootTargets("//A"); + + // then + var expectedDependencies = HashSet.of(b, c, d); + assertThat(dependencies).isEqualTo(expectedDependencies); + } + + @Test + public void shouldReturnDirectAndTransitiveDependenciesForTargetWithTransitiveDependencies() { + // tree: + // '?' - queried target + // '+' - should be returned + // '-' - shouldn't be returned + // capital letter - root target + // ┌───────┐ + // │ A │ + // │ ? │ + // └┬─────┬┘ + // ┌▽───┐┌▽┐ + // │ b ││c│ + // │ + ││+│ + // └┬──┬┘└─┘ + // ┌▽┐┌▽┐ + // │d││e│ + // │+││+│ + // └─┘└─┘ + + // given + var a = targetInfo("//A", List.of("//b", "//c")); + var b = targetInfo("//b", List.of("//d", "//e")); + var c = targetInfo("//c", List.of()); + var d = targetInfo("//d", List.of()); + var e = targetInfo("//e", List.of()); + + var idToTargetInfo = HashSet.of(a, b, c, d, e).toMap(TargetInfo::getId, x -> x); + var rootTargets = HashSet.of("//A"); + var dependencyTree = new DependencyTree(idToTargetInfo, rootTargets); + + // when + var dependencies = dependencyTree.transitiveDependenciesWithoutRootTargets("//A"); + + // then + var expectedDependencies = HashSet.of(b, c, d, e); + assertThat(dependencies).isEqualTo(expectedDependencies); + } + + @Test + public void + shouldReturnDirectAndTransitiveDependenciesForTargetWithTransitiveDependenciesIncludingDeepRootTarget() { + // tree: + // '?' - queried target + // '+' - should be returned + // '-' - shouldn't be returned + // capital letter - root target + // ┌──────────┐ + // │ A │ + // │ ? │ + // └┬────────┬┘ + // ┌▽──────┐┌▽┐ + // │ b ││c│ + // │ + ││+│ + // └┬─────┬┘└─┘ + // ┌▽───┐┌▽┐ + // │ D ││e│ + // │ + ││+│ + // └┬──┬┘└─┘ + // ┌▽┐┌▽┐ + // │f││g│ + // │+││+│ + // └─┘└─┘ + + // given + var a = targetInfo("//A", List.of("//b", "//c")); + var b = targetInfo("//b", List.of("//D", "//e")); + var c = targetInfo("//c", List.of()); + var d = targetInfo("//D", List.of("//f", "//g")); + var e = targetInfo("//e", List.of()); + var f = targetInfo("//f", List.of()); + var g = targetInfo("//g", List.of()); + + var idToTargetInfo = HashSet.of(a, b, c, d, e, f, g).toMap(TargetInfo::getId, x -> x); + var rootTargets = HashSet.of("//A", "//D"); + var dependencyTree = new DependencyTree(idToTargetInfo, rootTargets); + + // when + var dependencies = dependencyTree.transitiveDependenciesWithoutRootTargets("//A"); + + // then + var expectedDependencies = HashSet.of(b, c, d, e, f, g); + assertThat(dependencies).isEqualTo(expectedDependencies); + } + + @Test + public void + shouldReturnDirectAndTransitiveDependenciesForTargetWithTransitiveDependenciesIncludingDeepRootTargetAndExcludingDirectRootTargets() { + // tree: + // '?' - queried target + // '+' - should be returned + // '-' - shouldn't be returned + // capital letter - root target + // ┌───────┐ + // │ A │ + // │ ? │ + // └┬─────┬┘ + // ┌▽───┐┌▽─────────┐ + // │ B ││ c │ + // │ - ││ + │ + // └┬──┬┘└┬─────┬──┬┘ + // ┌▽┐┌▽┐┌▽───┐┌▽┐┌▽┐ + // │d││e││ F ││g││h│ + // │-││-││ + ││+││+│ + // └─┘└─┘└┬──┬┘└─┘└─┘ + // ┌▽┐┌▽┐ + // │i││j│ + // │+││+│ + // └─┘└┬┘ + // ┌▽┐ + // │k│ + // │+│ + // └┬┘ + // ┌▽┐ + // │L│ + // │+│ + // └─┘ + + // given + var a = targetInfo("//A", List.of("//B", "//c")); + var b = targetInfo("//B", List.of("//d", "//e")); + var c = targetInfo("//c", List.of("//F", "//g", "//h")); + var d = targetInfo("//d", List.of()); + var e = targetInfo("//e", List.of()); + var f = targetInfo("//F", List.of("//i", "//j")); + var g = targetInfo("//g", List.of()); + var h = targetInfo("//h", List.of()); + var i = targetInfo("//i", List.of()); + var j = targetInfo("//j", List.of("//k")); + var k = targetInfo("//k", List.of("//L")); + var l = targetInfo("//L", List.of()); + + var idToTargetInfo = + HashSet.of(a, b, c, d, e, f, g, h, i, j, k, l).toMap(TargetInfo::getId, x -> x); + var rootTargets = HashSet.of("//A", "//B", "//F", "//L"); + var dependencyTree = new DependencyTree(idToTargetInfo, rootTargets); + + // when + var dependencies = dependencyTree.transitiveDependenciesWithoutRootTargets("//A"); + + // then + var expectedDependencies = HashSet.of(c, f, g, h, i, j, k, l); + assertThat(dependencies).isEqualTo(expectedDependencies); + } + + @Test + public void + shouldReturnDirectAndTransitiveDependenciesForTargetWithTransitiveDependenciesIncludingDeepRootTargetAndExcludingDirectRootTargetsWhichIsNotARootForTheTree() { + // tree: + // '?' - queried target + // '+' - should be returned + // '-' - shouldn't be returned + // capital letter - root target + // ┌───────┐ + // │ A │ + // └┬─────┬┘ + // ┌▽───┐┌▽─────────┐ + // │ B ││ c │ + // └┬──┬┘└┬─────┬──┬┘ + // ┌▽┐┌▽┐┌▽───┐┌▽┐┌▽┐ + // │d││e││ F ││g││h│ + // │-││-││ ? ││-││-│ + // └─┘└─┘└┬──┬┘└─┘└─┘ + // ┌▽┐┌▽┐ + // │i││j│ + // │+││+│ + // └─┘└┬┘ + // ┌▽┐ + // │k│ + // │+│ + // └┬┘ + // ┌▽┐ + // │L│ + // │+│ + // └─┘ + + // given + var a = targetInfo("//A", List.of("//B", "//c")); + var b = targetInfo("//B", List.of("//d", "//e")); + var c = targetInfo("//c", List.of("//F", "//g", "//h")); + var d = targetInfo("//d", List.of()); + var e = targetInfo("//e", List.of()); + var f = targetInfo("//F", List.of("//i", "//j")); + var g = targetInfo("//g", List.of()); + var h = targetInfo("//h", List.of()); + var i = targetInfo("//i", List.of()); + var j = targetInfo("//j", List.of("//k")); + var k = targetInfo("//k", List.of("//L")); + var l = targetInfo("//L", List.of()); + + var idToTargetInfo = + HashSet.of(a, b, c, d, e, f, g, h, i, j, k, l).toMap(TargetInfo::getId, x -> x); + var rootTargets = HashSet.of("//A", "//B", "//F", "//L"); + var dependencyTree = new DependencyTree(idToTargetInfo, rootTargets); + + // when + var dependencies = dependencyTree.transitiveDependenciesWithoutRootTargets("//F"); + + // then + var expectedDependencies = HashSet.of(i, j, k, l); + assertThat(dependencies).isEqualTo(expectedDependencies); + } + + private TargetInfo targetInfo(String id, List dependenciesIds) { + var dependencies = dependenciesIds.map(this::dependency); + + return TargetInfo.newBuilder().setId(id).addAllDependencies(dependencies).build(); + } + + private Dependency dependency(String id) { + return Dependency.newBuilder().setId(id).build(); + } +}