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/lib.py b/tools/ctexplain/lib.py
new file mode 100644
index 0000000..1ddb8c9
--- /dev/null
+++ b/tools/ctexplain/lib.py
@@ -0,0 +1,57 @@
+# Lint as: python3
+# Copyright 2020 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.
+"""General-purpose business logic."""
+from typing import Tuple
+
+# Do not edit this line. Copybara replaces it with PY2 migration helper..third_party.bazel.tools.ctexplain.bazel_api as bazel_api
+from tools.ctexplain.types import ConfiguredTarget
+
+
+def analyze_build(bazel: bazel_api.BazelApi, labels: Tuple[str, ...],
+ build_flags: Tuple[str, ...]) -> Tuple[ConfiguredTarget, ...]:
+ """Gets a build invocation's configured targets.
+
+ Args:
+ bazel: API for invoking Bazel.
+ labels: The targets to build.
+ build_flags: The build flags to use.
+
+ Returns:
+ Configured targets representing the build.
+
+ Raises:
+ RuntimeError: On any invocation errors.
+ """
+ cquery_args = [f'deps({",".join(labels)})']
+ cquery_args.extend(build_flags)
+ (success, stderr, cts) = bazel.cquery(cquery_args)
+ if not success:
+ raise RuntimeError("invocation failed: " + stderr.decode("utf-8"))
+
+ # We have to do separate calls to "bazel config" to get the actual configs
+ # from their hashes.
+ hashes_to_configs = {}
+ cts_with_configs = []
+ for ct in cts:
+ # Don't use dict.setdefault because that unconditionally calls get_config
+ # as one of its parameters and that's an expensive operation to waste.
+ if ct.config_hash not in hashes_to_configs:
+ hashes_to_configs[ct.config_hash] = bazel.get_config(ct.config_hash)
+ config = hashes_to_configs[ct.config_hash]
+ cts_with_configs.append(
+ ConfiguredTarget(ct.label, config, ct.config_hash,
+ ct.transitive_fragments))
+
+ return tuple(cts_with_configs)