0x01 背景

上篇中已经介绍了bazel的基本工作原理和相关的概念。这篇将继续介绍下,现有的makefile构建工程如何切换到bazel构建系统。

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

再次说明下其特性:

  1. 多语言支持,并且支持扩展到任何语言的构建。

    扩展DSL是starlyke语言,为Python的一个子集,容易上手。这一点是cmake和其他构建系统不具备的。
  2. 支持缓存。
  3. 支持分布式构建。
  4. 支持最小化构建。

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

0x02 makefile的问题

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

  1. 各个组件之间的依赖难以管理。 makefile只支持将所有的源码放在一个目录下,然后由顶层的。

    这个依赖关系只能是有经验的人知道,新人想最小化编译时,只能试错,发现有依赖未构建时,再去手动构建。
  2. 增量式构建难以控制。

    这一点和第1点是相关的,因为依赖不能自动化构建,所以增量式构建也不具备。
  3. 构建速度不足,难以做缓存。

    makefile本身不支持缓存,需要额外的工作自行实现缓存。

0x03 需要解决的问题

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

  1. bazel的规则中需要定义输入输出

    makefile中的输出一般不定义。比如如下的makefile中。这个makefile中就没有定义明确的输出。
all:
gcc hello.c

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

install:
bash -x make.sh
  1. makefile一般需要在当前目录下执行

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

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

  1. 对应语言的扩展不大可能无缝支持。

    多半需要自己去修改构建的代码。比如我遇到的1个c语言的工程,使用cc_external_rule就行不通。
  2. 额外的学习成本。

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

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

  1. 现有的makefile工程可以无痛过渡。
  2. 新开发或者大规模重构的工程,可以直接上bazel。

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

0x04 过渡方案

方案要点:

  1. 使用bazel的沙盒环境变量,保留makefile的构建时的环境变量。

    这些环境变量可能INSTALLROOT这种安装环境变量,也可能有LD_LIBRARY_PATH这种编译链接环境变量。
  2. 使用genrule规则,可以快速切换到makefile所在的目录,执行make。

示例工程见gitee。https://gitee.com/linmaolin/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 总结

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

  1. 产出物是伪造的,对于bazel来说不是sound(可感知)的。无法缓存INSTALLROOT的产生物。
  2. 产生物不可感知,那么INSTALLROOT被删除的情况下,bazel的构建可能什么都不会做,因为它不知道INSTALLROOT的存在。

这2个问题作如下解答:

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

实验相关的代码已经上库。

https://gitee.com/linmaolin/bazel-demo

  • makefile 分支是基础的makefile代码。
  • bazel 分支是添加了bazel的构建的代码。

bazel简介(二)——从makefile向bazel转变(使用genrule)的更多相关文章

  1. {Django基础七之Ajax} 一 Ajax简介 二 Ajax使用 三 Ajax请求设置csrf_token 四 关于json 五 补充一个SweetAlert插件(了解)

    Django基础七之Ajax 本节目录 一 Ajax简介 二 Ajax使用 三 Ajax请求设置csrf_token 四 关于json 五 补充一个SweetAlert插件(了解) 一 Ajax简介 ...

  2. Windbg 脚本命令简介 二, Windbg command

    Windbg  脚本命令简介 二, Windbg  script command $<, $><, $$<, $$><, $$>a< (Run Scri ...

  3. {Django基础七之Ajax} 一 Ajax简介 二 Ajax使用 三 Ajax请求设置csrf_token 四 关于json 五 补充一个SweetAlert插件(了解)

    {Django基础七之Ajax} 一 Ajax简介 二 Ajax使用 三 Ajax请求设置csrf_token 四 关于json 五 补充一个SweetAlert插件(了解)   Django基础七之 ...

  4. Google Bazel简介

    最近跑一个代码,需要用到Bazel. Bazel

  5. WPF Binding值转换器ValueConverter使用简介(二)-IMultiValueConverter

    注: 需要继承IMultiValueConverter接口,接口使用和IValueConverter逻辑相同. 一.MultiBinding+Converter 多值绑定及多值转换实例 当纵向流量大于 ...

  6. 【 MAKEFILE 编程基础之二】MAKEFILE 书写规划以及语法规则!

    本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/gcc-makefile/768.html   ...

  7. Hibernate框架简介(二)基本使用增、删、改、查

    一.Hibernate框架简介 Hibernate是一个优秀的Java持久化层解决方案,是当今主流的对象-关系映射(ORM,ObjectRelationalMapping)工具 1.1.理解持久化 瞬 ...

  8. Selenium简介(二)--基于CORE/IDE的简单应用

    参考  http://blog.csdn.net/iamqa/article/details/4398240 Selenium简介(一)--总体介绍  http://blog.csdn.net/iam ...

  9. Linux学习二:Makefile基础

    文首感谢http://www.chinaunix.net 作者:gunguymadman的分享 makefile关系到了整个工程的编译规则.一个工程中的源文件不计数,其按类型.功能.模块分别放在若干个 ...

  10. Redis简介二

    一.直接安装     1.Windows版本的Redis下载地址:https://github.com/dmajkic/redis/downloads  ,选择一个你想要下载的版本下载即可~     ...

随机推荐

  1. 你了解Vim的增删改查吗 ?

    增: 在Vim的Normal模式中输入A/I/O,a/i/o字符进行对应的增加操作. 删 在Vim的Normal模式中, 输入x 删除光标对应的一个字符(4x代表删除4个字符): 输入dd删除光标所在 ...

  2. 高通Android UEFI XBL 代码流程分析

    高通Android UEFI XBL 代码流程分析 背景 之前学习的lk阶段点亮LCD的流程算是比较经典,但是高通已经推出了很多种基于UEFI方案的启动架构. 所以需要对这块比较新的技术进行学习.在学 ...

  3. Linux查看系统占用

    ## 查看内存占用 #CPU占用最多的前10个进程: ps auxw|head -1;ps auxw|sort -rn -k3|head -10 #内存消耗最多的前10个进程 ps auxw|head ...

  4. 在OwinSelfHost项目中获取客户端IP地址

    在OwinSelfHost项目中,获取客户端的IP地址可以通过以下方法获得: base.Request.GetOwinContext().Request.RemoteIpAddress 创建一个Owi ...

  5. Linux常用指令及shell脚本记录

    记录一些常用指令在博客上,以防哪天因太久不敲而忘却,还可以直接翻看博客记录,不用再一条条百度搜...... 一.Linux常用指令 一.设置文件权限为aapp用户及用户组-- chown -R app ...

  6. MDI子窗口+事件与委托的一个例程

    1首先,新建WinForm的.NetFramWork的工程并添加2个Form: 2设置 Form1为MDI主窗口: [属性]-- 将以上属性改为 True; 另外,也可以采用代码形式: this.Is ...

  7. centos下安装Docker容器

    安装前的准备工作 1.列出docker安装过的相关包 sudo yum list installed | grep docker 2.删除相关安装包 #根据查找出来的进行删除,不同版本可能有不一样的情 ...

  8. [oeasy]python0027_整合程序_延迟输出时间_整合两个py程序

    ​ 整合程序 回忆上次内容 通过搜索发现 time中有函数可以延迟 time.sleep(1) 还可以让程序无限循环 while True: 现在需要两个程序的整合 循环延迟输出 时间输出 ​ 编辑 ...

  9. CF1468N 题解

    洛谷链接&CF 链接 题目简述 共有 \(T\) 组数据,对于每组数据: 有三个桶,五种垃圾,每个桶有固定的容量. 前三种垃圾分别放入三种桶中,第四种垃圾可以放进 \(1,3\) 桶中,第五种 ...

  10. [rCore学习笔记 07]移除标准库依赖

    改造Rust hello world 移除println!宏 rustc添加对裸机的支持 rustup target add riscv64gc-unknown-none-elf detail rus ...