现在,我有一个非常愚蠢的漂亮的打印脚本,它有点git-fu来查找格式化(无条件)然后通过clang-format -i运行它们。这种方法有几个缺点:
在过去,我能够用CMake做一些具有几个不错的属性的东西,我想在bazel中重现:
在CMake-land中,我使用了这个策略,受SCons代理目标技巧的启发:
引入虚拟目标(例如source - > source.formatted)。与此目标关联的操作有两个作用:a)运行clang-format -i source,b)输出/触摸名为source.formatted的文件(这保证了对于合理的文件系统,如果source.formatted比源更新,则源不需要重新格式化)
添加一个虚拟目标(target_name.aggregated_formatted),它聚合所有与特定库/可执行目标来源相对应的.formatted文件
使库/可执行目标依赖于target_name.aggregated_formatted作为预构建步骤
非常感谢任何帮助。
答案 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_library
。 lint_sources
和pretty_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_binary
对clang-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上获得原型实现。