老司机技术周报与字节音乐联合主办的 WWDC. 技术沙龙.上海站已于 7月 10 日在上海完美收官。本文将是该场最后一节张忻正(卡比)同学的《基于 Bazel 的 iOS MonoRepo 实践》分享总结。文章内容会继承分享中讲师的绝大部分思想,但是由于篇幅,可能会精简掉一部分内容。 此外此文的 PPT 会放在原文链接里面。由于 PR 限制,本文视频部分不对外发布,请大家理解。若对 Bazel 感兴趣可以通过文末方式加 B 站 Bazel 讨论群。
讲师:张忻正(卡比)技术专家,14 年加入 B 站,现任 bilibili 移动端架构师,曾维护 ijkplayer 及参与 ffmpeg 贡献,曾基于 bazel 重建 iOS 构建系统。
编辑:红纸,iOS 开发,老司机技术周报编辑,就职于淘系技术部
文章约定,B 站 => BiliBili,讲师 => 张忻正,作者 => 小编。小编 ≠ 讲师
在一个「版本控制」仓库包含多个 Projects/Applications/Libraries 。简单通俗点就是 All in one,所有代码就在一个仓库当中。Monorepo 其实并不只是单纯的一种技术,其包含着文化底蕴,团队公司的氛围,B 站在最初设想的时候,一个 Repo 包含前后双端,不过现在还没做到,当下只做到了 iOS 的 Monorepo。
讲师从两个方面阐述了其背景:
迭代模式缩短:随着软件项目的发展,工程的演进方式也发生了改变从传统的瀑布式开发到了敏捷开发,再到现在由 CI/CD 支撑下的班车制流水线式开发,我们可以看到工程迭代的速度正在逐渐缩短,一些同行的竞品也都是达到了周迭代的开发模式。
Muti Repo 带来的高复杂度:当工程的复杂程度越来越高时,工程管理也会开始越来越混乱
Monorepo 带来的特性:
原文阶段共 5 个,小编缩短篇幅,合并了 2 个。
这种模式下会带来什么样的问题呢
多个顶层业务 APP 异化程度越来越显著
超级应用平台
分支管理混乱
阶段二:拆分 Multi Repo
为了解决单体 App 的痛点,B 站进行了多模块的拆分,分治,一个工程解决不了的问题,拆成多个模块,可以减少一定的问题,但是却带来了一些其他新的问题。
都是复制粘贴
引用依赖低于
设计模式乱用
模块间通信及其反人类
阶段三:重回 Monorepo
那么古尔丹代价是什么呢?
为什么用 Monorepo & 回答这个问题之前,我们先来看下我们的输入 & 输出是什么?
产物 ipa:资源文件、二进制文件
原料:代码文件(m/cpp/swift/c/h)、签名、资源文件(xcasset/png/json/html/...)、二进制文件(.a/.framework)
那么 Xcode 在这其中扮演的角色就是将原料转换成了编译产物。再将 Xcode 进行拆解,编译的过程其实是调用 Tool Chains 的过程,Xcode 在编译过程中,调用了组合命令 xcodebuild,那么 Xcode 其实就是一个壳。将 xcodebuild 进行拆解,我们就可以得到一系列的编译工具,clang/actool/ibtool/... 等等一系列工具,当我们有了这些命令,我们就可以自己对原料进行加工,输出产物。不再依赖于 Xcode 即可进行编译。
Bazel 是 Google 内部 Blaze 的开源版本,Bazel 强调的五个设计原则:
效率:多级 Cache,每一层可以做高效的缓存,本地、远程,来支持增量构建
可扩展性:多种不同的语言可以抽象成一类 Action 进行编译
灵活性:提供内置规则,基于 starlark 的扩展,可以实现各种用户自定义的 rules
正确性:文件缓存是基于文件内容的(而 Xcode 是基于文件时间戳的)
可重复性:有沙盒,不被研发环境影响
B 站在用 Bazel 是在 18 年,使用 0.15.0 的版本,当然现在的 Bazel 对外已经放出了 5.0 的 beta 版本了。
说完了 Bazel 所带来的改变,那么支持 Bazel 需要哪些文件呢?其所需要的文件如下两类
- BUILD:描述文件
- WORKSPACE:用于描述项目所需的构建规则
Bazel 的所有描述文件其实都是 Starlark[1] 语法的代码文件,在文件中看到的都是执行函数,大家可以带着这个思想去看着两个文件会更方便理解一些
由于 BUILD 文件规则比较多,这边只是简单介绍,详情参考 Objective-C Rules[2] 和 rule_apple[3]
在声明完一个工程文件之后,我们就可以使用 Bazel 的一些命令来进行编译了。
bazel build //App:app
其实用 Bazel 编译就这么简单,不像 xcodebuild balabala 后面增加很多很多参数才能进行编译。当然其实这些参数都是被 build 后面的那些编译 rules 给隐藏起来了,如果我们要在 build 后面加参数也是可以的,或者我们可以提供 .bazelrc 将编译参数写在文件里面,让我们的 build 命令看起来更清爽。
OK,回归正题,其实 Bazel build 就是讲上述的 xcodebuild 工具链中的命令进行了封装,在本质上跟 xcodebuild 是没有任何区别的。
那么构建机的爽,研发胖友怎么办?
前面说到 Xcode 编译是个壳,其实 Xcode 还做了其他的很多事情的,比如说语法提示/高亮、LLDB 调试工具等等功能。回到最后,其实暂时还是离不开 Xcode,这里介绍一个工具叫做 tulsi[4],它能做的一件事情呢就是将 Bazel 的配置文件,逆向生成 .xcodeproj 工程。但是这个时候的 .xcodeproj 工程其实已经去掉了 Xcode 的编译功能了。这个时候 Xcode 就是个没有编译功能的壳,但是他还是有上面提到的语法提示/高亮、调试工具等一系列工具集合~
OK,这个时候研发也爽了,但是发展历程上又碰到了问题,代码安全性上的问题。部分核心代码或者敏感信息,即使在非常 Open 的企业文化中,为了数据安全,也不能完全公之于众。由此衍生出下面两种模式。
但是在这种模式下,由于 Bazel 的一个规则,一定的输入等于一定的输出,在这个权限管理的条件下,编译缓存是没办法共享的,每个人都要去 clean build。所以在研发模式(非构建机)情景下,牺牲一定正确性做一个变相提速。
在介绍变相提速前,我们先来介绍一下 c/oc 里面 #include/#import 在预编译过程中都是将整个文件导入到另外一个文件里面去的,顺着这个链路,如果底层的 .h 文件进行修改,整个编译链路都会被破坏掉进行重新编译。
所以 B 站做了一个头文件依赖不传递方案来达到提速效果。
相对于编译黑盒的 xcodebuild,Bazel 可以提供输出编译过程的分析内容,我们可以使用 Chrome 自带的 chrome://tracing 来解析编译过程,对每一个 Action 进行分析,查看编译耗时等,为我们提供了良好的可视环境,对后续的优化工作非常有利。
上手程度:
落后的工具链:
展望未来:
小编:全量上云毕竟也是一个不小的挑战。换个小一点的 Future。
多说无用,没有完美的架构解决方案,只有最适合的。如果你想尝试一下 Bazel 带来的新方案,下面这些链接内容,你一定不能错过~当然如果你有任何问题,也欢迎加入到我们的讨论里面来,详情请关注「老司机技术周报」公众号后台私聊 Bazel 加入我们的讨论群。
Starlark: https://github.com/bazelbuild/starlark
[2]Objective-C Rules: https://docs.bazel.build/versions/main/be/objective-c.html
[3]rule_apple: https://github.com/bazelbuild/rules_apple/tree/master/doc
[4]tulsi: https://tulsi.bazel.build/
本文分享自微信公众号 - 老司机技术周报(LSJCoding)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
|