使用genrule如何从makefile向bazel转变

91次阅读
没有评论

共计 3870 个字符,预计需要花费 10 分钟才能阅读完成。

使用 genrule 如何从 makefile 向 bazel 转变,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

0x01 背景

现有的 makefile 构建工程如何切换到 bazel 构建系统。

bazel 提供了丰富的扩展方式,当然也支持从目前的 makefile 过渡到 bazel 构建。

再次说明下其特性:

多语言支持,并且支持扩展到任何语言的构建。
扩展 DSL 是 starlyke 语言,为 Python 的一个子集,容易上手。这一点是 cmake 和其他构建系统不具备的。

支持缓存。

支持分布式构建。

支持最小化构建。
在一个大型系统中,一个人可能只需要负责其中的一个小组件,这个组件可能又依赖其他组件。当这个组件需要更新测试时,只希望去构建这个组件依赖的组件和这个组件本身,其他不相关的可以不构建,这样可以使用构建过程更快捷。bazel 就支持这种方式。

0x02 makefile 的问题

上文中说过 makefile 在处理小规模软件时还不错,当规模增大时,makefile 有以下问题。

各个组件之间的依赖难以管理。makefile 只支持将所有的源码放在一个目录下,然后由顶层的。
这个依赖关系只能是有经验的人知道,新人想最小化编译时,只能试错,发现有依赖未构建时,再去手动构建。

增量式构建难以控制。
这一点和第 1 点是相关的,因为依赖不能自动化构建,所以增量式构建也不具备。

构建速度不足,难以做缓存。
makefile 本身不支持缓存,需要额外的工作自行实现缓存。

0x03 需要解决的问题

虽然说 bazel 兼容 makefile 的构建,但也不是直接的支持。需要对 bazel 的扩展方式有一些了解。也有以下几个问题需要解决。

bazel 的规则中需要定义输入输出
makefile 中的输出一般不定义。比如如下的 makefile 中。这个 makefile 中就没有定义明确的输出。

all:
 gcc hello.c

或者是这样的。在 makefile 中规则中是去执行一个 make.sh 脚本。

install:
 bash -x make.sh

makefile 一般需要在当前目录下执行
makefile 中定义的输入和输出,都是基于当前的目录,而 bazel 是在顶层执行的,它的工作目录就是顶层的 WORKSPACE 所在的目录。这对 makefile 来说是不友好的。

针对这 2 个问题,通过查看 bazel 的 issue 和文档并没有发现好的解决方式。回答人基本上就推荐你去用对应语言的扩展,这对很多发有工程来说是不现实的。原因有:

对应语言的扩展不大可能无缝支持。
多半需要自己去修改构建的代码。比如我遇到的 1 个 c 语言的工程,使用 cc_external_rule 就行不通。

额外的学习成本。
这个对应的语言的扩展也是需要时间去学习的,也是隐藏的成本。

所以,对 bazel 有兴趣的多语言构建项目,希望以这样的理想方式过渡。

现有的 makefile 工程可以无痛过渡。

新开发或者大规模重构的工程,可以直接上 bazel。

通过总结,给出了以下的过渡方案,基本上可以满足再有的 makefile 工程。

0x04 过渡方案

方案要点:

使用 bazel 的沙盒环境变量,保留 makefile 的构建时的环境变量。
这些环境变量可能 INSTALLROOT 这种安装环境变量,也可能有 LD_LIBRARY_PATH 这种编译链接环境变量。

使用 genrule 规则,可以快速切换到 makefile 所在的目录,执行 make。

示例工程见 gitee。https://gitee.com/ul1n/bazel-demo/tree/makefile 包含 src 和 lib 两个目录。各自有自己的 makefile。其中 src 依赖 lib。

方案中的第 1 点可以非常方便地通过 bazel build 时的选项 –action_env 传递。第 2 点实现有点曲折,不过并不复杂。这里详细说明下。

通过熟悉 bazel 文档我们知道 genrule 可以实现任何构建过程。这里我们希望如下的 genrule。只定义基本的属性,其中 cmd 里可以 cd 到 BUILD_PATH,BUILD_PATH 可以自动变化,不同的子目录,就变化为所属的目录相对路径。同时可以生成一个伪目标,满足 genrule 的要求。

genrule(
 name= hello ,
 srcs=[srcs],
 cmd= cd $(BUILD_PATH)   make ,
 outs=[test]
)

那么如何自动地控制 BUILD_PATH 的环境变量呢?bazel 不支持直接传递环境变量给 genrule 的上下文,换说法就是 cmd 中的环境变量,需要提前声明好。通过查看官方的一些示例,可以这样来实现。

注:BUILD 文件中涉及的 bazel 的语法可以参考官方文档进行熟悉。

定义一个 def.bzl 文件。这个 bzl 就是可以为引用它的 BUILD 文件中引入一个 BUILD_PATH 的环境变量。

def _var_providing_rule_impl(ctx):
 build_path = ctx.build_file_path
 loc, _ = build_path.rsplit(/ , 2)
 return [platform_common.TemplateVariableInfo({ BUILD_PATH :loc}),]
var_providing_rule = rule(
 implementation = _var_providing_rule_impl,
 attrs = {  var_value : attr.string() }
)

然后在 lib 目录下添加一个 BUILD 文件。这份文件编写的非常通用,可以放在任何一个 makefile 目录下。除了有其他依赖需要在 genrule 的 srcs 属性中添加。

load(//:def.bzl ,  var_providing_rule)
var_providing_rule(
 name= set_build_path ,
 var_value= 
filegroup(
 name= srcs ,
 srcs=glob([**])
genrule(
 name =  default ,
 srcs=[srcs],
 cmd= cd $(BUILD_PATH)   make   cd -   touch $(RULEDIR)/out.txt ,
 visibility=[//visibility:public],
 outs=[out.txt],
 toolchains=[:set_build_path]
)

在 src 目录下添加如下的 BUILD 文件,与 lib 中的基本上一致,只是 srcs 中多了对 lib 中规则的依赖。

load(//:def.bzl ,  var_providing_rule)
var_providing_rule(
 name= set_build_path ,
 var_value= 
filegroup(
 name= srcs ,
 srcs=glob([**])
genrule(
 name =  default ,
 srcs=[srcs ,  //lib:default],
 cmd= cd $(BUILD_PATH)   make   cd -   touch $(RULEDIR)/out.txt ,
 outs=[out.txt],
 toolchains=[:set_build_path]
)

这里对 cmd 参数做一下说明。

1  切换到 BUILD 文件所在的目录。2  执行 make。这个也可以换成需要的命令,比如 make install。3  切换到上一个目录,也就是 WORKSPACE 文件所在的目录。4  创建一个伪输出 out.txt。RULEDIR 是 bazel 约定的规则产出物的目录。是一个相对路径,这是为什么第 3 步中要切回目录的原因。用 连接是为了在失败时能立刻退出执行。

添加完成后,就可以通过如下的命令执行这个构建过程了。

bazel build --sandbox_writable_path=$INSTALLROOT --action_env=C_INCLUDE_PATH=$INSTALLROOT/include --action_env=INSTALLROOT=$INSTALLROOT //src:default

其中 –sandbox_writable_path 选项是为了开放权限,保证 makefile 中需要执行的目录创建操作。另外两个环境变量,则是为了保证原来的 makefile 可以成功构建。执行完成后,会在当前目录的 tmp/bin/ 目录下生成 app 可执行文件。tmp/lib 下生成动态链接库。这样一来,只要配置的环境变量合适,就可以完美执行原有的构建流程了。

0x05 总结

这种实现方式可能不是最好的,但目前来说是最直接的。当然也希望有知道更好方式的同学告知下。目前这种实现方式还有以下几个问题。

产出物是伪造的,对于 bazel 来说不是 sound(可感知)的。无法缓存 INSTALLROOT 的产生物。

产生物不可感知,那么 INSTALLROOT 被删除的情况下,bazel 的构建可能什么都不会做,因为它不知道 INSTALLROOT 的存在。

这 2 个问题作如下解答:如果是需要缓存的大型模块,那么可以产生真正的产出物,同时复制一份到 RULEDIR。但 INSTALLROOT 被删除这一块无法通过技术手段保证。

关于使用 genrule 如何从 makefile 向 bazel 转变问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注丸趣 TV 行业资讯频道了解更多相关知识。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-25发表,共计3870字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)