背景
由于公司产品需求,需要在iOS编译发布中支持静态库包,而我们默认的编译发布是动态库,chromium官方也是只支持动态库。
这个目标实际就是能使用ninja命令配合一定的编译参数就可以编译出静态库+在发布时能将静态产物抛给Agile。这里面预计设计两方面的改动:第一个就是我们的库代码,第二个是编译server。
解决方案
1.调研静态库编译方案
参考我们各个组件的编译方式(比如component(“net”)最终会编译出libnet.a),发现它们在产出目录中的.ninja文件中都是调用的alink(可参见build/toolchain/mac下tool(“alink”)),那么研究了一下alink的实现,实际上是调用了python build/toolchain/mac/filter_libtool.py libtool -static xxx TurboNet.rsp,这样的命令来生成静态库的,前提是要存在.rsp(ninja -d keeprsp即可产生,里面记录了该组件中所有的.o文件),那么大概也能猜出来执行这个命令,就是将.rsp中记录的.o文件全部打包最后生成.a。
我们实测执行这条命令也是可以顺利产出静态库,所以最早我的想法就是在我们的编译体系中(.gn文件)插入这么个命令,在gn中有action()、exec_script()可以用于执行脚本。不过实验了一会儿,发现实在不太好控制,原因是action、exe_script默认都是在编译最开始就会进行执行,而我的需要其实是要等到该组件所有.o文件+.rsp文件都产出了,再执行脚本,这个时序非常难控制,最后不得已放弃了。由此也可以看出gn这套google搞出来的编译系统,想要个人做一些灵活的改动(比如编译时序的更改)还是比较困难的。
然后注意到其实我们很多静态库都是使用static_library()这个gn中内置的工具来做的,因此把希望寄托在它身上,尝试了一下还是不行,原因在于static_library中只会把你定义为source的源文件给编译并打包成.a,所有的deps它完全不管,这不符合我们需求,我们想它能像shared_library一样将deps的东西也链接起来。
找了一些例子发现无论是gn中的componets 还是 static_library,在产出目录的.ninja中都能发现最后都落到了alink上(这种衔接在gn文件中找不到,应该是gn内部做的转换),而且alink后面还确实接了所有需要的.o。而我写的static_library在alink里面就只有source的那几个文件,所以总感觉还是有些猫腻,应该是可以有办法通过static_library使得alink能找到所有的.o的。
最后折腾了一番,在chromium论坛里找到了答案chromium.org/forum(这个帖子就是我发的[捂脸表情])
其实我们在static_library中加上complete_static_lib = true,即可编译出完整的静态库(包含所有deps)。
说到这里,觉得像类似gn这种,也算是一个规模不小的工具了,其中有蛮多技巧,在chromium也由专门的代码区供gn的commit,但确实平时也没精力去深入研究,只能是靠问题驱动。
附gn帮助文档:gn/reference.md,内容其实蛮多的。
解决了关键问题后,为了兼容现有编译以及满足不同编译需求,还做了一些措施,包括
新增ios_release | debug.gn,编译server优先以该文件中的编译参数来进行编译。
新增了is_turbonet_static开关,只有为true的时候才会编译出静态库。修改了build/config/ios/rules.gni中的template(“ios_framework_bundle”),当is_turbonet_static=true,执行static_library,否则执行默认的shared_library.
2.满足同时发布动态库 & 静态库需求
步骤1做完后,当时觉得就可以了,当时的想法是在平时的代码提交及合入,只编正常的动态库,在分支发布时,如果需要静态库,则要需要修改库代码的ios_debug | release.gn再push到icode触发agile编译,才能拿到静态库产出,实际上就是静态库、动态库需要分两次编译,并且对应两个不同的commit。结果这个方案遭到组里较多的challenge。
后面自己想想确实这样做非常不优雅,对RE来说也是非常难受的一件事,因为每次发布的时候,都需要多提交一次修改、多等待一次编译,最后产出还是分两次从agile上获取,如果我们某个版本由于比如修复bug发布次数多了的话,这个真有点逼死人的感觉。这个方案的唯一好处就是不会对现有的编译体系造成冲击,静态库也只是作为一个commit,各自独立,互不干涉。
权衡之后,还是决定去做动态库&静态库同时编译发布,这部分就主要是设计ios编译server的修改,包括如下:
1.新增静态库编译脚本compile_static.py,专门用于静态库编译。
当然,由于静态库 & 动态库都编译会导致编译时间double(这种就是对现有编译体系造成冲击的最大体现),因此又做了个改进,判断一下当前commit是否在分支上,因为我们是主干开发+分支发布,平时提交评审&合入主干,就不要执行这种对于的编译步骤了,可以节约挺多时间的。
另外为什么我没有把静态编译放在现有编译脚本里(对应编译server的compile_task.py),一方面是现在compile_task.py中的步骤已经比较繁琐,而且每个compile_task当初设计就是为了一次编译,如果强行再附加静态编译,并不是很好添加。另外一方面觉得这个东西本身就是一个多余 & 临时的东西(只是因为我们现在需要同时发布两种),不应该长久存在,因此就单独写,不去影响正常流程。2.在库代码中新增baidu/build目录,其中存放一些特殊的gn args文件,比如现在就存放着给手百的静态库args,步骤1中的compile_static.py执行时就会去寻找库代码baidu/build下的args,用它来进行编译。这样做也是避免把编译args写死在编译服务端,为以后可能的编译配置修改打个基础。
3.在jenkins平台配置编译步骤,在正常编译完成后,执行该脚本尝试静态库编译。并在最后分不同文件夹兜住所有的静态 & 动态编译产出。
由于jenkins平台可以灵活的增删编译步骤、执行脚本等,因此借用它的能力,把动态库 & 静态库的编译 & 打包做出来了,而不是把这坨都交给编译server去做,尽量减轻编译server的负担,由于jenkins平台实际上是一个具有上帝视角的角色,放在这上面,感觉看起来也很舒服。
以下为compile_static.py