iOS Monorepo 全源码解决方案原创
一、移动端开发现状
在组件化的浪潮下,公司引入多仓开发对工程架构进行解耦、跨业务技术能力复用,并辅以组件(混合)二进制化进行编译提速。不过随着工程规模增长、业务复杂度提升,多仓二进制的弊端日益凸显:
- 合码效率低下:多仓的引入使开发流程变复杂,最有代表性的合码环节。一次合码涉及到主仓和多个组件,每个组件要跑 Pipeline 流程进行版本发布。因涉及到组件发布,从而引入了 MR 锁,进而导致吞吐量有限。如果某个组件失败,那么 MR 需要重新跑一遍流程。这种模式提升了 CI 复杂度,降低了合码效率(封板排队时间可达 6h+)
- 依赖管理衍生问题:稳定性差,多仓使环境依赖度变高,稳定性变成多个仓库稳定性的乘积。即使每个仓库成功率是 99.9%,每次 install 成功的概率也仅有 74%;版本溯源性差,项目通过依赖动态决议生成,无法做到 single source of truth。
- 代码的可视性和可控性降低:跨组件重构困难,全量静态检测无从入手,并且很难统一架构规范;本地开发体验差,工程代码可信度低,无法直接对代码进行开发调试,本地开发需要更多的工具和流程来保证代码的可视性和可控性。
诚然,多仓方案很好的落实了组件化思想,但为解决上述多仓体系的问题,公司衍生了大量的优化和效率工具,其从结果看仅聚焦于解决局部问题,难以进一步提升优化空间。更多新挑战的出现使我们意识到当前模式下无法对这些问题进行收敛。对于头条、抖音、飞书这些超级 APP 在多仓中进行开发已经陷入困境。
为保证公司客户端项目长期稳定的发展、从根源上解决问题,我们围绕单仓提出了一套完整的解决方案。
二、Monorepo 全源码概念和优缺点
Monorepo(Mono Repository)指的是将多个相关项目或模块的代码集中管理于一个仓库的软件开发模式。具体来说,Monorepo 将所有的源代码和配置文件等存储在一个版本控制仓库中,并使用相同的构建和部署系统来管理和交付代码。
全源码(Full Source)是指将整个软件系统的源代码均存放于一个仓库中,源码为单一可信源。
全源码的概念通常与 Monorepo 联系在一起,将概念定义为 Monorepo 全源码解决方案,是区分于当前流行的组件二进制化开发模式。下图中 MULTI-REPO 为前多仓开发模式,每个组件独立 git 存储;MONO-REPO 为单仓模式,组件同主仓合并,只保留一个 git。
2.1 Advantages
- 便于代码复用:所有项目代码集中于单一仓库,相似的功能更便捷地抽象为公共库,并直接由项目引用。提高代码的可维护性和开发效率。
- 简化依赖管理:无需依赖管理器,所有引用的依赖项都存在于同一代码库中,更加方便地进行构建和管理。
- 原子提交:可以原子性地更改多个项目以避免多仓下不同版本依赖同步的兼容性问题。
- 大规模代码重构:开发人员可访问整个项目,从而更加容易地进行代码重构,并确保每个部分都能正常工作。
- 跨团队协作:通过源代码依赖改进其他团队正在开发的项目,从而实现代码所有权的灵活性。
- 工程质量提升:Monorepo 倡导开放,透明,共享的组织文化,有利于开发者成长,代码质量的提升。
2.2 Disadvantages
- 版本信息丢失:Monorepo 使用相同的版本号,无法对每个项目进行单独的版本控制。
- 缺乏权限控制:无法根据需要授予对代码库的访问权限,所有代码都在同一个项目中可能存在安全问题。
- 磁盘空间占用:默认情况下需检出所有项目,需要更多的存储空间。
三、Monorepo 全源码在字节中的应用实践
BitSky 是公司内 Monorepo 全源码项目代号,旨在建设客户端 Monorepo 基础设施,提升客户端开发效率和体验。业务方通过 bitsky 工具套件进行集成使用。
暂时无法在飞书文档外展示此内容
合仓 套件帮助业务工程平稳迁移 Monorepo;构建服务保证 Monorepo 下高效、稳定的构建;工程架构模块为工程去除 Cocoapods 等依赖管理工具,提供统一依赖分层服务;Developer Tools,提供统一、便捷的 GUI 工具,让研发人员更高效进行本地开发工作。(该章节主要介绍图示中软件服务部分)
3.1 合仓套件:合并代码仓库
项目的首要任务是将现有仓库平滑迁移到 Monorepo。迁仓因涉及多个团队通力协作,故面临较大的业务挑战。为此,项目早期便确定了清晰的目标和计划,采用灰度逐步迁移的方式,避免过度干预业务的正常运转。同时,为了确保迁移的顺利进行,进行全面的风险评估和管理,并采用工具支持迁移的过程。
围绕上述原则,我们执行时分三个阶段进行:
- 项目前期:根据现有 DevOps 平台度量数据对 Monorepo 收益数据预评估,搭建实验仓库做技术预研。
- 灰度阶段:所有组件支持多仓开发和主仓开发这两种模式,保证业务人员平滑迁移。
- 上线阶段:DevOps 度量平台获取相应数据进行收益验证,并通过问卷调查与研发同学完成 NPS 回访。
迁仓技术上挑战主要来自 Git ,仓库变大后带来的的性能问题通过配置优化进行有效缓解。上线当天采用 ****Git 流量预热的削峰策略减缓 GitLab 的流量压力。
3.2 构建服务:迁移构建系统
Xcode 内置构建系统因自身缓存能力薄弱、代码封闭、不支持分布式,导致其构建性能一般、上限低。在 Monorepo 全源码项目中,全量编译时编译单元总数在 30,000 上下,很明显不能支撑这样的场景。在对比市面上构建系统后,Bazel 作为我们的迁移选择 {Fast,Correct} Choose Two!
构建配置的迁移
多仓体系下通过 xcodeproj、podspec 描述构建规则,而 Bazel 体系构建配置以 BUILD、WORKSPACE 形式呈现,这个环节我们做了两个事情:
BazelGenerator:构建系统能正常运行取决于调用编译器、链接器等工具链时编译参数以及构建依赖的正确性,通过该研发工具将构建规则迁移至 Bazel 体系。
BUILD DSL:定制 bitsky 的构建配置使 Bazel 的构建配置语义化,降低研发同学迁移学习成本。
bitsky_library(
name = "Common",
hdrs = [":Module_hdrs"],
srcs = [":Module_srcs"]
)
分布式能力建设
Bazel 提供了非常优秀的分布式能力。我们在工程上落地了分布式缓存及分布式构建基础能力的同时,也设计开发了依赖发现、多级缓存、边缘节点、预缓存等策略,提升了构建效率。
产物一致性校验
产物一致性校验(Universal deterministic builds)指的是在软件构建过程中,在任何平台上构建、任何构建系统、编译器、链接器,最终生成的构建产物(如可执行文件、库等)都是相同的,不会因为平台、工具等因素导致构建产物的差异。优势在于可以确保构建产物的一致性,降低了出现因环境差异导致的问题的风险。更重要的是,提高构建的可重复性和可验证性,为新技术上线做保障。
为达成该目标,我们开发了产物一致性校验工具,从可执行文件级别来进行资源、符号级别对齐。并对 Application 包内图片、文本 等资源文件级别进行校准。
此外,单元测试、集成测试、回归测试也是上线中必要的环节。
3.3 工程架构:管理工程依赖
CocoaPods 在多仓模式下除去自身的依赖管理能力外,承担了非常多的非自身功能,如组件二进制集成、hmap 的编译优化,混编能力的支持。迁移 Bazel 构建系统后这些优化能力名正言顺的通过构建系统来承担。
而在 Monorepo 全源码体系下,二三方组件已经在工程内,CocoaPods 的实际能力已经不再适合 Monorepo,我们开发了轻量依赖管理能力来完全替代它。去除 CocoaPods 后我们彻底解决了下面的几个大问题:
- 依赖问题:依赖冲突、依赖下载、依赖解析、源仓库服务失常等造成效率问题的环节被完全移除。
- 慢速构建:CocoaPods 每次构建项目都需要下载和编译所有的依赖库,导致构建时间变长。
- 单一可信源(Single Source Of Truth):仓库内集成源码,主仓内为唯一版本源不会有二进制、源码版本不一致的问题。
移除 CocoaPods 不代表放弃组件化,在 Monorepo 生态中我们也设立了相应的组件依赖关系验证及组件模块分层服务。
3.4 Developer Tools:优化本地体验
Xcode 是苹果开发者本地开发主要的 IDE 工具,不仅帮助开发人员创建各种类型的应用程序,还拥有丰富的工具和功能,使得开发过程更加便捷高效。但 Xcode IDE 对外置构建系统支持并不友好,加上大型 App 源码数量级别普遍在 1w~10w ,Xcode 在这种情况下表现不佳,其响应慢、定制性差的缺点更加明显。
在本地开发中通过开源工具 Tulsi、BuildService、IndexImport 的定制化开发,补齐了 Xcode 体系中索引、高亮、日志、进度条等语言能力,使 Xcode 体验与之前无异。通过这些工具我们把 Xcode 的优化牢牢把握在自己的手上,也很好的提升了 Xcode 易用性。
除去上述模块外,BitSky 打通集团内 DevOps 平台服务以及基础服务并协同编译监控分析平台 Hummer 打造完善的指标数据、全链路监控、预警防劣化重要能力,提供完整的 monorepo 研发体验及研发效率。
四、数据收益
研发流程上,头条、飞书完成仓库合并后 ,收益数据(以飞书工程为例):
构建效率上,头条完成构建系统迁移后,编译耗时 PCT50 降低 20%,PCT 90 降低 50%。构建整体流程(含 Pod)耗时降低 50%,效果对比图:
五、小结
5.1 问题和挑战
应用实践章节概述了解决方案核心模块,而相较于落地执行中的问题和细节还只是冰山一角。这些问题有些是通用的比如说新在 Monorepo 下如何保持组件化/模块化、工程怎么进行进行依赖分层治理、如何建设落实防劣化方案,而有些问题也会因项目的不同而不可借鉴。千里之行始于足下,未来我们也在向着 VFS、多构建系统、多端同仓的方向做进一步探索。
5.2 我们的经验
Monorepo 全源码方案并不适用所有移动端项目,大部分项目的架构变革不已个人的意志为转移的,康威定律第一条:“设计系统的架构受制于产生这些设计的组织的沟通结构” 很好的诠释了这个观点。但任何大型技术的迁移在一个不断发展的 App 中都是必要的,只有通过迁移才能不断地推进技术的进步和发展,并持续提升研发效率、保证业务的竞争力。
Monorepo 全源码方案为大型移动端开发提供了全面可行性验证以及宝贵经验。未来,我们会将现有的功能回馈到 bazel 社区,并且推出一系列文章来讲述 BitSky 套件的工作流程及原理。
参考文章: