如何使用 workspace_status_command 的输出构建自定义规则?
How can I build custom rules using the output of workspace_status_command?
bazel build
标志 --workspace_status_command
支持调用脚本来检索例如存储库元数据,这也称为构建标记,可在 java_binary
.
等规则中使用
我想使用此元数据创建自定义规则。
我想将其用于通用支持功能。它应该接收 git 版本和一些其他属性,并创建一个 version.go
可用作依赖项的输出文件。
所以我开始了在各种 bazel 存储库中查看规则的旅程。
像rules_docker
这样的规则支持在container_image
中使用stamp
标记,并让您在属性中引用状态输出。
rules_go
在go_binary
的x_defs
属性中支持它。
这对我的目的来说是理想的,我深入研究了...
看来我可以用 ctx.actions.expand_template
using the entries in ctx.info_file
or ctx.version_file
作为 substitutions
的字典来得到我想要的东西。但是我没有弄清楚如何获取这些文件的字典。这两个文件似乎是 "unofficial",它们不是 ctx
文档的一部分。
基于我已经发现的内容:如何根据状态命令输出获得 dict
?
如果那不可能,shortest/simplest 访问自定义规则的 workspace_status_command
输出的方法是什么?
我一直在你所在的地方,我最终沿着你开始探索的道路前进。我生成了一个 JSON 描述,其中还包括从 git 收集的信息以与结果打包在一起,我最终做了这样的事情:
def _build_mft_impl(ctx):
args = ctx.actions.args()
args.add('-f')
args.add(ctx.info_file)
args.add('-i')
args.add(ctx.files.src)
args.add('-o')
args.add(ctx.outputs.out)
ctx.actions.run(
outputs = [ctx.outputs.out],
inputs = ctx.files.src + [ctx.info_file],
arguments = [args],
progress_message = "Generating manifest: " + ctx.label.name,
executable = ctx.executable._expand_template,
)
def _get_mft_outputs(src):
return {"out": src.name[:-len(".tmpl")]}
build_manifest = rule(
implementation = _build_mft_impl,
attrs = {
"src": attr.label(mandatory=True,
allow_single_file=[".json.tmpl", ".json_tmpl"]),
"_expand_template": attr.label(default=Label("//:expand_template"),
executable=True,
cfg="host"),
},
outputs = _get_mft_outputs,
)
//:expand_template
在我的例子中是一个标签,指向 py_binary
执行转换本身。我很乐意了解一种更好的(更原生的,更少的跃点)方法来做到这一点,但是(现在)我选择了:它有效。对该方法和您的担忧的评论很少:
- 据我所知,您无法读入(文件并在 Skylark 中执行操作)本身...
- ...说到这里,无论如何,将转换(工具)和构建描述(bazel)分开可能不是一件坏事。
- 官方文档的构成可能存在争议,但
ctx.info_file
可能不会出现在参考手册中,它在源代码树中有记载。 :) 其他领域也是如此(我希望这不是因为那些接口被认为还没有提交)。
为了 src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleContextApi.java
中的完整性,有:
@SkylarkCallable(
name = "info_file",
structField = true,
documented = false,
doc =
"Returns the file that is used to hold the non-volatile workspace status for the "
+ "current build request."
)
public FileApi getStableWorkspaceStatus() throws InterruptedException, EvalException;
编辑:评论中要求的一些额外细节。
在我的 workspace_status.sh
中,例如下面一行:
echo STABLE_GIT_REF $(git log -1 --pretty=format:%H)
在我的 .json.tmpl
文件中我会有:
"ref": "${STABLE_GIT_REF}",
我选择了 shell 类似要替换的文本符号,因为它对许多用户来说很直观并且易于匹配。
至于替换,实际代码的相关(CLI 不包括在内)部分为:
def get_map(val_file):
"""
Return dictionary of key/value pairs from ``val_file`.
"""
value_map = {}
for line in val_file:
(key, value) = line.split(' ', 1)
value_map.update(((key, value.rstrip('\n')),))
return value_map
def expand_template(val_file, in_file, out_file):
"""
Read each line from ``in_file`` and write it to ``out_file`` replacing all
${KEY} references with values from ``val_file``.
"""
def _substitue_variable(mobj):
return value_map[mobj.group('var')]
re_pat = re.compile(r'${(?P<var>[^} ]+)}')
value_map = get_map(val_file)
for line in in_file:
out_file.write(re_pat.subn(_substitue_variable, line)[0])
EDIT2:这就是 Python 脚本如何将 python 脚本暴露给其余的 bazel。
py_binary(
name = "expand_template",
main = "expand_template.py",
srcs = ["expand_template.py"],
visibility = ["//visibility:public"],
)
基于 Ondrej 的回答,我现在使用这样的东西(在 SO 编辑器中改编,可能包含小错误):
tools/bazel.rc
:
build --workspace_status_command=tools/workspace_status.sh
tools/workspace_status.sh
:
echo STABLE_GIT_REV $(git rev-parse HEAD)
version.bzl
:
_VERSION_TEMPLATE_SH = """
set -e -u -o pipefail
while read line; do
export "${line% *}"="${line#* }"
done <"$INFILE" \
&& cat <<EOF >"$OUTFILE"
{ "ref": "${STABLE_GIT_REF}"
, "service": "${SERVICE_NAME}"
}
EOF
"""
def _commit_info_impl(ctx):
ctx.actions.run_shell(
outputs = [ctx.outputs.outfile],
inputs = [ctx.info_file],
progress_message = "Generating version file: " + ctx.label.name,
command = _VERSION_TEMPLATE_SH,
env = {
'INFILE': ctx.info_file.path,
'OUTFILE': ctx.outputs.version_go.path,
'SERVICE_NAME': ctx.attr.service,
},
)
commit_info = rule(
implementation = _commit_info_impl,
attrs = {
'service': attr.string(
mandatory = True,
doc = 'name of versioned service',
),
},
outputs = {
'outfile': 'manifest.json',
},
)
bazel build
标志 --workspace_status_command
支持调用脚本来检索例如存储库元数据,这也称为构建标记,可在 java_binary
.
我想使用此元数据创建自定义规则。
我想将其用于通用支持功能。它应该接收 git 版本和一些其他属性,并创建一个 version.go
可用作依赖项的输出文件。
所以我开始了在各种 bazel 存储库中查看规则的旅程。
像rules_docker
这样的规则支持在container_image
中使用stamp
标记,并让您在属性中引用状态输出。
rules_go
在go_binary
的x_defs
属性中支持它。
这对我的目的来说是理想的,我深入研究了...
看来我可以用 ctx.actions.expand_template
using the entries in ctx.info_file
or ctx.version_file
作为 substitutions
的字典来得到我想要的东西。但是我没有弄清楚如何获取这些文件的字典。这两个文件似乎是 "unofficial",它们不是 ctx
文档的一部分。
基于我已经发现的内容:如何根据状态命令输出获得 dict
?
如果那不可能,shortest/simplest 访问自定义规则的 workspace_status_command
输出的方法是什么?
我一直在你所在的地方,我最终沿着你开始探索的道路前进。我生成了一个 JSON 描述,其中还包括从 git 收集的信息以与结果打包在一起,我最终做了这样的事情:
def _build_mft_impl(ctx):
args = ctx.actions.args()
args.add('-f')
args.add(ctx.info_file)
args.add('-i')
args.add(ctx.files.src)
args.add('-o')
args.add(ctx.outputs.out)
ctx.actions.run(
outputs = [ctx.outputs.out],
inputs = ctx.files.src + [ctx.info_file],
arguments = [args],
progress_message = "Generating manifest: " + ctx.label.name,
executable = ctx.executable._expand_template,
)
def _get_mft_outputs(src):
return {"out": src.name[:-len(".tmpl")]}
build_manifest = rule(
implementation = _build_mft_impl,
attrs = {
"src": attr.label(mandatory=True,
allow_single_file=[".json.tmpl", ".json_tmpl"]),
"_expand_template": attr.label(default=Label("//:expand_template"),
executable=True,
cfg="host"),
},
outputs = _get_mft_outputs,
)
//:expand_template
在我的例子中是一个标签,指向 py_binary
执行转换本身。我很乐意了解一种更好的(更原生的,更少的跃点)方法来做到这一点,但是(现在)我选择了:它有效。对该方法和您的担忧的评论很少:
- 据我所知,您无法读入(文件并在 Skylark 中执行操作)本身...
- ...说到这里,无论如何,将转换(工具)和构建描述(bazel)分开可能不是一件坏事。
- 官方文档的构成可能存在争议,但
ctx.info_file
可能不会出现在参考手册中,它在源代码树中有记载。 :) 其他领域也是如此(我希望这不是因为那些接口被认为还没有提交)。
为了 src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleContextApi.java
中的完整性,有:
@SkylarkCallable(
name = "info_file",
structField = true,
documented = false,
doc =
"Returns the file that is used to hold the non-volatile workspace status for the "
+ "current build request."
)
public FileApi getStableWorkspaceStatus() throws InterruptedException, EvalException;
编辑:评论中要求的一些额外细节。
在我的 workspace_status.sh
中,例如下面一行:
echo STABLE_GIT_REF $(git log -1 --pretty=format:%H)
在我的 .json.tmpl
文件中我会有:
"ref": "${STABLE_GIT_REF}",
我选择了 shell 类似要替换的文本符号,因为它对许多用户来说很直观并且易于匹配。
至于替换,实际代码的相关(CLI 不包括在内)部分为:
def get_map(val_file):
"""
Return dictionary of key/value pairs from ``val_file`.
"""
value_map = {}
for line in val_file:
(key, value) = line.split(' ', 1)
value_map.update(((key, value.rstrip('\n')),))
return value_map
def expand_template(val_file, in_file, out_file):
"""
Read each line from ``in_file`` and write it to ``out_file`` replacing all
${KEY} references with values from ``val_file``.
"""
def _substitue_variable(mobj):
return value_map[mobj.group('var')]
re_pat = re.compile(r'${(?P<var>[^} ]+)}')
value_map = get_map(val_file)
for line in in_file:
out_file.write(re_pat.subn(_substitue_variable, line)[0])
EDIT2:这就是 Python 脚本如何将 python 脚本暴露给其余的 bazel。
py_binary(
name = "expand_template",
main = "expand_template.py",
srcs = ["expand_template.py"],
visibility = ["//visibility:public"],
)
基于 Ondrej 的回答,我现在使用这样的东西(在 SO 编辑器中改编,可能包含小错误):
tools/bazel.rc
:
build --workspace_status_command=tools/workspace_status.sh
tools/workspace_status.sh
:
echo STABLE_GIT_REV $(git rev-parse HEAD)
version.bzl
:
_VERSION_TEMPLATE_SH = """
set -e -u -o pipefail
while read line; do
export "${line% *}"="${line#* }"
done <"$INFILE" \
&& cat <<EOF >"$OUTFILE"
{ "ref": "${STABLE_GIT_REF}"
, "service": "${SERVICE_NAME}"
}
EOF
"""
def _commit_info_impl(ctx):
ctx.actions.run_shell(
outputs = [ctx.outputs.outfile],
inputs = [ctx.info_file],
progress_message = "Generating version file: " + ctx.label.name,
command = _VERSION_TEMPLATE_SH,
env = {
'INFILE': ctx.info_file.path,
'OUTFILE': ctx.outputs.version_go.path,
'SERVICE_NAME': ctx.attr.service,
},
)
commit_info = rule(
implementation = _commit_info_impl,
attrs = {
'service': attr.string(
mandatory = True,
doc = 'name of versioned service',
),
},
outputs = {
'outfile': 'manifest.json',
},
)