@chengzhao-dev/dsh-cpp-workflow 0.3.0 → 0.5.0
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 +14 -11
- package/README.md +14 -11
- package/lib/index.js +4 -4
- package/lib/workspace.js +1 -56
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
- package/skills/building-cpp/SKILL.md +25 -12
- package/skills/building-cpp/assets/config/.clang-tidy +1 -0
- package/skills/building-cpp/assets/templates/projects/multi/CMakeLists.txt +2 -2
- package/skills/building-cpp/assets/templates/projects/shared-library/CMakeLists.txt +1 -1
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/CMakeLists.txt +1 -1
- package/skills/building-cpp/assets/templates/projects/single/CMakeLists.txt +1 -1
- package/skills/building-cpp/assets/templates/projects/static-library/CMakeLists.txt +1 -1
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/CMakeLists.txt +1 -1
- package/skills/building-cpp/examples/tooling.md +3 -3
- package/skills/building-cpp/references/build-script.md +11 -0
- package/skills/building-cpp/references/linking.md +7 -7
- package/skills/building-cpp/references/standards/README.md +1 -1
- package/skills/building-cpp/references/static-analysis.md +3 -3
- package/skills/building-cpp/steps/01-build.md +22 -35
- package/skills/building-cpp/steps/02-tooling.md +2 -2
- package/skills/configuring-cmake/SKILL.md +57 -0
- package/skills/configuring-cmake/assets/cmake/CompileSettings.cmake +126 -0
- package/skills/configuring-cmake/assets/cmake/LinkSettings.cmake +48 -0
- package/skills/configuring-cmake/examples/cmake.md +224 -0
- package/skills/{building-cpp → configuring-cmake}/references/cmake.md +54 -48
- 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 +17 -3
- package/skills/developing-cpp/examples/comments.md +173 -136
- package/skills/developing-cpp/examples/exceptions.md +38 -19
- package/skills/developing-cpp/examples/structure.md +75 -4
- package/skills/developing-cpp/references/comments.md +82 -99
- package/skills/developing-cpp/references/declaration-order.md +4 -2
- package/skills/developing-cpp/references/exceptions.md +12 -13
- package/skills/developing-cpp/references/file-template.md +131 -0
- package/skills/developing-cpp/references/glossary.md +3 -3
- package/skills/developing-cpp/references/google-cpp-style.md +8 -8
- package/skills/developing-cpp/references/language.md +4 -3
- package/skills/developing-cpp/references/layout.md +21 -12
- package/skills/developing-cpp/references/naming.md +5 -4
- package/skills/developing-cpp/references/project-design.md +1 -1
- package/skills/developing-cpp/references/standard-library.md +2 -1
- package/skills/developing-cpp/references/types.md +1 -2
- package/skills/developing-cpp/scripts/checking-comments.mjs +371 -0
- package/skills/developing-cpp/steps/01-behavior.md +1 -1
- package/skills/developing-cpp/steps/02-boundary.md +4 -4
- package/skills/developing-cpp/steps/03-declaration.md +7 -7
- package/skills/developing-cpp/steps/04-implementation.md +6 -5
- 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
- package/skills/building-cpp/examples/cmake.md +0 -150
- /package/skills/building-cpp/assets/templates/projects/multi/src/{greeting.cc → greeting.cpp} +0 -0
- /package/skills/building-cpp/assets/templates/projects/multi/src/{main.cc → main.cpp} +0 -0
- /package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/{greeting.cc → greeting.cpp} +0 -0
- /package/skills/building-cpp/assets/templates/projects/shared-library/src/{main.cc → main.cpp} +0 -0
- /package/skills/building-cpp/assets/templates/projects/single/src/{main.cc → main.cpp} +0 -0
- /package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/{greeting.cc → greeting.cpp} +0 -0
- /package/skills/building-cpp/assets/templates/projects/static-library/src/{main.cc → main.cpp} +0 -0
|
@@ -15,18 +15,18 @@
|
|
|
15
15
|
| 命名空间 | snake_case | `taskflow`, `warehouse` | `Taskflow`, `Warehouse` |
|
|
16
16
|
| 宏 | 全大写 + 项目前缀,尽量不用 | `CPP_WORKFLOW_ASSERT` | `ASSERT` |
|
|
17
17
|
|
|
18
|
-
- 文件名用 snake_case:`order_result.h`、`task_table.
|
|
18
|
+
- 文件名用 snake_case:`order_result.h`、`task_table.cpp`;测试文件 `task_table_test.cpp`。
|
|
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
|
|
|
26
26
|
| 角色 | 前缀 / 动词 | 例子 | 说明 |
|
|
27
27
|
| --- | --- | --- | --- |
|
|
28
28
|
| 查询 | `Get` 或访问器风格 | `GetCurrentOrder()`、`CurrentOrder()` | 只读、不改变状态;同一 API 选一种 |
|
|
29
|
-
| 判断 | `Is` / `Has` / `Can` | `
|
|
29
|
+
| 判断 | `Is` / `Has` / `Can`;成对规则用 `Completes` 等限定式 | `IsEmpty()`、`HasStock()`、`CompletesLine()` | 返回 `bool`,不抛异常 |
|
|
30
30
|
| 查找 | `Find` | `FindTask()` | 找不到是正常结果,返回 `optional` 或迭代器,不抛 |
|
|
31
31
|
| 计数 | `Count` | `CountPendingTasks()` | 返回数量,不修改状态 |
|
|
32
32
|
| 尝试 | `Try` | `TryReserve()` | 可能失败但不抛异常,返回结果类型 |
|
|
@@ -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()`:调用点看不出方向。
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
## 谓词与查询命名
|
|
48
48
|
|
|
49
49
|
- 返回 `bool` 的查询用肯定式,读起来像一句断言:`IsEmpty()`、`HasStock()`、`CanReserve()`、`CompletesLine()`。
|
|
50
|
+
- 前缀集就是这四类:`Is` / `Has` / `Can`,外加与成对规则一一对应的 `Completes`、`Would` 这类限定式;`Contains` 只保留给容器成员(`std::vector::empty` 一类已有标准名),不拿它当一般谓词。
|
|
50
51
|
- `Get` 只用于取值,`Set` 只用于设值;不要用 `CheckLine()` 当布尔 getter。
|
|
51
52
|
- `Check` / `Validate` 留给会报告错误、产生诊断或可能失败的操作;它们不返回纯 `bool` 查询。
|
|
52
53
|
- 假设性查询在名字里写明条件:`CompletesLine(position, cell)` 读作「这一手会成线吗」;需要更强语气时用 `WouldCompleteLine()`。同一 API 只选一种,改名时同步声明、定义、调用点、测试和注释。
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
- 平台变化(Windows/WSL/macOS 路径、动态库查找)放构建脚本或适配层,不进核心。
|
|
63
63
|
- 测试需要替换的外部依赖(时钟、随机源、输入输出)通过接口注入,不在核心内部直接调用。
|
|
64
64
|
|
|
65
|
-
公共头文件只暴露稳定契约;实现细节放 `.
|
|
65
|
+
公共头文件只暴露稳定契约;实现细节放 `.cpp` 或私有头。一个组件一个 `CMakeLists.txt`,组件的 `include/` 是对外接口,`src/` 是实现。
|
|
66
66
|
|
|
67
67
|
## 什么时候抽共享库
|
|
68
68
|
|
|
@@ -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
|
- 不在比较函数里写有副作用的逻辑;排序要求严格弱序,违反会导致未定义行为。
|
|
@@ -97,4 +98,4 @@ std::erase_if(values, [](int value) { return value % 2 == 0; });
|
|
|
97
98
|
- [ ] 没有保存 `string_view` / `span` 指向的临时对象。
|
|
98
99
|
- [ ] 修改容器大小前处理了迭代器失效。
|
|
99
100
|
- [ ] 能用算法表达时没有手写下标循环。
|
|
100
|
-
- [ ]
|
|
101
|
+
- [ ] 组合消息用 `std::format` 或流输出;`std::print` 只出现在 C++23 的程序边界,库代码没有。
|
|
@@ -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
|
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 只读注释审计:按 references/comments.md 的硬规则检查 C/C++ 源文件。
|
|
3
|
+
//
|
|
4
|
+
// 检查项:文件头横幅、横幅宽度、注释行宽度、Doxygen 标签、注释与声明的贴合、
|
|
5
|
+
// 类型注释存在性、成员功能清单、设计论证(方案对比)、@param 完整性、枚举成员注释一致性。
|
|
6
|
+
// 不重写任何文件。
|
|
7
|
+
//
|
|
8
|
+
// 用法:
|
|
9
|
+
// node checking-comments.mjs [--root DIR] [--width N] [--banner-width N] [--no-header] [--json] [--quiet]
|
|
10
|
+
//
|
|
11
|
+
// 退出码:0 无问题,1 发现问题,2 用法或读取错误。
|
|
12
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
import { pathToFileURL } from 'node:url'
|
|
15
|
+
|
|
16
|
+
export const EXIT = { ok: 0, issues: 1, usage: 2 }
|
|
17
|
+
|
|
18
|
+
const SOURCE_EXT = new Set([
|
|
19
|
+
'.h', '.hh', '.hpp', '.hxx', '.h++', '.inl', '.ipp', '.tpp',
|
|
20
|
+
'.cpp', '.cc', '.cxx', '.c++', '.c', '.m', '.mm',
|
|
21
|
+
])
|
|
22
|
+
const HEADER_EXT = new Set(['.h', '.hh', '.hpp', '.hxx', '.h++', '.inl', '.ipp', '.tpp'])
|
|
23
|
+
const SKIP_DIR = new Set([
|
|
24
|
+
'.git', '.hg', '.svn', 'build', 'build-release', 'out', 'node_modules', '_book', '.quarto', '.cache',
|
|
25
|
+
'.tmp', 'temp', 'third_party', 'third-party', 'external', 'vendor', 'deps', 'cmake-build-debug',
|
|
26
|
+
'cmake-build-release', '.vs', '.idea',
|
|
27
|
+
])
|
|
28
|
+
const FORBIDDEN_TAGS = ['@returns', '@brief', '@details', '@author', '@since', '@todo', '@date', '@file', '@copyright', '@version']
|
|
29
|
+
const BANNER = /^\/\/=+-+=+\/\/$/
|
|
30
|
+
const MEMBER_LIST = /提供[^。]{0,40}、[^。]{0,40}(的功能|的能力|的接口)|功能[::]/
|
|
31
|
+
const RATIONALE = /而不是|而非|代价.{0,4}忽略|只留一份|与其|比.{0,12}更|更(?:安全|快|清楚|直接|好写|难写)/
|
|
32
|
+
const FORWARD_REF = /(?:留在|归|交给|由)\s*`?[A-Z][A-Za-z0-9_]*`?/
|
|
33
|
+
const TYPE_SYSTEM = /值语义|自由拷贝/
|
|
34
|
+
const DECL_KEYWORDS = /^(class|struct|union|enum|namespace|using|typedef|template|#|if|else|for|while|switch|return|case|default)\b/
|
|
35
|
+
|
|
36
|
+
function usage(message) {
|
|
37
|
+
if (message) process.stderr.write(`错误:${message}\n`)
|
|
38
|
+
process.stderr.write('用法:node checking-comments.mjs [--root DIR] [--width N] [--banner-width N] [--no-header] [--json] [--quiet]\n')
|
|
39
|
+
return EXIT.usage
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseArgs(argv) {
|
|
43
|
+
const options = { root: undefined, width: 80, bannerWidth: 80, widthExplicit: false, bannerWidthExplicit: false, requireHeader: true, json: false, quiet: false }
|
|
44
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
45
|
+
const arg = argv[index]
|
|
46
|
+
if (arg === '--root') {
|
|
47
|
+
options.root = argv[index + 1]
|
|
48
|
+
index += 1
|
|
49
|
+
} else if (arg === '--width') {
|
|
50
|
+
options.width = Number(argv[index + 1])
|
|
51
|
+
options.widthExplicit = true
|
|
52
|
+
index += 1
|
|
53
|
+
} else if (arg === '--banner-width') {
|
|
54
|
+
options.bannerWidth = Number(argv[index + 1])
|
|
55
|
+
options.bannerWidthExplicit = true
|
|
56
|
+
index += 1
|
|
57
|
+
} else if (arg === '--no-header') {
|
|
58
|
+
options.requireHeader = false
|
|
59
|
+
} else if (arg === '--json') {
|
|
60
|
+
options.json = true
|
|
61
|
+
} else if (arg === '--quiet') {
|
|
62
|
+
options.quiet = true
|
|
63
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
64
|
+
options.help = true
|
|
65
|
+
} else {
|
|
66
|
+
throw new Error(`未知参数 ${arg}`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const key of ['width', 'bannerWidth']) {
|
|
70
|
+
if (!Number.isInteger(options[key]) || options[key] < 40) {
|
|
71
|
+
throw new Error(`--${key} 需要不小于 40 的整数`)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return options
|
|
75
|
+
}
|
|
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
|
+
|
|
89
|
+
/** 中文按两列计宽,用于判断渲染后的行宽。 */
|
|
90
|
+
export function displayWidth(text) {
|
|
91
|
+
let width = 0
|
|
92
|
+
for (const char of text) {
|
|
93
|
+
const code = char.codePointAt(0)
|
|
94
|
+
const wide =
|
|
95
|
+
(code >= 0x1100 && code <= 0x115f) ||
|
|
96
|
+
(code >= 0x2e80 && code <= 0xa4cf) ||
|
|
97
|
+
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
98
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
99
|
+
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
100
|
+
(code >= 0xff00 && code <= 0xff60) ||
|
|
101
|
+
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
102
|
+
(code >= 0x1f300 && code <= 0x1f64f) ||
|
|
103
|
+
(code >= 0x20000 && code <= 0x3fffd)
|
|
104
|
+
width += wide ? 2 : 1
|
|
105
|
+
}
|
|
106
|
+
return width
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 取出顶层逗号分隔的参数段,忽略模板与括号内的逗号。 */
|
|
110
|
+
function splitParams(text) {
|
|
111
|
+
const parts = []
|
|
112
|
+
let depth = 0
|
|
113
|
+
let current = ''
|
|
114
|
+
for (const char of text) {
|
|
115
|
+
if (char === '<' || char === '(' || char === '[' || char === '{') depth += 1
|
|
116
|
+
if (char === '>' || char === ')' || char === ']' || char === '}') depth = Math.max(0, depth - 1)
|
|
117
|
+
if (char === ',' && depth === 0) {
|
|
118
|
+
parts.push(current)
|
|
119
|
+
current = ''
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
current += char
|
|
123
|
+
}
|
|
124
|
+
if (current.trim() !== '') parts.push(current)
|
|
125
|
+
return parts.map((part) => part.trim()).filter((part) => part !== '' && part !== 'void')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** 取参数名:去掉默认值、数组后缀、引用与指针,保留最后一个标识符。 */
|
|
129
|
+
function paramName(part) {
|
|
130
|
+
let text = part
|
|
131
|
+
if (text.includes('=')) text = text.slice(0, text.indexOf('='))
|
|
132
|
+
text = text.replace(/\[[^\]]*\]/g, ' ').trim()
|
|
133
|
+
if (text === '...') return undefined
|
|
134
|
+
const tokens = text.match(/[A-Za-z_]\w*/g) ?? []
|
|
135
|
+
const last = tokens.at(-1)
|
|
136
|
+
if (!last) return undefined
|
|
137
|
+
if (['const', 'volatile', 'signed', 'unsigned', 'void'].includes(last)) return undefined
|
|
138
|
+
return last
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** 提取函数声明的参数名列表;不是函数声明时返回 undefined。 */
|
|
142
|
+
export function functionParams(declaration) {
|
|
143
|
+
const decl = declaration.trim().replace(/^template\s*<[^>]*>\s*/, '')
|
|
144
|
+
if (DECL_KEYWORDS.test(decl)) return undefined
|
|
145
|
+
const open = decl.indexOf('(')
|
|
146
|
+
if (open === -1) return undefined
|
|
147
|
+
const namePart = decl.slice(0, open).trim()
|
|
148
|
+
if (!/[~A-Za-z_]\w*$|operator\W/.test(namePart)) return undefined
|
|
149
|
+
let depth = 0
|
|
150
|
+
let close = -1
|
|
151
|
+
for (let index = open; index < decl.length; index += 1) {
|
|
152
|
+
const char = decl[index]
|
|
153
|
+
if (char === '(') depth += 1
|
|
154
|
+
if (char === ')') {
|
|
155
|
+
depth -= 1
|
|
156
|
+
if (depth === 0) {
|
|
157
|
+
close = index
|
|
158
|
+
break
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (close === -1) return undefined
|
|
163
|
+
const tail = decl.slice(close + 1).trim()
|
|
164
|
+
if (tail !== '' && !/^[;{:]/.test(tail) && !/^(const|noexcept|override|final|->|=\s*(0|default|delete))/.test(tail)) {
|
|
165
|
+
return undefined
|
|
166
|
+
}
|
|
167
|
+
return splitParams(decl.slice(open + 1, close)).map(paramName).filter(Boolean)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** 检查一段文本,返回问题列表;每个问题带 1 起始的行号。 */
|
|
171
|
+
export function checkText(text, options = {}) {
|
|
172
|
+
const width = options.width ?? 80
|
|
173
|
+
const bannerWidth = options.bannerWidth ?? 80
|
|
174
|
+
const requireHeader = options.requireHeader ?? true
|
|
175
|
+
const lines = text.split(/\r?\n/)
|
|
176
|
+
const issues = []
|
|
177
|
+
const add = (line, rule, message) => issues.push({ line, rule, message })
|
|
178
|
+
|
|
179
|
+
const firstContent = lines.findIndex((line) => line.trim() !== '')
|
|
180
|
+
if (requireHeader && firstContent !== -1 && !BANNER.test(lines[firstContent].trim())) {
|
|
181
|
+
add(firstContent + 1, 'header-missing', '文件开头缺少文件头横幅(//===...===//)')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
185
|
+
const line = lines[index]
|
|
186
|
+
const trimmed = line.trim()
|
|
187
|
+
if (BANNER.test(trimmed)) {
|
|
188
|
+
const actual = displayWidth(trimmed)
|
|
189
|
+
if (actual !== bannerWidth) add(index + 1, 'banner-width', `横幅宽度 ${actual} 列,应为 ${bannerWidth} 列`)
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
if (trimmed.startsWith('///') || (trimmed.startsWith('//') && !trimmed.startsWith('//!'))) {
|
|
193
|
+
if (displayWidth(trimmed) > width) add(index + 1, 'comment-width', `注释行 ${displayWidth(trimmed)} 列,超过 ${width} 列`)
|
|
194
|
+
for (const tag of FORBIDDEN_TAGS) {
|
|
195
|
+
if (trimmed.includes(tag)) add(index + 1, 'forbidden-tag', `禁止使用 ${tag},按 references/comments.md 的标签表改写`)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
201
|
+
if (!lines[index].trim().startsWith('///')) continue
|
|
202
|
+
if (lines[index].trim().startsWith('///<')) continue
|
|
203
|
+
const start = index
|
|
204
|
+
let end = index
|
|
205
|
+
while (end + 1 < lines.length && lines[end + 1].trim().startsWith('///') && !lines[end + 1].trim().startsWith('///<')) {
|
|
206
|
+
end += 1
|
|
207
|
+
}
|
|
208
|
+
index = end
|
|
209
|
+
let next = end + 1
|
|
210
|
+
if (next < lines.length && lines[next].trim() === '') {
|
|
211
|
+
add(end + 1, 'doc-gap', '注释与声明之间有空行:注释会挂到下一个声明,请删掉空行')
|
|
212
|
+
while (next < lines.length && lines[next].trim() === '') next += 1
|
|
213
|
+
}
|
|
214
|
+
if (next >= lines.length) continue
|
|
215
|
+
const declaration = lines[next].trim()
|
|
216
|
+
const block = lines.slice(start, end + 1).join('\n')
|
|
217
|
+
const isType = /^(template\s*<[^>]*>\s*)?(class|struct|union|enum)\b/.test(declaration)
|
|
218
|
+
if (RATIONALE.test(block)) {
|
|
219
|
+
add(start + 1, 'rationale', '注释在写设计取舍:方案对比与理由移到正文或实现注释,接口注释只留契约')
|
|
220
|
+
}
|
|
221
|
+
if (isType && MEMBER_LIST.test(block)) {
|
|
222
|
+
add(start + 1, 'member-list', '类型注释在罗列成员功能,应改写为概念与使用约定')
|
|
223
|
+
}
|
|
224
|
+
if (isType && FORWARD_REF.test(block)) {
|
|
225
|
+
add(start + 1, 'forward-ref', '类型注释在交代别的类型负责什么:只写本类型的契约,跨文件边界留给文件头')
|
|
226
|
+
}
|
|
227
|
+
if (isType && TYPE_SYSTEM.test(block)) {
|
|
228
|
+
add(start + 1, 'type-system', '类型注释在复述类型系统已表达的性质:可拷贝与 const 查询由声明给出,删掉这句')
|
|
229
|
+
}
|
|
230
|
+
const params = functionParams(declaration)
|
|
231
|
+
if (!params) continue
|
|
232
|
+
const documented = [...block.matchAll(/@param(?:\[[^\]]*\])?\s+(\w+)/g)].map((match) => match[1])
|
|
233
|
+
if (documented.length === 0) continue
|
|
234
|
+
for (const name of params) {
|
|
235
|
+
if (!documented.includes(name)) add(start + 1, 'param-missing', `参数 ${name} 没有 @param 说明`)
|
|
236
|
+
}
|
|
237
|
+
for (const name of documented) {
|
|
238
|
+
if (!params.includes(name)) add(start + 1, 'param-extra', `@param ${name} 在声明里不存在`)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
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
|
+
|
|
254
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
255
|
+
const trimmed = lines[index].trim()
|
|
256
|
+
if (!/^(template\s*<[^>]*>\s*)?enum\b/.test(trimmed)) continue
|
|
257
|
+
let body = index
|
|
258
|
+
while (body < lines.length && !lines[body].includes('{')) body += 1
|
|
259
|
+
if (body >= lines.length) continue
|
|
260
|
+
let commented = 0
|
|
261
|
+
let bare = 0
|
|
262
|
+
for (let cursor = body + 1; cursor < lines.length; cursor += 1) {
|
|
263
|
+
const line = lines[cursor].trim()
|
|
264
|
+
if (line.startsWith('}')) break
|
|
265
|
+
if (line === '' || line.startsWith('//') || line.startsWith('#')) continue
|
|
266
|
+
const code = line.replace(/\/\/.*$/, '').trim()
|
|
267
|
+
if (/^[A-Za-z_]\w*\s*(=[^,]+)?,?$/.test(code)) {
|
|
268
|
+
if (line.includes('///<')) commented += 1
|
|
269
|
+
else bare += 1
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (commented > 0 && bare > 0) {
|
|
273
|
+
add(index + 1, 'enum-partial', '枚举成员注释需全写或全不写,当前只有部分成员有 ///<')
|
|
274
|
+
}
|
|
275
|
+
index = body
|
|
276
|
+
}
|
|
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
|
+
|
|
302
|
+
return issues.sort((left, right) => left.line - right.line)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** 递归收集源文件,跳过生成目录与隐藏目录。 */
|
|
306
|
+
export function collectFiles(root) {
|
|
307
|
+
const files = []
|
|
308
|
+
const walk = (dir) => {
|
|
309
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
310
|
+
const full = path.join(dir, entry.name)
|
|
311
|
+
if (entry.isDirectory()) {
|
|
312
|
+
if (SKIP_DIR.has(entry.name.toLowerCase()) || entry.name.startsWith('.')) continue
|
|
313
|
+
walk(full)
|
|
314
|
+
continue
|
|
315
|
+
}
|
|
316
|
+
if (entry.isFile() && SOURCE_EXT.has(path.extname(entry.name).toLowerCase())) files.push(full)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (statSync(root).isFile()) return [root]
|
|
320
|
+
walk(root)
|
|
321
|
+
return files.sort()
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function main(argv) {
|
|
325
|
+
let options
|
|
326
|
+
try {
|
|
327
|
+
options = parseArgs(argv)
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return usage(error.message)
|
|
330
|
+
}
|
|
331
|
+
if (options.help) return usage()
|
|
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
|
+
}
|
|
338
|
+
let files
|
|
339
|
+
try {
|
|
340
|
+
files = collectFiles(root)
|
|
341
|
+
} catch (error) {
|
|
342
|
+
return usage(`读取 ${root} 失败:${error.message}`)
|
|
343
|
+
}
|
|
344
|
+
const results = []
|
|
345
|
+
for (const file of files) {
|
|
346
|
+
let text
|
|
347
|
+
try {
|
|
348
|
+
text = readFileSync(file, 'utf8')
|
|
349
|
+
} catch (error) {
|
|
350
|
+
process.stderr.write(`错误:读取 ${file} 失败:${error.message}\n`)
|
|
351
|
+
return EXIT.usage
|
|
352
|
+
}
|
|
353
|
+
for (const issue of checkText(text, options)) results.push({ file: path.relative(root, file) || file, ...issue })
|
|
354
|
+
}
|
|
355
|
+
if (options.json) {
|
|
356
|
+
process.stdout.write(`${JSON.stringify({ root, files: files.length, issues: results }, null, 2)}\n`)
|
|
357
|
+
} else if (results.length > 0) {
|
|
358
|
+
for (const issue of results) process.stdout.write(`${issue.file}:${issue.line}: [${issue.rule}] ${issue.message}\n`)
|
|
359
|
+
const touched = new Set(results.map((issue) => issue.file)).size
|
|
360
|
+
process.stdout.write(`发现 ${results.length} 处注释问题,涉及 ${touched} 个文件。\n`)
|
|
361
|
+
} else if (!options.quiet) {
|
|
362
|
+
process.stdout.write(`注释检查通过:${files.length} 个文件。\n`)
|
|
363
|
+
}
|
|
364
|
+
return results.length === 0 ? EXIT.ok : EXIT.issues
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
368
|
+
process.exitCode = main(process.argv.slice(2))
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export { HEADER_EXT }
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
- 一次只定一个可验收的切片。切片太大就拆成两个行为句,分别走完四步。
|
|
17
17
|
- 失败方式也要写:非法参数、空输入、越界、文件不存在时,是返回错误值、打印提示,还是抛异常。
|
|
18
18
|
- 行为句不绑定实现。「用 `std::vector` 保存」是声明或实现的决定,不是行为。
|
|
19
|
-
- 需要新增输出时,组合消息用 `std::format
|
|
19
|
+
- 需要新增输出时,组合消息用 `std::format`;`std::print` 只在 C++23 且是程序边界(`apps/`、`main`)时可用,库代码不直接打印。
|
|
20
20
|
|
|
21
21
|
## 情况
|
|
22
22
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
```text
|
|
8
8
|
<component>/include/<name>.h 公共接口
|
|
9
|
-
<component>/src/<name>.
|
|
9
|
+
<component>/src/<name>.cpp 实现
|
|
10
10
|
tests/ 测试
|
|
11
11
|
app 只做参数、输入输出和装配
|
|
12
12
|
```
|
|
@@ -16,7 +16,7 @@ app 只做参数、输入输出和装配
|
|
|
16
16
|
- 库 target 不依赖终端、GUI、网络脚本或全局单例;它只接收值、返回值、在无法完成任务时抛异常。
|
|
17
17
|
- 程序 target 链接库,负责读参数、读标准输入、打印结果、把退出码翻译成进程退出码。
|
|
18
18
|
- 公共头文件直接放在组件的 `include/` 下,从仓库根写完整路径包含,例如 `#include "task_list/include/task_list.h"`;不要写裸文件名或 `../` 相对路径。
|
|
19
|
-
- 依赖方向从程序指向库。库不反向包含程序的 `cli.h`,也不引用 `main.
|
|
19
|
+
- 依赖方向从程序指向库。库不反向包含程序的 `cli.h`,也不引用 `main.cpp`。
|
|
20
20
|
- 测试放 `tests/`,测试 target 链接被测库;测试不复制库的实现。
|
|
21
21
|
- 只有一个可执行文件、没有复用需求时,可以不分库;一旦出现第二个调用方或测试需要直接调用,再拆库。
|
|
22
22
|
- 抽共享库前先确认第二个真实使用方和相同语义;只是“看起来会复用”不构成理由。抽象理由写进项目技能或设计注释。
|
|
@@ -25,11 +25,11 @@ app 只做参数、输入输出和装配
|
|
|
25
25
|
|
|
26
26
|
## 情况
|
|
27
27
|
|
|
28
|
-
**单文件程序**:`src/main.
|
|
28
|
+
**单文件程序**:`src/main.cpp` 可以有 `main()` 和私有辅助函数;一旦辅助逻辑需要测试,把它移到库组件。
|
|
29
29
|
|
|
30
30
|
**领域核心 + 外围**:规则核心(状态、合法性、结果)只依赖标准和领域类型;终端、GUI、Web、脚本都在适配层。依赖方向从外向内,核心不知道适配层存在。
|
|
31
31
|
|
|
32
|
-
**库 + 程序**:库放在 `<component>/`,程序放在 `app/` 或根 `src/main.
|
|
32
|
+
**库 + 程序**:库放在 `<component>/`,程序放在 `app/` 或根 `src/main.cpp`;程序只包含库的公共头。公共头只暴露稳定契约,实现细节放 `.cpp` 或私有头。
|
|
33
33
|
|
|
34
34
|
**静态库 / 动态库**:接口相同,差别在链接方式和运行时库路径;都遵守「库不依赖终端」。选哪种由 `building-cpp` 的定构建决定。
|
|
35
35
|
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## 产出
|
|
4
4
|
|
|
5
|
-
公共头文件、类型与函数声明、命名、声明顺序和声明注释。声明要能让调用方在不读 `.
|
|
5
|
+
公共头文件、类型与函数声明、命名、声明顺序和声明注释。声明要能让调用方在不读 `.cpp` 的情况下知道怎么用、什么时候失败。
|
|
6
6
|
|
|
7
7
|
## 通用情况
|
|
8
8
|
|
|
9
|
-
- 语言标准由项目声明决定,新工程默认 C++23,支持 C++11–C++23;头文件用 `#define`
|
|
9
|
+
- 语言标准由项目声明决定,新工程默认 C++23,支持 C++11–C++23;头文件用 `#define` 守卫,名字由源码树全路径生成,不要用 `#pragma once`。槽位与守卫名按 `references/file-template.md`。
|
|
10
10
|
- 命名按 `references/naming.md`:类型与函数 PascalCase,变量与参数 snake_case,类成员尾随 `_`、结构体成员不尾随,常量与枚举子 `k` 前缀。布尔查询与 `Check` 的分工见同文件「谓词与查询命名」。
|
|
11
11
|
- 声明顺序按 `references/declaration-order.md`:类型 → 静态常量 → 工厂 → 构造/赋值/析构 → 其他函数 → 数据成员。
|
|
12
12
|
- 公共头文件自包含:自身需要的头都直接包含,不依赖调用方的包含顺序。
|
|
13
13
|
- 单参数构造函数写 `explicit`;可查询的成员函数加 `[[nodiscard]]`;默认成员初始化写在声明处,不在构造函数里重复。
|
|
14
|
-
- 头文件只就地定义大约 10 行以内的短函数;更长或需要完整定义的普通函数只在 `.
|
|
15
|
-
- 失败是调用方的正常结果时,用结果类型、`bool` + 输出参数或 `std::optional` 表达;函数无法完成自己的任务、或构造函数无法建立 invariant
|
|
16
|
-
-
|
|
14
|
+
- 头文件只就地定义大约 10 行以内的短函数;更长或需要完整定义的普通函数只在 `.cpp` 定义,模板和 `constexpr` 例外。
|
|
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,9 +22,9 @@
|
|
|
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
|
|
|
29
29
|
## 正反例指针
|
|
30
30
|
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## 产出
|
|
4
4
|
|
|
5
|
-
`.
|
|
5
|
+
`.cpp` 实现文件中的函数体、操作顺序、分组空白和实现注释。实现只兑现第 1 步的行为和第 3 步的声明,不新增公共接口。
|
|
6
6
|
|
|
7
7
|
## 通用情况
|
|
8
8
|
|
|
9
9
|
- 函数保持短小;一个函数只做一件事。超过约 40 行时先找可以命名的中间步骤,不要用注释把长函数切成几段。
|
|
10
10
|
- 按「准备 → 操作 → 收尾」排列语句,用空行分组;组与组之间的空行表达阶段,不用大段叙述。
|
|
11
|
-
- `.
|
|
12
|
-
-
|
|
13
|
-
- 错误处理:失败是调用方正常结果时返回结果类型、`bool` 或 `std::optional
|
|
11
|
+
- `.cpp` 的第一个 include 是自己的配对头,然后直接包含本文件用到的标准库头;不要依赖公共头替本文件转发声明。包含顺序见 `references/google-cpp-style.md`;文件槽位见 `references/file-template.md`。
|
|
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
|
|
|
@@ -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()
|