搞了个 C++ 构建系统
我平时的工作内容是开发在服务端上运行的网络程序,主要语言是 C++,并且几乎全部跑在统一的机器环境上。所以我们一直以来都在使用一套简单从 blade-build 魔改来的编译系统。这个系统的本质是一个 Unix Makefile
的生成器,这个生成器使用 Python2
编写,代码有点难以维护。(毕竟使用动态语言的东西就是这样,出活还是很快的,但是时间一长了,代码就变的很难维护。因为后来的人,很难完全理解其中每个变量的类型,这对于我这种长期写静态强类型语言的人来说,是很痛苦的。)
在这样的基础上,我就萌生了把这套系统推倒重来的想法,这次我想用一个大家都熟悉,并且一定会写的语言来完成,它还一定是强类型的。在这几个条件下面,我超爱的 Rust
就被我排除在外了,因为好像我们周围会写的人也不是很多。再加上人家 cmake
也是用 C++ 来写的,我一想,我应该也比人家不差啥。再说,我还可以去 cmake
里面去偷很多实现出来。就这么决定了,用 C++ 来实现这个新的构建系统。
1 JK-Build
首先就是名字了,为啥叫 jk
,那肯定有很多不可爱的人有奇奇怪怪的想法了!我当时起这个名字的时候,完全是图这两个按键在键盘上位于很近的位置,并且由于我是个 vim
的用户,其实手也常年在这两个按键附近,他们真的很近,很方便,就随手用了这个名字。
1.1 那现在的系统有啥问题呢
- 首先就是,原罪之一就是
Python
。这个系统生成Makefile
的速度在有的时候,它实在是有点慢了。 - 如果
BUILD
文件有改动了,它需要自己手动运行命令来重新生成。 - 对于 lint 来说,它的规则是尝试
lint
所有文件的,但是其实有很多文件是写出来放在那里,可以只搞了一半的(我就经常这样)。在这种时候我的代码是没法通过编译的,更不可能过 lint 了。 - 没有进度条。看看人家
cmake
,还有个进度显示呢。这不就决定了我在编译的时候,是去喝杯咖啡,还是去拿个可乐嘛! - 维护困难。
Python
的代码有点难以维护,反正我在改的时候,是全靠着print
,到运行的时候观察每个变量到底是什么类型来debug
的。 - 对于
clang-base
的工具,没有过匹配。大部分的,C++ 的代码检查和补全工具,其实都是建立在clang
之上的,而clang
会需要 compile database 来找到每个文件应该如何被编译,然后才能有 AST 或者其他的补全、提示什么的。
1.2 那和以前有什么不同呢
1.2.1 BUILD
文件和 targets
从 BUILD
文件来看,是没什么不同的:
BUILD
文件依旧是Python
的语法。- 所有的目标构建依旧是一些
Python
的函数。 - 暂时来说,可用的构建目标类型也没有增加。(毕竟完全兼容旧系统就有很多的工作了)
新的系统,第一个目标就是完全兼容旧的所有已有的 BUILD
文件。毕竟,迁移的工作难度小了,新的系统才容易被大家接受。所以,所有的 BUILD
文件还依旧会是以前那样:
cc_library(name = "base",
cppflags = [ "-Ilibrary", ],
srcs = [ "*.cpp" ],
excludes = [ "*_test.cpp" ],
ldflags = [ "-ldl" ],
deps = [
"//library/logging/BUILD:logging",
"//library/memory/BUILD:memory",
":clock",
],
)
但是从可用的参数和意义上来说,还是有很多不同的。比如,构建目标 cc_library
现在有个参数叫 cppflags
,现在我们就面临着这个参数的值没法继承的问题。往往如果在一个 target 里写了一个 -Ixxxx
的参数,在依赖它的 target 里面还是不可避免的要写上。所以在新的系统中,给 cc_library
以及它的所有派生目标都加入了可以继承的,includes
和 defines
参数,用来传递信息给依赖这个目标的目标。
对于这部分的变化,就去文档上看就好啦。(虽然现在还没有)
1.2.2 编译进度
还有进度条的加入。新的构建系统,在编译的时候,会有一个和 cmake
一样的计数进度的显示。这个地方 cmake
是通过统计一个目录下的文件个数来实现的,非常巧妙。具体的实现是,在一个编译命令的提示信息显示的同时,在一个特定的目录下也 touch
出来一个独一无二的文件,并且进度就应该是这个目录下的文件个数除以总的应有的文件个数。那如果文件没有改变怎么办?不会编译,那对应的计数不就没法统计进去了嘛?是的!所以,它还维护了一份总的计数给到一个 target
最后的命令上,这个命令是无论如何都会强制执行的,它会一次 touch
这个 target
应有的全部的文件。比如像这样:
all: lib.a
.PHONY: all
lib.a: a.o b.o c.o d.o e.o
@jk-print --number=1,2,3,4,5,6 'lib.a'
...
a.o: a.cpp a.h
@jk-print --number=1 'compiling a.cpp'
...
b.o: b.cpp b.h
@jk-print --number=2 'compiling b.cpp'
...
c.o: c.cpp c.h
@jk-print --number=3 'compiling c.cpp'
...
d.o: d.cpp d.h
@jk-print --number=4 'compiling d.cpp'
...
e.o: e.cpp e.h
@jk-print --number=5 'compiling e.cpp'
...
这样就可以完美的把没有进行编译的文件的都补上了。
1.2.3 compile database
现在在生成 Unix Makefile
的同时,还会在项目的根目录生成一个 compile_commands.json
文件,作为其他 clang-base
的工具的基础。你可以把它和比如 clangd
或者 ccls
来配合。提供项目代码的补全、提示个高亮之类的功能。当然,如果你会自己写一些工具的话,就更好了!
1.2.4 内置的 cmake-base
的第三方库支持
原本使用一个第三方库,那可麻烦了。我们要自己写一个用来下载安装这个第三方库的一个脚本。自己要小心的处理好,下载文件的逻辑,是不是使用的代理。文件下载之后是不是完整,要不要进行 md5
校验。文件怎么解压,包括后面的 cmake
命令怎么搞,怎么填写这一系列事情。虽然我尝试将一部分工作,封装在了一些内置的包里面,但是依旧有点麻烦。所以这次趁着重写,干脆就重新完整的提供了一个内置的支持!
cmake_library(
url = "http://xxxxx.xxx/xxx.tar.gz",
sha256 = "xxxxxx",
type = "tar.gz",
header_only = False,
job = 10,
)
1.2.5 独立的进程
现在终于不再依赖,所有的项目都是 media_build
的一个子目录了!你可以任意组织你的项目目录,可以随意的把不管是 libray
还是 protocol
都作为一个 sub-project
放在自己的项目目录里,用自己想用的任意方式来管理!
1.3 最后的最后
不过,现在还在开发过程啦。
还有一些对以前的兼容没有实现。但是这也已经是一个全新的构建系统了!最初搞的时候,不光考虑了兼容,很多新的功能还有参数设计,也参考了 blaze
,buck
还有 cmake
的样子。也应该算是取出来这三个里面,我认为用起来比较方便,舒服的部分吧。
不过缺点还是,这个系统的最初目标就是构建服务端的程序的,对于环境方面没有过多的考虑。所以基本不兼容 windows
,macos
能不能用还有全看缘分。这个就没啥计划了… 要是恰好能用!那可太好了!