如何使用workspace_status_command的输出构建自定义规则?

时间:2018-06-01 17:00:39

标签: bazel

bazel build标记--workspace_status_command支持调用脚本来检索,例如存储库元数据,这也称为构建标记,可在java_binary等规则中使用。

我想使用此元数据创建自定义规则。 我想将它用于一个通用的支持功能。它应该接收git版本和一些其他属性,并创建一个version.go输出文件,可用作依赖项。

所以我开始了解各种bazel存储库中的规则。

rules_docker之类的规则支持在stamp中使用container_image标记,并允许您在属性中引用状态输出。

rules_gox_defs的{​​{1}}属性中支持它。

这对我的目的来说是理想的,我挖了......

看起来我可以使用ctx.actions.expand_template中的条目作为go_binary的字典,通过ctx.info_file or ctx.version_file得到我想要的内容。但我还没弄清楚如何获取这些文件的字典。这两个文件似乎是"非官方的",它们不属于substitutions文档。

基于我已经发现的东西:如何根据状态命令输出获得ctx

如果不可能,从自定义规则访问dict输出的最短/最简单方法是什么?

2 个答案:

答案 0 :(得分:0)

我一直都在你的位置,我最终走上了你开始探索的道路。我生成一个JSON描述,其中还包括从git收集的信息以及包含结果的包,我最终做了类似这样的事情:

def _build_mft_impl(ctx):
    args = ctx.actions.args()
    args.add('-f')
    args.add(ctx.info_file)
    args.add('-i')
    args.add(ctx.files.src)
    args.add('-o')
    args.add(ctx.outputs.out)
    ctx.actions.run(
        outputs = [ctx.outputs.out],
        inputs = ctx.files.src + [ctx.info_file],
        arguments = [args],
        progress_message = "Generating manifest: " + ctx.label.name,
        executable = ctx.executable._expand_template,
    )

def _get_mft_outputs(src):
    return {"out": src.name[:-len(".tmpl")]}

build_manifest = rule(
        implementation = _build_mft_impl,
        attrs = {
            "src": attr.label(mandatory=True,
                              allow_single_file=[".json.tmpl", ".json_tmpl"]),
            "_expand_template": attr.label(default=Label("//:expand_template"),
                                           executable=True,
                                           cfg="host"),
        },
        outputs = _get_mft_outputs,
    )

//:expand_template在我的案例中是一个标签,指向执行转化的py_binary。我很乐意了解更好(更本土,更少跳跃)的方式,但是(现在)我选择了:它有效。关于方法和您的顾虑的评论很少:

  • 您无法读入AFAIK(文件并在Skylark中执行操作)本身......
  • ...说到这一点,无论如何都要保持转换(工具)和构建描述(bazel)并不是一件坏事。
  • 可能会对官方文档的构成进行辩论,但ctx.info_file可能不会出现在参考手册中,它会在源代码树中记录。 :)对于其他领域也是如此(我希望这不是因为那些接口被认为还没有提交)。

为了src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleContextApi.java中的完整性,有:

@SkylarkCallable(
  name = "info_file",
  structField = true,
  documented = false,
  doc =
  "Returns the file that is used to hold the non-volatile workspace status for the "
      + "current build request."
)
public FileApi getStableWorkspaceStatus() throws InterruptedException, EvalException;

编辑:评论中提到的额外细节。

在我的workspace_status.sh中我会得到以下一行:

echo STABLE_GIT_REF $(git log -1 --pretty=format:%H)

在我的.json.tmpl文件中,我会:

"ref": "${STABLE_GIT_REF}",

我选择了像文本符号这样的shell来替换,因为它对很多用户来说很直观,而且很容易匹配。

至于替换,实际代码的相关部分(CLI保留在此部分之外)将是:

def get_map(val_file):
    """
    Return dictionary of key/value pairs from ``val_file`.
    """
    value_map = {}

    for line in val_file:
        (key, value) = line.split(' ', 1)
        value_map.update(((key, value.rstrip('\n')),))
    return value_map


def expand_template(val_file, in_file, out_file):
    """
    Read each line from ``in_file`` and write it to ``out_file`` replacing all
    ${KEY} references with values from ``val_file``.
    """
    def _substitue_variable(mobj):
        return value_map[mobj.group('var')]
    re_pat = re.compile(r'\${(?P<var>[^} ]+)}')
    value_map = get_map(val_file)
    for line in in_file:
        out_file.write(re_pat.subn(_substitue_variable, line)[0])

EDIT2:这就是Python脚本如何将python脚本暴露给bazel的其余部分。

py_binary(
    name = "expand_template",
    main = "expand_template.py",
    srcs = ["expand_template.py"],
    visibility = ["//visibility:public"],
)

答案 1 :(得分:0)

根据Ondrej的回答,我现在使用这样的东西(在SO编辑器中修改,可能包含小错误):

tools/bazel.rc

build --workspace_status_command=tools/workspace_status.sh

tools/workspace_status.sh

echo STABLE_GIT_REV $(git rev-parse HEAD)

version.bzl

_VERSION_TEMPLATE_SH = """
set -e -u -o pipefail

while read line; do
  declare "${line% *}"="${line#* }"
done <"$INFILE" \
&& cat <<EOF >"$OUTFILE"
{ "ref": "${STABLE_GIT_REF}"
, "service": "${SERVICE_NAME}"
}
EOF
"""

def _commit_info_impl(ctx):
  ctx.actions.run_shell(
      outputs = [ctx.outputs.outfile],
      inputs = [ctx.info_file],
      progress_message = "Generating version file: " + ctx.label.name,
      command = _VERSION_TEMPLATE_SH,
      env = {
        'INFILE': ctx.info_file.path,
        'OUTFILE': ctx.outputs.version_go.path,
        'SERVICE_NAME': ctx.attr.service,
      },
  )

commit_info = rule(
    implementation = _commit_info_impl,
    attrs = {
      'service': attr.string(
          mandatory = True,
          doc = 'name of versioned service',
      ),
    },
    outputs = {
      'outfile': 'manifest.json',
    },
)