繁体   English   中英

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

[英]Optional Output from Bazel Action? (SWIG rule for Bazel)

我正在研究使用 SWIG(版本 4.0.1)从 C++ 代码中制作 python 库的 bazel 规则(使用版本 5.2.0),改编自Z2C39BC19B761AC36DCZ0462 库中的规则 我遇到的问题是,根据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

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

(output 中是否存在.h文件取决于ctx.file.source.path处的.i文件是否使用 SWIG 的“directors”功能。)

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.write之前添加一个ctx.actions.run ,认为我可以生成一个虚拟的“.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 "$@"

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

对于子孙后代,这就是我的_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/bash\n\nset -e\ntouch " + 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 的ctx.actions.run ,无论swig是否生成它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM