如何将DVC拉取的文件声明为输出供其他Bazel目标依赖?
问题
我用DVC替代git-lfs按需获取大型数据集,仓库里已经提交了.dvc文件,运行dvc pull就能把文件下载到源码树里。我已经把这个操作封装成了Bazel genrule:
dvc_data( name = "my_rule", src = "data.dvc", )
这对直接从源码树读取的消费者没问题,但现在我想把拉取的文件本身声明为输出,供其他Bazel规则依赖(类似exports_files,但针对生成的文件)。比如我想要这样的用法:
dvc_publish_files( name = "my_data_publish", src = "my_data.dvc", produced = ["my_big_file.bin"], ) exports_files([ "other_file.json", ]) my_consumer( name = "Consumer", src = "src.file", inputs = [ ":my_big_file.bin", ":other_file.json", ] )
我当前的宏只生成一个标记文件:
def dvc_data(name, src, **kwargs): """ Emits a Bazel genrule that runs `dvc pull` on a given directory, writing the stamp file that marks the pull has succeeded to bazel-bin. """ sanitized = _sanitize_label(src) out_file = "dvc_pull_stamp__{}".format(sanitized) native.genrule( name = name, outs = [out_file], cmd = """ # Alias to the DVC wrapper (which handles lock files) DVC_BIN=$$(realpath $(location //:dvc)) && \ dvc=$$(pwd)/$(location //:dvc_wrapper) && \ src_path=$(location {src}) && \ src_dir=$$(dirname "$$src_path") && \ src_file=$$(basename "$$src_path") && \ cd "$$src_dir" && \ $$dvc "$$DVC_BIN" pull "$$src_file" && \ cd - && \ # changes to the .dvc file will be noticed cat "$$src_path" > "$(RULEDIR)/{out}" """.format(src = src, out = out_file), srcs = [src], tools = ["//:dvc_wrapper", "//:dvc"], local = 1, output_to_bindir = True, **kwargs )
我知道需要声明outs并把文件从源码树复制到Bazel输出目录,但搞不出稳定的版本。请问怎么编写Bazel规则/宏,运行dvc pull并把拉取的文件声明为下游规则可依赖的输出?
解决方案
核心思路是让Bazel明确追踪拉取后的文件:将目标文件加入genrule的输出列表,拉取完成后把文件复制到Bazel的输出目录($(RULEDIR)),这样下游规则就能直接通过标签依赖这些文件。
改造后的宏实现
def dvc_publish_files(name, src, produced, **kwargs): """ 运行`dvc pull`拉取指定.dvc文件对应的数据集,并将指定文件暴露为Bazel输出,供下游规则直接依赖。 """ sanitized = _sanitize_label(src) stamp_file = "dvc_pull_stamp__{}".format(sanitized) # 将标记文件和要暴露的文件一起作为输出 outs = [stamp_file] + produced # 生成每个目标文件的复制命令 copy_cmds = [] for file in produced: copy_cmds.append("cp -f \"$$src_dir/{file}\" \"$(RULEDIR)/{file}\"".format(file=file)) native.genrule( name = name, outs = outs, cmd = """ # 调用DVC wrapper处理锁文件 DVC_BIN=$$(realpath $(location //:dvc)) && \ dvc=$$(pwd)/$(location //:dvc_wrapper) && \ src_path=$(location {src}) && \ src_dir=$$(dirname "$$src_path") && \ src_file=$$(basename "$$src_path") && \ cd "$$src_dir" && \ $$dvc "$$DVC_BIN" pull "$$src_file" && \ # 将拉取的文件复制到Bazel输出目录 {copy_cmds} && \ cd - && \ # 生成标记文件,追踪.dvc文件的变更 cat "$$src_path" > "$(RULEDIR)/{stamp_file}" """.format( src=src, stamp_file=stamp_file, copy_cmds=" && \\\n ".join(copy_cmds) ), srcs = [src], tools = ["//:dvc_wrapper", "//:dvc"], local = 1, output_to_bindir = True, **kwargs ) def _sanitize_label(label): # 将Bazel标签中非法的字符替换为下划线 return label.replace("/", "_").replace(".", "_")
关键改动说明
- 明确输出文件:把
produced参数指定的文件加入outs列表,让Bazel追踪这些文件的状态,下游规则可以直接通过标签引用。 - 复制文件到输出目录:拉取完成后,将源码树中的目标文件复制到
$(RULEDIR),确保Bazel能识别这些文件为规则的产物。 - 保留标记文件:继续生成标记文件,确保当
.dvc文件内容变更时,Bazel会重新触发拉取操作。
使用示例
dvc_publish_files( name = "my_data_publish", src = "my_data.dvc", produced = ["my_big_file.bin"], ) my_consumer( name = "Consumer", src = "src.file", inputs = [ ":my_big_file.bin", # 直接依赖拉取后的文件 ":other_file.json", ] )
注意事项
produced参数中的文件路径必须和.dvc配置中记录的路径一致,且是相对于.dvc文件所在目录的路径。- 由于
dvc pull是本地操作(local = 1),该规则无法在远程构建缓存中复用,只能在配置了DVC的环境中运行。 - 如果需要处理目录或通配符,可扩展宏的逻辑来匹配目标路径。
内容的提问来源于stack exchange,提问作者Prunus Persica
相关产品推荐
相关产品推荐

