Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save wchargin/5e6a43a203d6c95454aae2886c5b54e4 to your computer and use it in GitHub Desktop.
Save wchargin/5e6a43a203d6c95454aae2886c5b54e4 to your computer and use it in GitHub Desktop.
//tools/list_outputs
From 73d4d74d79d8e058c143d9535ee2680d64a9342f Mon Sep 17 00:00:00 2001
From: William Chargin <wchargin@gmail.com>
Date: Mon, 15 Apr 2019 22:14:12 -0700
Subject: [PATCH] Add //tools/list_outputs for aquery result parsing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This commit adds a tool that interprets the results of `bazel aquery` to
list all files output by a particular target.
See Stack Overflow question: https://stackoverflow.com/q/47859615
Tested:
Can be tested informally by running against itself:
```
$ tmp="$(mktemp)"
$ bazel aquery //tools/list_outputs --output=textproto >"${tmp}" 2>/dev/null
$ bazel run //tools/list_outputs -- \
> --aquery_result="${tmp}" \
> --label=//tools/list_outputs:list_outputs \
> 2>/dev/null
bazel-out/k8-fastbuild/bin/tools/list_outputs/list_outputs
bazel-out/k8-fastbuild/bin/tools/list_outputs/list_outputs.runfiles_manifest
bazel-out/k8-fastbuild/bin/tools/list_outputs/list_outputs.runfiles/MANIFEST
bazel-out/k8-fastbuild/internal/_middlemen/tools_Slist_Uoutputs_Slist_Uoutputs-runfiles
bazel-out/k8-fastbuild/bin/tools/list_outputs/list_outputs.runfiles.SOURCES
$ rm "${tmp}"
```
The `…/internal/_middlemen/…` file that it lists is correctly parsed
from the action graph, but doesn’t actually represent a file on disk.
Similar issues can exist with artifacts in external repositories, which
look like `external/six_archive/six.py` (again, not a file on disk).
It would be nice to add automated tests for this; I just haven’t gotten
around to it.
Signed-off-by: William Chargin <wchargin@gmail.com>
---
tools/list_outputs/BUILD | 32 +++++++++
tools/list_outputs/list_outputs.py | 108 +++++++++++++++++++++++++++++
2 files changed, 140 insertions(+)
create mode 100644 tools/list_outputs/BUILD
create mode 100644 tools/list_outputs/list_outputs.py
diff --git a/tools/list_outputs/BUILD b/tools/list_outputs/BUILD
new file mode 100644
index 0000000000..72727d74ce
--- /dev/null
+++ b/tools/list_outputs/BUILD
@@ -0,0 +1,32 @@
+# Copyright 2019 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.
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"]) # Apache 2.0
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+)
+
+py_binary(
+ name = "list_outputs",
+ srcs = ["list_outputs.py"],
+ srcs_version = "PY2AND3",
+ deps = [
+ "//third_party/py/abseil",
+ "//src/main/protobuf:analysis_py_proto",
+ ],
+)
diff --git a/tools/list_outputs/list_outputs.py b/tools/list_outputs/list_outputs.py
new file mode 100644
index 0000000000..cb59846fb8
--- /dev/null
+++ b/tools/list_outputs/list_outputs.py
@@ -0,0 +1,108 @@
+# Copyright 2019 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.
+r"""Parse an `aquery` result to list outputs created for a target.
+
+Use this binary in conjunction with `bazel aquery` to determine the
+paths on disk to output files of a target.
+
+Example usage: first, query the action graph for the target that you
+want to analyze:
+
+ bazel aquery //path/to:target --output=textproto >/tmp/aquery_result
+
+Then, from the Bazel repository:
+
+ bazel run //tools/list_outputs -- \
+ --aquery_result /tmp/aquery_result \
+ --label //path/to:target \
+ ;
+
+This will print a list of zero or more output files emitted by the given
+target, like:
+
+ bazel-out/k8-fastbuild/foo.genfile
+ bazel-out/k8-fastbuild/bar.genfile
+
+If the provided label does not appear in the output graph, an error will
+be raised.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import sys
+
+from absl import app
+from absl import flags
+from google.protobuf import text_format
+from src.main.protobuf import analysis_pb2
+
+
+flags.DEFINE_string(
+ "aquery_result",
+ None,
+ "Path to file containing result of `bazel aquery ... --output=textproto`.",
+)
+flags.DEFINE_string(
+ "label",
+ None,
+ "Label whose outputs to print.",
+)
+
+
+def die(message):
+ sys.stderr.write("fatal: %s\n" % (message,))
+ sys.exit(1)
+
+
+def main(unused_argv):
+ if flags.FLAGS.aquery_result is None:
+ raise app.UsageError("Missing `--aquery_result` argument.")
+ if flags.FLAGS.label is None:
+ raise app.UsageError("Missing `--label` argument.")
+
+ if flags.FLAGS.aquery_result == "-":
+ aquery_result = sys.stdin.read()
+ else:
+ with open(flags.FLAGS.aquery_result) as infile:
+ aquery_result = infile.read()
+ label = flags.FLAGS.label
+
+ action_graph_container = analysis_pb2.ActionGraphContainer()
+ text_format.Merge(aquery_result, action_graph_container)
+
+ matching_targets = [
+ t for t in action_graph_container.targets
+ if t.label == label
+ ]
+ if len(matching_targets) != 1:
+ die(
+ "expected exactly one target with label %r; found: %s"
+ % (label, sorted(t.label for t in matching_targets))
+ )
+ target = matching_targets[0]
+
+ all_artifact_ids = frozenset(
+ artifact_id
+ for action in action_graph_container.actions
+ if action.target_id == target.id
+ for artifact_id in action.output_ids
+ )
+ for artifact in action_graph_container.artifacts:
+ if artifact.id in all_artifact_ids:
+ print(artifact.exec_path)
+
+
+if __name__ == "__main__":
+ app.run(main)
--
2.21.0.313.ge35b8cb8e2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment