Bazel 如何使用workspace\u status\u命令的输出构建自定义规则?

Bazel 如何使用workspace\u status\u命令的输出构建自定义规则?,bazel,Bazel,bazel build标志--workspace\u status\u命令支持调用脚本来检索例如存储库元数据,这也称为构建戳记,在java\u binary等规则中可用 我想使用此元数据创建一个自定义规则。 我想用它来实现一个通用的支持功能。它应该接收git版本和一些其他属性,并创建一个可用作依赖项的version.go输出文件 所以我开始了一段旅程,在各种bazel存储库中寻找规则 像Rules\u docker这样的规则支持在container\u image中使用stamp进行戳记,并允许

bazel build
标志
--workspace\u status\u命令
支持调用脚本来检索例如存储库元数据,这也称为构建戳记,在
java\u binary
等规则中可用

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

所以我开始了一段旅程,在各种bazel存储库中寻找规则

Rules\u docker
这样的规则支持在
container\u image
中使用
stamp
进行戳记,并允许您在属性中引用状态输出

rules\u go
go\u binary
x\u defs
属性中支持它

这将是我的理想目标,我在

看起来我可以通过将中的条目用作
替换的字典来获得所需的内容。但我不知道如何找到这些文件的字典。这两个文件似乎是“非官方的”,它们不是
ctx
文档的一部分

在我已经发现的基础上:如何根据status命令输出获取
dict


如果这是不可能的,那么访问自定义规则的
workspace\u status\u命令
输出的最短/最简单的方法是什么?

我一直在您所在的位置,我最终遵循您开始探索的路径。我生成了一个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_二进制文件。我很乐意了解一种更好的(更地道,更少的啤酒花)方法,但是(现在)我同意:它有效。对该方法和您的担忧很少有评论:

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

为确保
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\u 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])

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

tools/bazel.rc

build --workspace_status_command=tools/workspace_status.sh
tools/workspace\u 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',
    },
)
\u版本\u模板\u SH=”“”
设置-e-u-o管道故障
边读边做
导出“${line%*}”=“${line}”

完成我会发誓会有一条评论要求更详细一点。我已经清理了这些信息,保留了相关部分,并将其添加到了答案中。是的,对不起。有一条评论。但我删除了它,因为我最初没有接到本机Python的呼叫,并且没有正确地解决您的解决方案。由于缺乏编辑权限,我将其删除。我很抱歉目前正在根据您编写的内容试验genrule-感谢您的更新。如果我有进展,我将添加另一个答案。不用担心。我还添加了构建文件中的一个片段,该片段将python脚本公开给世界其他地方(标签用于上面规则的
\u expand\u template
)。我更喜欢让bazel像python一样处理python。这是显而易见的。并且有助于移植构建环境。这些示例是一个更大的包中的一部分,用作特定项目/自定义构建结果打包的外部依赖项。因此,发布所有内容可能会更混乱,而不是更有用。我希望我的回答能回报您。Thanks的惊人信息!
_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',
    },
)