分类目录归档:C/C++

基于 git flow + gitlab 协作开发:01 抛出问题

很久以来,我一直在寻找一个适合小型团队独立项目的 git 协同工作流。主要原因是实际工作中很难在繁忙的迭代中兼顾真正的协同和代码质量管理。造成的现象就是在一个以月维度发布版本的产品中出现各种各样的分支、hotfix。到底哪个是主线,哪个分支修复了哪些问题、不同的分支是否与主线同步更新都是未知数。如果不叫一个从开始就参与到项目中的人给你做介绍,很难去做维护。

我参与的团队中使用的是内部 gitlab 服务器做代码托管。我调研了 git flow / github flow / gitlab flow,每种工作流都有各种优势。gitlab 官方大名鼎鼎的 gitlab flow 工作流,可以支撑大部分真正进入敏捷开发的团队和项目组。但在一些观念尚未真正敏捷而敏捷先行的项目组中,gitlab flow 工作流很难“入乡随俗”。而 github flow 的简单粗暴缺又无法满足现有团队的需求。最终我的目光转移到了 git flow。

下面我们将以开发过程中实际遇到的问题为媒介,来介绍从工程管理到代码审查直至 CI/CD 在我们的团队中是如何运作的。但本文不会涉及到 git flow 的细节,更多请您参考:

混乱的分支管理

在我历史参与过的项目中,我看到过不同形式的分支命名,诸如 Dev_* / dev_* / Rel_* / Release_* / hotfix* 等等不同格式的分支命名规范。每个分支的 Git 路线图看起来错综复杂、犬牙交错!上一个版本与本次开发版本中命名格式不同、分支中出现人名、分支中出现公司名、分支中出现英文介词,这些都见过。而最终这些分支终结于哪里?好像没有一个人能说的特别清楚。

而 git flow 工作流和其工具链(我更喜欢叫它工具链,因为它帮我们完成的是一套命令和合集)则帮我们把这些都预先圈定好,通过固定的指令将分支命名保存为统一的格式。如果你使用默认的初始化参数,那么格式将是下面的样子:

E:\Documents\Repositories\solutions\git-flow (master -> origin)
$ git flow init -d
Using default branch names.
No branches exist yet. Base branches must be created now.
Branch name for production releases: [master]
Branch name for "next release" development: [develop]

How to name your supporting branch prefixes?
Feature branches? [feature/]
Bugfix branches? [bugfix/]
Release branches? [release/]
Hotfix branches? [hotfix/]
Support branches? [support/]
Version tag prefix? []
Hooks and filters directory? [E:/Documents/Repositories/solutions/git-flow/.git/hooks]
  • master 分支,始终最后稳定版本内容的分支,由 release 分支合并养成
  • develop 分支,所有新功能开发的基础、开发阶段冒烟修复问题等
  • feature/* 分支,一切功能开发的子分支,基于 develop,完成后合并到 develop
  • bugfix/* 分支,用于修复缺陷的分支名前缀。
  • release/* 分支,保存每一个版本的迭代信息,由 develop 分支生成
  • hotfix/* 分支,用于线上版本紧急修复的分支
  • support/* 分支,一般用于特殊功能支持的分支(不合并到主分支)

通过 git flow 工具链创建的分支,如 git flow hotfix start 1.0.1 会以固定格式进行命名,这样可以清晰的告诉我们哪些分支起源于哪里、用于做什么、终结于哪里。gitlab 中查看到的分支结构也会变得非常清爽。下图是 gitflow 的工作流的示例图:

Vincent Driessen 在 A successful Git branching model 一文中以另一种形式展示了 gitflow 工作流的详情图:

Hotfix 分支内容容易被遗忘

产品上线后,避免不了会出现一些测试过程中没有发现的边缘问题在客户场景出现,这就需要我们发布 hotfix 来帮助客户解决问题。但往往有些场景因为手动操作开启新的 hotfix 分支后很容易忘记将修改合并到发布分支和开发分支,版本发布比较多以后,会发现有一些 hotfix 分支在项目总仓库中,再加上命名的不规范,最终会不确定这些分支到底有没有合并到主干和开发分支。而 hotfix 有两种不同的场景,分别如下:

  • 线上最新版本出现问题,需要 hotfix
  • 线上非最新版本出现问题,需要 hotfix

两种情况下 hotfix 的流程是不一样的,线上最新版本出现问题,一般修复完成后需要将 hotfix 修改的内容合并到主干分支并且合并到当前开发人员正在开发的分支,确保下一次发布新版本的时候这个修复已经合并过去了。在合并完成后还需要打 tag 来表示本次修复的输出产物。git flow 工具链可以将这一系列操作自动化。当在最新版本中做对应的 hotfix 后,你看到的分支路线图类似于下图:

这些路线图结构清晰,一眼即可看懂。而线上非最新版本出现问题,在修复过程中要根据情况决定是否需要合并的到主干分支和当前开发人员正在开发的分支上。因为有些问题可能在后续版本已经修复而客户因各种原因无法升级到最新版本,所以需要在指定版本中做定制开发,这个可能并不能叫做一个常规的 hotfix,在 gitflow 工作流中,有些人更愿意称呼为 support branch。像下图一样:

其中 1.0.2 和 1.0.3 是针对旧版的一个支持修复,可能这个修复已经在主分支不适用了,但是我们依然是先创建了一个 support 分支,并在这个分支中最具体的 hotfix。这样可能会出现多个分叉,如果你的产品也有很多客户在适用就版本,那可能避免不了会出现这种场景。

参差不齐的 Git level

我们不能要求每一个从业人员对 Git 的熟悉程度都达到炉火纯青,如果项目组有 1~2 个人并且都 Git 各类场景都非常熟悉,我相信不需要引入什么工作流。但避免不了一个项目中有各类角色参与进来。他们可能在是精通某个领域的技术人才,也可能是架构设计的专家。但他们并不一定对团队协作开发非常熟悉,即使在参与项目前三番五次的传递过,而在没有真正的协作前,永远也不会知道这里面有多少陷阱。git flow 工具链将各类复杂场景简单化,只需要通过一些简单的命令就可以让参与项目的人员一起融入到协作中,如:

// 开始和完成一个功能
git flow feature start "name of your feature"
git flow feature finish

// 开始和完成一个版本的发布
git flow release start "version of your release"
git flow release finish

// 开始和完成一个 hotfix
git flow hotfix start "version of your hotfix"
git flow hotfix finish

但切记,git flow 不是什么教条、更不是什么灵丹妙药,理解他的工作流思维,而不是生搬硬套才能让团队协作更加流畅。这些仅是协作中的一部分,更大的一部分是协作过程中的代码审查,如何在 git flow 模式下工作配合 gitlab 做好代码审查,我将在下一篇文章中详细介绍。

使用 CMake + Git Submodule 方式管理项目三方库

在项目开发过程中,避免不了要使用一些开源的三方库,我参加过的一些团队有不同的管理三方库的方式。不同的方式都各有优缺点,我们先列举一下碰到过的管理方式,说一些他们的优缺点,最后再来讨论我们今天介绍的管理方式弥补了哪些缺点。

统一管理三方库

一些团队是直接将三方库编译好放到 CI 机器上,在本地开发或者 CI 出项目的 Release 包时去做依赖拷贝。这样做有以下几点好处:

  1. 三方库不需要每次都重新编译,需要时拷贝,包括符号调试文件也可以一并维护。
  2. 大家公用一份三方库,确保三方库的出处都是一样的。

但是这样管理也有一些不足。

  1. 如果不同的项目需要依赖不同版本、不同分支的三方库,则统一的管理无法满足需求
  2. 三方库的版本管理迭代会保留诸多历史文件,一旦历史版本被清理则很难追溯历史缺陷的符号调试文件

上传三方库文件到 Git 中

编译好直接上传到项目的仓库中,这种方式有一些好处,比如:

  1. 工程下载下来不需要多余配置,启动 IDE 直接编译不废话。
  2. 自己想依赖什么版本就什么版本,不受限于其他项目

当然缺点显而易见:

  1. 静态库文件编译后非常大,上传到 Git 仓库中仓库会异常庞大,特别是三方库二进制文件的更新。
  2. 这种形式很容易出现源码和三方库文件不匹配的情况,这个项目编译好的复制到其他项目直接用,管理混乱。
  3. 跨平台项目要上传不同平台的三方库二进制文件到工程中

使用 CMake + Git Submodule 形式管理

通过以上的描述,我们已经很容易能看清楚不同的管理方式的优劣了。本文推荐的方法可以弥补一些上面方式的不足,但可能也因不同团队而异。推荐的方式大概分以下步骤。

  1. 将你需要依赖的三方库设置为 Git Submodule,每个项目可以依赖不同版本的三方库,不需要上传二进制文件。
  2. 脚本利用 CMake 自动编译三方库工程,不同平台可以使用 CMake 自动编译,不需要单独维护个别版本。
  3. CI 出 Release 时自动编译三方库并保留匹配版本的调试符号文件,和本地源码原始匹配不易混乱。

我们以一个 libyuv 三方库举例。首先将项目添加为 Git Submodule

git submodule add https://chromium.googlesource.com/libyuv/libyuv third_party/libyuv

添加完成后,我们写一个自动编译三方库的脚本,Windows 平台下使用批处理、macOS 下使用 Shell 脚本。

# Windows: Generate visual studio solution
cmake -H. -Boutput -G"Visual Studio 15 2017"
# Build release library
cmake --build output --config Release --target yuv
# Build debug library
cmake --build output --config Debug --target yuv
# macOS: Generate makefile
cmake -H. -DCMAKE_MACOSX_RPATH=OFF -DCMAKE_BUILD_TYPE=Release -Boutput -G"Unix Makefiles"
# Static library
cmake --build output --target yuv

这样在不同平台我们就可以自动编译三方库二进制文件了,本地开发时只需要将 Submodules 更新下来,头文件和库文件路径都修改为三方库文件的路径。CI 出 Release 包时也是先将 Submodules 更新下来编译后再编译项目主体。

Qt QML qtquickcontrols2.conf 使用系统未安装的自定义字体

现在很多视觉设计最终给出的字体可能并不是所有系统都安装过的,所以项目中可能需要单独把字体文件打包进去在程序运行的时候加载。Qt QML 有多种方式加载一个自定义字体,本文将介绍所有我知道的方式,大家根据自己的需要选择使用任意一种即可。

继续阅读

2020 新的开始

2019 年是“进展缓慢”的一年,也是我人生中具有重要意义的一年,更是在经历了诸多波折后回到人生正常状态的一年。虽然还有很多遗憾(包括家庭、感情、工作),但我还是要感谢这一年所有遇到过的人和经历过的事情,这些都无形中让我慢慢走向一个三十而立的人。

我将原有的个人博客域名指向了这个站点,这也意味着未来只会维护这一个博客,这里会有我成长所有相关的内容。按照惯例,总结过去一年的得失,对未来做一个展望。人总是要前进,在前进的道路上才能发现生活的意义。

继续阅读

编译最新版本 CEF(76.0.3809.132)包含 MP3 MP4 AVI FLV 多媒体支持

本文将引导你使用 CEF 官方工具编译出目前(2019年9月5日)最新版本 CEF ,包含详细的步骤和常见问题,编译完成后的 CEF 具备完整功能的 cef_sandbox.lib 和完整的多媒体功能(如常用的 MP3 MP4 FLV AVI 等)支持。来吧,先让我们找一台高性能电脑。

准备工作

  1. 安装 Visual Studio 2017 最新版本,安装时全部选择为默认路径
  2. 100G 以上 SSD 硬盘 + 高性能 CPU(如 Intel 8 系列以上带 K 的 CPU)内存建议 16G
  3. 设置系统虚拟内存页面文件大小为自动(否则可能会编译过程中报错)
  4. 一个稳定的 VPN 网络可以让你快速下载 CEF 和 Chromium 代码

继续阅读

Windows 10 键盘 ALT 键与 WIN 键颠倒

最近重新装了家里台式机电脑系统,但是发现键盘左侧和右侧的 ALT 与 WIN 键功能都颠倒了,搜索了很久资料也没有找到为什么会出现这种情况,但是解决办法还是有的。导入以下注册表即可让 ALT 和 WIN 键对换功能

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:00,00,00,00,00,00,00,00,03,00,00,00,38,00,5B,E0,5B,E0,38,00,00,00,00,00

以上注册表内容来自一篇文章,我根据这篇文章修改的具体内容,请参考:https://www.qiansw.com/windows-through-the-registry-to-make-ctrl-and-alt-swap.html

继续阅读

Qt for iOS 应用使用自定义动态库 Framework

本文内容适合当你想使用 Qt 调用 xcode 开发的动态库 framework 做 iOS 应用的场景,文中涉及到原始动态库 framework 文件的配置,以及 Qt 如何引入和打包动态库 framework 到自身程序中的方法。

动态库 framework 的配置

默认 xcode 创建的 framework 都已经是动态库形式了,所以这里不多介绍,请确保你的 Build Settings 中以下设置是无误的,特别是 Dynamic Library Install Name Base,它决定了动态库能否被应用成功搜索到。

对于 @rpath 的说明,请看这里:https://www.cnblogs.com/csuftzzk/p/mac_run_path.html

Qt 项目配置引入 framework

Qt 引入 framework 只需要给 LIBS 追加 framework 的路径和要链接的文件就可以了,配置如下,请注意使用 framework 参数是 -F 和 -framework(注意大小写)

LIBS += -F$$PWD/../../build/Products/Release
LIBS += -framework my_dylib.framework

这样引入还是不行的,因为 iOS 应用部署上去以后动态库的 framework 文件是不会跟随打包进去的,所以你还需要做一件事情就是把 framework 打包到你的应用中,配置如下:

MY_DYLIB_FRAMEWORK.files = $$PWD/../../build/Products/Release-iphoneos/my_dylib.framework
MY_DYLIB_FRAMEWORK.path = /Frameworks
QMAKE_BUNDLE_DATA += MY_DYLIB_FRAMEWORK

分别指定了 framework 文件的路径和要被打包进去的目标路径,这里是将 $$PWD/../../build/Products/Release-iphoneos/my_dylib.framework 这个 framework 打包到了应用的 /Framework 目录下。最终你看到的应用目录结构是这样的

├── Default-568h@2x.png
├── Frameworks
│   └── my_dylib.framework
│       ├── Info.plist
│       ├── ReadMe.txt
│       ├── _CodeSignature
│       │   └── CodeResources
│       └── my_dylib
├── Info.plist
├── LaunchScreen.nib
├── PkgInfo
├── _CodeSignature
│   └── CodeResources
└── cross-platform-demo

可以看到,mu_dylib.framework 文件已经在我们应用的 /Framework 目录下了,这样文件就被打包进去了,而且当你使用 Qt 编译程序的时候,在 Build Shadow 目录下会看到生成对应的 xcodeproj 文件,可以直接使用 xcode 打开,使用 xcode 打开项目后可以看到项目的 Build Phases 下面多了一条 Copy file to bundle 的项目:

但是当你尝试在 iOS 或者模拟机中运行这个应用时你会发现又有新的错误了,如下所示:

dyld: Library not loaded: @rpath/my_dylib.framework/my_dylib
  Referenced from: /var/containers/Bundle/Application/D0143CDE-FFFE-4343-BFD9-D70DA66C831F/cross-platform-demo.app/cross-platform-demo
  Reason: image not found
program received signal 6, thread:15362b

我明明已经把文件复制进去了,为什么还是会提示,还记得上面我们提到的 Dynamic Library Install Name Base 吗?这是动态库 framework 设置的,根据上面文章的资料,我们要在调用该模块的应用中设置 rpath 的搜索范围,让其能找到我们的动态库文件。Qt 项目中添加如下配置:

# 添加应用的 runpath 路径,因为 my_dylib 动态库 Framework 设置的 install path 为 rpath,所以应用使用时需要单独设置
QMAKE_LFLAGS += -Wl,-rpath,@loader_path/Frameworks

如此设置后,在 Qt 中就可以成功编译程序并运行在模拟器或真机上了,如果还有任何疑问欢迎留言我们一起讨论。

Qt Quick QSettings 配置信息保存位置

Qt Quick 给我们提供了非常方便的配置文件管理功能,它不仅仅可以在 C++ 中访问,也可以在 QML 中直接访问,最近在看 Qt Examples 目录下的 gallery 项目示例时,虽然知道用的是 QSettings 保存的持久化数据,但是不知道配置保存在哪里了,遂到 Qt 官网查询了一下,有英文阅读能力的可直接参考官网:https://doc.qt.io/qt-5/qsettings.html,以下为照搬翻译:

继续阅读