web-dev-qa-db-ja.com

Bazel:生成されたファイルへのパスをどのように取得しますか?

Bazelでは、ビルドターゲットが与えられた場合、スクリプト(Bazelの外部で実行されている)は生成されたファイルへのパスをどのように取得しますか?

シナリオ:Bazelを使用してビルドを実行し、ビルドが完了したら、結果をサーバーにコピーします。コピーするファイルを知る必要があります。ファイルのリストをハードコーディングすることはできますが、それはしたくありません。

簡単な例:このBazelスクリプト:

genrule(
    name = "main",
    srcs = ["main.in"],
    outs = ["main.out"],
    cmd = "cp $< $@",
)

次に、main.inという名前のファイルを作成してからbazel build :mainを実行すると、bazelは次のように報告します。

INFO: Found 1 target...
Target //:main up-to-date:
  bazel-genfiles/main.out
INFO: Elapsed time: 6.427s, Critical Path: 0.40s

つまり、bazel-genfiles/main.outがあります。しかし、そのパスを取得するためにどのような機械可読技術を使用できますか? (bazel buildの出力を解析することはできますが、それを行うことはお勧めしません。)

私が見つけた最も近い方法は、bazel query --output=xml :mainに関する情報をXML形式でダンプする:mainを使用することです。出力には次の行が含まれます。

<rule-output name="//:main.out"/>

それは私が欲しいものにとても近いです。ただし、nameはBazelのラベル形式です。パスとして取得する方法がわかりません。

そのnameフィールドで何らかの文字列置換を実行して、それをbazel-genfiles/main.outに変換することができます。しかし、それでも信頼できません。私のgenruleoutput_to_bindir = 1が含まれていた場合、出力は代わりにbazel-bin/main.outになります。

さらに、すべてのルールのXML出力に<rule-output>フィールドがあるわけではありません。たとえば、私のBUILDファイルにCライブラリを作成するための次のコードがある場合:

cc_library(
    name = "mylib",
    srcs = glob(["*.c"])
)

bazel query --output=xml :mylibの出力には、<rule-output>またはその他の役立つ情報は含まれていません。

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<query version="2">
  <rule class="cc_library" location="/Users/mikemorearty/src/bazel/test1/BUILD:8:1" name="//:mylib">
    <string name="name" value="mylib"/>
    <list name="srcs">
      <label value="//:foo.c"/>
    </list>
    <rule-input name="//:foo.c"/>
    <rule-input name="//tools/defaults:crosstool"/>
    <rule-input name="@bazel_tools//tools/cpp:stl"/>
  </rule>
</query>
7
Mike Morearty

bazel aquery を使用してアクショングラフをクエリすると、この情報を取得できます。

これは少しリッチな例で、1つのgenruleからの2つの出力ファイルがあります。

$ ls
BUILD  main.in  WORKSPACE
$ cat WORKSPACE
$ cat BUILD
genrule(
    name = "main",
    srcs = ["main.in"],
    outs = ["main.o1", "main.o2"],
    cmd = "cp $< $(location main.o1); cp $< $(location main.o2)",
)
$ cat main.in
hello

bazel aquery //:main --output=textprotoを使用して、機械可読出力でアクショングラフをクエリします(プロトは analysis.ActionGraphContainer ):

$ bazel aquery //:main --output=textproto >aquery_result 2>/dev/null
$ cat aquery_result
artifacts {
  id: "0"
  exec_path: "main.in"
}
artifacts {
  id: "1"
  exec_path: "external/bazel_tools/tools/genrule/genrule-setup.sh"
}
artifacts {
  id: "2"
  exec_path: "bazel-out/k8-fastbuild/genfiles/main.o1"
}
artifacts {
  id: "3"
  exec_path: "bazel-out/k8-fastbuild/genfiles/main.o2"
}
actions {
  target_id: "0"
  action_key: "dd7fd759bbecce118a399c6ce7b0c4aa"
  mnemonic: "Genrule"
  configuration_id: "0"
  arguments: "/bin/bash"
  arguments: "-c"
  arguments: "source external/bazel_tools/tools/genrule/genrule-setup.sh; cp main.in bazel-out/k8-fastbuild/genfiles/main.o1; cp main.in bazel-out/k8-fastbuild/genfiles/main.o2"
  input_dep_set_ids: "0"
  output_ids: "2"
  output_ids: "3"
}
targets {
  id: "0"
  label: "//:main"
  rule_class_id: "0"
}
dep_set_of_files {
  id: "0"
  direct_artifact_ids: "0"
  direct_artifact_ids: "1"
}
configuration {
  id: "0"
  mnemonic: "k8-fastbuild"
  platform_name: "k8"
}
rule_classes {
  id: "0"
  name: "genrule"
}

データがすべて1か所にあるわけではありませんが、次の点に注意してください。

  • iDが2および3のアーティファクトは、2つの目的の出力ファイルに対応し、ワークスペースルートを基準にしたディスク上のファイルへのパスとしてこれらのアーティファクトの出力場所を一覧表示します。
  • ターゲットID0artifactsエントリは、アーティファクトID 2および3に関連付けられています。そして
  • iD "0"targetsエントリは、//:mainラベルに関連付けられています。

この単純な構造を考えると、スクリプトを簡単にまとめて、提供されたラベルに対応するすべての出力ファイルを一覧表示できます。 Bazelのanalysis.protoの定義または外部リポジトリからのその言語バインディングに直接依存する方法が見つからないため、次のスクリプトをbazelbuild/bazelリポジトリ自体にパッチすることができます。

tools/list_outputs/list_outputs.py

# 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)

tools/list_outputs/BUILD

# 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",
    ],
)

Gitパッチとして、あなたの便宜のために: https://Gist.github.com/wchargin/5e6a43a203d6c95454aae2886c5b54e4

このコードは、正確性について確認または検証されていないことに注意してください。主に例として提供します。それがあなたにとって有用であるなら、多分今週末、私はそれのためにいくつかのテストを書いて、それをバゼル自体に対してPRすることができます。

3
wchargin

bazelを2回実行する間、出力パスは同じである必要があります。つまり、//path/to:targetをビルドしてからbazel cleanをビルドし、再度ビルドすると、同じファイルが生成されるはずです。この出力ファイルは一定であるため、実行できます

ls "$(bazel info bazel-genfiles)/main.out"

これにより、ビルドが発生したときにそのファイルが作成される場所への参照が得られると思います(ビルドされません)。

ターゲットから、実行しているrules_*に依存するファイル名に移動しようとしている場合。たとえば、 rules_go の場合、出力パスはgo_libraryターゲットへの引数によって異なります。 rules_goチームは最近、プロジェクトについて この動作 を文書化しました。

バイナリ出力パスは、通常、バージョン間で安定している必要があり、あまり違いがないことを信頼できます。ただし、私の経験では、この問題は一般に、プロセスの以前の外部部分をgenruleまたはカスタムルールとしてBazelに移動することを検討する必要があることを示しています。たとえば、以前はこのトリックを使用してNPMパッケージをアセンブルしていましたが、現在はすべてを実行しています Bazelで そして、NPMへのアップロードに関心のある.tarを生成する単一のターゲットがあります。たぶん、あなたが何をしたいのかについていくつかの詳細をフォローアップすることができ、Bazelビルドパスを理解している外部システムに依存しないソリューションを実行できるかもしれません。

4
achew22