Bazel构建时如何解析配置文件生成多语言常量并依赖构建产物
解决方法
整体逻辑
完全可以实现类似Protobuf的使用体验,核心是自定义Bazel规则:将YAML常量文件作为输入,构建阶段调用你写的Python转换脚本,生成对应语言的常量代码文件,业务代码直接依赖生成的规则目标即可,无需在运行时处理YAML。
具体实现步骤
步骤1:编写Python转换脚本
写一个通用转换工具,接收三个入参:输入YAML路径、目标语言、输出文件路径,你可以根据需要扩展支持的语言。示例代码如下:
# yaml_to_const.py import yaml import sys import json def generate_ts(data, output_path): # 生成符合要求的TS代码 content = f"export default {json.dumps(data, indent=2)}" with open(output_path, "w", encoding="utf-8") as f: f.write(content) def generate_py(data, output_path): # 生成Python常量代码 content = "" for k, v in data.items(): content += f"{k.upper()} = {repr(v)}\n" with open(output_path, "w", encoding="utf-8") as f: f.write(content) def generate_rs(data, output_path): # 生成Rust常量代码,可根据需要调整为struct或者独立const content = "use serde::Serialize;\n\n" content += "#[derive(Debug, Serialize)]\n" content += "pub const CONSTS: &str = r#\"" + json.dumps(data) + "\"#;\n" with open(output_path, "w", encoding="utf-8") as f: f.write(content) def main(): input_path = sys.argv[1] lang = sys.argv[2] output_path = sys.argv[3] with open(input_path, encoding="utf-8") as f: data = yaml.safe_load(f) if lang == "ts": generate_ts(data, output_path) elif lang == "py": generate_py(data, output_path) elif lang == "rs": generate_rs(data, output_path) else: raise ValueError(f"Unsupported language {lang}") if __name__ == "__main__": main()
步骤2:自定义Bazel规则
在项目中新建yaml_const.bzl文件,定义构建规则,将转换脚本和YAML输入关联,生成各语言可识别的依赖目标:
load("@rules_ts//ts:defs.bzl", "TsInfo") load("@rules_python//python:defs.bzl", "PyInfo") load("@rules_rust//rust:providers.bzl", "RustLibraryInfo") def _yaml_const_impl(ctx): # 生成TS输出 ts_out = ctx.actions.declare_file(ctx.attr.name + ".ts") ctx.actions.run( inputs = [ctx.file.src], outputs = [ts_out], executable = ctx.executable._converter, args = [ctx.file.src.path, "ts", ts_out.path], ) # 生成Python输出 py_out = ctx.actions.declare_file(ctx.attr.name + ".py") ctx.actions.run( inputs = [ctx.file.src], outputs = [py_out], executable = ctx.executable._converter, args = [ctx.file.src.path, "py", py_out.path], ) # 生成Rust输出 rs_out = ctx.actions.declare_file(ctx.attr.name + ".rs") ctx.actions.run( inputs = [ctx.file.src], outputs = [rs_out], executable = ctx.executable._converter, args = [ctx.file.src.path, "rs", rs_out.path], ) # 返回各语言对应的Provider,让业务目标可以识别依赖 return [ TsInfo(transitive_sources = depset([ts_out])), PyInfo(transitive_sources = depset([py_out])), RustLibraryInfo(crate_root = rs_out, srcs = depset([rs_out])), ] yaml_const = rule( implementation = _yaml_const_impl, attrs = { "src": attr.label(allow_single_file = [".yaml", ".yml"], mandatory = True), "_converter": attr.label( default = "//path/to:yaml_to_const", executable = True, cfg = "exec", ), }, )
同时要在转换脚本所在目录的BUILD文件中,把Python脚本声明为可执行目标:
py_binary( name = "yaml_to_const", srcs = ["yaml_to_const.py"], deps = ["@pypi//pyyaml"], visibility = ["//visibility:public"], )
步骤3:业务代码使用
在常量YAML所在目录的BUILD文件中定义规则目标:
load("//path/to:yaml_const.bzl", "yaml_const") yaml_const( name = "app_const", src = "consts.yaml", visibility = ["//visibility:public"], )
各语言业务代码直接依赖这个目标即可:
- TypeScript代码:在
ts_library的deps中加入//path/to:app_const,直接import consts from './app_const'使用 - Python代码:在
py_library的deps中加入//path/to:app_const,直接from path.to.app_const import ITEM1使用 - Rust代码:在
rust_library的deps中加入//path/to:app_const,直接引入使用
替代方案(更省心)
如果你可以接受替换文件格式,直接用Protobuf定义常量是更稳定的方案:
- 把常量定义在proto文件中,使用固定值字段或者常量配置
- 直接用Bazel原生的proto规则 + 各语言的protoc插件生成代码
- 不用自己写转换脚本和自定义规则,生态支持更完善,还天然支持类型校验
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

