【问题标题】:How to integrate pretty-printing as part of build in bazel如何在 bazel 中集成漂亮的打印作为构建的一部分
【发布时间】:2017-06-02 22:51:40
【问题描述】:

现在,我有一个非常愚蠢的漂亮打印脚本,它执行一点 git-fu 来查找要格式化的文件(无条件地),然后通过 clang-format -i 运行这些文件。这种方法有几个缺点:

  1. 有些文件很大,打印出来要花很长时间。
  2. 无论底层文件是否实际更改,都会始终完成漂亮的打印。

过去,我可以用 CMake 做一些事情,这些事情有几个我想在 bazel 中重现的好属性:

  1. 只有在 代码经过 linting/漂亮打印/等之后才能构建代码。
  2. 只有 lint / pretty print / etc. 发生变化的东西
  3. 无论是否在 VC 下,打印的东西都很漂亮

在 CMake-land 中,我使用了这个策略,灵感来自 SCons 代理目标技巧:

  1. 引入一个虚拟目标(例如 source -> source.formatted)。与此目标相关联的操作有两件事:a) 运行 clang-format -i source,b) 输出/触摸一个名为 source.formatted 的文件(这保证对于合理的文件系统,如果 source.formatted 比 source 更新,则 source不需要重新格式化)

  2. 添加一个虚拟目标 (target_name.aggregated_formatted),它聚合与特定库/可执行目标的源对应的所有 .formatted 文件

  3. 使库/可执行目标依赖于 target_name.aggregated_formatted 作为预构建步骤

任何帮助将不胜感激。

【问题讨论】:

  • @Ainar-G 我的回答是否阐明了宏的实现?

标签: c++ bazel clang-format


【解决方案1】:

@abergmeier 是对的。让我们更进一步,实现宏及其组件。

我们将使用bazelbuild/examples 中的 C++ 第 1 阶段教程。

我们先搞砸hello-world.cc

#include <ctime>



#include <string>

#include <iostream>

std::string get_greet(const std::string& who) {
      return "Hello " + who;
}

void print_localtime() {
    std::time_t result =
          std::time(nullptr);
  std::cout << std::asctime(std::localtime(&result));
}

int main(int argc, char** argv) {
  std::string who = "world";
  if (argc > 1) {who = argv[1];}
  std::cout << get_greet(who) << std::endl;
  print_localtime();


  return 0;
}

这是构建文件:

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

由于cc_binaryclang-format 或一般的linting 一无所知,让我们创建一个名为clang_formatted_cc_binary 的宏并用它替换cc_binary。 BUILD 文件现在如下所示:

load(":clang_format.bzl", "clang_formatted_cc_binary")

clang_formatted_cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

接下来,创建一个名为 clang_format.bzl 的文件,其中包含一个名为 clang_formatted_cc_binary 的宏,它只是 native.cc_binary 的包装:

# In clang_format.bzl
def clang_formatted_cc_binary(**kwargs):
    native.cc_binary(**kwargs)

此时,您可以构建cc_binary 目标,但它还没有运行clang-format。我们需要在clang_formatted_cc_binary 中添加一个中间规则来做到这一点,我们称之为clang_format_srcs

def clang_formatted_cc_binary(name, srcs, **kwargs):
    # Using a filegroup for code cleaniness
    native.filegroup(
        name = name + "_unformatted_srcs",
        srcs = srcs,
    )

    clang_format_srcs(
        name = name + "_formatted_srcs",
        srcs = [name + "_unformatted_srcs"],
    )

    native.cc_binary(
        name = name,
        srcs = [name + "_formatted_srcs"],
        **kwargs
    )

请注意,我们已将 native.cc_binary 的源代码替换为格式化文件,但保留名称以允许在 BUILD 文件中就地替换 cc_binary -> clang_formatted_cc_binary

最后,我们将在同一个clang_format.bzl 文件中编写clang_format_srcs 规则的实现:

def _clang_format_srcs_impl(ctx):
    formatted_files = []

    for unformatted_file in ctx.files.srcs:
        formatted_file = ctx.actions.declare_file("formatted_" + unformatted_file.basename)
        formatted_files += [formatted_file]
        ctx.actions.run_shell(
            inputs = [unformatted_file],
            outputs = [formatted_file],
            progress_message = "Running clang-format on %s" % unformatted_file.short_path,
            command = "clang-format %s > %s" % (unformatted_file.path, formatted_file.path),
        )

    return struct(files = depset(formatted_files))

clang_format_srcs = rule(
    attrs = {
        "srcs": attr.label_list(allow_files = True),
    },
    implementation = _clang_format_srcs_impl,
)

此规则遍历目标的 srcs 属性中的每个文件,声明带有 formatted_ 前缀的“虚拟”输出文件,并在未格式化的文件上运行 clang-format 以生成虚拟输出。

现在,如果您运行 bazel build :hello-world,Bazel 将运行 clang_format_srcs 中的操作,然后再对格式化文件运行 cc_binary 编译操作。我们可以通过运行带有--subcommands 标志的bazel build 来证明这一点:

$ bazel build //main:hello-world --subcommands
..
SUBCOMMAND: # //main:hello-world_formatted_srcs [action 'Running clang-format on main/hello-world.cc']
.. 
SUBCOMMAND: # //main:hello-world [action 'Compiling main/formatted_hello-world.cc']
.. 
SUBCOMMAND: # //main:hello-world [action 'Linking main/hello-world']
..

查看formatted_hello-world.cc 的内容,看起来clang-format 完成了它的工作:

#include <ctime>
#include <string>

#include <iostream>

std::string get_greet(const std::string& who) { return "Hello " + who; }

void print_localtime() {
  std::time_t result = std::time(nullptr);
  std::cout << std::asctime(std::localtime(&result));
}

int main(int argc, char** argv) {
  std::string who = "world";
  if (argc > 1) {
    who = argv[1];
  }
  std::cout << get_greet(who) << std::endl;
  print_localtime();
  return 0;
}

如果你想要的只是格式化的源而不编译它们,你可以直接从clang_format_srcs 运行构建带有_formatted_srcs 后缀的目标:

$ bazel build //main:hello-world_formatted_srcs
INFO: Analysed target //main:hello-world_formatted_srcs (0 packages loaded).
INFO: Found 1 target...
Target //main:hello-world_formatted_srcs up-to-date:
  bazel-bin/main/formatted_hello-world.cc
INFO: Elapsed time: 0.247s, Critical Path: 0.00s
INFO: 0 processes.
INFO: Build completed successfully, 1 total action

【讨论】:

    【解决方案2】:

    您也许可以为此使用方面。不确定,如果确实有可能,Bazel-dev 可能会指出这一点。

    如果您熟悉规则和操作等,快速而肮脏的方法(类似于 CMake 黑客)是编写宏。例如cc_library 你会这样做:

    def clean_cc_library(name, srcs, **kwargs):
      lint_sources(
          name = "%s_linted" % name,
          srcs = srcs,
      )
    
      pretty_print_sources(
          name = "%s_pretty" % name,
          srcs = ["%s_linted"],
      )
    
      return native.cc_library(
        name = name,
        srcs = ["%s_pretty"],
        **kwargs
      ) 
    

    那么您当然需要将每个cc_library 替换为clean_cc_library。而lint_sourcespretty_print_sources 是您必须自己实现的规则,并且需要生成已清理文件的列表。

    【讨论】:

      【解决方案3】:

      @abergmeier 提到也许可以使用 Aspects。你可以,而且我已经制作了一个利用 Aspects 功能的通用 linting 系统的原型,因此无需修改 BUILD 文件即可使用像 clang_formatted_cc_library 这样的宏来代替核心​​规则。

      基本思想是有一个bazel build 步骤,它是一个纯函数f(linter, sources) -&gt; linted_sources_diff 和一个后续的bazel run 步骤,它将这些差异应用回源代码以修复lint 错误。

      原型实现可在https://github.com/thundergolfer/bazel-linting-system 获得。

      【讨论】:

        猜你喜欢
        • 2013-01-20
        • 2012-10-16
        • 2011-02-27
        • 2010-11-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-22
        相关资源
        最近更新 更多