如何将漂亮的印刷作为构建在bazel中的一部分进行整合

时间:2017-06-02 22:51:41

标签: c++ bazel clang-format

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

  1. 有些文件非常庞大,需要永远印刷。
  2. 无论基础文件是否实际发生变化,始终都会进行漂亮的打印。
  3. 在过去,我能够用CMake做一些具有几个不错的属性的东西,我想在bazel中重现:

    1. 只有在之后才构建代码它已经完成了linting / pretty打印等。
    2. 只有lint /漂亮的印刷品等已经改变的东西
    3. 漂亮的印刷品,无论是否在VC下
    4. 在CMake-land中,我使用了这个策略,受SCons代理目标技巧的启发:

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

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

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

      4. 非常感谢任何帮助。

3 个答案:

答案 0 :(得分:4)

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

如果您熟悉规则和操作等,快速而肮脏的方式(类似于CMake hackery)就是编写宏。对于例如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_librarylint_sourcespretty_print_sources是您必须自己实施并需要生成已清理文件列表的规则。

答案 1 :(得分:4)

@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;
}

这是BUILD文件:

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

由于cc_binaryclang-format一无所知,因此我们创建一个名为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_srcs文件中编写clang_format.bzl规则的实现:

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中的操作。我们可以通过运行带有bazel build标志的--subcommands来证明这一点:

$ 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;
}

如果只需要格式化的源文件而无需编译它们,则可以直接使用_formatted_srcs中带有clang_format_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 :(得分:0)

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

基本思想是有一个bazel build步骤是一个纯函数f(linter, sources) -> linted_sources_diff,随后的bazel run步骤是将这些差异应用于原始代码,以修复lint错误。

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