3

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命令,而不是提供完整的所有目标是否需要重建?

4

2 回答 2

4

咖啡脚本文件是否相互独立?如果第一个有效,每个文件都coffee单独运行,那么看起来就是这样。在这种情况下,第一个实际上会给你最大的并行性和增量。

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

在两个极端之间妥协的一种方法是创建一个宏: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 ,它允许您保留一个正在运行的咖啡实例,这样您就不必支付启动成本,但是二进制文件必须表现良好,并且需要更多的投资,因为您通常必须编写包装器才能将所有内容连接起来。

于 2016-05-13T18:50:42.047 回答
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“包”,并让每个包进行一次编译。

于 2017-12-17T22:48:39.890 回答