如何基于workspace_status_command输出构建Bazel自定义规则?
解决Bazel自定义规则中访问workspace_status_command输出的问题
我刚好做过类似的需求,结合rules_go和rules_docker的思路,给你两个核心解决方案:
一、如何将状态命令输出转为字典?
首先明确:在Starlark规则的实现函数中,无法直接读取ctx.info_file/ctx.version_file的内容并转为字典——因为这些是构建阶段的输入文件,Starlark的规则逻辑是声明式的,不能直接读取文件内容。
但你可以通过一个辅助action来解析状态文件为字典格式(比如JSON),步骤如下:
写一个简单的Python脚本
parse_status.py,用来将Bazel的status文件转为JSON:import sys import json status_dict = {} with open(sys.argv[1], 'r') as f: for line in f: line = line.strip() if not line: continue key, *value_parts = line.split() status_dict[key] = ' '.join(value_parts) print(json.dumps(status_dict))在自定义规则中,调用这个脚本生成JSON格式的状态字典文件:
def _parse_status_to_json(ctx): json_output = ctx.actions.declare_file("status.json") ctx.actions.run( inputs = [ctx.version_file], outputs = [json_output], executable = ctx.executable._parser, arguments = [ctx.version_file.path, json_output.path], ) return json_output # 把脚本声明为工具 _status_parser = rule( implementation = _parse_status_to_json, attrs = { "_parser": attr.label( executable = True, cfg = "exec", allow_single_file = True, default = Label("//path/to:parse_status.py"), ), }, )
不过说实话,除非你需要把字典传给其他复杂逻辑,否则这个方法有点冗余——下面的方式更直接。
二、自定义规则访问workspace_status输出的最简方式
直接利用ctx.actions.run_shell或envsubst,在构建阶段解析status文件并替换模板生成version.go,这也是rules_go的核心思路:
步骤1:准备模板文件
创建version.go.tpl,用{{KEY_NAME}}作为占位符(对应你status命令输出的键):
package version const ( GitCommit = "{{STABLE_GIT_COMMIT}}" GitTag = "{{STABLE_GIT_TAG}}" BuildTime = "{{BUILD_TIMESTAMP}}" )
步骤2:实现自定义规则
def _generate_version_file_impl(ctx): # 声明输出文件 output = ctx.actions.declare_file("version.go") # 依赖的模板和status文件(用ctx.version_file获取稳定的STABLE_*键) inputs = [ctx.file.template, ctx.version_file] # 用shell命令解析status文件为环境变量,再替换模板 ctx.actions.run_shell( inputs = inputs, outputs = [output], command = """ # 读取status文件,把每行的KEY VALUE转为环境变量 while IFS=' ' read -r key value; do export "$key"="$value" done < "$1" # 用envsubst替换模板中的占位符 envsubst < "$2" > "$3" """, arguments = [ ctx.version_file.path, ctx.file.template.path, output.path, ], ) # 返回输出文件作为规则产物 return [DefaultInfo(files = depset([output]))] # 定义规则 generate_version_file = rule( implementation = _generate_version_file_impl, attrs = { "template": attr.label(allow_single_file = True), }, )
步骤3:在BUILD中使用规则
generate_version_file( name = "version", template = "version.go.tpl", ) # 然后在go_library/go_binary中依赖这个规则 go_library( name = "my_lib", srcs = [":version", "other.go"], # ...其他属性 )
关键说明:
ctx.version_file包含所有以STABLE_开头的状态键(这些键不会随每次构建变化,适合作为版本信息);ctx.info_file包含所有状态键(包括临时的、每次构建可能变化的键,比如BUILD_TIMESTAMP)。- 你的
workspace_status_command脚本需要输出KEY VALUE格式的行,比如:# status.sh echo "STABLE_GIT_COMMIT $(git rev-parse HEAD)" echo "STABLE_GIT_TAG $(git describe --tags --abbrev=0 --exact-match 2>/dev/null || echo 'unknown')" echo "BUILD_TIMESTAMP $(date +%Y-%m-%dT%H:%M:%S)" - 记得在
.bazelrc或构建命令中启用:build --workspace_status_command=./status.sh
这种方式不需要额外的依赖,直接用系统自带的envsubst和shell命令,是最简洁的实现方式。
内容的提问来源于stack exchange,提问作者Arne Hormann
相关产品推荐
相关产品推荐

