@chengzhao-dev/dsh-cpp-workflow 0.1.1 → 0.3.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 +18 -15
- package/README.md +18 -15
- package/lib/index.js +7 -8
- package/lib/workspace.js +14 -14
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +7 -5
- package/skills/building-cpp/SKILL.md +8 -6
- package/skills/building-cpp/assets/config/.clang-format +5 -38
- package/skills/building-cpp/assets/config/.clang-tidy +8 -39
- package/skills/building-cpp/assets/config/.clangd +2 -5
- package/skills/building-cpp/assets/config/.vscode/extensions.json +2 -1
- package/skills/building-cpp/assets/config/.vscode/settings.json +16 -1
- package/skills/building-cpp/assets/templates/projects/common/running-build.sh +29 -18
- package/skills/building-cpp/assets/templates/projects/multi/CMakeLists.txt +9 -5
- package/skills/building-cpp/assets/templates/projects/multi/include/greeting.h +5 -3
- package/skills/building-cpp/assets/templates/projects/multi/src/greeting.cc +18 -0
- package/skills/building-cpp/assets/templates/projects/multi/src/main.cc +8 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/CMakeLists.txt +8 -3
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/CMakeLists.txt +15 -5
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/include/greeting.h +5 -3
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/greeting.cc +18 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/src/main.cc +8 -0
- package/skills/building-cpp/assets/templates/projects/single/CMakeLists.txt +7 -3
- package/skills/building-cpp/assets/templates/projects/single/src/main.cc +15 -0
- package/skills/building-cpp/assets/templates/projects/static-library/CMakeLists.txt +8 -3
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/CMakeLists.txt +15 -5
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/include/greeting.h +5 -3
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/greeting.cc +18 -0
- package/skills/building-cpp/assets/templates/projects/static-library/src/main.cc +8 -0
- package/skills/building-cpp/examples/cmake.md +17 -17
- package/skills/building-cpp/examples/environment.md +4 -3
- package/skills/building-cpp/examples/tooling.md +31 -16
- package/skills/building-cpp/references/build-script.md +2 -2
- package/skills/building-cpp/references/cmake.md +35 -17
- package/skills/building-cpp/references/encoding.md +49 -0
- package/skills/building-cpp/references/environment.md +17 -13
- package/skills/building-cpp/references/linking.md +6 -6
- package/skills/building-cpp/references/standards/README.md +165 -0
- package/skills/building-cpp/references/standards/c++11/README.md +50 -0
- package/skills/building-cpp/references/standards/c++11/cmake-snippet.cmake +7 -0
- package/skills/building-cpp/references/standards/c++14/README.md +48 -0
- package/skills/building-cpp/references/standards/c++14/cmake-snippet.cmake +7 -0
- package/skills/building-cpp/references/standards/c++17/README.md +49 -0
- package/skills/building-cpp/references/standards/c++17/cmake-snippet.cmake +7 -0
- package/skills/building-cpp/references/standards/c++20/README.md +49 -0
- package/skills/building-cpp/references/standards/c++20/cmake-snippet.cmake +7 -0
- package/skills/building-cpp/references/standards/c++23/README.md +50 -0
- package/skills/building-cpp/references/standards/c++23/cmake-snippet.cmake +7 -0
- package/skills/building-cpp/references/static-analysis.md +27 -15
- package/skills/building-cpp/scripts/checking-encoding.mjs +241 -0
- package/skills/building-cpp/scripts/checking-environment.mjs +21 -21
- package/skills/building-cpp/steps/01-build.md +10 -8
- package/skills/building-cpp/steps/02-tooling.md +9 -7
- package/skills/building-cpp/steps/03-check.md +9 -7
- package/skills/developing-cpp/SKILL.md +8 -6
- package/skills/developing-cpp/examples/comments.md +39 -8
- package/skills/developing-cpp/examples/exceptions.md +64 -45
- package/skills/developing-cpp/examples/naming.md +2 -2
- package/skills/developing-cpp/examples/structure.md +12 -9
- package/skills/developing-cpp/references/comments.md +28 -8
- package/skills/developing-cpp/references/declaration-order.md +7 -4
- package/skills/developing-cpp/references/exceptions.md +83 -46
- package/skills/developing-cpp/references/glossary.md +6 -6
- package/skills/developing-cpp/references/google-cpp-style.md +27 -37
- package/skills/developing-cpp/references/language.md +4 -4
- package/skills/developing-cpp/references/layout.md +14 -14
- package/skills/developing-cpp/references/naming.md +15 -6
- package/skills/developing-cpp/references/project-design.md +12 -3
- package/skills/developing-cpp/references/standard-library.md +1 -1
- package/skills/developing-cpp/steps/02-boundary.md +8 -7
- package/skills/developing-cpp/steps/03-declaration.md +9 -8
- package/skills/developing-cpp/steps/04-implementation.md +11 -7
- package/templates/cpp-workflow-project/SKILL.md +1 -1
- package/templates/cpp-workflow-project/environment.json +2 -1
- package/skills/building-cpp/assets/templates/projects/multi/src/greeting.cpp +0 -13
- package/skills/building-cpp/assets/templates/projects/multi/src/main.cpp +0 -8
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/greeting.cpp +0 -13
- package/skills/building-cpp/assets/templates/projects/shared-library/src/main.cpp +0 -8
- package/skills/building-cpp/assets/templates/projects/single/src/main.cpp +0 -9
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/greeting.cpp +0 -13
- package/skills/building-cpp/assets/templates/projects/static-library/src/main.cpp +0 -8
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# C++20 标准档案
|
|
2
|
+
|
|
3
|
+
[返回标准索引](../README.md)
|
|
4
|
+
|
|
5
|
+
| 项 | 值 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `standard` | `c++20` |
|
|
8
|
+
| CMake meta-feature | `cxx_std_20` |
|
|
9
|
+
| `CXX_STANDARD` | `20` |
|
|
10
|
+
| `__cplusplus` | `202002L` |
|
|
11
|
+
| clang-format 21/22 | `Standard: c++20` |
|
|
12
|
+
| 模板实用下限 | GCC 13 / Clang 16(`std::format`) |
|
|
13
|
+
|
|
14
|
+
## 最重要的三件事
|
|
15
|
+
|
|
16
|
+
1. `std::format` 是这一档的库门槛:libstdc++ 13+,libc++ 14+(特性宏到 19 才稳定),不是有 `-std=c++20` 就自动可用。
|
|
17
|
+
2. concepts/ranges/`std::span`/`std::bit_cast` 可以用;`std::print`、`std::expected`、`if consteval` 仍不能假设。
|
|
18
|
+
3. 模板在 `__cplusplus >= 202002L` 时切到 `std::format`;降回 C++17 时必须把它换回 `std::ostringstream`。
|
|
19
|
+
|
|
20
|
+
## 本档能用
|
|
21
|
+
|
|
22
|
+
- C++17 的全部内容。
|
|
23
|
+
- concepts、requires、ranges、`std::span`、`std::bit_cast`、`std::source_location`。
|
|
24
|
+
- `std::format`、协程、`std::jthread`、三路比较。
|
|
25
|
+
- 模块语法(工具链支持不完整,模板不使用)。
|
|
26
|
+
|
|
27
|
+
## 本档不能假设
|
|
28
|
+
|
|
29
|
+
- 没有 `std::print`、`std::expected`、`std::stacktrace`、`if consteval`、显式对象参数。
|
|
30
|
+
- `std::format` 不只看语言核心:libstdc++ 13+;libc++ 14+ 实现完成,但特性宏到 19 才稳定。
|
|
31
|
+
- 模块、协程的构建支持因编译器/构建系统而异;模板不依赖它们。
|
|
32
|
+
|
|
33
|
+
## CMake
|
|
34
|
+
|
|
35
|
+
把 [cmake-snippet.cmake](cmake-snippet.cmake) 的 target 名换成实际 target。模板从这一档开始会在 `__cplusplus >= 202002L` 时使用 `std::format`,此前用字符串拼接。
|
|
36
|
+
|
|
37
|
+
## 工具
|
|
38
|
+
|
|
39
|
+
- `.clang-format`:`Standard: c++20`。
|
|
40
|
+
- `.clangd`:不写 `-std`,从 `compile_commands.json` 读。
|
|
41
|
+
- `.clang-tidy`:Google 基线;不要为了 concepts 打开一整族非 Google 检查。
|
|
42
|
+
|
|
43
|
+
## 降档/升级
|
|
44
|
+
|
|
45
|
+
从 C++23 降到这里时,把 `std::print` 改成 `std::format` + `std::cout`,把 `std::expected` 改成 `std::optional` 或项目结果类型,去掉 `if consteval`。升级到 C++23 后仍要逐项确认库实现,不要因为 `-std=c++23` 通过就假定 `print`/`stacktrace` 可用。
|
|
46
|
+
|
|
47
|
+
## 验证
|
|
48
|
+
|
|
49
|
+
configure、build、run 必测;`compile_commands.json` 的最后一个 `-std=` 必须是 `-std=c++20`。模板在 C++20 下应走 `std::format` 分支。
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# C++23 标准档案
|
|
2
|
+
|
|
3
|
+
[返回标准索引](../README.md)
|
|
4
|
+
|
|
5
|
+
| 项 | 值 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `standard` | `c++23` |
|
|
8
|
+
| CMake meta-feature | `cxx_std_23` |
|
|
9
|
+
| `CXX_STANDARD` | `23` |
|
|
10
|
+
| `__cplusplus` | `202302L` |
|
|
11
|
+
| clang-format 21/22 | `Standard: Latest` |
|
|
12
|
+
| 模板实用下限 | GCC 13 / Clang 17(模板只用 `std::format`) |
|
|
13
|
+
|
|
14
|
+
## 最重要的三件事
|
|
15
|
+
|
|
16
|
+
1. C++23 在 GCC/Clang 上仍是 Partial;`std::print` 要 libstdc++ 14+/libc++ 19+,`std::stacktrace` 还要链接库。
|
|
17
|
+
2. clang-format 21/22 不认识 `c++23`,配置写 `Standard: Latest`;`.clangd` 不写 `-std`,否则会把编译数据库降回 C++20。
|
|
18
|
+
3. 模板默认 23,但模板只强依赖 `std::format`;不要因为默认 23 就在公共头里用 `std::expected`/`std::print`,先确认所有 target 和库门槛。
|
|
19
|
+
|
|
20
|
+
## 本档能用
|
|
21
|
+
|
|
22
|
+
- C++20 的全部内容。
|
|
23
|
+
- `std::print` / `std::println`、`std::expected`、`std::stacktrace`。
|
|
24
|
+
- `if consteval`、显式对象参数(deducing this)、`std::mdspan`、`std::flat_map`。
|
|
25
|
+
- ranges 的 C++23 增补(工具链实现为准)。
|
|
26
|
+
|
|
27
|
+
## 本档不能假设
|
|
28
|
+
|
|
29
|
+
- C++23 在 GCC/Clang 上仍是 Partial:具体设施要查编译器状态页,不能只看 `-std=c++23`。
|
|
30
|
+
- `std::print` 的库门槛是 libstdc++ 14+ / libc++ 19+;`std::stacktrace` 还要链接对应库,链接失败不是语法错误。
|
|
31
|
+
- `std::expected` 的 monadic 接口、`std::flat_map` 等设施在旧标准库上可能缺失。
|
|
32
|
+
- 模板只用 `std::format`,不会因为默认 C++23 就强依赖 `print`/`stacktrace`。
|
|
33
|
+
|
|
34
|
+
## CMake
|
|
35
|
+
|
|
36
|
+
把 [cmake-snippet.cmake](cmake-snippet.cmake) 的 target 名换成实际 target。默认模板的 `cxx_std_23` 和 `CXX_STANDARD 23` 必须与 `environment.json` 的 `standard` 一致。
|
|
37
|
+
|
|
38
|
+
## 工具
|
|
39
|
+
|
|
40
|
+
- `.clang-format`:`Standard: Latest`(21/22 不接受 `c++23`;`Latest` 按工具支持的最高标准解析)。
|
|
41
|
+
- `.clangd`:不写 `-std`,从 `compile_commands.json` 读;写死 `-std=c++20` 会让 `<print>` 和 `if consteval` 解析失败。
|
|
42
|
+
- `.clang-tidy`:Google 基线;不要因为 C++23 就打开 `cppcoreguidelines-*` 整包。
|
|
43
|
+
|
|
44
|
+
## 降档/升级
|
|
45
|
+
|
|
46
|
+
从 C++23 降到 C++20 见 [C++20 档案](../c++20/README.md)。升级到 C++26 前先确认 CMake 3.25+、编译器/标准库支持、clang-format 的 `c++26` 枚举;新增目录流程见 [标准索引](../README.md#新增标准)。
|
|
47
|
+
|
|
48
|
+
## 验证
|
|
49
|
+
|
|
50
|
+
configure、build、run 必测;`compile_commands.json` 的最后一个 `-std=` 必须是 `-std=c++23`。模板在 C++23 下走 `std::format` 分支;若使用 `print`/`stacktrace`,另做链接测试。
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# 静态分析与语言服务
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
三件语言工具各管一段:`clang-format` 排版、`clang-tidy` 诊断、`clangd` 提供补全/跳转/诊断。它们都读同一份编译数据库,但职责不重叠。编辑器侧只保留 CMake Tools 作为配置、构建、运行和调试入口;C/C++ 扩展只做 `CMake: Debug` 的调试后端,不参与语言服务。
|
|
4
4
|
|
|
5
5
|
## clang-format
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
clang-format --dry-run --Werror src/*.
|
|
8
|
+
clang-format --dry-run --Werror src/*.cc include/*.h tests/*.cc
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
- 检查用 `--dry-run --Werror`;需要自动修复时单独跑 `clang-format -i`,不要在构建脚本里悄悄改源码。
|
|
12
12
|
- 工程必须有根 `.clang-format`;缺失时先复制配置,不要回退到内置风格,回退会掩盖配置缺失。
|
|
13
13
|
- 只扫描项目源码:`src/`、`include/`、`apps/`、`tests/` 等实际存在的目录;不扫描 `build/`、第三方、生成文件。
|
|
14
14
|
- 配置文件在工程根 `.clang-format`;不要依赖用户主目录或编辑器内置配置。
|
|
15
|
-
- `
|
|
15
|
+
- `BasedOnStyle: Google`、`Standard: Latest`;clang-format 21/22 不接受 `c++23`,`Latest` 按工具支持的最高标准解析。列宽 80、2 空格、包含顺序都取 Google 预设,不另写 `ColumnLimit` 或 `IncludeCategories`。
|
|
16
16
|
- 格式化失败就是失败,不要用 `// clang-format off` 绕过;确需关闭时只包住无法排版的宏或表格,并写一句理由。
|
|
17
17
|
|
|
18
18
|
## clang-tidy
|
|
@@ -21,7 +21,7 @@ clang-format --dry-run --Werror src/*.cpp include/*.h tests/*.cpp
|
|
|
21
21
|
clang-tidy -p build/clang \
|
|
22
22
|
--header-filter="^$(pwd -P)/(include|src|apps|tests)/" \
|
|
23
23
|
--warnings-as-errors='*' \
|
|
24
|
-
src/*.
|
|
24
|
+
src/*.cc apps/*.cc tests/*.cc
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
- `-p` 指向当前编译器的构建目录,读取 `compile_commands.json`。
|
|
@@ -30,7 +30,7 @@ clang-tidy -p build/clang \
|
|
|
30
30
|
- 系统头默认被排除,不要加 `--system-headers`。
|
|
31
31
|
- 只扫描项目源文件;不要把 `build/` 或第三方源码传给 clang-tidy。
|
|
32
32
|
- `--warnings-as-errors='*'` 把警告当失败;修代码,不要新增 `NOLINT`。确需抑制时写 `// NOLINT(检查名): 理由`,理由必须具体。
|
|
33
|
-
- 配置在工程根 `.clang-tidy
|
|
33
|
+
- 配置在工程根 `.clang-tidy`;默认集合是 `google-*` 加 `readability-identifier-naming`。关闭某条时逐条写理由,不整族关闭;命名规则只配项目确实统一的项,不设 `FunctionCase`。
|
|
34
34
|
- 没有编译数据库时先回到 `building-cpp` 的定构建生成;不要用 `--` 手工拼编译参数绕过。
|
|
35
35
|
- `clang-tidy` 的诊断可能指向头文件;修头文件里的声明或实现,不要只改调用点。
|
|
36
36
|
|
|
@@ -41,17 +41,13 @@ clang-tidy -p build/clang \
|
|
|
41
41
|
```yaml
|
|
42
42
|
CompileFlags:
|
|
43
43
|
CompilationDatabase: build/clang
|
|
44
|
-
Add:
|
|
45
|
-
- -std=c++20
|
|
46
|
-
- -Wall
|
|
47
|
-
- -Wextra
|
|
48
44
|
Diagnostics:
|
|
49
45
|
Suppress:
|
|
50
46
|
- pp_file_not_found
|
|
51
47
|
```
|
|
52
48
|
|
|
53
49
|
- `CompilationDatabase` 指向当前编译器目录;GCC 模式改 `build/gcc`。
|
|
54
|
-
- `
|
|
50
|
+
- 不要在 `.clangd` 写 `-std`:它会覆盖 `compile_commands.json`,把 C++23 工程降成旧标准。标准只由构建声明和编译数据库决定。
|
|
55
51
|
- 静态检查规则只在 `.clang-tidy` 里维护;clangd 读取同一份配置,不要在这里复制检查列表。
|
|
56
52
|
- `Suppress` 只放确实无法避免的解析噪声(如 `pp_file_not_found`),并写理由。
|
|
57
53
|
|
|
@@ -82,7 +78,11 @@ Diagnostics:
|
|
|
82
78
|
|
|
83
79
|
```json
|
|
84
80
|
{
|
|
85
|
-
"recommendations": [
|
|
81
|
+
"recommendations": [
|
|
82
|
+
"llvm-vs-code-extensions.vscode-clangd",
|
|
83
|
+
"ms-vscode.cmake-tools",
|
|
84
|
+
"ms-vscode.cpptools"
|
|
85
|
+
]
|
|
86
86
|
}
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -96,13 +96,25 @@ Diagnostics:
|
|
|
96
96
|
"--background-index"
|
|
97
97
|
],
|
|
98
98
|
"cmake.buildDirectory": "${workspaceFolder}/build/clang",
|
|
99
|
-
"cmake.generator": "Ninja"
|
|
99
|
+
"cmake.generator": "Ninja",
|
|
100
|
+
"cmake.buildBeforeRun": true,
|
|
101
|
+
"cmake.exportCompileCommandsFile": true,
|
|
102
|
+
"cmake.debugConfig": {
|
|
103
|
+
"type": "cppdbg",
|
|
104
|
+
"request": "launch",
|
|
105
|
+
"MIMode": "lldb",
|
|
106
|
+
"miDebuggerPath": "/usr/bin/lldb",
|
|
107
|
+
"stopAtEntry": false
|
|
108
|
+
},
|
|
109
|
+
"C_Cpp.intelliSenseEngine": "disabled"
|
|
100
110
|
}
|
|
101
111
|
```
|
|
102
112
|
|
|
113
|
+
- 配置、构建、运行和调试只从 CMake Tools 进入;不要同时维护 `tasks.json` 或 `launch.json`,否则会出现第二套构建或启动路径。
|
|
114
|
+
- C/C++ 扩展只作为 CMake 快速调试的 `cppdbg` 后端;IntelliSense 关闭,补全、跳转、诊断和格式化只由 clangd 提供。
|
|
103
115
|
- 不推荐与 clangd 冲突的 C++ 智能提示插件;只保留一个语言服务。
|
|
104
116
|
- 不把 `compile_commands.json` 提交进仓库;它是构建产物。
|
|
105
|
-
- 切换编译器时同时改 `.clangd`、`settings.json` 和构建目录;三处不一致会让诊断指向旧代码。
|
|
117
|
+
- 切换编译器时同时改 `.clangd`、`settings.json` 和构建目录;三处不一致会让诊断指向旧代码。GCC 模式把 `MIMode` 改为 `gdb`、`miDebuggerPath` 改为 `/usr/bin/gdb`。
|
|
106
118
|
|
|
107
119
|
## 门禁分级
|
|
108
120
|
|
|
@@ -110,9 +122,9 @@ Diagnostics:
|
|
|
110
122
|
|
|
111
123
|
| 级别 | 典型检查 | 处理 |
|
|
112
124
|
| --- | --- | --- |
|
|
113
|
-
| 失败 | `
|
|
125
|
+
| 失败 | `google-*`、`readability-identifier-naming`、项目声明的致命集 | 修代码;不允许用 NOLINT 掩盖 |
|
|
114
126
|
| 失败 | 编译诊断、链接诊断、格式检查 | 直接失败 |
|
|
115
|
-
| 观察 |
|
|
127
|
+
| 观察 | 迁移期暂时无法修完的个别 `google-*` 子项 | 在项目技能里列清单、写负责人和退出条件;不默认全仓关闭 |
|
|
116
128
|
|
|
117
129
|
- 致命集宁可小、可解释,也不要把所有检查一刀切。
|
|
118
130
|
- 观察项不是免死金牌:新代码不应新增观察项,已有项只能减少。
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Read-only encoding audit for a Git work tree.
|
|
3
|
+
//
|
|
4
|
+
// Default profile "portable" is the cross-platform baseline: UTF-8 without BOM,
|
|
5
|
+
// LF line endings, one final newline. Profile "msvc" allows a UTF-8 BOM and CRLF
|
|
6
|
+
// only for MSVC-compiled C/C++ sources; Markdown, CMake, scripts and configs stay
|
|
7
|
+
// portable. It never rewrites files.
|
|
8
|
+
//
|
|
9
|
+
// Usage:
|
|
10
|
+
// node checking-encoding.mjs [--root DIR] [--profile portable|msvc] [--json] [--quiet]
|
|
11
|
+
//
|
|
12
|
+
// Exit codes: 0 clean, 1 issues found, 2 usage or Git error.
|
|
13
|
+
import { execFileSync } from 'node:child_process'
|
|
14
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
15
|
+
import path from 'node:path'
|
|
16
|
+
import { pathToFileURL } from 'node:url'
|
|
17
|
+
|
|
18
|
+
export const EXIT = { ok: 0, issues: 1, usage: 2 }
|
|
19
|
+
|
|
20
|
+
const UTF8_BOM = [0xef, 0xbb, 0xbf]
|
|
21
|
+
const UTF16_LE = [0xff, 0xfe]
|
|
22
|
+
const UTF16_BE = [0xfe, 0xff]
|
|
23
|
+
const UTF32_LE = [0xff, 0xfe, 0x00, 0x00]
|
|
24
|
+
const UTF32_BE = [0x00, 0x00, 0xfe, 0xff]
|
|
25
|
+
|
|
26
|
+
const CRLF_REQUIRED = new Set(['.bat', '.cmd'])
|
|
27
|
+
const MSVC_SOURCE = new Set([
|
|
28
|
+
'.c', '.cc', '.cpp', '.cxx', '.h', '.hh', '.hpp', '.hxx', '.inl', '.ixx', '.cppm', '.rc',
|
|
29
|
+
])
|
|
30
|
+
const ALWAYS_LF = new Set([
|
|
31
|
+
'.md', '.qmd', '.txt', '.rst', '.yml', '.yaml', '.json', '.jsonc', '.json5', '.toml',
|
|
32
|
+
'.ini', '.cfg', '.conf', '.sh', '.bash', '.zsh', '.fish', '.ps1', '.py', '.mjs', '.cjs',
|
|
33
|
+
'.js', '.jsx', '.ts', '.tsx', '.css', '.scss', '.sass', '.less', '.html', '.htm', '.xml',
|
|
34
|
+
'.svg', '.cmake', '.lua', '.sql', '.gitignore', '.gitattributes', '.editorconfig',
|
|
35
|
+
])
|
|
36
|
+
const NO_EXTENSION_LF = new Set(['CMakeLists.txt', 'Makefile', 'Justfile', 'Dockerfile', 'LICENSE'])
|
|
37
|
+
|
|
38
|
+
function usage(message) {
|
|
39
|
+
if (message) process.stderr.write(`错误:${message}\n`)
|
|
40
|
+
process.stderr.write('用法:node checking-encoding.mjs [--root DIR] [--profile portable|msvc] [--json] [--quiet]\n')
|
|
41
|
+
return EXIT.usage
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseArgs(argv) {
|
|
45
|
+
const options = { root: undefined, profile: 'portable', json: false, quiet: false }
|
|
46
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
47
|
+
const arg = argv[index]
|
|
48
|
+
if (arg === '--root') {
|
|
49
|
+
options.root = argv[index + 1]
|
|
50
|
+
index += 1
|
|
51
|
+
} else if (arg === '--profile') {
|
|
52
|
+
options.profile = argv[index + 1]
|
|
53
|
+
index += 1
|
|
54
|
+
} else if (arg === '--json') {
|
|
55
|
+
options.json = true
|
|
56
|
+
} else if (arg === '--quiet') {
|
|
57
|
+
options.quiet = true
|
|
58
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
59
|
+
options.help = true
|
|
60
|
+
} else {
|
|
61
|
+
throw new Error(`未知参数 ${arg}`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (options.profile !== 'portable' && options.profile !== 'msvc') {
|
|
65
|
+
throw new Error(`--profile 只能是 portable 或 msvc,收到 ${options.profile}`)
|
|
66
|
+
}
|
|
67
|
+
return options
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function gitRoot(from) {
|
|
71
|
+
try {
|
|
72
|
+
return execFileSync('git', ['-C', from, 'rev-parse', '--show-toplevel'], {
|
|
73
|
+
encoding: 'utf8',
|
|
74
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
75
|
+
}).trim()
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function listFiles(root) {
|
|
82
|
+
const output = execFileSync('git', ['-C', root, 'ls-files', '-z', '--cached', '--others', '--exclude-standard'], {
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
85
|
+
})
|
|
86
|
+
return output
|
|
87
|
+
.split('\0')
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.map((item) => path.join(root, item))
|
|
90
|
+
.filter((file) => {
|
|
91
|
+
try {
|
|
92
|
+
return statSync(file).isFile()
|
|
93
|
+
} catch {
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function startsWith(buffer, bytes) {
|
|
100
|
+
return bytes.every((byte, index) => buffer[index] === byte)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function bomOf(buffer) {
|
|
104
|
+
if (startsWith(buffer, UTF32_LE)) return 'UTF-32 LE'
|
|
105
|
+
if (startsWith(buffer, UTF32_BE)) return 'UTF-32 BE'
|
|
106
|
+
if (startsWith(buffer, UTF8_BOM)) return 'UTF-8'
|
|
107
|
+
if (startsWith(buffer, UTF16_LE)) return 'UTF-16 LE'
|
|
108
|
+
if (startsWith(buffer, UTF16_BE)) return 'UTF-16 BE'
|
|
109
|
+
return undefined
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isBinary(buffer) {
|
|
113
|
+
const limit = Math.min(buffer.length, 8192)
|
|
114
|
+
for (let index = 0; index < limit; index += 1) {
|
|
115
|
+
if (buffer[index] === 0) return true
|
|
116
|
+
}
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function inspect(file, root, profile) {
|
|
121
|
+
const rel = path.relative(root, file).split(path.sep).join('/')
|
|
122
|
+
const extension = path.extname(file).toLowerCase()
|
|
123
|
+
const source = MSVC_SOURCE.has(extension)
|
|
124
|
+
const alwaysLf = ALWAYS_LF.has(extension) || NO_EXTENSION_LF.has(path.basename(file))
|
|
125
|
+
const issues = []
|
|
126
|
+
const add = (kind, message) => issues.push({ path: rel, kind, message })
|
|
127
|
+
|
|
128
|
+
let buffer
|
|
129
|
+
try {
|
|
130
|
+
buffer = readFileSync(file)
|
|
131
|
+
} catch (error) {
|
|
132
|
+
add('read', `无法读取(${error.message})`)
|
|
133
|
+
return { rel, issues }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const bom = bomOf(buffer)
|
|
137
|
+
if (bom === 'UTF-8') {
|
|
138
|
+
if (profile !== 'msvc' || !source) {
|
|
139
|
+
add('bom', profile === 'msvc' && !source ? '只有 MSVC 编译的 C/C++ 源码可以带 BOM' : '不应带 BOM')
|
|
140
|
+
}
|
|
141
|
+
} else if (bom) {
|
|
142
|
+
add('bom', `发现 ${bom} BOM;只接受 UTF-8`)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (bom && bom !== 'UTF-8') {
|
|
146
|
+
return { rel, issues }
|
|
147
|
+
}
|
|
148
|
+
if (isBinary(buffer)) {
|
|
149
|
+
return { rel, issues }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let text
|
|
153
|
+
try {
|
|
154
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(buffer)
|
|
155
|
+
} catch {
|
|
156
|
+
add('utf8', '不是合法 UTF-8')
|
|
157
|
+
return { rel, issues }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const body = text.startsWith('\uFEFF') ? text.slice(1) : text
|
|
161
|
+
if (body.includes('\uFEFF')) {
|
|
162
|
+
add('bom', '文件中间含 U+FEFF')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (buffer.length > 0 && !body.endsWith('\n')) {
|
|
166
|
+
add('newline', '缺少末尾换行')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const crlf = (body.match(/\r\n/g) ?? []).length
|
|
170
|
+
const lf = (body.match(/(?<!\r)\n/g) ?? []).length
|
|
171
|
+
const cr = (body.match(/\r(?!\n)/g) ?? []).length
|
|
172
|
+
|
|
173
|
+
if (CRLF_REQUIRED.has(extension)) {
|
|
174
|
+
if (crlf === 0 || lf > 0 || cr > 0) add('eol', '应为 CRLF')
|
|
175
|
+
} else if (profile === 'msvc' && source && !alwaysLf) {
|
|
176
|
+
if ((crlf > 0 && lf > 0) || cr > 0) add('eol', '不要混用 CRLF 与 LF')
|
|
177
|
+
} else if (crlf > 0 || cr > 0) {
|
|
178
|
+
add('eol', '应为 LF')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { rel, issues }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function checkEncoding(root, profile = 'portable') {
|
|
185
|
+
const files = listFiles(root)
|
|
186
|
+
const results = []
|
|
187
|
+
for (const file of files) {
|
|
188
|
+
results.push(inspect(file, root, profile))
|
|
189
|
+
}
|
|
190
|
+
const issues = results.flatMap((result) => result.issues)
|
|
191
|
+
const counts = { files: results.length, issues: issues.length, bom: 0, eol: 0, utf8: 0, newline: 0, read: 0 }
|
|
192
|
+
for (const issue of issues) counts[issue.kind] = (counts[issue.kind] ?? 0) + 1
|
|
193
|
+
return { root, profile, files: results.length, issues, counts }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function main() {
|
|
197
|
+
let options
|
|
198
|
+
try {
|
|
199
|
+
options = parseArgs(process.argv.slice(2))
|
|
200
|
+
} catch (error) {
|
|
201
|
+
return usage(error.message)
|
|
202
|
+
}
|
|
203
|
+
if (options.help) {
|
|
204
|
+
process.stdout.write('检查 Git 工作树的 UTF-8/BOM、行尾与末尾换行;默认 portable。\n')
|
|
205
|
+
return EXIT.ok
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const base = options.root ? path.resolve(options.root) : process.cwd()
|
|
209
|
+
if (!existsSync(base)) return usage(`根目录不存在:${base}`)
|
|
210
|
+
const root = gitRoot(base)
|
|
211
|
+
if (!root) return usage(`不是 Git 工作树:${base}`)
|
|
212
|
+
|
|
213
|
+
let report
|
|
214
|
+
try {
|
|
215
|
+
report = checkEncoding(root, options.profile)
|
|
216
|
+
} catch (error) {
|
|
217
|
+
process.stderr.write(`错误:${error.message}\n`)
|
|
218
|
+
return EXIT.usage
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (options.json) {
|
|
222
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`)
|
|
223
|
+
} else if (!options.quiet || report.issues.length > 0) {
|
|
224
|
+
if (report.issues.length === 0) {
|
|
225
|
+
process.stdout.write(`编码检查:${report.files} 个文件,未发现问题(profile ${report.profile})。\n`)
|
|
226
|
+
} else {
|
|
227
|
+
process.stdout.write(`编码检查:${report.files} 个文件,发现 ${report.issues.length} 个问题(profile ${report.profile})。\n`)
|
|
228
|
+
for (const issue of report.issues) {
|
|
229
|
+
process.stdout.write(` ${issue.path}: [${issue.kind}] ${issue.message}\n`)
|
|
230
|
+
}
|
|
231
|
+
process.stdout.write(
|
|
232
|
+
`汇总:BOM ${report.counts.bom},行尾 ${report.counts.eol},UTF-8 ${report.counts.utf8},末尾换行 ${report.counts.newline}\n`,
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return report.issues.length === 0 ? EXIT.ok : EXIT.issues
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
240
|
+
process.exitCode = main()
|
|
241
|
+
}
|
|
@@ -15,6 +15,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
|
15
15
|
export const HOSTS = ['windows', 'linux', 'macos']
|
|
16
16
|
export const BUILD_HOSTS = ['native', 'wsl']
|
|
17
17
|
export const COMPILERS = ['clang', 'gcc']
|
|
18
|
+
export const STANDARDS = ['c++11', 'c++14', 'c++17', 'c++20', 'c++23']
|
|
19
|
+
export const DEFAULT_STANDARD = 'c++23'
|
|
18
20
|
const PROJECT_SKILL_DIR = path.join('.agents', 'skills', 'cpp-workflow-project')
|
|
19
21
|
const MAX_ASCENT = 64
|
|
20
22
|
|
|
@@ -34,8 +36,9 @@ function normalize(value) {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
|
-
* Validate the three axes and their combination.
|
|
38
|
-
*
|
|
39
|
+
* Validate the three environment axes, the language standard, and their combination.
|
|
40
|
+
* Pure: no filesystem or PATH.
|
|
41
|
+
* @returns {{ok: boolean, issues: string[], notes: string[], axes: {host?: string, build_host?: string, compiler?: string, standard?: string}}}
|
|
39
42
|
*/
|
|
40
43
|
export function validateEnvironment(value) {
|
|
41
44
|
const issues = []
|
|
@@ -53,6 +56,18 @@ export function validateEnvironment(value) {
|
|
|
53
56
|
host: normalize(value.host),
|
|
54
57
|
build_host: normalize(value.build_host),
|
|
55
58
|
compiler: normalize(value.compiler),
|
|
59
|
+
standard: normalize(value.standard),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (axes.standard === undefined || axes.standard === null || axes.standard === '') {
|
|
63
|
+
axes.standard = DEFAULT_STANDARD
|
|
64
|
+
notes.push(`standard 未声明或为空;按默认 ${DEFAULT_STANDARD} 处理,请在 environment.json 中显式写出`)
|
|
65
|
+
} else if (Array.isArray(axes.standard)) {
|
|
66
|
+
issues.push(`standard 只能声明一个值,当前是数组 [${axes.standard.join(', ')}]`)
|
|
67
|
+
} else if (typeof axes.standard !== 'string') {
|
|
68
|
+
issues.push(`standard 必须是字符串,当前是 ${typeof axes.standard}`)
|
|
69
|
+
} else if (!STANDARDS.includes(axes.standard)) {
|
|
70
|
+
issues.push(`standard 的值 "${axes.standard}" 非法;允许:${STANDARDS.join(' / ')}`)
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
for (const [key, allowed] of [
|
|
@@ -85,12 +100,12 @@ export function validateEnvironment(value) {
|
|
|
85
100
|
}
|
|
86
101
|
|
|
87
102
|
if (axes.host === 'windows' && axes.build_host === 'native') {
|
|
88
|
-
|
|
103
|
+
issues.push('本版本不能在 Windows 原生壳里构建')
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
for (const key of Object.keys(value)) {
|
|
92
|
-
if (!['host', 'build_host', 'compiler'].includes(key)) {
|
|
93
|
-
notes.push(`忽略未知键 "${key}";环境只认 host / build_host / compiler`)
|
|
107
|
+
if (!['host', 'build_host', 'compiler', 'standard'].includes(key)) {
|
|
108
|
+
notes.push(`忽略未知键 "${key}";环境只认 host / build_host / compiler / standard`)
|
|
94
109
|
}
|
|
95
110
|
}
|
|
96
111
|
|
|
@@ -245,25 +260,10 @@ export function checkLive(declaration, options = {}) {
|
|
|
245
260
|
}
|
|
246
261
|
const { host, build_host: buildHost, compiler } = validation.axes
|
|
247
262
|
|
|
248
|
-
if (host === 'windows' && buildHost === 'native') {
|
|
249
|
-
return {
|
|
250
|
-
ok: false,
|
|
251
|
-
issues: [
|
|
252
|
-
'Windows 原生构建缺少 POSIX 壳;本版本没有 PowerShell 构建脚本。缺的是壳,不是编译器。请改用 WSL 并把 build_host 改成 wsl,或补一个 PowerShell 版本。',
|
|
253
|
-
],
|
|
254
|
-
notes: validation.notes,
|
|
255
|
-
checks,
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
263
|
if (buildHost === 'wsl') {
|
|
260
264
|
if (platform !== 'linux' || !isWsl(env)) {
|
|
261
265
|
issues.push('声明 build_host=wsl,但当前进程不在 WSL 内;请在 WSL 里运行 --live')
|
|
262
266
|
}
|
|
263
|
-
} else if (host === 'windows') {
|
|
264
|
-
if (platform !== 'win32') {
|
|
265
|
-
issues.push(`声明 host=windows + build_host=native,但当前平台是 ${platform};请在 Windows 上运行 --live`)
|
|
266
|
-
}
|
|
267
267
|
} else if (host === 'linux' && platform !== 'linux') {
|
|
268
268
|
issues.push(`声明 host=linux,但当前平台是 ${platform};请在 Linux 上运行 --live`)
|
|
269
269
|
} else if (host === 'macos' && platform !== 'darwin') {
|
|
@@ -358,7 +358,7 @@ export function checkLive(declaration, options = {}) {
|
|
|
358
358
|
['clang-tidy', tidyVersion],
|
|
359
359
|
]) {
|
|
360
360
|
if (version && version.major < 16) {
|
|
361
|
-
issues.push(`${role} ${versionText(version)}
|
|
361
|
+
issues.push(`${role} ${versionText(version)} 过旧;本工作流要求 >= 16`)
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
364
|
|
|
@@ -16,28 +16,29 @@ build/
|
|
|
16
16
|
|
|
17
17
|
## 通用情况
|
|
18
18
|
|
|
19
|
-
先读 `environment.json
|
|
19
|
+
先读 `environment.json`。源文件清单来自 `developing-cpp` 的定边界和定实现;定构建只把它们写进 target,不重新决定库与程序的边界。三个环境轴加语言标准分别表示:
|
|
20
20
|
|
|
21
21
|
| 轴 | 取值 | 含义 |
|
|
22
22
|
| --- | --- | --- |
|
|
23
23
|
| `host` | `windows` / `linux` / `macos` | 编辑器与代理壳所在系统 |
|
|
24
|
-
| `build_host` | `native` / `wsl` | 执行编译和运行产物的环境;`native` 表示 `host` 本机,`wsl` 表示 Windows 的 WSL,且只允许 `host=windows` |
|
|
24
|
+
| `build_host` | `native` / `wsl` | 执行编译和运行产物的环境;`native` 表示 `host` 本机,`wsl` 表示 Windows 的 WSL,且只允许 `host=windows`;`windows` + `native` 本版本不支持 |
|
|
25
25
|
| `compiler` | `clang` / `gcc` | 用哪个编译器;一次只配一个 |
|
|
26
|
+
| `standard` | `c++11`–`c++23` | 一个工程一个语言标准;新工程默认 `c++23`,已有工程按原声明 |
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
环境轴为空、`standard` 非法或组合非法(如 `host=linux` + `build_host=wsl`、`host=windows` + `build_host=native`)时,停在定构建,先让用户把 `environment.json` 填对。不要替用户选 WSL、Clang 或构建目录。`windows` + `native` 只给一句「本版本不能在 Windows 原生壳里构建」,不要建议改 WSL、不要提出补 PowerShell 脚本。
|
|
28
29
|
|
|
29
30
|
构建规则:
|
|
30
31
|
|
|
31
|
-
- CMake 最低 3.20;`project(... LANGUAGES CXX)`;只有确有 C 源时才写 `LANGUAGES C CXX
|
|
32
|
+
- CMake 最低 3.20;`project(... LANGUAGES CXX)`;只有确有 C 源时才写 `LANGUAGES C CXX`。新工程默认标准 C++23;已有工程按 `environment.json` 的 `standard` 或现有 target 声明,不擅自升降。
|
|
32
33
|
- 生成器固定单配置 Ninja,不用 Ninja Multi-Config:同一源文件会写多条命令,clangd 默认只取第一条,容易读错数据库(clangd#2026);构建目录固定 `build/clang` 或 `build/gcc`。
|
|
33
34
|
- 一个 configure 只用一个编译器:`-DCMAKE_CXX_COMPILER=clang++` 或 `g++`。
|
|
34
35
|
- 程序 target 固定叫 `app`;库 target 用组件名,程序链接库。库与程序分开,不把 `main()` 编进库。
|
|
35
36
|
- 用 `target_include_directories`、`target_compile_features`、`target_link_libraries` 表达依赖;禁止全局 `include_directories()`、`add_definitions()`、`CMAKE_CXX_FLAGS` 污染所有 target。
|
|
36
|
-
- 库的公共 include 根用 `PUBLIC`;实现私有头用 `PRIVATE`;测试 target
|
|
37
|
+
- 库的公共 include 根用 `PUBLIC`;实现私有头用 `PRIVATE`;测试 target 只链接被测库的公共接口。头文件按 Google 规则从仓库根写完整路径,构建时把仓库根加入搜索路径,不要改成组件的 `include/` 根后再写裸文件名。
|
|
37
38
|
- `set(CMAKE_EXPORT_COMPILE_COMMANDS ON)`;Ninja 生成器下会产出 `compile_commands.json`。
|
|
38
39
|
- 输出目录统一:可执行文件到 `build/<compiler>/bin`,库到 `build/<compiler>/lib`;不要用源码目录当输出目录。
|
|
39
40
|
- 开启 `CMAKE_EXPORT_COMPILE_COMMANDS` 后,把 `build/<compiler>/compile_commands.json` 符号链接到 `build/compile_commands.json`,让 clangd 默认能找到当前数据库。本插件固定产出 `build/<compiler>/` 并在 `build/` 下维护链接,不生成 `out/build/`,也不把两套构建根混用。
|
|
40
|
-
- 目标标准写在 target 上:`target_compile_features(app PRIVATE
|
|
41
|
+
- 目标标准写在 target 上:`target_compile_features(app PRIVATE cxx_std_23)`;库用 `PUBLIC` 传播标准。再加 `set_target_properties(... CXX_STANDARD 23 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)`:meta-feature 只保证“至少”,`CXX_STANDARD_REQUIRED` 防 decay,`CXX_EXTENSIONS OFF` 把 `-std=gnu++23` 收成 `-std=c++23`。configure 后读 `compile_commands.json` 的最后一个 `-std=`,必须与 `environment.json` 的 `standard` 一致。
|
|
41
42
|
- 工程确有 C 源时才写 `LANGUAGES C CXX`,并且 `CMAKE_C_COMPILER` 与 `CMAKE_CXX_COMPILER` 必须来自同一套编译器(`clang`/`clang++` 或 `gcc`/`g++`);没有 C 源时只启用 CXX。
|
|
42
43
|
- 不写 `file(GLOB ...)`;源文件显式列出,新增文件时改 CMake。
|
|
43
44
|
- 不加 `-march=native`、`-ffast-math` 等改变语义或可移植性的选项;警告选项按团队约定统一加。
|
|
@@ -59,11 +60,11 @@ GCC 模式把目录和编译器换成 `build/gcc`、`g++`。工程确有 C 源
|
|
|
59
60
|
|
|
60
61
|
## 情况
|
|
61
62
|
|
|
62
|
-
**新工程**:从 `assets/templates/projects/` 选形态:`single` 单文件、`multi` 多文件单程序、`static-library` 静态库 + 程序、`shared-library` 动态库 + 程序。只复制目标工程缺的文件;已有的 `CMakeLists.txt`、`.clang-*`、源码不覆盖。模板里的 `{{PROJECT_NAME}}`
|
|
63
|
+
**新工程**:从 `assets/templates/projects/` 选形态:`single` 单文件、`multi` 多文件单程序、`static-library` 静态库 + 程序、`shared-library` 动态库 + 程序。只复制目标工程缺的文件;已有的 `CMakeLists.txt`、`.clang-*`、源码不覆盖。模板里的 `{{PROJECT_NAME}}` 换成真实工程名,默认标准 C++23;四套模板已按 `__cplusplus` 条件使用 `std::format`,把 `standard`、`cxx_std_*`、`CXX_STANDARD` 和 `.clang-format` 的解析档位一起降到 C++11–C++20 后仍应能构建,降完跑 `references/standards/README.md` 的五档矩阵。模板源码是 `.cc`,头文件用路径式 `#define` 守卫;新增文件沿用 `.cc` / `.h`,已有工程保持原有扩展名。
|
|
63
64
|
|
|
64
65
|
**已有工程**:先读现有 `CMakeLists.txt`,找出全局设置、GLOB、输出目录和 target 命名;只改与当前任务相关的一处。大改前先让通过检查的项目入口跑通,保留可回退点。
|
|
65
66
|
|
|
66
|
-
**库 + 程序**:库 target 的 `PUBLIC` include
|
|
67
|
+
**库 + 程序**:库 target 的 `PUBLIC` include 根按项目约定设置;本工作流把仓库根作为源码根,程序按 `#include "component/include/name.h"` 包含公共头,不直接包含库的 `src/`。静态库与动态库的接口相同;动态库额外设置运行时库路径。
|
|
67
68
|
|
|
68
69
|
**测试**:`enable_testing()` 放在顶层,`add_subdirectory(tests)` 在 `add_subdirectory(<component>)` 之后;测试 target 用 `add_test(NAME ... COMMAND ...)` 注册。没有测试时不要为了「看起来完整」加空测试。
|
|
69
70
|
|
|
@@ -74,3 +75,4 @@ GCC 模式把目录和编译器换成 `build/gcc`、`g++`。工程确有 C 源
|
|
|
74
75
|
- 全局 include、GLOB、把 `main()` 编进库:`examples/cmake.md`。
|
|
75
76
|
- 正确的最小库 + 程序:`examples/cmake.md`「最小可用形状」。
|
|
76
77
|
- target 与目录的完整说明:`references/cmake.md`。
|
|
78
|
+
- 五档标准的下限、降档替换和工具映射:`references/standards/README.md`。
|