多语言项目编译规则散落各处,Bazel 能把一次构建跑通吗?

编译规则还散在各语言自己的脚本里。想把一次构建收成同一条入口,就会去看 Bazel。它管的是怎么把代码构建出来,不是用来声明云上的基础设施,和站内那篇 OpenTofu 不是同一类问题。这里只看构建系统如何把一份工程从安装接到第一次成功的 bazel build。

安装页 写明,在 Ubuntu Linux、macOS 和 Windows 上,推荐用 Bazelisk 来安装或更新。页面同时列出了社区维护的包(Alpine、Arch、Debian、Fedora、Homebrew、Nixpkgs 等),并注明 Bazel 团队并不官方支持这些包,问题要找包的维护者。其他平台可以尝试从源码编译。安装页本身没有写死某一个发行号,只指向官方的 release 页面。装好之后,用 Bazel 构建程序 把日常入口写成:走到工作区根目录或其子目录,运行 bazel。不带参数时可以运行 bazel help,常用的子命令包括 build(构建指定目标)、clean(清理输出,并可选择停止服务器)以及查询类命令。第一次跑通一次构建,用到的就是 bazel build。

打开 入门入口 时,页面会落到 C++ 教程。安装页的导航里还能看到 Java、Go、Android、iOS 的入门,/start 目前指向的是 C++ 这一份,本文只跟这条路径,不把各语言的规则摊开。教程开头就说明:Bazel 是一套支持多语言构建的构建系统,这份入门用 C++ 项目做例子,并给出对多数语言都适用的工作流程。它不是一份把所有语言规则写全的手册,第一次跟着走完即可。教程要求先完成安装,建议同时装好 Git,然后克隆官方示例仓库 https://github.com/bazelbuild/examples,示例在 examples/cpp-tutorial 目录,分三个阶段。

工作区是存放源码和构建输出的目录。根目录要有 MODULE.bazel,用来标识这是一个 Bazel 工作区,并声明外部依赖。含有 BUILD 文件的目录则是一个包。以后若要把某个目录标成工作区,教程写的办法是在该目录里放一个空的 MODULE.bazel。这份入门的各个阶段里已经放好了。BUILD 里至少要有一条规则,用来说明如何得到可执行文件或库。每一条规则的实例叫做目标,指向一组源文件和依赖,也可以再指向别的目标。教程第一阶段的 BUILD 里用内置的 cc_binary 规则定义了名为 hello-world 的目标,源文件是 hello-world.cc,没有额外依赖。进入 cpp-tutorial/stage1 后执行:

bazel build //main:hello-world

//main: 是 BUILD 文件相对工作区根目录的位置,hello-world 是目标名。构建产物放在工作区根下的 bazel-bin 目录,随后可以直接运行 bazel-bin/main/hello-world,示例会打印 Hello world。教程第二阶段会在同一个包里先用 cc_library 做出库,再用 cc_binary 的 deps 指向它,从而演示如何把较大的工程拆成多个目标:Bazel 只重新构建有改动的部分,也可以同时构建工程的不同部分。第三阶段再拆成多个包。第一次只想看通一次构建,第一阶段已经够用,后面两个阶段可以等第一次跑通再跟。

构建结束时,文档说它会打印这次请求了哪些目标、成功了没有,以及产物在哪里,方便脚本解析结果。同一条 bazel build 再跑一遍且没有改动时,会很快结束,文档称之为空构建(null build),因为没有包要重新加载,也没有构建动作要执行。若源码或依赖有变化,它会只重做必要的动作,也就是增量构建。目标可以用 //foo/bar:wiz 这样的标签,也可以用 //foo/bar:all、//foo/...、//... 这类模式一次圈出一组目标。以 // 开头的模式相对当前工作区解析。这些语法在第一次跑通单个目标之后再查即可,不必一开始就记全。

你们仓库里是打算把某一个语言的目标写进 BUILD 跑通,还是已经准备把散落的脚本一起迁过去?