pythonbazelstarlark

Unable to use py_binary target as executable in a custom rule


I have a py_binary executable target, and want to use this from a custom rule. I am able to get this to work, but only by duplicating the dependencies of my py_binary target with my custom rule. Is there any way to avoid this duplication and automatically include the dependencies of the py_binary?

I have simplified my problem down to the following (reproduce by running the //foo:main target)

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: (empty)

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,
)

bazel/BUILD.bazel:

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")

Solution

  • You need to correctly collect the runfiles of the _greet binary. Instead of just using the _greet binary itself, try the following to include its dependencies (and data dependencies) aswell:

    DefaultInfo(
      executable = shell_script,
      runfiles = ctx._greet[DefaultInfo].default_runfiles
    )
    

    See also: https://bazel.build/extending/rules#runfiles