Bazel的扩展代码都是写在.bzl文件中,然后通过load()函数在WORKSPACE文件或者BUILD文件中调用。比如:
load("//build_tools/rules:maprule.bzl", "maprule")
可以加载的扩展有:rule、函数、常量。在.bzl文件中_开头的符号是私有的,不能被别的文件load。目前所有的.bzl文件都是可见的,不需要额外的export_files操作。
.bzl文件都是由Skylark语言所写的,这是一个类Python的语言,不过有些地方与python稍有不同。我们无需研究特别深入,不过这个文档Skylark简介推荐看下。
Macro和Rule
这是bazel的扩展中非常重要的两个概念,这里会做个大体的介绍,下面有细节介绍。
一个macro是指一个rule的实例化,也就是说在macro中会调用一些rule来做事情。当一个BUILD文件里面很多内容具有重复性和复杂性的时候,就可以使用macro来做代码的重复使用的简化。
rule比macro要强大的多,它可以深入到bazel的内部并且完全控制正在发生的事情。比如可以传递信息到其他的rule中。
总的来说:如果你想重复使用简单的逻辑,那么就使用macro。如果macro变得非常复杂,那么就推荐将其变成一个rule。比如对一个新的编程语言的支持,就是通过rule来实现的。不过rule只是给高级用户使用的,期待绝大多数的用户永远不会需要写一个rule,而只是load和call已经存在的rule就够了。
Macro细节
创建macro
macro没有别的功能,只是用来做封装和方便代码重用。正如其名,这其实是个宏,只在bazel的load阶段完成之前存在,load完成之后macro就不村子了,bazel只会看到这些宏所创建的rules。
native rules(也就是不需要load()语句加载的rules)可以通过native 模块来调用,比如:
def my_macro(name, visibility=None):
native.cc_library(
name = name,
srcs = ["main.cc"],
visibility = visibility,
)
如果需要在macro中知道package的名字(在BUILD中调用这个宏的package),可以在macro中参考使用native.package_name()。
举例
- 创建rule的macro
empty.bzl
: # 在这个文件中定义macro创建一个rule
def _impl(ctx):
print("This rule does nothing")
empty = rule(implementation=_impl)
extension.bzl
: # 在这个文件中调用
# Loading the rule. The rule doesn't have to be in a separate file.
load("//pkg:empty.bzl", "empty")
def macro(name, visibility=None):
# Creating the rule.
empty(name = name, visibility = visibility)
BUILD
: # 在BUILD中调用
load("//pkg:extension.bzl", "macro")
macro(name = "myrule")
- 创建native rule的macro
extension.bzl
: # 定义macro,且实例化一个genrule
def macro(name, visibility=None):
# Creating a native genrule.
native.genrule(
name = name,
outs = [name + ".txt"],
cmd = "echo hello > $@",
visibility = visibility,
)
BUILD
: # 调用macro
load("//pkg:extension.bzl", "macro")
macro(name = "myrule")
- 创建多个rule
def _impl(ctx):
return struct([...],
# When instrumenting this rule, again hide implementation from
# users.
instrumented_files(
source_attributes = ["srcs", "csrcs"],
dependency_attributes = ["deps", "cdeps"]))
# This rule is private and can only be accessed from the current file.
_cc_and_something_else_binary = rule(implementation=_impl)
# This macro is public, it's the public interface to instantiate the rule.
def cc_and_something_else_binary(name, srcs, deps, csrcs, cdeps):
cc_binary_name = "%s.cc_binary" % name
native.cc_binary(
name = cc_binary_name,
srcs = csrcs,
deps = cdeps,
visibility = ["//visibility:private"]
)
_cc_and_something_else_binary(
name = name,
srcs = srcs,
deps = deps,
# A label attribute so that this depends on the internal rule.
cc_binary = cc_binary_name,
# Redundant labels attributes so that the rule with this target name knows
# about everything it would know about if cc_and_something_else_binary
# were an actual rule instead of a macro.
csrcs = csrcs,
cdeps = cdeps)
Debugging
bazel query –output=build //my/path:all可以看到BUILD文件在展开macro,globs,loops之后的模样。
Errors
如果需要报错,可以使用fail函数:
def my_macro(name, deps, visibility=None):
if len(deps) < 2:
fail("Expected at least two values in deps")
# ...
上述内容的一个样例:
假设BUILD内之前之前有这么一个genrule():
genrule(
name = "file",
outs = ["file.txt"],
cmd = "$(location generator) some_arg > $@",
tools = [":generator"],
)
现在想在BUILD中重复使用上面的rule:
load("//path:generator.bzl", "file_generator")
file_generator(
name = "file",
arg = "some_arg",
)
可以看到需要在path/generator.bzl定义如下:
def file_generator(name, arg, visibility=None):
native.genrule(
name = name,
outs = [name + ".txt"],
cmd = "$(location generator) %s > $@" % arg,
tools = ["//test:generator"],
visibility = visibility,
)
接下来如果想要知道这个宏最后展开内容是什么,可以:
$ bazel query --output=build :file
# /absolute/path/test/ext.bzl:42:3
genrule(
name = "file",
tools = ["//test:generator"],
outs = ["//test:file.txt"],
cmd = "$(location generator) some_arg > $@",
)
转载需保留链接来源:软件玩家 » Bazel的扩展代码