本文来自
武让 极狐 GitLab 高级解决方案架构师
🌟 前一篇文章,作者介绍了嵌入式开发场景的代码治理特点与诉求,以及该场景下的代码管理工具与形式之 SVN 与 Git。前情回顾 👉 嵌入式开发场景下的代码治理计划(上)
本文承前启后,将介绍嵌入式开发场景的代码治理分仓、权限与依赖问题,以及基于 Git 的多仓治理。
2.2 分仓、权限与依赖问题
在上文中我曾经简略介绍了 SVN 和 Git 在分仓模式和权限治理上的一些区别,简略来说:
- SVN 反对目录受权,罕用于大仓模式;
- Git 仅反对仓库受权,罕用于分仓模式,也反对大仓模式。
这本是一个简略的选择题:要么留在 SVN,保留现有的目录受权模式;要么应用 Git 分仓,权限也落在不同的仓库上;要么应用 Git 大仓,权限就管制在整个仓库上;直到另一个需要突破了这个均衡:代码复用。
做过 C/C++ 且又做过 Java、Python、Node 等语言开发的技术人员经常会感叹 C/C++ 短少好的包管理工具,艳羡 Java 有 Maven、Python 有 Pip、Node 有 Npm,而 C/C++ 始终深陷 DLL 天堂、代码版本与交付版本不统一、反复造轮子等各式各样依赖问题的泥潭,不可自拔。
首先 C/C++ 因为其语言个性,构建时经常是零碎底层相干,所以打包时须要思考操作系统、体系架构、编译器版本、构建类型等一系列因素。
且须要关注一些包本身的属性:纯头文件库、动态库还是动静库,以及包的构建参数(比方优化级别、是否开启 exception 和 rtti 的编译选项等),还有指定裁剪性(个性宏)等配置。
另外,因为 C/C++ 的规范库(glibc 和 libstdc++)存在版本兼容性问题,以及 C++ 存在 ABI 兼容性问题,这会让包的版本治理超过语义化版本(SemVer)所能解决的问题范畴,导致包的创建者须要在发包的时候为包的兼容性做更多的思考。
最初,因为 C/C++ 语言在语法上不足包级别的模块化机制,会让包的符号抵触以及依赖解决变得艰难。如果还不够,再加上穿插编译的场景,相对会让一个通用 C/C++ 包管理器的复杂度超过其它任何语言。
包管理器的目标就是对包进行版本控制,解决我的项目间的依赖问题,也能从根本上解决代码复用的问题。但因为 C/C++ 包管理器的复杂度,导致 C/C++ 开发人员在过来很长一段时间内没有包管理工具可用。在这样的背景下,大家对于 C/C++ 的代码复用倒退出了很多模式:
- 基于代码文件目录划分
我的项目划分好模块,定义好本人的目录,协商出一个 Common 目录作为公共的头文件目录,而后对不同的开发人员调配不同的目录权限就能够分工协作了。
看起来是不是十分眼生,这就是 SVN 的应用形式。然而这个 Common 目录可能又须要被另一个我的项目 B 所援用,那么我的项目 B 整个代码也加进这个代码库。最初所有的模块都耦合到一起,代码库收缩的很快,导致重大的性能问题。
- 基于代码复用工具划分
因为 SVN 性能问题,很多开发者开始尝试迁徙到 Git,把代码分不到不同的代码库中。但因为没有包管理工具,嵌入式的我的项目须要先将我的项目关联的代码库拉到一起,而后再构建。而 Git 自身不提供仓库之间的关联关系,这就对分库之后的聚合治理提出了需要。起初呈现了 git submodule
、git subtree
、git repo
等工具,就是为了解决分仓后,如何把我的项目再聚合进去,从而实现项目管理和代码复用。
但这些都是在代码文件级别的复用,减少了治理的复杂度。
- 基于 CMake
一些构建工具的倒退,为 C/C++ 的代码复用引入了更好的形式。例如 CMake 从 3.0 版本开始被称之为“Modern CMake”,是因为它引入了 target 的概念,以及基于 target 建设起了构建的依赖可见性和流传管制机制。这些都更好的反对了代码在构建上的模块化,借助 CMake 的 ExternalProject 和 find_package 个性,使得咱们能够从指定的 http 或者 git 分支下载、构建、装置和援用代码库。
然而这种复用形式,对于间接依赖的治理仍旧是有余的,没有方法做到全链条的依赖解析、依赖追溯、抵触裁决,以及基于变更进行最小范畴的重构建和公布治理。
- 基于包管理工具
解铃还须系铃人,问题倒退到最初还是回到问题自身,C/C++ 没有好的包管理工具,那就做一个。
Conan 是一款杰出的开源 C/C++ 包管理器。它排汇了很多现代化包管理器的设计思维,摸索解决通用 C/C++ 包管理器的各种挑战。它须要应用 Python 进行配置,目前在国内的遍及度还不算高,相干的文档教程也不是很齐全,相对来说有肯定的门槛,但 Conan 能够说是目前 C/C++ 惟一可用的包管理工具,也可能是真正的破局者。
这就是为什么一些软件企业从 SVN 迁徙到 Git 没有那么大阻力,而从事嵌入式开发的企业则不同的起因。归根到底是 C/C++ 不足好的依赖管理手段,而企业、管理者、开发人员始终都面临代码复用这个问题,并心愿通过从 SVN 迁徙到 Git 来解决这个问题。但显然这个问题仅依附 SVN 或者 Git 本身是无奈解决的。
2.3 基于 Git 进行多仓治理
既然 Git 当初是代码治理的支流计划,并且依附 Git 本身无奈解决分仓后的多仓治理问题和代码复用问题,那就须要借助一些其余的工具和办法,其实都是上文中提到过的。尽管这些工具和办法自身不够欠缺,但对于处于不同阶段不同场景的企业和用户,能够有个参考,毕竟软件世界没有银弹。
Git submodule
Git submodule 能够让一个 Git 仓库作为另一个仓库的子目录,从而实现在一个代码库中援用其余的代码库进行复用。
Git submodule 的特点如下:
- 在主库中通过
git submodule add < 子库 git 地址 >
命令实现援用子库代码; - 在主库中通过
.gitmodule
文件来记录主库和子库的援用关系。
- 主库只是援用了子库的 SHA,并没有间接拷贝子库代码,所以子库的代码变更内容在主库上不可见,只是在本地拉取时将对应的子库拉取到本地。所以在 Git 服务器上看不到残缺的我的项目代码,这也意味着无奈实现对于整个我的项目的代码评审。
-
Clone 主库不会主动 Clone 子库,除非在 Clone 主库时:
- 执行 g
it submodule update
; - 执行
git clone --recursive
; - 增加 Git 全局配置 git config –global submodule.recurse true。
- 执行 g
-
子库
commit/push
也须要在主库pull/commit/push
,容易忘记导致代码未同步或者主库关联了旧的子库:- 可在主库执行
git submodule foreach 'git pull origin master'
更新所有子库; - 因为该问题导致主库、子库在解决抵触、切换分支时变得非常复杂,且容易出错。
- 可在主库执行
集体不倡议在理论我的项目中应用 Git submodule,除非能无效解决以上问题,或者可在大量的我的项目中进行试用再进行决策。
Git subtree
Git subtree 与 Git submodule 性能相似,但目前 Git subtree 在开发人员中的呼声高于 Git submodule。
Git subtree 的特点如下:
- 在主库中通过
git subtree add --prefix=< 主库子目录 > < 子库 git 地址 > < 子库分支 >
命令实现援用子库代码。
- 主库拷贝了子库的代码,所以在 Git 服务器上能够看到残缺的我的项目代码,也能够实现整个我的项目的代码评审。所以
Git submodule is Link, Git subtree is Copy
. 也意味着 Git subtree 的性能略差,会减少主库的大小。
- 无奈通过默认的 Git 命令将主库的代码变更同步到子库,须要适应新的工作流。
# 增加子库
git subtree add --prefix=< 主库子目录 > < 子库 git 地址 > < 子库分支 >
# 从子库中拉取
git subtree pull --prefix=< 主库子目录 > < 子库 git 地址 > < 子库分支 >
# 推送到子库
git subtree push --prefix=< 主库子目录 > < 子库 git 地址 > < 子库分支 >
- 可通过一些工具和办法简化工作流:
1. 设置 Git 别名以简化操作:
# 在 .gitconfig 文件中增加
[alias] gits = subtree
# 而后执行命令
gits add --prefix=< 主库子目录 > < 子库 git 地址 > < 子库分支 >
2. 设置子库为主库的远端别名以简化操作:
# 增加子库为主库的 remote 别名
git remote add -f subA <subA>.git
# 而后执行命令
git subtree add --prefix=A subA master
git subtree pull --prefix=A subA master
3. 应用第三方工具 git-subrepo。
集体倡议能够在依赖场景不简单的中小型我的项目中应用 Git subtree,以防止性能问题,它的体验和工作形式绝对比拟敌对。
Script/CMake
最简略的方法往往最无效,通过脚本来拉取或者相干子库的代码,将脚本放在主库中,按需执行,比方拉取相干子库代码:
git clone -b < 子库 A 分支 > < 子库 A git 地址 > < 本地目录 A >
git clone -b < 子库 B 分支 > < 子库 B git 地址 > < 本地目录 B >
或者通过 CMake 的 FetchContent 来实现,能够参考《C++ 工程依赖治理新方向:CMake & Git | KC 的废墟堆》,基于 FetchContent 能够再封装一套脚本。
集体倡议也是能够在依赖场景不简单的中小型我的项目中应用,比拟轻量和灵便,但有肯定的技术门槛,甚至须要专人来做,这对做传统嵌入式开发的团队是个挑战。
Git-Repo
Git-Repo 是 Google 开源的一款 Git 客户端工具,是为了搭配 Google 开源的代码管理工具 Gerrit 进行应用,而 Gerrit 是为了治理 Android 的开源我的项目 AOSP 而设计的。
Google 是为数不多的保持大仓模式(Monorepo)的巨头公司,AOSP 也是一个大型项目,一个我的项目蕴含了数百个代码库,彼此之间存在依赖关系。所以为了更好的治理这些仓库,Google 造成了本人独特的 AOSP 工作流,Gerrit 通过一个我的项目清单 Manifest.xml
来组织仓库关系,Git-Repo 就能够通过 Manifest.xml
来实现代码的批量拉取和推送。
Git-Repo 和 Gerrit 次要是解决了 多仓治理 的问题,除了对仓库进行批量操作,Gerrit 还反对跨代码库进行评审,所以 AOSP 从流程到零碎到工具都是相辅相成的。Git-Repo 能够在肯定水平上解决代码复用问题,不过 Gerrit 自身不是商业化产品,没有厂商技术支持,且 Gerrit 的用户体验较差,复杂度较高,所以当嵌入式我的项目应用 AOSP 专用的工具,又显得有点水土不服。
借鉴 Git-Repo 和 Manifest.xml
的思维,阿里应用 Golang 重写了一个 Git-Repo-Go 工具,能够在 GitLab/ 极狐 GitLab、GitHub 等以 Git 为底层的代码管理系统上取得 Git-Repo 批量操作代码库的体验。
然而上文中也提到,Git-Repo 和 Gerrit 是相辅相成的,Gerrit 反对跨代码库进行代码评审,反对更丰盛的权限管理模式,为多仓下的代码治理和评审提供了根底。而 GitLab/ 极狐 GitLab、GitHub 等代码管理系统目前还是以分仓模式为主,原生不提供这种业务性能,所以导致 Git-Repo-Go 仅仅是实现了 Git-Repo 的局部性能,这时未免狐疑这么折腾为啥不间接用 Gerrit。
Conan
终极计划,如果我的项目依赖绝对简单,须要在我的项目级别进行代码评审,且要思考依赖解析、循环依赖、依赖追踪等问题,那么以上工具计划都不必思考了,它们都无奈从根本上解决问题,所以 Conan 这个工具必须死磕下来,不论是头文件、动态库还是动静库的治理,Conan 都能在肯定水平上满足,尽管它自身具备肯定的复杂性,但目前没有更好的路能够走了。
当然如果违心疾速解决问题,出名制品库厂商 JFrog 提供了 Conan Center 以及相干解决方案,我就不多打广告了。如果违心折腾,Conan 自身开源,且能够通过 SonaType 的开源制品库 Nexsus 实现,这也变相提供了另一种“低成本”的计划。
💡 后续推送剧透:围绕企业对于嵌入式开发场景的诉求,极狐 GitLab 提供了一整套解决方案,能够较好的解决嵌入式开发场景下的种种问题,下期将为你介绍。
援用
欢送订阅关注~