You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 12:43:13