Bazel Action的可选输出?(巴泽尔的SWIG规则)



我正在研究一个bazel规则(使用5.2.0版本(,该规则使用SWIG(4.0.1版本(从C++代码中创建一个python库,该库改编自tensorflow库中的一个规则。我遇到的问题是,根据ctx.file.source.path的内容,swig调用可能会生成必要的.h文件。如果是这样,下面的规则非常有效。如果没有,我得到:

ERROR: BUILD:31:11: output 'foo_swig_h.h' was not created
ERROR: BUILD:31:11: SWIGing foo.i. failed: not all outputs were created or valid

如果从_py_swig_gen_impl中删除了h_out内容,那么当swig不生成.h文件时,下面的规则非常有效。但是,如果swig确实产生了一个,bazel似乎会忽略它,并且native.cc_binary无法编译它,导致gcc失败,在foo_swig_cc.cc#include <foo_swig_cc.h>行出现"没有这样的文件或目录"错误。

(输出中.h文件的存在与否取决于ctx.file.source.path处的.i文件是否使用SWIG的"控制器"功能。(

def _include_dirs(deps):
return depset(transitive = [dep[CcInfo].compilation_context.includes for dep in deps]).to_list()
def _headers(deps):
return depset(transitive = [dep[CcInfo].compilation_context.headers for dep in deps]).to_list()
# Bazel rules for building swig files.
def _py_swig_gen_impl(ctx):
module_name = ctx.attr.module_name
cc_out = ctx.actions.declare_file(module_name + "_swig_cc.cc")
h_out = ctx.actions.declare_file(module_name + "_swig_h.h")
py_out = ctx.actions.declare_file(module_name + ".py")
args = ["-c++", "-python", "-py3"]
args += ["-module", module_name]
args += ["-I" + x for x in _include_dirs(ctx.attr.deps)]
args += ["-I" + x.dirname for x in ctx.files.swig_includes]
args += ["-o", cc_out.path]
args += ["-outdir", py_out.dirname]
args += ["-oh", h_out.path]
args.append(ctx.file.source.path)
outputs = [cc_out, h_out, py_out]
ctx.actions.run(
executable = "swig",
arguments = args,
mnemonic = "Swig",
inputs = [ctx.file.source] + _headers(ctx.attr.deps) + ctx.files.swig_includes,
outputs = outputs,
progress_message = "SWIGing %{input}.",
)
return [DefaultInfo(files = depset(direct = [cc_out, py_out]))]
_py_swig_gen = rule(
attrs = {
"source": attr.label(
mandatory = True,
allow_single_file = True,
),
"swig_includes": attr.label_list(
allow_files = [".i"],
),
"deps": attr.label_list(
allow_files = True,
providers = [CcInfo],
),
"module_name": attr.string(mandatory = True),
},
implementation = _py_swig_gen_impl,
)
def py_wrap_cc(name, source, module_name = None, deps = [], copts = [], **kwargs):
if module_name == None:
module_name = name
python_deps = [
"@local_config_python//:python_headers",
"@local_config_python//:python_lib",
]
# First, invoke the _py_wrap_cc rule, which runs swig. This outputs:
# `module_name.cc`, `module_name.py`, and, sometimes, `module_name.h` files.
swig_rule_name = "swig_gen_" + name
_py_swig_gen(
name = swig_rule_name,
source = source,
swig_includes = ["//third_party/swig_rules:swig_includes"],
deps = deps + python_deps,
module_name = module_name,
)
# Next, we need to compile the `module_name.cc` and `module_name.h` files
# from the previous rule. The `module_name.py` file already generated
# expects there to be a `_module_name.so` file, so we name the cc_binary
# rule this way to make sure that's the resulting file name.
cc_lib_name = "_" + module_name + ".so"
native.cc_binary(
name = cc_lib_name,
srcs = [":" + swig_rule_name],
linkopts = ["-dynamic", "-L/usr/local/lib/"],
linkshared = True,
deps = deps + python_deps,
)
# Finally, package everything up as a python library that can be depended
# on. Note that this rule uses the user-given `name`.
native.py_library(
name = name,
srcs = [":" + swig_rule_name],
srcs_version = "PY3",
data = [":" + cc_lib_name],
imports = ["./"],
)

从广义上讲,我的问题是,如何用一条规则来最好地处理这件事。我曾尝试在ctx.actions.run之前添加一个ctx.actions.write,认为我可以生成一个伪".h"文件,如果需要,该文件将被覆盖。这给了我:

ERROR: BUILD:41:11: for foo_swig_h.h, previous action: action 'Writing file foo_swig_h.h', attempted action: action 'SWIGing foo.i.'

我的下一个想法是删除h_out内容,然后尝试通过某种glob调用来捕获cc_binary规则的h文件。

我看到了两种方法:添加一个属性来指示它是否适用,或者编写一个包装脚本来无条件地生成它。

添加一个属性意味着类似于"has_h": attr.bool(),然后在_py_swig_gen_impl中使用该属性使ctx.actions.declare_file(module_name + "_swig_h.h")成为条件属性。

包装器脚本选项意味着对executable:使用类似的东西

#!/bin/bash
set -e
touch the_path_of_the_header
exec swig "$@"

这将无条件地创建输出,然后swig将覆盖它(如果适用(。如果它不适用,那么在Bazel规则中传递一个空头文件应该是无害的。

对于子孙后代来说,这就是我的_py_swig_gen_impl在实现@Brian上面的建议后的样子:

def _py_swig_gen_impl(ctx):
module_name = ctx.attr.module_name
cc_out = ctx.actions.declare_file(module_name + "_swig_cc.cc")
h_out = ctx.actions.declare_file(module_name + "_swig_h.h")
py_out = ctx.actions.declare_file(module_name + ".py")
include_dirs = _include_dirs(ctx.attr.deps)
headers = _headers(ctx.attr.deps)
args = ["-c++", "-python", "-py3"]
args += ["-module", module_name]
args += ["-I" + x for x in include_dirs]
args += ["-I" + x.dirname for x in ctx.files.swig_includes]
args += ["-o", cc_out.path]
args += ["-outdir", py_out.dirname]
args += ["-oh", h_out.path]
args.append(ctx.file.source.path)
outputs = [cc_out, h_out, py_out]
# Depending on the contents of `ctx.file.source`, swig may or may not
# output a .h file needed by subsequent rules. Bazel doesn't like optional
# outputs, so instead of invoking swig directly we're going to make a
# lightweight executable script that first `touch`es the .h file that may
# get generated, and then execute that. This means we may be propagating
# an empty .h file around as a "dependency" sometimes, but that's okay.
swig_script_file = ctx.actions.declare_file("swig_exec.sh")
ctx.actions.write(
output = swig_script_file,
is_executable = True,
content = "#!/bin/bashnnset -entouch " + h_out.path + "nexec swig "$@"",
)
ctx.actions.run(
executable = swig_script_file,
arguments = args,
mnemonic = "Swig",
inputs = [ctx.file.source] + headers + ctx.files.swig_includes,
outputs = outputs,
progress_message = "SWIGing %{input}.",
)
return [
DefaultInfo(files = depset(direct = outputs)),
]

ctx.actions.write生成建议的bash脚本:

#!/bin/bash
set -e
touch %{h_out.path}
exec swig "$@"

这保证了预期的h_out将始终由ctx.actions.run输出,无论swig是否生成它。

最新更新