@chengzhao-dev/dsh-cpp-workflow 0.3.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +10 -9
- package/README.md +10 -9
- package/lib/index.js +4 -4
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
- package/skills/building-cpp/SKILL.md +32 -11
- package/skills/building-cpp/references/build-script.md +11 -0
- package/skills/building-cpp/references/linking.md +1 -1
- package/skills/building-cpp/references/standards/README.md +1 -1
- package/skills/building-cpp/steps/01-build.md +22 -35
- package/skills/configuring-cmake/SKILL.md +65 -0
- package/skills/configuring-cmake/assets/cmake/CompileSettings.cmake +126 -0
- package/skills/configuring-cmake/assets/cmake/LinkSettings.cmake +48 -0
- package/skills/{building-cpp → configuring-cmake}/examples/cmake.md +84 -10
- package/skills/{building-cpp → configuring-cmake}/references/cmake.md +49 -43
- package/skills/configuring-cmake/references/cxx-standard.md +67 -0
- package/skills/configuring-cmake/references/modules.md +105 -0
- package/skills/configuring-cmake/steps/01-targets.md +53 -0
- package/skills/configuring-cmake/steps/02-modules.md +37 -0
- package/skills/configuring-cmake/steps/03-check.md +39 -0
- package/skills/developing-cpp/SKILL.md +9 -1
- package/skills/developing-cpp/examples/{comments.md → comments-declaration.md} +25 -51
- package/skills/developing-cpp/examples/comments-implementation.md +53 -0
- package/skills/developing-cpp/examples/exceptions.md +38 -19
- package/skills/developing-cpp/examples/structure.md +71 -0
- package/skills/developing-cpp/references/comments.md +49 -137
- package/skills/developing-cpp/references/exceptions.md +12 -13
- package/skills/developing-cpp/references/language.md +1 -0
- package/skills/developing-cpp/references/naming.md +2 -2
- package/skills/developing-cpp/references/standard-library.md +1 -0
- package/skills/developing-cpp/references/types.md +1 -2
- package/skills/developing-cpp/scripts/checking-comments.mjs +57 -2
- package/skills/developing-cpp/steps/03-declaration.md +4 -4
- package/skills/developing-cpp/steps/04-implementation.md +5 -4
- package/skills/building-cpp/assets/cmake/applying-warnings.cmake +0 -10
- package/skills/building-cpp/assets/cmake/fetching-googletest.cmake +0 -15
- package/skills/building-cpp/assets/cmake/setting-rpath.cmake +0 -8
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
# 注释
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
注释分四层:文件头、类型注释、函数注释、实现注释;配置文件的注释另算。层级越靠外,越讲概念与契约;越靠里,越讲实现理由。同一件事只写一次。
|
|
4
4
|
|
|
5
5
|
## 总则
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- **第一句先写结果或限制**:类型给调用方什么,函数正常返回什么、失败时返回什么或抛什么,调用方必须守住哪条边界。第二句以后才写条件、参数与实现理由。
|
|
8
|
+
- 公共注释回答「这是什么」和「调用方必须遵守什么」;实现注释回答「为什么这样写」和「边界在哪里」。设计取舍、方案对比与演进历史写到书稿、README 或提交说明。
|
|
8
9
|
- 用中文散文写完整句子,句号结尾;标识符、标准库类型、`@param` 这类标签保持原文。
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- 命名、分文件、`enum class`、异常与结果类型这类全项目风格约定只写进项目文档与读者正文;注释不替项目解释风格,也不写「用 X 而不是 Y」的理由。
|
|
10
|
+
- 行宽跟项目 `.clang-format` 的 `ColumnLimit` 走(Google 基线是 80 列,中文按两列计);横幅与列宽同宽,不另设数字。
|
|
11
|
+
- 写之前三问:删掉这句,读者会误解设计或做错事吗?这句只是复述签名或代码吗?这个限制能不能由类型、`const` 或 `[[nodiscard]]` 表达?第一问为否就删,第二问为是就删,第三问为是就改代码。
|
|
12
|
+
- 命名、分文件、`enum class`、异常与结果类型这类全项目风格约定只写进项目文档与读者正文;注释不替项目解释风格。
|
|
13
13
|
|
|
14
14
|
## `///` 与 `//` 的分工
|
|
15
15
|
|
|
16
16
|
- `///` 只给声明:头文件里的类型、函数、枚举值与公开数据成员(行尾补一句用 `///<`)。调用方和 Doxygen 从这里取契约。
|
|
17
|
-
- `//`
|
|
17
|
+
- `//` 给实现:函数体内的步骤与边界说明、局部变量、文件头横幅、配置文件的说明。
|
|
18
18
|
- 不给局部变量和函数体里的语句写 `///`;文件头也不写成 `///`,否则编辑器会把它挂到紧随其后的第一个声明上。
|
|
19
|
-
-
|
|
19
|
+
- 判断看一句话写的是「调用方要遵守的承诺」还是「维护者要知道的写法」:前者用 `///`,后者用 `//`。
|
|
20
20
|
|
|
21
21
|
## 长度预算
|
|
22
22
|
|
|
23
|
-
| 层 | 行数 | 写什么 |
|
|
24
|
-
| --- | --- | --- |
|
|
25
|
-
| 文件头 | 3–6 |
|
|
26
|
-
| 类型注释 | 6–14 | 概念、使用约定 |
|
|
27
|
-
| 函数注释 | 3–10 |
|
|
28
|
-
| 数据成员 | 行尾 1 句 |
|
|
29
|
-
| 实现注释 | 1–3 |
|
|
23
|
+
| 层 | 行数 | 第一句 | 写什么 |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| 文件头 | 3–6 | 这个文件是什么 | 由哪几部分组成、不负责什么 |
|
|
26
|
+
| 类型注释 | 6–14 | 这个类型给调用方什么 | 概念、使用约定 |
|
|
27
|
+
| 函数注释 | 3–10 | 正常返回什么、失败时返回或抛什么 | 参数与失败方式 |
|
|
28
|
+
| 数据成员 | 行尾 1 句 | 存什么 | 范围或单位、初值的含义 |
|
|
29
|
+
| 实现注释 | 1–3 | 为什么这样写或边界在哪里 | 代价 |
|
|
30
30
|
|
|
31
|
-
行数只是防止失控的锚点,不是配额。内容不够就少写,像「检查棋盘是否已满」这种名字已经说清的,一句契约即可。
|
|
32
31
|
|
|
33
32
|
## 文件头
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
每个手写源文件开头写一段文件头:这个文件负责什么、分成哪几块、不负责什么。不写作者、版权年份、变更日志和外部资料路径。
|
|
36
35
|
|
|
37
36
|
```cpp
|
|
38
37
|
//===----------------------------------------------------------------------===//
|
|
@@ -43,172 +42,85 @@
|
|
|
43
42
|
```
|
|
44
43
|
|
|
45
44
|
- C++ 用 `//`,CMake 用 `#`,YAML/JSON 用 `#` 或 `//`。
|
|
46
|
-
- 横幅宽度等于 `ColumnLimit`:`//===` 起头、`===//` 收尾,中间补
|
|
47
|
-
- 超过约 200 行的文件用章节横幅分组,组名是读者要跳转的锚点;一个文件不超过 8
|
|
48
|
-
- 生成文件、第三方文件、纯数据文件不写文件头。
|
|
45
|
+
- 横幅宽度等于 `ColumnLimit`:`//===` 起头、`===//` 收尾,中间补 `-`;章节横幅同一格式。
|
|
46
|
+
- 超过约 200 行的文件用章节横幅分组,组名是读者要跳转的锚点;一个文件不超过 8 个横幅。
|
|
49
47
|
|
|
50
48
|
## 类型注释
|
|
51
49
|
|
|
52
50
|
对象是类、结构体、枚举和类型别名。写两段,段间空一行:
|
|
53
51
|
|
|
54
|
-
1.
|
|
55
|
-
2.
|
|
52
|
+
1. 结果:这个类型是什么、给谁用、保证什么。
|
|
53
|
+
2. 限制:本类型不变量、调用方必须遵守的条件、失败方式与所有权。
|
|
56
54
|
|
|
57
|
-
不写设计段落:为什么这样切分职责、为什么不选另一种写法,属于书稿的设计讨论,注释里不出现。分层说明放文件头,类注释只讲本类型。
|
|
58
55
|
|
|
59
56
|
```cpp
|
|
60
|
-
///
|
|
57
|
+
/// 井字棋棋盘:保存九格状态,并回答满盘、成线与胜负。
|
|
61
58
|
///
|
|
62
|
-
/// 3×3
|
|
59
|
+
/// 格子数量固定为 3×3;落子只改变单个格子,不移动其他格子。
|
|
63
60
|
class Board {
|
|
64
61
|
```
|
|
65
62
|
|
|
66
|
-
判据:删掉这句,读者会不会做错事、或把它用到别的类型上?读签名、`const`、`[[nodiscard]]` 或成员类型就知道的事,写出来只是重复一遍,删。
|
|
67
|
-
|
|
68
|
-
按下面的次序取内容,够了就停,不必每段都凑满:
|
|
69
|
-
|
|
70
|
-
1. 一句话职责:这个类型是什么、给谁用。
|
|
71
|
-
2. 使用约定:什么时候该用它、失败与前置条件是什么。
|
|
72
|
-
3. 非显然保证:所有权、生命周期、异常保证、复杂度;签名里看得出的不写。
|
|
73
|
-
4. 线程规则:只有实例会被多个线程访问时才写,写清哪些操作可以并发、哪些要调用方自己同步。
|
|
74
|
-
5. 不变式:几条始终成立的条件;能写成 `assert` 或 `static_assert` 的,写断言而不是注释。
|
|
75
63
|
|
|
76
64
|
禁止事项:
|
|
77
65
|
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
- 不写方案对比与取舍论证:`用 X 而不是 Y`、`比……更……`、`复制的代价可以忽略`、`只留一份实现` 这类句子属于设计讨论,不进注释。
|
|
84
|
-
- 不写「本类使用快速排序所以很快」这种广告句;要写清楚代价与适用规模。
|
|
85
|
-
|
|
86
|
-
枚举与只承载数据的结构体可以短一些:概念一段,`///<` 先写这个取值表示什么,需要时用冒号补一小句读者能核实的补充(条件、动作或结果);两段都要短,不重复名字,不写「因为……所以……」的长句。
|
|
87
|
-
|
|
88
|
-
```cpp
|
|
89
|
-
/// 对局结果。
|
|
90
|
-
enum class GameResult : std::uint8_t {
|
|
91
|
-
kInProgress = 0, ///< 游戏进行中:还可以落子。
|
|
92
|
-
kCrossWin = 1, ///< X 获胜:达成三连。
|
|
93
|
-
kNoughtWin = 2, ///< O 获胜:达成三连。
|
|
94
|
-
kDraw = 3, ///< 平局:棋盘已满,双方都没有三连。
|
|
95
|
-
};
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
冒号后的补充只要是读者扫一眼就能核实的条件就够了;要展开判定过程或推导,留给函数实现注释或正文。
|
|
66
|
+
- 不写成员清单,不按声明顺序复述接口。函数各自的契约写在函数上。
|
|
67
|
+
- 不写跨类分工。边界靠类型与所有权表达,注释只写本类型保证什么。
|
|
68
|
+
- 不写类型系统已经表达的性质(`值语义`、`所有查询都是 const`)。
|
|
69
|
+
- 不写尚未发生的用法、方案对比与取舍论证、广告句。
|
|
70
|
+
- 枚举与只承载数据的结构体:第一句写这个取值表示什么,需要时用冒号补一小句可核实的条件。
|
|
99
71
|
|
|
100
72
|
## 函数注释
|
|
101
73
|
|
|
102
74
|
两段式,短函数可以只留第一段:
|
|
103
75
|
|
|
104
|
-
1.
|
|
76
|
+
1. 结果或限制:这个函数给调用方什么结果;失败时返回什么、抛什么,或声明它不抛。
|
|
105
77
|
2. 契约:`@param`、`@return`、`@throws`,写范围、单位、所有权和失败方式。
|
|
106
78
|
|
|
79
|
+
|
|
107
80
|
```cpp
|
|
108
|
-
///
|
|
109
|
-
///
|
|
110
|
-
/// 越界、已占或要写空格子都返回 false,不抛异常。
|
|
81
|
+
/// 放置成功返回 true;坐标越界、目标已占或要写空格子时返回 false,不抛异常。
|
|
111
82
|
///
|
|
112
83
|
/// @param position 目标行列坐标。
|
|
113
84
|
/// @param cell_type 要放置的棋盘格子类型,必须是 kX 或 kO。
|
|
114
|
-
/// @return 放置成功返回 true;坐标越界、要写的是空格子或目标已有棋子时返回 false,棋盘不变。
|
|
115
85
|
[[nodiscard]] bool PlaceCell(CellPosition position, CellType cell_type);
|
|
116
86
|
```
|
|
117
87
|
|
|
118
|
-
- 布尔查询:`@return` 写「什么情况下为 true
|
|
119
|
-
- `@param`
|
|
88
|
+
- 布尔查询:`@return` 写「什么情况下为 true」,再补一句「不检查什么」。
|
|
89
|
+
- `@param` 要么全写,要么全不写;写范围、单位或所有权,不写「索引」这类同义反复。
|
|
120
90
|
- `@return` 在返回类型自明时可以省略;`[[nodiscard]]` 已表达「必须使用」时不再重复。
|
|
121
|
-
- `@throws` 必须与 `references/exceptions.md`
|
|
122
|
-
-
|
|
123
|
-
- 参数是输出参数时标 `[out]`,既读又写时标 `[in,out]`。
|
|
91
|
+
- `@throws` 必须与 `references/exceptions.md` 一致;不抛异常的函数不写。
|
|
92
|
+
- 私有函数同样写契约,不能默认调用者知道前置条件;输出参数标 `[out]`,既读又写标 `[in,out]`。
|
|
124
93
|
|
|
125
94
|
## 数据成员与实现注释
|
|
126
95
|
|
|
127
|
-
数据成员用行尾注释,写「存什么 + 范围或单位 +
|
|
96
|
+
数据成员用行尾注释,写「存什么 + 范围或单位 + 初值的含义」,同类成员对齐。不复述 `cells_[row][col]` 这类存储布局:数组维度和访问方式由声明表达。
|
|
128
97
|
|
|
129
98
|
```cpp
|
|
130
99
|
Board board_; // 对局棋盘。
|
|
131
100
|
Player current_player_ = Player::kCross; // 当前轮到的玩家,开局时 X 先手。
|
|
132
101
|
```
|
|
133
102
|
|
|
134
|
-
实现注释只写三样东西:为什么这样写、这里的边界条件、这样做的代价。1–3
|
|
135
|
-
|
|
136
|
-
```cpp
|
|
137
|
-
// 只有对手能立刻成线时才挡:先看自己的胜手,避免把能赢的一步让给对方。
|
|
138
|
-
if (CanWinNow(game, player)) {
|
|
139
|
-
return win_move;
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
- 不逐行翻译代码,不在 `if` 与 `else` 之间挂注释,分支理由写进各自分支内部。
|
|
144
|
-
- 允许写局部取舍(为什么这里接受 O(n²)、为什么排序要稳定、为什么缓存这一层);不写变更史(「原来……现在……」)和外部资料路径。
|
|
145
|
-
- 注释掉的代码不保留,需要的从版本历史取。
|
|
103
|
+
实现注释只写三样东西:为什么这样写、这里的边界条件、这样做的代价。1–3 行,放在被解释语句上方;第一句先写理由或边界,不复述代码。
|
|
146
104
|
|
|
147
|
-
|
|
105
|
+
`.cpp` / `.cc` 的函数体默认**不写**注释:公开契约已经在头文件的 `///` 里,实现再写一遍只会随代码漂移。大型工程的共识是把接口说明留在声明上——[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Implementation_Comments) 只在难懂、不明显的地方写实现注释,并明确不要描述代码本身;[LLVM Coding Standards](https://www.llvm.org/docs/CodingStandards.html#commenting) 同样要求写意图而不是逐步翻译。文件头横幅保留,它对应两家的文件说明,不是函数体解说。
|
|
148
106
|
|
|
149
|
-
|
|
150
|
-
- 类型收窄:从宽类型读入再检查能否收进窄类型,或 `static_cast` 之前的范围判断。
|
|
151
|
-
- 输入与解析:为什么清失败位、为什么丢弃整行、为什么按 64 位读再收窄。
|
|
152
|
-
- 外部格式:手写 JSON、协议字段顺序、编码处理的约定,以及改动时要同步的文件。
|
|
153
|
-
- 回退路径:`switch` 覆盖全部枚举后仍留的 `return`、断言拦不住的兜底分支、`continue` 而不是报错的场合。
|
|
154
|
-
- 状态不变量:写入后哪些成员已经一致、失败时为什么不需要撤销。
|
|
155
|
-
- 刻意重复或刻意不共享:为了可读性重复一小段判断、为了零依赖不复用库函数。
|
|
107
|
+
唯一保留的一类:删掉这句话,下一次修改会得到错误结果,而且代码本身看不出那个结果。句子要写出错误结果,不写「省掉一次扫线」这类口号。典型位置:
|
|
156
108
|
|
|
157
|
-
|
|
109
|
+
- 次序依赖:先判胜再判满,否则最后一子会变成平局。
|
|
110
|
+
- 类型收窄与解析:先按 64 位读再收窄;解析失败后必须丢掉整行。
|
|
111
|
+
- 状态不变量、外部格式、回退路径、刻意重复。
|
|
112
|
+
- 共享方式:`thread_local` 之类的可见性。
|
|
158
113
|
|
|
159
|
-
|
|
114
|
+
循环体里逐行复述 `push_back`、`return` 的注释一律删;不写变更史、外部资料路径和注释掉的代码。CMake 的契约注释不在这里,写法见 `../../configuring-cmake/references/cmake.md`。
|
|
160
115
|
|
|
161
|
-
|
|
162
|
-
// TODO: 阶段 3 - 支持撤销任务状态更新
|
|
163
|
-
// TODO: issue #42 - 把编号检查抽到 TaskTable
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
`TODO:` 后先写定位(阶段名或 issue 号),再写一个短横线,再写动作。不写 `TODO(某人)`,不写没有动作的 TODO。
|
|
167
|
-
|
|
168
|
-
## 配置注释
|
|
169
|
-
|
|
170
|
-
`.clang-format`、`.clang-tidy`、`.clangd`、CMake 的配置项旁边写「为什么这样设」,不写「这一项是什么」。
|
|
171
|
-
|
|
172
|
-
```yaml
|
|
173
|
-
# 列宽、缩进、包含顺序都取 Google 预设,不逐项覆盖。
|
|
174
|
-
Language: Cpp
|
|
175
|
-
BasedOnStyle: Google
|
|
176
|
-
Standard: Latest
|
|
177
|
-
```
|
|
116
|
+
## TODO、配置、改写次序与边界
|
|
178
117
|
|
|
179
|
-
|
|
180
|
-
- `.clang-format` 按逻辑组分段,每组一句说明这一组在管什么,不逐键写「设置 X 为 Y」。
|
|
181
|
-
- `.clangd` 每个键组一句话,说明为什么需要这个数据库或参数。
|
|
182
|
-
- `.clang-tidy` 的每个关闭项写一句理由;没有理由的关闭项删掉。同一个理由只写一次。
|
|
118
|
+
`TODO:` 后先写定位(阶段名或 issue 号),再写一个短横线,再写动作;不写 `TODO(某人)`,不写没有动作的 TODO。
|
|
183
119
|
|
|
184
|
-
|
|
120
|
+
`.clang-format`、`.clang-tidy`、`.clangd`、CMake 的配置项旁边写「为什么这样设」,不写「这一项是什么」;配置注释贴在被解释的键上方。
|
|
185
121
|
|
|
186
|
-
-
|
|
187
|
-
- 同一个概念只用一种译法,术语表见 `references/glossary.md`;中文与英文、数字之间留一个空格。
|
|
188
|
-
- 不写「显然」「众所周知」这类评判读者的词,也不写「注意」「重要」这类没有信息量的加重。
|
|
122
|
+
只改注释时仍走第 3、4 步,然后回读:文件头 → 类型(结果/限制 + 约定)→ 函数(结果/失败方式 + 参数)→ 数据成员(存什么/约束/初值)→ 实现(为什么/边界)→ 术语与新符号是否真实存在 → 运行 `checking-comments.mjs` 兜底。
|
|
189
123
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
只改注释时仍走第 3、4 步,然后按这个次序回读:
|
|
193
|
-
|
|
194
|
-
1. 文件头:是什么、由哪几块组成、不负责什么。
|
|
195
|
-
2. 类型注释:概念、约定;删掉成员清单与设计论证。
|
|
196
|
-
3. 函数注释:承诺、参数与失败方式;与 `references/exceptions.md` 一致。
|
|
197
|
-
4. 数据成员:存什么、约束、初值。
|
|
198
|
-
5. 实现注释:只留为什么和边界,逐行字幕全部删掉。
|
|
199
|
-
6. 回读:同一概念一种译法;注释里出现的新函数名、新常量是否真的存在。
|
|
200
|
-
7. 机械化兜底:`node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>` 检查横幅宽度、禁止标签、注释与声明的贴合、类型成员清单、设计论证、`@param` 完整性与枚举行尾注释;它不判断内容好坏,只守住格式底线。
|
|
201
|
-
|
|
202
|
-
## 与读者正文的边界
|
|
203
|
-
|
|
204
|
-
- 注释写给维护者,正文写给读者;两边各自写自己的文件,不互相抄。
|
|
205
|
-
- 设计理由、方案比较与算法取舍写在正文或实现注释里,公开接口注释只留契约。
|
|
124
|
+
- 注释写给维护者,正文写给读者;设计理由、方案比较与算法取舍写在正文或实现注释里,公开接口只留契约。
|
|
206
125
|
- `///`、`@param`、横幅不进入 `.qmd`、YAML、CSS。
|
|
207
|
-
|
|
208
|
-
## 禁止
|
|
209
|
-
|
|
210
|
-
- 用注释代替类型、`const`、`[[nodiscard]]` 能表达的限制。
|
|
211
|
-
- 大段 ASCII 表格、装饰图案、表情符号,以及把注释当提交说明或变更日志。
|
|
212
|
-
- 写外部资料路径、跨仓库引用、团队流程;这些属于文档。
|
|
213
|
-
- 写成员功能清单(`提供……、……的功能`)与按声明顺序复述接口。
|
|
214
|
-
- 写注释的元叙述:不交代「其余内容见某处注释」「两处不重复」「成员用法写在行尾注释里」这类注释布局;读者要的事实直接写出来,不写「哪里写了什么」。
|
|
126
|
+
- 禁止:用注释代替类型/`const`/`[[nodiscard]]`;大段表格、装饰图案、表情;外部资料路径、跨仓库引用、团队流程;成员功能清单;注释元叙述(「其余内容见某处」「两处不重复」)。
|
|
@@ -37,21 +37,18 @@ class ParseError : public std::runtime_error {
|
|
|
37
37
|
};
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
标准库已经命名的失败直接用标准类型:坐标越界用 `std::out_of_range`,调用时不变量已破坏用 `std::logic_error`,正常输入里的解析失败可以用 `std::runtime_error`。只有调用方必须把这些失败和上述类型区分开时,才新增派生自它们的类型;不要为了「看起来规范」给每个参数错误都建一支。项目若定义自己的异常基类(如 `Error`),先定基类再按领域派生,调用方捕获基类就能接住整族异常。
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
## 两种构建模式的三层合同
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
库的失败方式在 Debug 与 Release 下必须可预测;只有诊断信息的详略可以不同。
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
struct read_error : virtual io_error {};
|
|
51
|
-
struct file_read_error : virtual file_error, virtual read_error {};
|
|
52
|
-
```
|
|
46
|
+
- 返回值:两种模式完全一致。函数用返回值报告可恢复失败时,Debug 不得改成抛异常,Release 也不得把异常改成 false。
|
|
47
|
+
- 标准异常:两种模式的抛出条件相同。越界抛 `std::out_of_range`,前置条件被破坏抛 `std::logic_error`;项目若定义 `Error` 基类,派生类型同时派生自 `std::exception`,Debug 的 `what()` 可带 `file:line`,Release 只留值。
|
|
48
|
+
- `Check`:项目保留这种断言时,两种模式都检查,失败时打印诊断并终止;用于 invariant 已被破坏、继续执行没有意义的场合。没有 `Check` 的项目直接用 `assert` 或 `std::terminate` 表达同一边界,不另写包装。
|
|
49
|
+
- `DebugCheck`:项目保留时只在 Debug 生效,`NDEBUG` 后展开为空;用于代价高、只在开发期值得做的自检。不得用它承担 Release 也必须执行的检查。
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
`Check` 不是 `[[noreturn]]`:条件成立时正常返回。`DebugCheck` 的参数在 Release 不求值,不能放入有副作用的表达式。
|
|
55
52
|
|
|
56
53
|
## 抛出与捕获
|
|
57
54
|
|
|
@@ -72,7 +69,7 @@ E.31:`catch` 子句从派生到基类、从具体到通用排序,`catch (...
|
|
|
72
69
|
|
|
73
70
|
## 不抛的边界
|
|
74
71
|
|
|
75
|
-
E.12:在不可能或不可接受抛异常的地方用 `noexcept`。E.13:直接拥有资源的函数不要抛,资源释放应交给 RAII。E.16:析构、`deallocate`、`swap` 以及异常类型的拷贝/移动构造不得失败;析构和 `swap` 应当是 `noexcept`。
|
|
72
|
+
E.12:在不可能或不可接受抛异常的地方用 `noexcept`。声明前先读实现:函数体里出现 `at()` 或其他可能抛出的调用,就去掉 `noexcept`。正例是只比较两个整数的 `IsInRange`;反例是用 `at()` 写入并可能抛 `std::out_of_range` 的 `PlaceCell`。E.13:直接拥有资源的函数不要抛,资源释放应交给 RAII。E.16:析构、`deallocate`、`swap` 以及异常类型的拷贝/移动构造不得失败;析构和 `swap` 应当是 `noexcept`。
|
|
76
73
|
|
|
77
74
|
E.30:不要写异常规范(`throw(...)` 之类);C++11 之后用 `noexcept` 表达「不抛」。需要无异常构建时,由项目单独决定,不要把这套异常指导改写成两面都对的模糊话。
|
|
78
75
|
|
|
@@ -113,4 +110,6 @@ Config ReadConfig(const std::filesystem::path& path);
|
|
|
113
110
|
- [ ] 析构、`swap`、异常类型的拷贝/移动不抛。
|
|
114
111
|
- [ ] 没有用异常做正常分支,也没有在每个函数里捕获所有异常。
|
|
115
112
|
- [ ] 入口有统一捕获,消息可定位,退出码符合项目合同。
|
|
116
|
-
- [ ] 公共声明的 `@throws`
|
|
113
|
+
- [ ] 公共声明的 `@throws` 与实际抛出类型一致,且 Debug/Release 的抛出条件相同。
|
|
114
|
+
- [ ] 每个 `noexcept` 函数的函数体不调用 `at()` 或其他会抛的接口。
|
|
115
|
+
- [ ] 项目若保留 `Check` / `DebugCheck`:`Check` 两模式都生效,`DebugCheck` 只在 Debug 生效;没有让 Debug 抛、Release 返回 false。
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
- 变量有名字、类型、存储期和作用域。局部变量在离开作用域时销毁;`static` 局部变量在首次执行到声明时初始化,程序结束时销毁。
|
|
9
9
|
- 声明让名字可见,定义分配存储或给出函数体。一个实体可以声明多次,但只能定义一次;头文件只放声明,定义放 `.cpp`。
|
|
10
10
|
- 引用是已有对象的别名,必须初始化,不能重新绑定;指针可以为空,也可以改指向。接口优先用引用表达“必须存在”,用指针表达“可以不存在”。
|
|
11
|
+
- `*this` 不用于复制当前对象:要一份副本就直说语义(如“推演落子”只读原数据,或显式写一个复制函数)。`return *this` 仍只属于赋值运算符,井字棋没有这种运算符。
|
|
11
12
|
|
|
12
13
|
## 初始化与列表初始化
|
|
13
14
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- 布尔变量用 `is_` / `has_` / `can_` 开头:`is_valid`、`has_stock`;不要用 `flag`、`status`。
|
|
20
20
|
- 容器用复数:`tasks`、`orders`;单个元素用单数。
|
|
21
21
|
- 缩写按单词处理:`url`、`id`、`http`,不要写 `URL` 混进 snake_case;类型名里可以 `HttpClient`。
|
|
22
|
-
-
|
|
22
|
+
- 不用单字母名:坐标用 `position`,行、列用 `row`、`col`;`i` / `j` 只用于没有领域名字的计数(如遍历固定长度的下标)。也不要用 `data`、`info`、`tmp` 这类不说明内容的名字。
|
|
23
23
|
|
|
24
24
|
## 语义角色
|
|
25
25
|
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
| 访问器 | `Get` + PascalCase,或 snake_case | `GetHistory()`、`history()` | Google 允许两种;同一 API 只选一种,不用成员名直接当函数名 |
|
|
38
38
|
|
|
39
39
|
- 返回 `bool` 的 lambda 也用 `is_` / `has_` / `can_` 命名:`auto is_empty = [](const Order& order) { ... };`。
|
|
40
|
-
- `Find` 不把“没找到”当异常;`Get`
|
|
40
|
+
- `Find` 不把“没找到”当异常;`Get` 在前置条件不满足时抛标准异常:坐标越界用 `std::out_of_range`,调用时不变量已破坏用 `std::logic_error`;只有调用方必须把这些类型区分开时,才新增派生自它们的类型。两种构建模式的抛出条件相同。两者不要互换。
|
|
41
41
|
- 查询返回多个值时用 `struct`,不要用三个 `GetXxx()` 加输出参数。
|
|
42
42
|
|
|
43
43
|
- 禁止裸 `To()` / `Of()`:调用点看不出方向。
|
|
@@ -62,6 +62,7 @@ std::erase_if(values, [](int value) { return value % 2 == 0; });
|
|
|
62
62
|
| 二分查找 | `std::lower_bound` / `std::upper_bound`(要求已排序) |
|
|
63
63
|
|
|
64
64
|
- 算法接收迭代器区间,不接收容器;调用时写 `values.begin(), values.end()` 或 C++20 `std::ranges::sort(values)`。
|
|
65
|
+
- 嵌套数组整体赋同一个值时用 `std::ranges::fill` 与 `std::views::join`,例见 `examples/structure.md`;逐格做不同处理时仍用 `row` / `col` 循环。
|
|
65
66
|
- 谓词只做判断,不改元素;需要修改时用 `transform`。
|
|
66
67
|
- 捕获列表只捕获需要的变量;默认 `[&]` 容易悬空,默认 `[=]` 容易复制大对象。
|
|
67
68
|
- 不在比较函数里写有副作用的逻辑;排序要求严格弱序,违反会导致未定义行为。
|
|
@@ -124,8 +124,7 @@ class TaskBoard {
|
|
|
124
124
|
| 按键 | `map`、`unordered_map` | 缺键则插入默认值 | 缺键则抛 `std::out_of_range` |
|
|
125
125
|
| 无下标 | `set`、`unordered_set`、`multimap` | 无 `operator[]` / `at` | — |
|
|
126
126
|
|
|
127
|
-
- 只在调用点已经证明下标合法时用 `[]`;否则用 `at`
|
|
128
|
-
- `std::span` 在 C++20 没有 `at`;`span::at` 到 C++26 才进入标准,不要假称 C++20 可用。
|
|
127
|
+
- 只在调用点已经证明下标合法时用 `[]`;否则用 `at` 或先做边界检查。坐标类型只保存位置,不拥有容器区间;区间判断属于持有尺寸的类型(如 `Board::IsInRange`)。`std::span` 在 C++20 没有 `at`,`span::at` 到 C++26 才进入标准。
|
|
129
128
|
- `map::operator[]` 会插入,不是“读取可能缺失的键”;只读查询用 `find` / `contains` / `at`。
|
|
130
129
|
- 契约失败的消息用 `std::format`,见 `references/exceptions.md`。
|
|
131
130
|
|
|
@@ -21,7 +21,7 @@ const SOURCE_EXT = new Set([
|
|
|
21
21
|
])
|
|
22
22
|
const HEADER_EXT = new Set(['.h', '.hh', '.hpp', '.hxx', '.h++', '.inl', '.ipp', '.tpp'])
|
|
23
23
|
const SKIP_DIR = new Set([
|
|
24
|
-
'.git', '.hg', '.svn', 'build', 'out', 'node_modules', '_book', '.quarto', '.cache',
|
|
24
|
+
'.git', '.hg', '.svn', 'build', 'build-release', 'out', 'node_modules', '_book', '.quarto', '.cache',
|
|
25
25
|
'.tmp', 'temp', 'third_party', 'third-party', 'external', 'vendor', 'deps', 'cmake-build-debug',
|
|
26
26
|
'cmake-build-release', '.vs', '.idea',
|
|
27
27
|
])
|
|
@@ -40,7 +40,7 @@ function usage(message) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function parseArgs(argv) {
|
|
43
|
-
const options = { root: undefined, width: 80, bannerWidth: 80, requireHeader: true, json: false, quiet: false }
|
|
43
|
+
const options = { root: undefined, width: 80, bannerWidth: 80, widthExplicit: false, bannerWidthExplicit: false, requireHeader: true, json: false, quiet: false }
|
|
44
44
|
for (let index = 0; index < argv.length; index += 1) {
|
|
45
45
|
const arg = argv[index]
|
|
46
46
|
if (arg === '--root') {
|
|
@@ -48,9 +48,11 @@ function parseArgs(argv) {
|
|
|
48
48
|
index += 1
|
|
49
49
|
} else if (arg === '--width') {
|
|
50
50
|
options.width = Number(argv[index + 1])
|
|
51
|
+
options.widthExplicit = true
|
|
51
52
|
index += 1
|
|
52
53
|
} else if (arg === '--banner-width') {
|
|
53
54
|
options.bannerWidth = Number(argv[index + 1])
|
|
55
|
+
options.bannerWidthExplicit = true
|
|
54
56
|
index += 1
|
|
55
57
|
} else if (arg === '--no-header') {
|
|
56
58
|
options.requireHeader = false
|
|
@@ -72,6 +74,18 @@ function parseArgs(argv) {
|
|
|
72
74
|
return options
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
/** 从工程 .clang-format 读取 ColumnLimit;没有配置时返回 undefined。 */
|
|
78
|
+
function readColumnLimit(root) {
|
|
79
|
+
const dir = statSync(root).isDirectory() ? root : path.dirname(root)
|
|
80
|
+
try {
|
|
81
|
+
const text = readFileSync(path.join(dir, '.clang-format'), 'utf8')
|
|
82
|
+
const match = text.match(/^\s*ColumnLimit:\s*(\d+)\s*$/m)
|
|
83
|
+
return match ? Number(match[1]) : undefined
|
|
84
|
+
} catch {
|
|
85
|
+
return undefined
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
75
89
|
/** 中文按两列计宽,用于判断渲染后的行宽。 */
|
|
76
90
|
export function displayWidth(text) {
|
|
77
91
|
let width = 0
|
|
@@ -225,6 +239,18 @@ export function checkText(text, options = {}) {
|
|
|
225
239
|
}
|
|
226
240
|
}
|
|
227
241
|
|
|
242
|
+
for (let index = 0; index + 1 < lines.length; index += 1) {
|
|
243
|
+
const trimmed = lines[index].trim()
|
|
244
|
+
if (!trimmed.startsWith('//') || trimmed.startsWith('///') || trimmed.startsWith('//!')) continue
|
|
245
|
+
const next = lines[index + 1].trim()
|
|
246
|
+
if (next === '' || next.startsWith('//')) continue
|
|
247
|
+
if (!/(下标|存储|对应坐标|行优先)/.test(trimmed)) continue
|
|
248
|
+
const identifiers = next.match(/[A-Za-z_]\w*/g) ?? []
|
|
249
|
+
if (identifiers.some((identifier) => trimmed.includes(identifier))) {
|
|
250
|
+
add(index + 1, 'restates-next', '实现注释在复述下一行声明或存储细节:删掉这句,让声明自己表达')
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
228
254
|
for (let index = 0; index < lines.length; index += 1) {
|
|
229
255
|
const trimmed = lines[index].trim()
|
|
230
256
|
if (!/^(template\s*<[^>]*>\s*)?enum\b/.test(trimmed)) continue
|
|
@@ -249,6 +275,30 @@ export function checkText(text, options = {}) {
|
|
|
249
275
|
index = body
|
|
250
276
|
}
|
|
251
277
|
|
|
278
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
279
|
+
const trimmed = lines[index].trim()
|
|
280
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('///') || !/\bnoexcept\b/.test(trimmed)) continue
|
|
281
|
+
let bodyStart = index
|
|
282
|
+
if (!trimmed.includes('{')) {
|
|
283
|
+
let next = index + 1
|
|
284
|
+
while (next < lines.length && lines[next].trim() === '') next += 1
|
|
285
|
+
if (next >= lines.length || !lines[next].trim().startsWith('{')) continue
|
|
286
|
+
bodyStart = next
|
|
287
|
+
}
|
|
288
|
+
const afterBrace = trimmed.slice(trimmed.indexOf('{') + 1)
|
|
289
|
+
if (afterBrace.includes('.at(')) {
|
|
290
|
+
add(index + 1, 'noexcept-throws', 'noexcept 函数体内调用 at():越界会抛 std::out_of_range,去掉 noexcept 或改用 [] 与显式越界检查')
|
|
291
|
+
continue
|
|
292
|
+
}
|
|
293
|
+
for (let cursor = bodyStart + 1; cursor < lines.length; cursor += 1) {
|
|
294
|
+
if (/^\}/.test(lines[cursor])) break
|
|
295
|
+
if (lines[cursor].includes('.at(')) {
|
|
296
|
+
add(cursor + 1, 'noexcept-throws', 'noexcept 函数体内调用 at():越界会抛 std::out_of_range,去掉 noexcept 或改用 [] 与显式越界检查')
|
|
297
|
+
break
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
252
302
|
return issues.sort((left, right) => left.line - right.line)
|
|
253
303
|
}
|
|
254
304
|
|
|
@@ -280,6 +330,11 @@ function main(argv) {
|
|
|
280
330
|
}
|
|
281
331
|
if (options.help) return usage()
|
|
282
332
|
const root = options.root ? path.resolve(options.root) : process.cwd()
|
|
333
|
+
const columnLimit = readColumnLimit(root)
|
|
334
|
+
if (columnLimit !== undefined) {
|
|
335
|
+
if (!options.widthExplicit) options.width = columnLimit
|
|
336
|
+
if (!options.bannerWidthExplicit) options.bannerWidth = columnLimit
|
|
337
|
+
}
|
|
283
338
|
let files
|
|
284
339
|
try {
|
|
285
340
|
files = collectFiles(root)
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
- 公共头文件自包含:自身需要的头都直接包含,不依赖调用方的包含顺序。
|
|
13
13
|
- 单参数构造函数写 `explicit`;可查询的成员函数加 `[[nodiscard]]`;默认成员初始化写在声明处,不在构造函数里重复。
|
|
14
14
|
- 头文件只就地定义大约 10 行以内的短函数;更长或需要完整定义的普通函数只在 `.cpp` 定义,模板和 `constexpr` 例外。
|
|
15
|
-
- 失败是调用方的正常结果时,用结果类型、`bool` + 输出参数或 `std::optional` 表达;函数无法完成自己的任务、或构造函数无法建立 invariant
|
|
16
|
-
- 声明注释按 `references/comments.md`
|
|
15
|
+
- 失败是调用方的正常结果时,用结果类型、`bool` + 输出参数或 `std::optional` 表达;函数无法完成自己的任务、或构造函数无法建立 invariant 时,才在声明注释里写清抛出的异常类型;越界用 `std::out_of_range`,前置条件被破坏用 `std::logic_error`,只有调用方必须区分时才新增派生类型;Debug/Release 的抛出条件相同。判据见 `references/exceptions.md`。
|
|
16
|
+
- 声明注释按 `references/comments.md` 分四层写:文件头的范围与边界、类型注释的结果/限制与约定、函数注释的第一句结果或失败方式与契约、数据成员的行尾说明。类型注释不罗列成员功能、不写方案对比与设计论证;全项目的风格理由写进项目文档,不写进注释。
|
|
17
17
|
- `std::format` 用于组合消息;声明里不要承诺 `std::print`(C++23)。
|
|
18
18
|
|
|
19
19
|
## 情况
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
**改公共接口**:先找所有调用方;改签名、改返回类型、改异常声明都算破坏性变更。能加默认参数或重载时优先加,不要静默改变旧调用的含义。
|
|
24
24
|
|
|
25
|
-
**异常声明**:构造函数、`Get` 类查询、解析函数最容易抛;在声明注释里写「当……时抛出 `
|
|
25
|
+
**异常声明**:构造函数、`Get` 类查询、解析函数最容易抛;在声明注释里写「当……时抛出 `std::out_of_range`」这样的具体类型,不要只写「可能抛异常」,也不要给每个参数错误都新建类型。类型选择见 `references/exceptions.md`。
|
|
26
26
|
|
|
27
27
|
**注释**:声明注释用 `///` 写中文散文,只写调用方需要知道的;实现细节留给 `.cpp`。`@param`、`@return`、`@throws` 要么全写要么全不写,标识符和标签保持英文。写完运行 `node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>`,横幅、标签、参数完整性、成员清单与设计论证由脚本兜底。文件头、章节横幅和 TODO 的格式见 `references/comments.md`。
|
|
28
28
|
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
|
|
31
31
|
- 命名、访问器、语义角色:`examples/naming.md`。
|
|
32
32
|
- 异常声明与返回值选择:`examples/exceptions.md`。
|
|
33
|
-
- 声明注释写成了实现说明:`examples/comments.md
|
|
33
|
+
- 声明注释写成了实现说明:`examples/comments-declaration.md`。
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
- 函数保持短小;一个函数只做一件事。超过约 40 行时先找可以命名的中间步骤,不要用注释把长函数切成几段。
|
|
10
10
|
- 按「准备 → 操作 → 收尾」排列语句,用空行分组;组与组之间的空行表达阶段,不用大段叙述。
|
|
11
11
|
- `.cpp` 的第一个 include 是自己的配对头,然后直接包含本文件用到的标准库头;不要依赖公共头替本文件转发声明。包含顺序见 `references/google-cpp-style.md`;文件槽位见 `references/file-template.md`。
|
|
12
|
-
-
|
|
13
|
-
- 错误处理:失败是调用方正常结果时返回结果类型、`bool` 或 `std::optional
|
|
12
|
+
- `.cpp` 的函数体默认不写注释:契约在头文件的 `///` 里。只有删掉会让下一次修改得到错误结果、而代码本身看不出的地方才写一句(次序依赖、类型收窄、解析失败整行丢弃、共享方式),写在被解释语句上方,不复述代码,记录变更史的内容留给提交说明。
|
|
13
|
+
- 错误处理:失败是调用方正常结果时返回结果类型、`bool` 或 `std::optional`;函数无法完成自己的任务时抛异常,不拿异常做正常分支。Debug 与 Release 用同一套返回值与抛出条件,只有诊断详略不同。两种模式都要守的 invariant 用 `Check`(项目保留时),只在开发期做的自检用 `DebugCheck`;没有这两个宏就分别用 `assert` 与 `std::terminate`。捕获时按 `const` 引用,只处理能处理的类型,其余继续抛。
|
|
14
|
+
- 声明 `noexcept` 前核对函数体:出现 `at()` 或其他可能抛出的调用就去掉。越界用 `at()` 抛 `std::out_of_range`,这个函数就不能标 `noexcept`。
|
|
14
15
|
- 组合输出用 `std::format`,再写入 `std::cout` / `std::cerr`;不要在库实现里直接打印。C++23 且库支持时程序边界可用 `std::print`,C++20 及更早没有。
|
|
15
16
|
- 不写 C 风格强制转换;用 `static_cast` / `std::bit_cast` 等具名转换。
|
|
16
17
|
- 所有权用 `std::unique_ptr`;没有共享所有权的理由时不用 `std::shared_ptr`。
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
|
|
25
26
|
**重构**:一次只动一种东西(改名、抽函数、换容器)。每步都让测试保持绿色;行为句不变。
|
|
26
27
|
|
|
27
|
-
**异常边界**:库内部让异常向上传播;程序入口捕获 `std::exception
|
|
28
|
+
**异常边界**:库内部让异常向上传播;程序入口捕获 `std::exception`(或项目自己的异常基类),用 `std::format("错误: {}", error.what())` 打印,返回项目约定的非零退出码。不要吞掉异常后继续用无效状态。
|
|
28
29
|
|
|
29
30
|
## 交回构建
|
|
30
31
|
|
|
@@ -32,6 +33,6 @@
|
|
|
32
33
|
|
|
33
34
|
## 正反例指针
|
|
34
35
|
|
|
35
|
-
- 实现注释复述代码、分组混乱:`examples/comments.md`「实现注释」。
|
|
36
|
+
- 实现注释复述代码、分组混乱:`examples/comments-implementation.md`「实现注释」。
|
|
36
37
|
- 异常类型与结果类型混用:`examples/exceptions.md`「什么时候抛」。
|
|
37
38
|
- `std::format` 与流输出混用得当:`examples/exceptions.md`「异常类型与消息」。
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# 固定版本拉取 GoogleTest;只在 tests/ 需要时调用。
|
|
2
|
-
include(FetchContent)
|
|
3
|
-
|
|
4
|
-
function(cpp_workflow_fetch_googletest)
|
|
5
|
-
if(POLICY CMP0135)
|
|
6
|
-
cmake_policy(SET CMP0135 NEW)
|
|
7
|
-
endif()
|
|
8
|
-
FetchContent_Declare(
|
|
9
|
-
googletest
|
|
10
|
-
URL https://codeload.github.com/google/googletest/tar.gz/refs/tags/v1.17.0
|
|
11
|
-
URL_HASH SHA256=65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c
|
|
12
|
-
)
|
|
13
|
-
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
|
14
|
-
FetchContent_MakeAvailable(googletest)
|
|
15
|
-
endfunction()
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# 让构建目录里的程序直接找到同目录的共享库;安装规则另配。
|
|
2
|
-
function(cpp_workflow_set_build_rpath target)
|
|
3
|
-
if(APPLE)
|
|
4
|
-
set_target_properties(${target} PROPERTIES BUILD_RPATH "@loader_path/../lib")
|
|
5
|
-
else()
|
|
6
|
-
set_target_properties(${target} PROPERTIES BUILD_RPATH "$ORIGIN/../lib")
|
|
7
|
-
endif()
|
|
8
|
-
endfunction()
|