Bazel可以批量N个需要重建为单个命令的M个文件吗?

时间:2016-05-13 15:29:11

标签: bazel

Google Bazel构建工具可以轻松解释特定目录树中的每个CoffeeScript文件是否需要编译为相应的输出JavaScript文件:

# Runs "coffee" 100 times if there are 100 files:
# will run slowly if most of them need rebuilding.

[genrule(
  name = 'compile-' + f,
  srcs = [f],
  outs = [f.replace('src/', 'static/').replace('.coffee', '.js')],
  cmd = 'coffee --compile --map --output $$(dirname $@) $<',
) for f in glob(['src/**/*.coffee'])]

但是,如果给出100个CoffeeScript文件,这将分别调用咖啡工具100,为编译过程增加了几秒钟。

或者,这可以写成一个命令,它将100个文件作为输入并产生100个文件作为输出:

# Runs "coffee" once on all the files:
# very slow in the case that only 1 file was edited.

coffee_files = glob(['src/**/*.coffee'])

genrule(
  name = 'compile-coffee-files',
  srcs = coffee_files,
  outs = [f.replace('src/', 'static/').replace('.coffee', '.js') for f in coffee_files],
  cmd = 'coffee --compile --map --output @D $(SRCS)',
)

有没有办法向Bazel解释咖啡可以同时用多个文件调用,如果N个目标已经过时,那么只有N个源文件应该提供给coffee命令,而不是所有目标的完整列表,是否需要重建?

2 个答案:

答案 0 :(得分:4)

coffeescript文件是否彼此独立?如果第一个工作,每个文件分别通过coffee运行,那么它似乎是这样。在这种情况下,第一个实际上会给你最大的并行性和增量性。

即使运行100次咖啡比使用100个文件运行咖啡要慢一些,您只需在第一次编译所有内容时支付该费用。当您更改1个文件时,将不会重新编译其他99个文件。但是,如果coffee的启动时间非常长,以至于100个文件实际上可以忽略不计,那么您可以坚持将它们全部编译成一个大的genrule。

在两个极端之间妥协的一种方法是创建一个宏:http://bazel.io/docs/skylark/macros.html

def compile_coffee(name, srcs):
  native.genrule(
    name = name,
    srcs = srcs,
    outs = [f.replace('src/', 'static/').replace('.coffee', '.js') for f in srcs],
    cmd = 'coffee --compile --map --output @D $(SRCS)',
  )

然后您可以在构建文件中使用compile_coffee宏,将构建组织到适当大小的目标中:

load("//pkg/path/to:coffee.bzl", "compile_coffee")

compile_coffee(
  name = "lib",
  srcs = glob(["*.coffee"]))

还有完整的云雀规则:http://bazel.io/docs/skylark/rules.html但如果咖啡脚本文件并不真正相互依赖,那么这可能不是必需的。

还有持久的工作人员:http://bazel.io/blog/2015/12/10/java-workers.html,它允许你保持一个正在运行的咖啡实例,这样你就不必支付启动成本,但二进制文件必须表现良好,并且有点更多的投资是因为您通常必须编写包装以便连接所有内容。

答案 1 :(得分:0)

这会一次将20个文件传递给CoffeeScript编译器:

BUILD

load(":myrules.bzl", "coffeescript")

coffee_files = glob(["src/*.coffee"])

# 'coffeescript' is a macro, but it will make a target named 'main'
coffeescript(
    name = "main",
    srcs = coffee_files
)

myrules.bzl

def _chunks(l, n):
    n = max(1, n)
    return [l[i:i+n] for i in range(0, len(l), n)]

def coffeescript(name, srcs):
    i = 0
    all_outs = []
    for chunk in _chunks(srcs, 20):
        chunk_name = "{}-{}".format(name, i)
        outs = [f.replace('src/', 'static/').replace('.coffee', '.js') for f in chunk] + \
               [f.replace('src/', 'static/').replace('.coffee', '.js.map') for f in chunk]
        all_outs += outs
        native.genrule(
            name = chunk_name,
            srcs = chunk,
            outs = outs,
            cmd = "coffee --compile --map --output $(@D)/static $(SRCS)"
        )
        i += 1

    # make a filegroup with the original name that groups together all
    # of the output files
    native.filegroup(
        name = name,
        srcs = all_outs,
    )

然后,bazel build :main将构建所有CoffeeScript文件,一次20个。

但这确实有一些缺点:

  • 如果修改了一个CoffeeScript文件,则会重新编译20个文件。不只是一个。

  • 如果添加或删除文件,那么很多文件 - 从那时起直到文件列表的结尾 - 将被重新编译。

我发现最好的方法是做@ahumesky建议的事情:将事情分解成合理大小的Bazel&#34;包&#34;,并让每个包进行一次编译。