如何在Bazel自定义规则中复用py_binary依赖,避免重复配置?
问题描述
我有一个py_binary可执行目标,希望在自定义Bazel规则中调用它。目前功能已经实现,但必须重复配置该py_binary的依赖项。有没有办法避免这种重复,自动包含py_binary的依赖?
我把问题简化成了以下示例(可通过运行//foo:main目标复现):
greet/BUILD.bazel:
py_binary( name = "greet", srcs = ["greet.py"], deps = ["@rules_python//python/runfiles"], visibility = ["//visibility:public"], )
greet/greet.py:
from rules_python.python.runfiles import runfiles print("hello world")
foo/BUILD.bazel:
load("//bazel:defs.bzl", "foo_binary") foo_binary( name = "main", )
bazel/BUILD.bazel:
(空文件)
bazel/defs.bzl:
def _foo_binary(ctx): shell_script = ctx.actions.declare_file("run.sh") ctx.actions.write( output = shell_script, content = """#!/bin/bash $0.runfiles/svkj/{runtime} """.format(runtime=ctx.executable._greet.short_path), is_executable = True, ) return [ DefaultInfo( executable = shell_script, runfiles = ctx.runfiles( files = ctx.files._greet + ctx.files.deps + ctx.files._python, collect_data = True ), ), ] foo_binary = rule( implementation = _foo_binary, attrs = { "deps": attr.label_list( allow_files=True, ), "_greet": attr.label( default = "//greet:greet", cfg = "exec", executable = True, ), # TODO - Why is it necessary to add this explicitly? "_python": attr.label_list( allow_files=True, default = ["@python3_9//:python3", "@rules_python//python/runfiles"], ), }, executable = True, )
WORKSPACE:
workspace(name = "svkj") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "rules_python", sha256 = "5fa3c738d33acca3b97622a13a741129f67ef43f5fdfcec63b29374cc0574c29", strip_prefix = "rules_python-0.9.0", url = "https://github.com/bazelbuild/rules_python/archive/refs/tags/0.9.0.tar.gz", ) load("@rules_python//python:repositories.bzl", "python_register_toolchains") python_register_toolchains( name = "python3_9", python_version = "3.9", ) load("@python3_9//:defs.bzl", "interpreter")
解决方案
要自动包含py_binary的依赖,无需手动重复声明_python这类依赖项,核心是直接复用目标//greet:greet的DefaultInfo中提供的runfiles,而非手动收集文件。
修改bazel/defs.bzl如下:
def _foo_binary(ctx): shell_script = ctx.actions.declare_file("run.sh") ctx.actions.write( output = shell_script, content = """#!/bin/bash $0.runfiles/svkj/{runtime} """.format(runtime=ctx.executable._greet.short_path), is_executable = True, ) # 获取_greet目标的DefaultInfo,复用它的runfiles greet_default_info = ctx.attr._greet[DefaultInfo] # 合并自定义规则的deps和_greet的runfiles combined_runfiles = ctx.runfiles(files = ctx.files.deps).merge(greet_default_info.default_runfiles) return [ DefaultInfo( executable = shell_script, runfiles = combined_runfiles, ), ] foo_binary = rule( implementation = _foo_binary, attrs = { "deps": attr.label_list( allow_files=True, ), "_greet": attr.label( default = "//greet:greet", cfg = "exec", executable = True, ), # 移除手动声明的_python依赖 }, executable = True, )
原理说明
py_binary目标的DefaultInfo已经包含了它运行所需的所有依赖(包括Python解释器、runfiles库等),通过ctx.attr._greet[DefaultInfo]可直接获取这些信息。- 使用
runfiles.merge()方法将自定义规则的deps和_greet的runfiles合并,无需手动重复声明依赖。 - 移除
_python属性后,规则会自动继承py_binary的所有依赖,彻底避免重复配置。
修改后运行bazel run //foo:main即可正常执行,且无需维护重复的依赖列表。
内容的提问来源于stack exchange,提问作者Steve Vermeulen
相关产品推荐
相关产品推荐

