在日常开发中,我们经常通过各类 IDE 工具来自动修正代码风格,但由于部分 IDE 工具与 clang-format 配合不是特别完善,导致保存或者按下分号、冒号以后代码自动格式化导致错乱,或者格式化时间过长等问题。这在日常开发中是很难让人接受的。 那么我们有没有办法在开发过程中不去让 clang-format 自动格式化,而是在提交代码时检查一次就够了呢?答案是可以的。Git 天生提供了 pre-commit hooks 能力,允许我们预设一些检查脚本在提交前做一些检查。手动编写脚本是比较麻烦的,而且不同开发者的不同环境适配也是棘手的问题。其实早就有人想到了这些事情,pre-commit 工具就是为这个而生的。

手动配置 pre-commit

clang-format、pre-commit 可以通过 pip 来安装,安装完成后在你的项目目录下新建一个配置文件 .pre-commit-config.yaml,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: trailing-whitespace
- id: check-added-large-files
- id: check-merge-conflict
- id: end-of-file-fixer

- repo: https://github.com/pocc/pre-commit-hooks
rev: v1.3.4
hooks:
- id: clang-format
args: [--style=File]

该配置文件告诉我们要到 `https://github.com/pre-commit/pre-commit-hooks\` 中下载已经开发好的一些检查工具,比如行末尾是否有不必要的空格、是否提交了体积较大的文件等。clang-format 的检查也同样具备。在项目目录下执行如下命令来安装这些钩子到本地:

1
pre-commit install

pre-commit 会自动读取 .pre-commit-config.yaml 的配置来下载并安装指定钩子,这些钩子最终都会以脚本的方式安装到 .git/hooks/pre-commit 文件中。此时你再提交代码时响应的钩子会自动运行开始检查你修改过的代码文件,正常情况下返回如下内容:

1
2
3
4
5
6
Trim Trailing Whitespace.................................................Passed
Check for added large files..............................................Passed
Check for merge conflicts................................................Passed
Fix End of Files.........................................................Passed
clang-format.............................................................Passed
cpplint..................................................................Passed

表示所有检查项检查通过了,但如果有不符合要求的内容,则会出现如下错误:

1
2
3
4
5
6
7
8
9
10
11
12
Trim Trailing Whitespace.................................................Failed
- hook id: trailing-whitespace
- exit code: 1
- files were modified by this hook

Fixing nim/dllmain.cpp

Check for added large files..............................................Passed
Check for merge conflicts................................................Passed
Fix End of Files.........................................................Passed
clang-format.............................................................Passed
cpplint..................................................................Passed

一些钩子是可以自动帮你修复代码风格错误的,如果不能自动修复则按提示修复代码内容即可。

通过 CMake 自动配置 pre-commit

在实际的团队协作中,你很难要求所有人都去手动安装这些钩子来提高代码可读性。特别是新人加入团队,如果这些环境都需要手动配置,那光配置项目的时间可能就要很久。所以我们希望它能自动化掉。我们的项目是通过 CMake 来管理的,所以可以在 CMake 中加入如下代码,让工程在初始化的时候自动去安装 clang-format、pre-commit,并自动执行 pre-commit install 将钩子安装到每个开发人员仓库的 .git/hooks 目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Pre-commit hooks
IF (NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/.git/hooks/pre-commit)
# FIND_PACKAGE(Python3 COMPONENTS Interpreter Development)
IF (POLICY CMP0094) # https://cmake.org/cmake/help/latest/policy/CMP0094.html
CMAKE_POLICY(SET CMP0094 NEW) # FindPython should return the first matching Python
ENDIF ()
# needed on GitHub Actions CI: actions/setup-python does not touch registry/frameworks on Windows/macOS
# this mirrors PythonInterp behavior which did not consult registry/frameworks first
IF (NOT DEFINED Python_FIND_REGISTRY)
SET(Python_FIND_REGISTRY "LAST")
ENDIF ()
IF (NOT DEFINED Python_FIND_FRAMEWORK)
SET(Python_FIND_FRAMEWORK "LAST")
ENDIF ()
FIND_PACKAGE(Python REQUIRED COMPONENTS Interpreter)
MESSAGE(STATUS "Python executable: ${Python_EXECUTABLE}")
EXECUTE_PROCESS(COMMAND ${Python_EXECUTABLE} -m pip install clang-format pre-commit)
EXECUTE_PROCESS(COMMAND pre-commit install WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
ENDIF ()

这样我们就完美的实现了自动安装 pre-commit 钩子的方法。如果您的项目是通过其他工程管理工具来管理的,可以找到一个开发人员必要的入口文件、脚本来添加这些能力。