Repository: pantsbuild/pants

Issue:
Resolve RuleGraph issue preventing codegen backend loading
The protobuf backend is temporarily disabled: see #10645.

An overview of the problem space is now posted in an isolated corner of the docsite: https://www.pantsbuild.org/v2.0/docs/internal-rules-architecture
Add Support to Remove Specific Transitive Dependencies 
It would be nice to have the ability to remove transitive dependencies from a binary target. My use case is that I have some `python_libary` targets that depend on TensorFlow. It would be possible with split up code in such a way to avoid this, but It would end up creating more targets and files than is necessary for the project. I have a set of `python_binary` targets that shouldn't have Tenserflow, so this is just increasing the size of the resulting pex file. 

This is probably possible to do by creating a new target or extending the `python_binary` one.  I've read through the docs on how to create a new target. I haven't figured out how to do it, although it seems possible. It would be really helpfull to have a documented way of doing this. 

One alternative is to extend a feature that is in pants already `!some_dep` in the dependencies is ignored. This doesn't currently apply to transitive dependencies. The extension could be `!!some_dep` is removed from transitive dependencies as well. The concern being that this might make the interface a little to magical. 

I think this could be a useful feature for other people's use cases since there are a lot of large dependencies out there that can cause bloat in output binaries. 


Discussion:

Thanks for writing this out, @charlesoconor! 

I think the best path forward is `!!`. It's much more scalable than requiring people to write a new target type. With a new target type, I'd be concerned a user may end up needing to create >1 custom `python_binary` targets, and it getting confusing when to use what.

Here's what I'm thinking for semantics:
* You can only use `!!` on "roots", i.e. targets that don't depend on other targets, such as `python_binary` or `python_library`. This will reduce the risk of someone putting `!!` in some `python_library` and then accidentally breaking a bunch of things that depend on that library.
   - Specifically, target types will opt into supporting transitive excludes by setting a class property `supports_transitive_excludes` on a custom `Dependencies` field.
   - If the target type doesn't support `!!`, then we will error with an error message asking if they meant `!`, along with enumerating which target types do support `!!`.
* `!!` will ignore both from direct _and_ transitive dependencies.

If you find yourself frequently using the same transitive ignore—such as TensorFlow in your case—you could use a macro to automatically add that ignore to your targets.
The solution sounds perfect for my use case. 
Hey folks! Very sorry, but this feature needed to be temporarily disabled for the alpha: it will be re-enabled via #10683.

Resolving patch:
diff --git a/src/python/pants/backend/codegen/protobuf/python/register.py b/src/python/pants/backend/codegen/protobuf/python/register.py
--- a/src/python/pants/backend/codegen/protobuf/python/register.py
+++ b/src/python/pants/backend/codegen/protobuf/python/register.py
@@ -18,11 +18,3 @@ def rules():
 
 def target_types():
     return [ProtobufLibrary]
-
-
-raise Exception(
-    "The pants.backend.codege.protobuf.python backend is temporarily disabled for this 2.0 alpha "
-    "release, due to performance issues. If you require protobuf support, please do not use this "
-    "Pants release.  This is a very temporary measure, to allow alpha testing to proceed on other "
-    "features. Protobuf support will be fully restored before the 2.0 release."
-)
diff --git a/src/python/pants/engine/internals/graph.py b/src/python/pants/engine/internals/graph.py
--- a/src/python/pants/engine/internals/graph.py
+++ b/src/python/pants/engine/internals/graph.py
@@ -311,9 +311,20 @@ async def transitive_targets(targets: Targets) -> TransitiveTargets:
         )
         visited.update(queued)
 
-    transitive_targets = TransitiveTargets(tuple(targets), FrozenOrderedSet(visited))
     _detect_cycles(tuple(t.address for t in targets), dependency_mapping)
-    return transitive_targets
+
+    transitive_wrapped_excludes = await MultiGet(
+        Get(WrappedTarget, AddressInput, address_input)
+        for t in (*targets, *visited)
+        for address_input in t.get(Dependencies).unevaluated_transitive_excludes
+    )
+    transitive_excludes = FrozenOrderedSet(
+        wrapped_t.target for wrapped_t in transitive_wrapped_excludes
+    )
+
+    return TransitiveTargets(
+        tuple(targets), FrozenOrderedSet(visited.difference(transitive_excludes))
+    )
 
 
 # -----------------------------------------------------------------------------------------------


Test patch:
diff --git a/src/python/pants/backend/codegen/protobuf/python/rules_integration_test.py b/src/python/pants/backend/codegen/protobuf/python/rules_integration_test.py
--- a/src/python/pants/backend/codegen/protobuf/python/rules_integration_test.py
+++ b/src/python/pants/backend/codegen/protobuf/python/rules_integration_test.py
@@ -26,7 +26,6 @@
 from pants.testutil.option_util import create_options_bootstrapper
 
 
-@pytest.mark.skip(reason="TODO(#10683)")
 class ProtobufPythonIntegrationTest(ExternalToolTestBase):
     @classmethod
     def target_types(cls):
diff --git a/src/python/pants/backend/python/util_rules/python_sources_test.py b/src/python/pants/backend/python/util_rules/python_sources_test.py
--- a/src/python/pants/backend/python/util_rules/python_sources_test.py
+++ b/src/python/pants/backend/python/util_rules/python_sources_test.py
@@ -4,8 +4,7 @@
 from textwrap import dedent
 from typing import Iterable, List, Optional, Type
 
-import pytest
-
+from pants.backend.codegen.protobuf.python.rules import rules as protobuf_rules
 from pants.backend.codegen.protobuf.target_types import ProtobufLibrary
 from pants.backend.python.target_types import PythonSources
 from pants.backend.python.util_rules.python_sources import (
@@ -39,6 +38,7 @@ def rules(cls):
         return (
             *super().rules(),
             *python_sources_rules(),
+            *protobuf_rules(),
             QueryRule(PythonSourceFiles, (PythonSourceFilesRequest, OptionsBootstrapper)),
             QueryRule(StrippedPythonSourceFiles, (PythonSourceFilesRequest, OptionsBootstrapper)),
         )
@@ -191,7 +191,6 @@ def test_files_not_used_for_source_roots(self) -> None:
             targets, include_files=True, source_roots=["src/py", "src/files"]
         ).source_roots == ("src/py",)
 
-    @pytest.mark.skip(reason="TODO(#10683)")
     def test_python_protobuf(self) -> None:
         self.create_file(
             "src/protobuf/dir/f.proto",
diff --git a/src/python/pants/engine/internals/engine_test.py b/src/python/pants/engine/internals/engine_test.py
--- a/src/python/pants/engine/internals/engine_test.py
+++ b/src/python/pants/engine/internals/engine_test.py
@@ -235,8 +235,7 @@ def test_nonexistent_root(self) -> None:
         with self.assertRaises(ValueError) as cm:
             self.scheduler(rules, include_trace_on_error=False)
         assert (
-            "No installed rules return the type A: Is the rule that you're expecting to run "
-            "registered?"
+            "No installed rules return the type A, and it was not provided by potential callers of "
         ) in str(cm.exception)
 
     def test_missing_query_rule(self) -> None:
diff --git a/src/python/pants/engine/internals/graph_test.py b/src/python/pants/engine/internals/graph_test.py
--- a/src/python/pants/engine/internals/graph_test.py
+++ b/src/python/pants/engine/internals/graph_test.py
@@ -139,7 +139,6 @@ def get_target(name: str) -> Target:
     assert transitive_targets.closure == FrozenOrderedSet([root, d2, d1, d3, t2, t1])
 
 
-@pytest.mark.skip(reason="#10683")
 def test_transitive_targets_transitive_exclude(transitive_targets_rule_runner: RuleRunner) -> None:
     transitive_targets_rule_runner.add_to_build_file(
         "",
diff --git a/src/python/pants/engine/rules_test.py b/src/python/pants/engine/rules_test.py
--- a/src/python/pants/engine/rules_test.py
+++ b/src/python/pants/engine/rules_test.py
@@ -580,11 +580,16 @@ def a_from_b(b: B) -> A:
         with self.assertRaises(Exception) as cm:
             create_scheduler(rules)
         assert (
-            "No installed rules return the type B: Is the rule that you're expecting to run "
-            "registered?\nIf rather than being computed by a rule, that type should be provided "
-            "from outside the rule graph, consider adding it as an input for the relevant "
-            "QueryRule."
-        ) == str(cm.exception)
+            "No installed rules return the type B, and it was not provided by potential "
+            "callers of "
+        ) in str(cm.exception)
+        assert (
+            "If that type should be computed by a rule, ensure that that rule is installed."
+        ) in str(cm.exception)
+        assert (
+            "If it should be provided by a caller, ensure that it is included in any relevant "
+            "Query or Get."
+        ) in str(cm.exception)
 
     @pytest.mark.skip(reason="TODO(#10649): figure out if this tests is still relevant.")
     def test_not_fulfillable_duplicated_dependency(self):
diff --git a/src/python/pants/init/load_backends_integration_test.py b/src/python/pants/init/load_backends_integration_test.py
--- a/src/python/pants/init/load_backends_integration_test.py
+++ b/src/python/pants/init/load_backends_integration_test.py
@@ -12,8 +12,6 @@ def discover_backends() -> List[str]:
     backends = {
         str(register_py.parent).replace("src/python/", "").replace("/", ".")
         for register_py in register_pys
-        # TODO: See https://github.com/pantsbuild/pants/issues/10683.
-        if register_py != Path("src/python/pants/backend/codegen/protobuf/python/register.py")
     }
     always_activated = {"pants.core", "pants.backend.project_info", "pants.backend.pants_info"}
     return sorted(backends - always_activated)
diff --git a/src/rust/engine/rule_graph/src/tests.rs b/src/rust/engine/rule_graph/src/tests.rs
--- a/src/rust/engine/rule_graph/src/tests.rs
+++ b/src/rust/engine/rule_graph/src/tests.rs
@@ -58,6 +58,17 @@ fn insufficient_query() {
     .contains("No installed rules return the type b"));
 }
 
+#[test]
+fn no_rules() {
+  let rules: Vec<Rule> = vec![];
+  let queries = vec![Query::new("a", vec![])];
+
+  assert!(RuleGraph::new(rules, queries)
+    .err()
+    .unwrap()
+    .contains("No installed rules return the type a"));
+}
+
 #[test]
 fn ambiguity() {
   let rules = vec![
@@ -73,7 +84,7 @@ fn ambiguity() {
 }
 
 #[test]
-fn nested_basic() {
+fn nested_single() {
   let rules = vec![
     Rule("a", "a_from_b", vec![DependencyKey("b", Some("c"))]),
     Rule(
@@ -215,10 +226,8 @@ fn natural_loop() {
   graph.find_root_edges(vec!["D"], "A").unwrap();
 }
 
-/// TODO: Possibly related to https://github.com/pantsbuild/pants/issues/10683.
-#[ignore]
 #[test]
-fn wide_cycle() {
+fn multi_path_cycle() {
   let _logger = env_logger::try_init();
   let rules = vec![
     Rule(
@@ -276,6 +285,7 @@ fn mutual_recursion() {
 
 #[test]
 fn wide() {
+  let _logger = env_logger::try_init();
   let rules = vec![
     Rule("Output", "one", vec![DependencyKey("Output", Some("A"))]),
     Rule(
@@ -301,6 +311,517 @@ fn wide() {
   graph.find_root_edges(vec!["D"], "Output").unwrap();
 }
 
+#[test]
+fn reduced_source_roots() {
+  let _logger = env_logger::try_init();
+  let rules = vec![
+    Rule("SourceRootConfig", "construct_scope_source", vec![]),
+    Rule(
+      "OptionalSourceRootsResult",
+      "get_optional_source_roots",
+      vec![
+        DependencyKey("SourceRootsRequest", None),
+        DependencyKey("OptionalSourceRoot", Some("SourceRootRequest")),
+      ],
+    ),
+    Rule(
+      "Digest",
+      "<intrinsic>",
+      vec![DependencyKey("RemovePrefix", None)],
+    ),
+    Rule(
+      "Snapshot",
+      "<intrinsic>",
+      vec![DependencyKey("Digest", None)],
+    ),
+    Rule(
+      "Digest",
+      "<intrinsic>",
+      vec![DependencyKey("DigestSubset", None)],
+    ),
+    Rule(
+      "Digest",
+      "<intrinsic>",
+      vec![DependencyKey("PathGlobs", None)],
+    ),
+    Rule(
+      "Digest",
+      "<intrinsic>",
+      vec![DependencyKey("MergeDigests", None)],
+    ),
+    Rule(
+      "SourceRootsResult",
+      "get_source_roots",
+      vec![
+        DependencyKey("SourceRootsRequest", None),
+        DependencyKey("OptionalSourceRootsResult", Some("SourceRootsRequest")),
+      ],
+    ),
+    Rule(
+      "OptionalSourceRoot",
+      "get_optional_source_root",
+      vec![
+        DependencyKey("SourceRootRequest", None),
+        DependencyKey("SourceRootConfig", None),
+        DependencyKey("Snapshot", Some("PathGlobs")),
+        DependencyKey("OptionalSourceRoot", Some("SourceRootRequest")),
+      ],
+    ),
+    Rule(
+      "StrippedSourceFiles",
+      "strip_source_roots",
+      vec![
+        DependencyKey("SourceFiles", None),
+        DependencyKey("Snapshot", Some("DigestSubset")),
+        DependencyKey("SourceRootsResult", Some("SourceRootsRequest")),
+        DependencyKey("Snapshot", Some("RemovePrefix")),
+        DependencyKey("Digest", Some("DigestSubset")),
+        DependencyKey("Digest", Some("RemovePrefix")),
+        DependencyKey("Snapshot", Some("MergeDigests")),
+      ],
+    ),
+  ];
+  let queries = vec![Query::new("StrippedSourceFiles", vec!["SourceFiles"])];
+  let graph = RuleGraph::new(rules, queries).unwrap();
+
+  graph.validate_reachability().unwrap();
+}
+
+#[test]
+fn reduced_codegen_cycle() {
+  let _logger = env_logger::try_init();
+  let rules = vec![
+    Rule(
+      "Process",
+      "setup_pex_cli_process",
+      vec![
+        DependencyKey("PexCliProcess", None),
+        DependencyKey("ProcessResult", Some("Process")),
+      ],
+    ),
+    Rule(
+      "ProcessResult",
+      "fallible_to_exec_result_or_raise",
+      vec![
+        DependencyKey("FallibleProcessResult", None),
+        DependencyKey("ProductDescription", None),
+      ],
+    ),
+    Rule(
+      "MultiPlatformProcess",
+      "upcast_process",
+      vec![DependencyKey("Process", None)],
+    ),
+    Rule(
+      "ProductDescription",
+      "get_multi_platform_request_description",
+      vec![DependencyKey("MultiPlatformProcess", None)],
+    ),
+    Rule(
+      "FallibleProcessResult",
+      "remove_platform_information",
+      vec![DependencyKey("MultiPlatformProcess", None)],
+    ),
+  ];
+  let queries = vec![Query::new("Process", vec!["PexCliProcess"])];
+  let graph = RuleGraph::new(rules, queries).unwrap();
+
+  graph.validate_reachability().unwrap();
+}
+
+#[test]
+fn full_scale_target() {
+  let _logger = env_logger::try_init();
+  let rules = vec![
+    Rule(
+      "InferredDependencies",
+      "infer_python_conftest_dependencies",
+      vec![
+        DependencyKey("InferConftestDependencies", None),
+        DependencyKey("HydratedSources", Some("HydrateSourcesRequest")),
+        DependencyKey("AncestorFiles", Some("AncestorFilesRequest")),
+        DependencyKey("Owners", Some("OwnersRequest")),
+      ],
+    ),
+    Rule(
+      "ThirdPartyModuleToAddressMapping",
+      "map_third_party_modules_to_addresses",
+      vec![DependencyKey("Targets", Some("AddressSpecs"))],
+    ),
+    Rule(
+      "InjectedDependencies",
+      "inject_dependencies",
+      vec![
+        DependencyKey("InjectProtobufDependencies", None),
+        DependencyKey("Address", Some("AddressInput")),
+      ],
+    ),
+    Rule(
+      "Targets",
+      "resolve_targets",
+      vec![
+        DependencyKey("UnexpandedTargets", None),
+        DependencyKey("Subtargets", Some("Address")),
+      ],
+    ),
+    Rule(
+      "Owners",
+      "find_owners",
+      vec![
+        DependencyKey("OwnersRequest", None),
+        DependencyKey("Targets", Some("AddressSpecs")),
+        DependencyKey("UnexpandedTargets", Some("AddressSpecs")),
+        DependencyKey("BuildFileAddress", Some("Address")),
+      ],
+    ),
+    Rule(
+      "AddressesWithOrigins",
+      "resolve_addresses_with_origins",
+      vec![
+        DependencyKey("Specs", None),
+        DependencyKey("AddressesWithOrigins", Some("AddressSpecs")),
+        DependencyKey("AddressesWithOrigins", Some("FilesystemSpecs")),
+      ],
+    ),
+    Rule(
+      "InferredDependencies",
+      "infer_python_dependencies",
+      vec![
+        DependencyKey("InferPythonDependencies", None),
+        DependencyKey("StrippedSourceFiles", Some("SourceFilesRequest")),
+        DependencyKey("PythonModuleOwner", Some("PythonModule")),
+      ],
+    ),
+    Rule(
+      "InferredDependencies",
+      "infer_python_init_dependencies",
+      vec![
+        DependencyKey("InferInitDependencies", None),
+        DependencyKey("HydratedSources", Some("HydrateSourcesRequest")),
+        DependencyKey("AncestorFiles", Some("AncestorFilesRequest")),
+        DependencyKey("Owners", Some("OwnersRequest")),
+      ],
+    ),
+    Rule(
+      "PythonModuleOwner",
+      "map_module_to_address",
+      vec![
+        DependencyKey("PythonModule", None),
+        DependencyKey("FirstPartyModuleToAddressMapping", None),
+        DependencyKey("ThirdPartyModuleToAddressMapping", None),
+      ],
+    ),
+    Rule(
+      "DownloadedExternalTool",
+      "download_external_tool",
+      vec![
+        DependencyKey("ExternalToolRequest", None),
+        DependencyKey("Digest", Some("DownloadFile")),
+        DependencyKey("ExtractedDigest", Some("MaybeExtractable")),
+      ],
+    ),
+    Rule(
+      "GlobalOptions",
+      "construct_scope_",
+      vec![DependencyKey("ScopedOptions", Some("Scope"))],
+    ),
+    Rule(
+      "PexEnvironment",
+      "find_pex_python",
+      vec![DependencyKey("BinaryPaths", Some("BinaryPathRequest"))],
+    ),
+    Rule(
+      "ProcessResult",
+      "fallible_to_exec_result_or_raise",
+      vec![
+        DependencyKey("FallibleProcessResult", None),
+        DependencyKey("ProductDescription", None),
+      ],
+    ),
+    Rule(
+      "HydratedSources",
+      "hydrate_sources",
+      vec![
+        DependencyKey("HydrateSourcesRequest", None),
+        DependencyKey("WrappedTarget", Some("Address")),
+        DependencyKey(
+          "GeneratedSources",
+          Some("GeneratePythonFromProtobufRequest"),
+        ),
+      ],
+    ),
+    Rule(
+      "Digest",
+      "<intrinsic>",
+      vec![DependencyKey("MergeDigests", None)],
+    ),
+    Rule(
+      "SourceFiles",
+      "determine_source_files",
+      vec![
+        DependencyKey("SourceFilesRequest", None),
+        DependencyKey("HydratedSources", Some("HydrateSourcesRequest")),
+      ],
+    ),
+    Rule(
+      "UnexpandedTargets",
+      "resolve_unexpanded_targets",
+      vec![
+        DependencyKey("Addresses", None),
+        DependencyKey("WrappedTarget", Some("Address")),
+      ],
+    ),
+    Rule(
+      "ExtractedDigest",
+      "maybe_extract",
+      vec![
+        DependencyKey("MaybeExtractable", None),
+        DependencyKey("ProcessResult", Some("Process")),
+      ],
+    ),
+    Rule(
+      "AddressesWithOrigins",
+      "addresses_with_origins_from_address_specs",
+      vec![
+        DependencyKey("AddressSpecs", None),
+        DependencyKey("Address", Some("AddressInput")),
+        DependencyKey("TargetAdaptor", Some("Address")),
+        DependencyKey("UnexpandedTargets", Some("Addresses")),
+        DependencyKey("AddressFamily", Some("Dir")),
+      ],
+    ),
+    Rule(
+      "BuildFileAddress",
+      "find_build_file",
+      vec![
+        DependencyKey("Address", None),
+        DependencyKey("AddressFamily", Some("Dir")),
+      ],
+    ),
+    Rule(
+      "AncestorFiles",
+      "find_missing_ancestor_files",
+      vec![DependencyKey("AncestorFilesRequest", None)],
+    ),
+    Rule(
+      "Digest",
+      "<intrinsic>",
+      vec![DependencyKey("DownloadFile", None)],
+    ),
+    Rule(
+      "BinaryPaths",
+      "find_binary",
+      vec![
+        DependencyKey("BinaryPathRequest", None),
+        DependencyKey("FallibleProcessResult", Some("Process")),
+      ],
+    ),
+    Rule(
+      "OptionalSourceRootsResult",
+      "get_optional_source_roots",
+      vec![
+        DependencyKey("SourceRootsRequest", None),
+        DependencyKey("OptionalSourceRoot", Some("SourceRootRequest")),
+      ],
+    ),
+    Rule(
+      "AddressesWithOrigins",
+      "addresses_with_origins_from_filesystem_specs",
+      vec![
+        DependencyKey("FilesystemSpecs", None),
+        DependencyKey("Owners", Some("OwnersRequest")),
+      ],
+    ),
+    Rule(
+      "RegisteredTargetTypes",
+      "registered_target_types_singleton",
+      vec![],
+    ),
+    Rule(
+      "Addresses",
+      "strip_address_origins",
+      vec![DependencyKey("AddressesWithOrigins", None)],
+    ),
+    Rule(
+      "FallibleProcessResult",
+      "remove_platform_information",
+      vec![DependencyKey("FallibleProcessResultWithPlatform", None)],
+    ),
+    Rule(
+      "ScopedOptions",
+      "scope_options",
+      vec![
+        DependencyKey("Scope", None),
+        DependencyKey("_Options", None),
+      ],
+    ),
+    Rule(
+      "TransitiveTargets",
+      "transitive_targets",
+      vec![
+        DependencyKey("Targets", None),
+        DependencyKey("Targets", Some("DependenciesRequest")),
+      ],
+    ),
+    Rule(
+      "FirstPartyModuleToAddressMapping",
+      "map_first_party_modules_to_addresses",
+      vec![
+        DependencyKey("Targets", Some("AddressSpecs")),
+        DependencyKey("StrippedSourceFiles", Some("SourceFilesRequest")),
+      ],
+    ),
+    Rule(
+      "Digest",
+      "<intrinsic>",
+      vec![DependencyKey("DigestSubset", None)],
+    ),
+    Rule(
+      "_Options",
+      "parse_options",
+      vec![DependencyKey("OptionsBootstrapper", None)],
+    ),
+    Rule(
+      "GeneratedSources",
+      "generate_python_from_protobuf",
+      vec![
+        DependencyKey("GeneratePythonFromProtobufRequest", None),
+        DependencyKey("DownloadedExternalTool", Some("ExternalToolRequest")),
+        DependencyKey("ProcessResult", Some("Process")),
+        DependencyKey("TransitiveTargets", Some("Addresses")),
+        DependencyKey("StrippedSourceFiles", Some("SourceFilesRequest")),
+        DependencyKey("Digest", Some("MergeDigests")),
+        DependencyKey("SourceRoot", Some("SourceRootRequest")),
+      ],
+    ),
+    Rule(
+      "SourceRoot",
+      "get_source_root",
+      vec![
+        DependencyKey("SourceRootRequest", None),
+        DependencyKey("OptionalSourceRoot", Some("SourceRootRequest")),
+      ],
+    ),
+    Rule(
+      "MultiPlatformProcess",
+      "upcast_process",
+      vec![DependencyKey("Process", None)],
+    ),
+    Rule(
+      "TargetAdaptor",
+      "find_target_adaptor",
+      vec![
+        DependencyKey("Address", None),
+        DependencyKey("AddressFamily", Some("Dir")),
+      ],
+    ),
+    Rule(
+      "Address",
+      "resolve_address",
+      vec![DependencyKey("AddressInput", None)],
+    ),
+    Rule(
+      "FallibleProcessResultWithPlatform",
+      "<intrinsic>",
+      vec![DependencyKey("MultiPlatformProcess", None)],
+    ),
+    Rule(
+      "SourceRootsResult",
+      "get_source_roots",
+      vec![
+        DependencyKey("SourceRootsRequest", None),
+        DependencyKey("OptionalSourceRootsResult", Some("SourceRootsRequest")),
+      ],
+    ),
+    Rule(
+      "OptionalSourceRoot",
+      "get_optional_source_root",
+      vec![
+        DependencyKey("SourceRootRequest", None),
+        DependencyKey("OptionalSourceRoot", Some("SourceRootRequest")),
+      ],
+    ),
+    Rule(
+      "StrippedSourceFiles",
+      "strip_source_roots",
+      vec![
+        DependencyKey("SourceFiles", None),
+        DependencyKey("SourceRootsResult", Some("SourceRootsRequest")),
+        DependencyKey("Digest", Some("DigestSubset")),
+      ],
+    ),
+    Rule(
+      "Subtargets",
+      "generate_subtargets",
+      vec![
+        DependencyKey("Address", None),
+        DependencyKey("WrappedTarget", Some("Address")),
+      ],
+    ),
+    Rule(
+      "ProductDescription",
+      "get_multi_platform_request_description",
+      vec![DependencyKey("MultiPlatformProcess", None)],
+    ),
+    Rule(
+      "Addresses",
+      "resolve_dependencies",
+      vec![
+        DependencyKey("DependenciesRequest", None),
+        DependencyKey("RegisteredTargetTypes", None),
+        DependencyKey("Address", Some("AddressInput")),
+        DependencyKey("InjectedDependencies", Some("InjectProtobufDependencies")),
+        DependencyKey("WrappedTarget", Some("Address")),
+        DependencyKey("InferredDependencies", Some("InferPythonDependencies")),
+        DependencyKey("InferredDependencies", Some("InferInitDependencies")),
+        DependencyKey("InferredDependencies", Some("InferConftestDependencies")),
+        DependencyKey("Subtargets", Some("Address")),
+      ],
+    ),
+    Rule(
+      "WrappedTarget",
+      "resolve_target",
+      vec![
+        DependencyKey("Address", None),
+        DependencyKey("RegisteredTargetTypes", None),
+        DependencyKey("WrappedTarget", Some("Address")),
+        DependencyKey("TargetAdaptor", Some("Address")),
+      ],
+    ),
+    Rule(
+      "AddressFamily",
+      "parse_address_family",
+      vec![
+        DependencyKey("GlobalOptions", None),
+        DependencyKey("Dir", None),
+      ],
+    ),
+  ];
+  let queries = vec![
+    Query::new("AddressesWithOrigins", vec!["OptionsBootstrapper", "Specs"]),
+    Query::new("UnexpandedTargets", vec!["OptionsBootstrapper", "Specs"]),
+    Query::new("Addresses", vec!["OptionsBootstrapper", "Specs"]),
+    Query::new("Owners", vec!["OptionsBootstrapper", "OwnersRequest"]),
+    Query::new("Targets", vec!["OptionsBootstrapper", "Specs"]),
+    Query::new("TransitiveTargets", vec!["OptionsBootstrapper", "Specs"]),
+    Query::new(
+      "HydratedSources",
+      vec!["OptionsBootstrapper", "HydrateSourcesRequest"],
+    ),
+    Query::new(
+      "SourceFiles",
+      vec!["OptionsBootstrapper", "SourceFilesRequest"],
+    ),
+    Query::new(
+      "StrippedSourceFiles",
+      vec!["OptionsBootstrapper", "SourceFiles"],
+    ),
+  ];
+  let graph = RuleGraph::new(rules, queries).unwrap();
+
+  graph.validate_reachability().unwrap();
+}
+
 impl super::TypeId for &'static str {
   fn display<I>(type_ids: I) -> String
   where
@@ -343,7 +864,7 @@ impl super::DisplayForGraph for Rule {
 
 impl fmt::Display for Rule {
   fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
-    write!(f, "{:?}", self)
+    write!(f, "{}({}) -> {}", self.1, self.2.len(), self.0)
   }
 }