未加星标

Aspects: the fan-fic of build rules

字体大小 | |
[系统(linux) 所属分类 系统(linux) | 发布者 店小二04 | 时间 2017 | 作者 红领巾 ] 0人收藏点击收藏

Aspects are a feature of Bazel that are basically like fan-fic, if build rules were stories: aspects let you add features that require intimate knowledge of the build graph, but that that the rule maintainer would never want to add.

For example, let’s say we want to be able to generate Makefiles from a Bazel project’s C++ targets. Bazel isn’t going to add support for this to the built-in C++ rules. However, lots of projects might want to support a couple of build systems, so it would be nice to be able to automatically generate build files for Make. So let’s say we have a simple Bazel C++ project with a couple of rules in the BUILD file:

cc_library( name = "lib", srcs = ["lib.cc"], hdrs = ["lib.h"], ) cc_binary( name = "bin", srcs = ["bin.cc"], deps = [":lib"], )

We can use aspects to piggyback on Bazel’s C++ rules and generate new outputs (Makefiles) from them. It’ll take each Bazel C++ rule and generate a .o-file make target for it. For the cc_binary, it’ll link all of the .o files together. Basically, we’ll end up with a Makefile containing:

bin : bin.o lib.o g++ -o bin bin.o lib.o bin.o : bin.cc g++ -c bin.cc lib.o : lib.cc g++ -c lib.cc

(If you have any suggestions about how to make this better, please let me know in the comments, I’m definitely not an expert on Makefiles and just wanted something super-simple.) I’m assuming a basic knowledge of Bazel and Skylark (e.g., you’ve written a Skylark macro before).

Create a .bzl file to hold your aspect. I’ll call mine make.bzl . Add the aspect definition:

makefile = aspect( implementation = _impl, attr_aspects = ["deps"], )

This means that the aspect will follow the “deps” attribute to traverse the build graph. We’ll invoke it on //:bin , and it’ll follow //:bin ‘s dep to //:lib . The aspect’s implementation will be run on both of these targets.

Add the _impl function. We’ll start by just generating a hard-coded Makefile:

def _impl(target, ctx): # If this is a cc_binary, generate the actual Makefile. outputs = [] if ctx.rule.kind == "cc_binary": output = ctx.new_file("Makefile") content = "bin : bin.cc lib.cc lib.h\n\tg++ -o bin bin.cc lib.cc\n" ctx.file_action(content = content, output = output) outputs = [output] return struct(output_groups = {"makefiles" : set(outputs)})

Now we can run this:

$ bazel build //:bin --aspects make.bzl%makefile --output_groups=makefiles INFO: Found 1 target... INFO: Elapsed time: 0.901s, Critical Path: 0.00s $

Bazel doesn’t print anything, but it has generated bazel-bin/Makefile . Let’s create a symlink to it in our main directory, since we’ll keep regenerating it and trying it out:

$ ln -s bazel-bin/Makefile Makefile $ make g++ -o bin bin.cc lib.cc $

The Makefile works, but is totally hard-coded. To make it more dynamic, first we’ll make the aspect generate a .o target for each Bazel rule. For this, we need to look at the sources and propagate that info up.

The base case is:

source_list= [f.path for src in ctx.rule.attr.srcs for f in src.files] cmd = target.label.name + ".o : {sources}\n\tg++ -c {sources}".format( sources = " ".join(source_list) )

Basically: run g++ on all of the srcs for a target. You can add a print(cmd) to see what cmd ends up looking like. (Note: We should probably do something with headers and include paths here, too, but I’m trying to keep things simple and it isn’t necessary for this example.)

Now we want to collect this command, plus all of the commands we’ve gotten from any dependencies (since this aspect will have already run on them):

transitive_cmds = [cmd] for dep in ctx.rule.attr.deps: transitive_cmds += dep.cmds

Finally, at the end of the function, we’ll return this whole list of commands, so that rules “higher up” in the tree have deps with a “cmds” attribute:

return struct( output_groups = {"makefiles" : set(outputs)}, cmds = transitive_cmds, )

Now we can change our output file to use this list:

ctx.file_action( content = "\n\n".join(transitive_cmds) + "\n", output = output )

Altogether, our aspect implementation now looks like:

def _impl(target, ctx): source_list= [f.path for src in ctx.rule.attr.srcs for f in src.files] cmd = target.label.name + ".o : {sources}\n\tg++ -c {sources}".format( sources = " ".join(source_list) ) # Collect all of the previously generated Makefile targets. transitive_cmds = [cmd] for dep in ctx.rule.attr.deps: transitive_cmds += dep.cmds # If this is a cc_binary, generate the actual Makefile. outputs = [] if ctx.rule.kind == "cc_binary": output = ctx.new_file("Makefile") ctx.file_action( content = "\n\n".join(transitive_cmds) + "\n", output = output ) outputs = [output] return struct( output_groups = {"makefiles" : set(outputs)}, cmds = transitive_cmds, )

If we run this, we get the following Makefile:

bin.o : bin.cc g++ -c bin.cc lib.o : lib.cc g++ -c lib.cc

Getting closer!

Now we need the last “bin” target to be automatically generated, so we need to keep track of all the intermediate .o files we’re going to link together. To do this, we’ll add a “dotos” list that this aspect propagates up the deps.

This is similar to the transitive_cmds list, so add a couple lines to our deps traversal function:

# Collect all of the previously generated Makefile targets. dotos = [ctx.label.name + ".o"] transitive_cmds = [cmd] for dep in ctx.rule.attr.deps: dotos += dep.dotos transitive_cmds += dep.cmds

Now propagate them up the tree:

return struct( output_groups = {"makefiles" : set(outputs)}, cmds = transitive_cmds, dotos = dotos, )

And finally, add binary target to the Makefile:

# If this is a cc_binary, generate the actual Makefile. outputs = [] if ctx.rule.kind == "cc_binary": output = ctx.new_file("Makefile") content = "{binary} : {dotos}\n\tg++ -o {binary} {dotos}\n\n{deps}\n".format( binary = target.label.name, dotos = " ".join(dotos), deps = "\n\n".join(transitive_cmds) ) ctx.file_action(content = content, output = output) outputs = [output]

If we run this, we get:

bin : bin.o lib.o g++ -o bin bin.o lib.o bin.o : bin.cc g++ -c bin.cc lib.o : lib.cc g++ -c lib.cc

Documentation about aspects can be found on bazel.io . Like Skylark rules, I find aspects a little difficult to read because they are inherently recursive functions, but it helps to break it down (and use lots of prints).

This entry was posted Tuesday, January 10th, 2017 at 9:22 am and is filed underBazel. You can skip to the end and leave a response. Pinging is currently not allowed.

本文系统(linux)相关术语:linux系统 鸟哥的linux私房菜 linux命令大全 linux操作系统

主题: C++
分页:12
转载请注明
本文标题:Aspects: the fan-fic of build rules
本站链接:http://www.codesec.net/view/522744.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 系统(linux) | 评论(0) | 阅读(12)