ctexplain: first functional check-in

https://github.com/bazelbuild/bazel/pull/11511 set up basic project structure.  This PR adds minimum working functionality.

Specifically, you can run it with a build command and it reports basic stats on the build's graph.

Example:

```
$ bazel-bin/tools/ctexplain/ctexplain -b "//testapp:foo"
Collecting configured targets for //testapp:foo... done in 0.62 s.

Configurations: 3
Targets: 79
Configured targets: 92 (+16.5% vs. targets)
Targets with multiple configs: 13
```

Notes:
* Changed import structure to prefer module imports over function, class imports (style guide recommendation)
* Set up structure for injecting arbitrary analyses. Each analysis consumes the build's set of configured targets and can output whatever it wants.
* Implemented one basic analysis
* Structured code to make it easy to fork output formatters (e.g. for machine-readable output). But tried not to add speculative inheritance / boilerplate too soon

Context: [Measuring Configuration Overhead](https://docs.google.com/document/d/10ZxO2wZdKJATnYBqAm22xT1k5r4Vp6QX96TkqSUIhs0/edit).
Work towards #10613

Closes #11829.

PiperOrigin-RevId: 328325094
diff --git a/tools/ctexplain/types.py b/tools/ctexplain/types.py
index 2ac480f..c47ab9f 100644
--- a/tools/ctexplain/types.py
+++ b/tools/ctexplain/types.py
@@ -26,16 +26,20 @@
 @dataclass(frozen=True)
 class Configuration():
   """Stores a build configuration as a collection of fragments and options."""
-  # BuildConfiguration.Fragments in this configuration, as base names without
-  # packages. For example: ["PlatformConfiguration", ...].
-  fragments: Tuple[str, ...]
+  # Mapping of each BuildConfiguration.Fragment in this configuration to the
+  # FragmentOptions it requires.
+  #
+  # All names are qualified up to the base file name, without package prefixes.
+  # For example, foo.bar.BazConfiguration appears as "BazConfiguration".
+  # foo.bar.BazConfiguration$Options appears as "BazeConfiguration$Options".
+  fragments: Mapping[str, Tuple[str, ...]]
   # Mapping of FragmentOptions to option key/value pairs. For example:
   # {"CoreOptions": {"action_env": "[]", "cpu": "x86", ...}, ...}.
   #
   # Option values are stored as strings of whatever "bazel config" outputs.
   #
   # Note that Fragment and FragmentOptions aren't the same thing.
-  options: [Mapping[str, Mapping[str, str]]]
+  options: Mapping[str, Mapping[str, str]]
 
 
 @dataclass(frozen=True)
@@ -49,7 +53,7 @@
   config_hash: str
   # Fragments required by this configured target and its transitive
   # dependencies. Stored as base names without packages. For example:
-  # "PlatformOptions".
+  # "PlatformOptions" or "FooConfiguration$Options".
   transitive_fragments: Tuple[str, ...]