如何让bazel的`sh_binary`目标依赖于其他二进制目标?

12

我已经设置了bazel来构建一些CLI工具,执行各种数据库维护任务。每个工具都是一个py_binarycc_binary目标,通过命令行调用并传入某个数据文件的路径:它会处理该文件并将结果存储在数据库中。

现在,我需要创建一个依赖包,其中包含数据文件和shell脚本,用于调用这些CLI工具以执行特定于应用程序的数据库操作。

然而,似乎没有办法从只包含sh_binary目标和数据文件的新包中依赖于现有的py_binarycc_binary目标。尝试这样做会导致错误,例如:

ERROR: /workspace/shbin/BUILD.bazel:5:12: in deps attribute of sh_binary rule //shbin:run: py_binary rule '//pybin:counter' is misplaced here (expected sh_library)
有没有一种方法可以使用 sh_binary 从 shell 脚本调用/依赖于现有的 Bazel 二进制目标?此处提供了一个完整的例子: https://github.com/psigen/bazel-mixed-binaries 注: 无法使用 py_librarycc_library 替代 py_binarycc_binary,因为 (a) 需要调用两种语言来处理数据文件,以及 (b) 这些工具来自上游存储库,已经被设计为 CLI 工具。同时,也不能将所有数据文件放入 CLI 工具包中,因为存在多个特定于应用程序的包,这些包不能混合使用。
3个回答

20
你可以创建一个genrule来运行这些工具作为构建的一部分,或者创建一个sh_binary,通过属性依赖于这些工具并运行它们。

使用genrule方法

这是更简单的方法,让你在构建过程中运行这些工具。

genrule(
    name = "foo",
    tools = [
        "//tool_a:py",
        "//tool_b:cc",
    ],
    srcs = [
        "//source:file1",
        ":file2",
    ],
    outs = [
        "output_file1",
        "output_file2",
    ],
    cmd = "$(location //tool_a:py) --input=$(location //source:file1) --output=$(location output_file1) && $(location //tool_b:cc) < $(location :file2) > $(location output_file2)",
)

sh_binary方法

这种方法更加复杂,但可以让你在构建过程中运行sh_binary(如果它在genrule.tools中,类似于前面的方法),或者在构建后从bazel-bin下运行。

sh_binary中,您必须对工具进行数据依赖:

sh_binary(
    name = "foo",
    srcs = ["my_shbin.sh"],
    data = [
        "//tool_a:py",
        "//tool_b:cc",
    ],
)

然后,在sh_binary中,您需要使用内置于Bazel中的所谓“Bash runfiles库”来查找二进制文件的运行时路径。该库的文档位于其源文件中
这个想法是:
  1. sh_binary必须依赖于特定的目标
  2. 你需要复制粘贴一些样板代码sh_binary的顶部(原因在这里描述)
  3. 然后,您可以使用rlocation函数查找二进制文件的运行时路径

例如,您的my_shbin.sh可能如下所示:

#!/bin/bash
# --- begin runfiles.bash initialization ---
...
# --- end runfiles.bash initialization ---

path=$(rlocation "__main__/tool_a/py")
if [[ ! -f "${path:-}" ]]; then
  echo >&2 "ERROR: could not look up the Python tool path"
  exit 1
fi
$path --input=$1 --output=$2

在rlocation路径参数中的__main__是工作区的名称。由于您的WORKSPACE文件中没有定义工作区名称的"workspace"规则,Bazel将使用默认的工作区名称__main__

谢谢@Laszlo,第二种方法非常有效! - psigen
感谢您的编辑建议psigen! 显然它被拒绝了。我已经编辑了原始帖子,加入了 shell 脚本的 bug 修复。 - László
2
如果您正在尝试使用第二种方法,请确保您复制粘贴到您的shell脚本中的样板是来自主分支,因为它在此帖子首次撰写以来已经有所更改。 - Brian Barnes
我尝试使用相同的方法在Bazel中启动Java二进制目标,但不幸的是我遇到了一个错误“无法找到运行文件目录。(设置$JAVA_RUNFILES以抑制搜索。)”。我应该在哪里找到这些Java运行文件? - user14042594

8

一种干净的方法是使用 args$(location)

BUILD 文件的内容:

py_binary(
    name = "counter",
    srcs = ["counter.py"],
    main = "counter.py",
)

sh_binary(
    name = "run",
    srcs = ["run.sh"],
    data = [":counter"],
    args = ["$(location :counter)"],
)

counter.py(你的工具)的内容:

print("This is the counter tool.")

run.sh的内容(您的Bash脚本):

#!/bin/bash
set -eEuo pipefail

counter="$1"
shift

echo "This is the bash script, about to call the counter tool."
"$counter"

这里是一个演示,展示了bash脚本调用Python工具的过程:

$ bazel run //example:run 2>/dev/null
This is the bash script, about to call the counter tool.
This is the counter tool.

还值得一提的是这个注释(来自文档):

如果你在 bazel 之外运行目标时(例如通过手动执行位于 bazel-bin/ 中的二进制文件),则不会传递参数。


1
MVP就在这里!这是唯一对我有效的解决方案,谢谢。 - Kyle Bridenstine
这个非常有效。但是,如果sh文件中有一个通过pip安装的包,例如celery,并且您执行了一个操作,例如celery -A xxx worker -l INFO &> /app/logs/celery.log &,您仍然会收到关于celery的错误。有没有办法让sh_binary知道已安装的软件包?请参见此问题https://stackoverflow.com/questions/71602479/does-bazel-sh-binary-allow-calling-of-scripts-that-depends-on-some-pip-packages - E_K
你的 sh 二进制文件可以任意操作,包括设置 PYTHONPATH。但这会导致你的构建不是完全遗传的,所以最好避免使用它。 - ron rothman

5

对我来说更简单的方法是在data部分将cc_binary添加为依赖项。 在prefix/BUILD中进行操作。

cc_binary(name = "foo", ...)
sh_test(name = "foo_test", srcs = ["foo_test.sh"], data = [":foo"])

foo_test.sh 中,工作目录不同,因此您需要找到正确的二进制文件的 prefix

#! /usr/bin/env bash

executable=prefix/foo

$executable ...

这也是您可以依赖于其他目标创建的共享库,以便在py_binarypy_library中使用的方式。 - ahans
这应该是被接受的答案。没有必要为此使用rlocation。 - Brian Barnes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接