【问题标题】:How can I build custom rules using the output of workspace_status_command?如何使用 workspace_status_command 的输出构建自定义规则?
【发布时间】:2019-06-06 02:46:54
【问题描述】:

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

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

因此,我开始了查看各种 bazel 存储库中规则的旅程。

rules_docker 等规则支持在container_image 中标记stamp,并让您在属性中引用状态输出。

rules_gogo_binaryx_defs 属性中支持它。

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

使用ctx.info_file or ctx.version_file 中的条目作为substitutions 的字典,我似乎可以通过ctx.actions.expand_template 获得我想要的东西。但我不知道如何获取这些文件的字典。而且这两个文件似乎是“非官方的”,它们不是ctx 文档的一部分。

基于我已经发现的内容:如何根据状态命令输出获得dict

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

【问题讨论】:

    标签: bazel


    【解决方案1】:

    我一直在你所在的地方,我最终走上了你开始探索的道路。我生成了一个 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。我很乐意了解一种更好的(更本地化,更少跳数)的方法,但是(现在)我选择了:它有效。关于方法和您的担忧的几位 cmets:

    • 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 脚本暴露给 bazel 其余部分的 Python 脚本的方式。

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

    【讨论】:

    • 我会发誓有一条评论要求提供更多细节。我已经清理了这些位并保留了相关部分并将其添加到答案中。
    • 是的,对不起。有评论。但是我删除了它,因为我最初没有收到对本机 Python 的调用,并且我没有正确解决您的解决方案。由于缺乏编辑权限,我将其删除。我目前正在根据您所写的内容试验一个类型 - 感谢您的更新。如果我得到任何地方,我会添加另一个答案。
    • 不用担心。我还从 BUILD 文件中添加了一个 sn-p,它将 python 脚本公开给世界其他地方(用于上述规则的_expand_template 属性的标签)。我更喜欢让 bazel 把 python 当作 python 处理。很明显它是什么。并帮助移植构建环境。这些示例是较大包的一部分,用作构建结果的项目特定/自定义打包的外部依赖项。因此,发布所有内容可能会更令人困惑而不是有用。
    • 希望我的回答能回报您。感谢您提供惊人的信息!
    【解决方案2】:

    基于 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
      export "${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',
        },
    )
    

    【讨论】:

      猜你喜欢
      • 2011-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多