Bazel не будет создавать файлы зависимой цели - PullRequest
0 голосов
/ 19 июня 2020

У меня есть правило Bazel, которое создает выходной файл и может при желании иметь зависимость от аналогичной цели, например:

load(":node.bzl", "node")

node(
        name = "one",
        src = "one.txt",
)

node(
        name = "two",
        src = "two.txt",
        dep = "one",
)

Теперь, если я вызываю bazel build :two, я хочу, чтобы он build: сначала один, а затем: два, каждая из которых создает выходной файл. Казалось бы, это так же просто, как написать файл node.bzl, например

def _node_impl(ctx):
      out_file = ctx.actions.declare_file(ctx.attr.name)
      print("ok running: out_file={} src={}".format(out_file.path, ctx.file.src.path))
      ctx.actions.run_shell(
            outputs = [out_file],
            inputs = [ctx.file.src],
            command = ("echo {} >{}".format(ctx.file.src.path, out_file.path)),
      )
      print("done running")

      return [
              DefaultInfo(
                    files = depset([out_file]),
              ),
      ]

node = rule(
      implementation = _node_impl,
      attrs = {
            "src": attr.label(
                  mandatory = True,
                  allow_single_file = True,
            ),
            "dep": attr.label(
            ),
      },
)

, но, увы, это создаст только файл «два», а не зависимый «один»:

INFO: Starting clean (this may take a while). Consider using --async if the clean takes more than several minutes.
bazel build :two
DEBUG: nok/node.bzl:3:12: ok running: out_file=bazel-out/k8-fastbuild/bin/one src=one.txt
DEBUG: nok/node.bzl:9:12: done running
DEBUG: nok/node.bzl:3:12: ok running: out_file=bazel-out/k8-fastbuild/bin/two src=two.txt
DEBUG: nok/node.bzl:9:12: done running
INFO: Analyzed target //:two (4 packages loaded, 9 targets configured).
INFO: Found 1 target...
Target //:two up-to-date:
  bazel-bin/two
INFO: Elapsed time: 0.491s, Critical Path: 0.05s
INFO: 1 process: 1 linux-sandbox.
INFO: Build completed successfully, 2 total actions

Обратите внимание, что вывод отладки подтверждает, что он видит зависимость при построении графа зависимостей, когда дело доходит до выполнения зависимой цели, он решает пропустить ее.

Теперь я ' Мы добились этого, добавив поставщика, который добавляет транзитивную зависимость к депонированию выходных файлов, но разве bazel не должен делать это автоматически? Вот мое решение:

NodeProv = provider(fields = ["out_file"])

def _node_impl(ctx):
      out_file = ctx.actions.declare_file(ctx.attr.name)
      print("ok running: out_file={} src={}".format(out_file.path, ctx.file.src.path))
      ctx.actions.run_shell(
            outputs = [out_file],
            inputs = [ctx.file.src],
            command = ("echo {} >{}".format(ctx.file.src.path, out_file.path)),
      )
      print("done running")

      dep_file = [ctx.attr.dep[NodeProv].out_file] if ctx.attr.dep else []

      return [
              DefaultInfo(
                    files = depset([out_file], transitive = [depset(dep_file)]),
              ),
              NodeProv(
                      out_file = out_file
              ),
      ]

node = rule(
      implementation = _node_impl,
      attrs = {
            "src": attr.label(
                  mandatory = True,
                  allow_single_file = True,
            ),
            "dep": attr.label(
                  providers = [(NodeProv)],
            ),
      },
)

Это действительно создает обе цели:

INFO: Analyzed target //:two (4 packages loaded, 9 targets configured).
INFO: Found 1 target...
Target //:two up-to-date:
  bazel-bin/one
  bazel-bin/two
INFO: Elapsed time: 0.532s, Critical Path: 0.07s
INFO: 2 processes: 2 linux-sandbox.
INFO: Build completed successfully, 3 total actions

Что думает коллективный коллективный разум, это рекомендуемый способ решения этой проблемы или есть лучший подход ?

1 Ответ

2 голосов
/ 19 июня 2020

Таким образом, анализируются как one, так и two (т. Е. Выполняются функции реализации для обоих, как вы видели), но действие в one не запускается, потому что от этого действия ничего не зависит.

Причина, по которой

Target //:two up-to-date:
  bazel-bin/two

не отображает bazel-bin/one, заключается в том, что в список попадают только файлы из целей верхнего уровня (т. Е. Целей, указанных в командной строке). Это сделано намеренно, потому что, если у вас много целей в графе сборки, вы, вероятно, не захотите видеть в списке все их выходные файлы.

Когда вы изменили правило, чтобы файлы из зависимостей помещались в целевом DefaultInfo вы в основном сказали, что файлы из зависимостей цели являются выходными данными самой цели, поэтому затем запускается действие из one, а его выходные данные печатаются в командной строке.

Бывают случаи, когда вы действительно хотите «пересылать» файлы из зависимостей, но гораздо более типично помещать файлы в провайдеры (как вы это сделали), а затем читать этих провайдеров, чтобы использовать файлы из зависимостей в качестве входных данных для другие действия. Примерно так:

def _node_impl(ctx):
  out_file = ctx.actions.declare_file(ctx.attr.name)
  dep_file = [ctx.attr.dep[NodeProv].out_file] if ctx.attr.dep else []
  inputs = [ctx.file.src] + dep_file
  ctx.actions.run_shell(
    outputs = [out_file],
    inputs = inputs,
    command = "cat {input_paths} > {out_path}".format(
      input_paths = " ".join([f.path for f in inputs]),
      out_path = out_file.path),
  )

  return [
    DefaultInfo(files = depset([out_file])),
    NodeProv(out_file = out_file),
  ]

Обратите внимание, что при этом bazel-bin/one по-прежнему не будет печататься в сообщении об обновлении (опять же, это обычно намеренно), но файл one из целевого объекта one, и его содержимое будет использовано при построении файла two из целевого two.

(обратите внимание, что для реального набора правил вам нужно используйте args , чтобы лучше использовать память при работе со строками для командных строк)

...