背景
移动端开发现状
在组件化的浪潮下,公司引入多仓开发对工程架构进行解耦、跨业务技术能力复用,并辅以组件(混合)二进制化进行编译提速。不过随着工程规模增长、业务复杂度提升,多仓二进制的弊端日益凸显:
- 合码效率低下:多仓的引入使开发流程变复杂,最有代表性的合码环节一次合码涉及到主仓和多个组件,每个组件要跑 Pipeline 流程进行版本发布。这种模式提升了 CI 复杂度,降低了合码效率。
- 依赖管理衍生问题:稳定性差,多仓使环境依赖度变高,稳定性变成多个仓库稳定性的乘积。需要手动识别依赖层级,先发版被依赖的库,多版本并行开发时,经常发版失败需要写死依赖版本。
- 代码的可视性和可控性降低:跨组件重构困难,全量静态检测无从入手,并且很难统一架构规范;本地开发体验差,工程代码可信度低,无法直接对代码进行开发调试,本地开发需要更多的工具和流程来保证代码的可视性和可控性。
诚然,多仓方案很好的落实了组件化思想,但为解决上述多仓体系的问题,公司衍生了大量的优化和效率工具,其从结果看仅聚焦于解决局部问题,难以进一步提升优化空间。
目前头条、抖音、B站、谷歌等公司采用了Monorepo的方案解决上述问题。
● 代码复用:Monorepo 可以让不同的项目或模块共享代码和依赖项,避免重复编写和维护相同的代码。
● 依赖管理:Monorepo 可以让不同的项目或模块共享依赖项,避免依赖冲突和版本管理问题。
● 统一构建:使用 Bazel 可以统一管理 Monorepo 中的构建规则和依赖项,提高构建效率和可靠性。
● 版本控制:Monorepo 可以让不同的项目或模块共享版本控制系统,简化版本管理和发布流程。
Monorepo 全源码概念和优缺点
Monorepo(Mono Repository)指的是将多个相关项目或模块的代码集中管理于一个仓库的软件开发模式。具体来说,Monorepo 将所有的源代码和配置文件等存储在一个版本控制仓库中,并使用相同的构建和部署系统来管理和交付代码。
全源码(Full Source)是指将整个软件系统的源代码均存放于一个仓库中,源码为单一可信源。
全源码的概念通常与 Monorepo 联系在一起,将概念定义为 Monorepo 全源码解决方案,是区分于当前流行的组件二进制化开发模式。下图中 MULTI-REPO 为前多仓开发模式,每个组件独立 git 存储;MONO-REPO 为单仓模式,组件同主仓合并,只保留一个 git。
优点:
● 便于代码复用:所有项目代码集中于单一仓库,相似的功能更便捷地抽象为公共库,并直接由项目引用。提高代码的可维护性和开发效率。
● 简化依赖管理:无需依赖管理器,所有引用的依赖项都存在于同一代码库中,更加方便地进行构建和管理。
● 原子提交:可以原子性地更改多个项目以避免多仓下不同版本依赖同步的兼容性问题。
● 大规模代码重构:开发人员可访问整个项目,从而更加容易地进行代码重构,并确保每个部分都能正常工作。
● 跨团队协作:通过源代码依赖改进其他团队正在开发的项目,从而实现代码所有权的灵活性。
● 工程质量提升:Monorepo 倡导开放,透明,共享的组织文化,有利于开发者成长,代码质量的提升。
缺点
● 版本信息丢失:Monorepo 使用相同的版本号,无法对每个项目进行单独的版本控制。(多端同仓)
● 缺乏权限控制:无法根据需要授予对代码库的访问权限,所有代码都在同一个项目中可能存在安全问题。
● 磁盘空间占用:默认情况下需检出所有项目,需要更多的存储空间。
为何选择Bazel
Bazel 是一种快速、正确且可扩展的构建工具,具有集成测试功能,可在行业领先的生态系统中支持多种语言、代码库和平台。
Bazel 速度快
Bazel 可以确切知道每个构建命令需要哪些输入文件,从而仅在一组输入文件在每次构建之间发生更改时重新运行,从而避免不必要的工作。 它会在同一计算机或远程构建节点上以尽可能高的并行性运行构建命令。如果构建结构允许,它可以同时运行数千个构建或测试命令。
这受到内存、磁盘和远程构建农场(如果有)的多个缓存层支持。在 Google,我们通常会将缓存命中率达到 99% 以上。
Bazel 正确
Bazel 可确保您的二进制文件仅基于您自己的源代码构建。Bazel 操作在单独的沙盒中运行,Bazel 会跟踪构建的每个输入文件,并且只会在需要时重新运行构建命令。这样可以使您的二进制文件保持最新状态,以便相同的源代码始终生成相同的二进制文件。
对无休止的 make clean
调用,以及追寻实际上在从未被构建的源代码中解决的幽灵 bug 的说辞。
Bazel 具有可扩展性
自行编写规则和宏,针对各种项目的特定需求自定义 Bazel,从而充分发挥 Bazel 的功能。
Bazel 规则是使用 Starlark 编写的,Starlark 是我们的内部编程语言,是 Python 的子集。Starlark 让大多数开发者能够使用规则编写功能,同时还能创建可在整个生态系统中使用的规则。
迁移构建系统Bazel
- 迁移至 Bazel 体系,配置BUILD和WORKSPACE
- 兼容同时使用cocoaPods和Bazel
- .podspec转BUILD 开源方案
- install.sh 脚本改造
最终目录如下:
cocoapods生成的工程叫Test1Bazel.xcworkspace,Bazel生成的工程叫:xxxxxBazel.xcodeproj
Build文件:
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application")
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "lib",
srcs = glob(["Sources/Delegate/*.swift"]),
data = [
"Resources/Main.storyboard",
],
deps = ["//Sources/Network:AFNetworking"],
)
ios_application(
name = "xxxxxxBazel",
bundle_id = "com.wuwei.swift",
app_icons = glob(["Assets.xcassets/AppIcon.appiconset/**"]),
families = [
"iphone",
],
infoplists = ["Resources/Info.plist"],
launch_storyboard = "LaunchScreen.storyboard",
minimum_os_version = "14.0",
visibility = ["//visibility:public"],
deps = [":lib"],
)
load(
"@rules_xcodeproj//xcodeproj:defs.bzl",
"top_level_target",
"xcodeproj",
)
xcodeproj(
name = "xcodeproj",
build_mode = "bazel",
project_name = "xxxxxxBazel",
tags = ["manual"],
top_level_targets = [
":xxxxxxBazel",
],
)
管理工程依赖
CocoaPods 在多仓模式下除去自身的依赖管理能力外,承担了非常多的非自身功能,
如组件二进制集成、hmap 的编译优化,混编能力的支持。
迁移 Bazel 构建系统后这些优化能力名正言顺的通过构建系统来承担。
在 Monorepo 生态中我们也应该设立相应的组件依赖关系验证及组件模块分层服务。Bazel 可以设置所有 target 的可见域,开发者可以将 target 标记为私有,以防止被其他项目错误地依赖,这种约束帮助我们规范代码仓库之中各个库之间的依赖关系。
优化本地体验
在本地开发中通过开源工具 Tulsi、BuildService、IndexImport 的定制化开发,补齐 Xcode 体系中索引、高亮、日志、进度条等语言能力,使 Xcode 体验与之前无异。通过这些工具我们把 Xcode 的优化牢牢把握在自己的手上,也很好的提升了 Xcode 易用性。
Tulsi已经不维护了,官方推荐rules_xcodeproj
最后
Bazel生成的Xcode工程里面的Build settings配置项需要额外的脚本快速配置,因为Build文件改动后Xcode工程每次需要重新生成,手动修改不现实。
最后的最后,我想说:我最大的愿望是世界和平!!