├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── Makefile ├── README.md ├── answer ├── CMakeLists.txt ├── include │ └── answer │ │ └── answer.hpp └── tests │ ├── CMakeLists.txt │ └── test_check_the_answer.cpp ├── assets ├── ccmake.png ├── dependency-graph.drawio └── dependency-graph.svg ├── curl_wrapper ├── CMakeLists.txt ├── curl_wrapper.cpp └── include │ └── curl_wrapper │ └── curl_wrapper.hpp ├── main.cpp └── wolfram ├── CMakeLists.txt ├── alpha.cpp └── include └── wolfram └── alpha.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | Standard: Latest 3 | IndentWidth: 4 4 | ColumnLimit: 120 5 | AccessModifierOffset: -4 6 | NamespaceIndentation: All 7 | IndentCaseLabels: false 8 | BinPackArguments: false 9 | SortUsingDeclarations: false 10 | BreakBeforeBinaryOperators: NonAssignment 11 | SpacesBeforeTrailingComments: 1 12 | DerivePointerAlignment: true 13 | PointerAlignment: Right 14 | AlignTrailingComments: false 15 | SpacesInContainerLiterals: false 16 | AllowShortFunctionsOnASingleLine: None 17 | IncludeBlocks: Preserve 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(answer) 3 | 4 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 5 | include(CTest) 6 | endif() 7 | 8 | set(CMAKE_CXX_STANDARD 11) 9 | 10 | add_subdirectory(answer) 11 | 12 | add_subdirectory(curl_wrapper) 13 | add_subdirectory(wolfram) 14 | 15 | add_executable(answer_app main.cpp) 16 | target_link_libraries(answer_app libanswer) 17 | 18 | #[[ 19 | 使用如下命令构建本项目: 20 | 21 | cmake -B build -DWOLFRAM_APPID=YAPKJY-8XT9VEYPX9 # 生成构建目录(建议更换 WOLFRAM_APPID) 22 | cmake --build build # 执行构建 23 | ctest --test-dir build -R "^answer." # 运行 libanswer 测试 24 | ./build/answer_app # 运行 answer_app 程序 25 | #]] 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | WOLFRAM_APPID := 2 | 3 | .PHONY: build 4 | build: configure 5 | cmake --build build 6 | 7 | .PHONY: configure 8 | configure: 9 | cmake -B build -DWOLFRAM_APPID=${WOLFRAM_APPID} 10 | 11 | .PHONY: run 12 | run: 13 | ./build/answer_app 14 | 15 | .PHONY: test 16 | test: 17 | ctest --test-dir build -R "^answer." 18 | 19 | .PHONY: clean 20 | clean: 21 | rm -rf build 22 | 23 | # 24 | # 可以使用 Make 来更方便地调用 CMake 命令: 25 | # 26 | # make build WOLFRAM_APPID=xxx 27 | # make test 28 | # make run 29 | # make clean 30 | # 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern CMake By Example 2 | 3 | ## 知识点总结 4 | 5 | ### [`0_helloworld`](../../tree/0_helloworld) 6 | 7 | `Makefile` 基本格式: 8 | 9 | ```makefile 10 | name: dependencies 11 | commands 12 | ``` 13 | 14 | 例如: 15 | 16 | ```makefile 17 | hello: main.cpp 18 | $(CXX) -o hello main.cpp # CXX 是 Make 的内置变量 19 | echo "OK" 20 | ``` 21 | 22 | 构建 & 运行命令: 23 | 24 | ```shell 25 | $ make hello 26 | $ ./hello 27 | ``` 28 | 29 | ### [`1_helloworld`](../../tree/1_helloworld) 30 | 31 | 变量赋值: 32 | 33 | ```makefile 34 | CC := clang 35 | CXX := clang++ # 可通过 make CXX=g++ 形式覆盖 36 | 37 | objects := main.o 38 | ``` 39 | 40 | 使用变量: 41 | 42 | ```makefile 43 | hello: $(objects) 44 | $(CXX) -o $@ $(objects) # $@ 是自动变量,表示 target 名 45 | 46 | main.o: main.cpp 47 | $(CXX) -c main.cpp 48 | ``` 49 | 50 | ### [`2_ask_for_answer`](../../tree/2_ask_for_answer) 51 | 52 | Make 可以自动推断 `.o` 文件需要用什么命令从什么文件编译: 53 | 54 | ```makefile 55 | objects := main.o answer.o 56 | 57 | answer: $(objects) 58 | $(CXX) -o $@ $(objects) 59 | 60 | main.o: answer.hpp 61 | answer.o: answer.hpp 62 | ``` 63 | 64 | ### [`4_switch_to_cmake`](../../tree/4_switch_to_cmake) 65 | 66 | `CMakeLists.txt` 基本格式: 67 | 68 | ```cmake 69 | cmake_minimum_required(VERSION 3.9) 70 | project(answer) 71 | 72 | add_executable(answer main.cpp answer.cpp) 73 | ``` 74 | 75 | 生成 & 构建 & 运行命令: 76 | 77 | ```shell 78 | cmake -B build # 生成构建目录,-B 指定生成的构建系统代码放在 build 目录 79 | cmake --build build # 执行构建 80 | ./build/answer # 运行 answer 程序 81 | ``` 82 | 83 | ### [`5_split_library`](../../tree/5_split_library) 84 | 85 | 项目中可以复用的部分可以拆成 library: 86 | 87 | ```cmake 88 | add_library(libanswer STATIC answer.cpp) 89 | ``` 90 | 91 | `STATIC` 表示 `libanswer` 是个静态库。 92 | 93 | 使用(链接)library: 94 | 95 | ```cmake 96 | add_executable(answer main.cpp) 97 | target_link_libraries(answer libanswer) 98 | ``` 99 | 100 | ### [`6_subdirectory`](../../tree/6_subdirectory) 101 | 102 | 功能独立的模块可以放到单独的子目录: 103 | 104 | ``` 105 | . 106 | ├── answer 107 | │ ├── answer.cpp 108 | │ ├── CMakeLists.txt 109 | │ └── include 110 | │ └── answer 111 | │ └── answer.hpp 112 | ├── CMakeLists.txt 113 | └── main.cpp 114 | ``` 115 | 116 | ```cmake 117 | # CMakeLists.txt 118 | add_subdirectory(answer) 119 | 120 | add_executable(answer_app main.cpp) 121 | target_link_libraries(answer_app libanswer) # libanswer 在 answer 子目录中定义 122 | ``` 123 | 124 | ```cmake 125 | # answer/CMakeLists.txt 126 | add_library(libanswer STATIC answer.cpp) 127 | target_include_directories(libanswer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) 128 | ``` 129 | 130 | `CMAKE_CURRENT_SOURCE_DIR` 是 CMake 内置变量,表示当前 `CMakeLists.txt` 文件所在目录,此处其实可以省略。 131 | 132 | `target_include_directories` 的 `PUBLIC` 参数表示这个包含目录是 `libanswer` 的公开接口一部分,链接 `libanswer` 的 target 可以 `#include` 该目录中的文件。 133 | 134 | ### [`7_use_libcurl`](../../tree/7_use_libcurl) 135 | 136 | 系统中安装的第三方库可以通过 `find_package` 找到,像之前的 `libanswer` 一样链接: 137 | 138 | ```cmake 139 | find_package(CURL REQUIRED) 140 | target_link_libraries(libanswer PRIVATE CURL::libcurl) 141 | ``` 142 | 143 | `REQUIRED` 表示 `CURL` 是必须的依赖,如果没有找到,会报错。 144 | 145 | `PRIVATE` 表示“链接 `CURL::libcurl`”是 `libanswer` 的私有内容,不应对使用 `libanswer` 的 target 产生影响,注意和 `PUBLIC` 的区别。 146 | 147 | `CURL` 和 `CURL::libcurl` 是约定的名字,其它第三方库的包名和 library 名可在网上查。 148 | 149 | ### [`8_link_libs_in_same_root`](../../tree/8_link_libs_in_same_root) 150 | 151 | 可以链接同一项目中其它子目录中定义的 library: 152 | 153 | ```cmake 154 | # CMakeLists.txt 155 | add_subdirectory(answer) 156 | add_subdirectory(curl_wrapper) 157 | add_subdirectory(wolfram) 158 | ``` 159 | 160 | ```cmake 161 | # answer/CMakeLists.txt 162 | add_library(libanswer STATIC answer.cpp) 163 | target_include_directories(libanswer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) 164 | target_link_libraries(libanswer PRIVATE wolfram) 165 | ``` 166 | 167 | ```cmake 168 | # wolfram/CMakeLists.txt 169 | add_library(wolfram STATIC alpha.cpp) 170 | target_include_directories(wolfram PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) 171 | target_link_libraries(wolfram PRIVATE curl_wrapper) 172 | ``` 173 | 174 | ```cmake 175 | # curl_wrapper/CMakeLists.txt 176 | find_package(CURL REQUIRED) 177 | add_library(curl_wrapper STATIC curl_wrapper.cpp) 178 | target_include_directories(curl_wrapper 179 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) 180 | target_link_libraries(curl_wrapper PRIVATE CURL::libcurl) 181 | ``` 182 | 183 | 搞清楚项目各模块间的依赖关系很重要: 184 | 185 | ![](assets/dependency-graph.svg) 186 | 187 | ### [`9_cache_string`](../../tree/9_cache_string) 188 | 189 | #### Cache 变量 190 | 191 | 私密的 App ID、API Key 等不应该直接放在代码里,应该做成可配置的项,从外部传入。除此之外还可通过可配置的变量来控制程序的特性、行为等。在 CMake 中,通过 cache 变量实现: 192 | 193 | ```cmake 194 | set(WOLFRAM_APPID "" CACHE STRING "WolframAlpha APPID") 195 | ``` 196 | 197 | `set` 第一个参数是变量名,第二个参数是默认值,第三个参数 `CACHE` 表示是 cache 变量,第四个参数是变量类型,第五个参数是变量描述。 198 | 199 | `BOOL` 类型的 cache 变量还有另一种写法: 200 | 201 | ```cmake 202 | set(ENABLE_CACHE OFF CACHE BOOL "Enable request cache") 203 | option(ENABLE_CACHE "Enable request cache" OFF) # 和上面基本等价 204 | ``` 205 | 206 | Cache 变量的值可在命令行调用 `cmake` 时通过 `-D` 传入: 207 | 208 | ```shell 209 | cmake -B build -DWOLFRAM_APPID=xxx 210 | ``` 211 | 212 | 也可用 `ccmake` 在 TUI 中修改: 213 | 214 | ![](assets/ccmake.png) 215 | 216 | #### `target_compile_definitions` 217 | 218 | 要让 C++ 代码能够拿到 CMake 中的变量,可添加编译时宏定义: 219 | 220 | ```cmake 221 | target_compile_definitions(libanswer PRIVATE WOLFRAM_APPID="${WOLFRAM_APPID}") 222 | ``` 223 | 224 | 这会给 C++ 代码提供一个 `WOLFRAM_APPID` 宏。 225 | 226 | ### [`10_interface_library`](../../tree/10_interface_library) 227 | 228 | Header-only 的库可以添加为 `INTERFACE` 类型的 library: 229 | 230 | ```cmake 231 | add_library(libanswer INTERFACE) 232 | target_include_directories(libanswer INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) 233 | target_compile_definitions(libanswer INTERFACE WOLFRAM_APPID="${WOLFRAM_APPID}") 234 | target_link_libraries(libanswer INTERFACE wolfram) 235 | ``` 236 | 237 | 通过 `target_xxx` 给 `INTERFACE` library 添加属性都要用 `INTERFACE`。 238 | 239 | ### [`11_target_compile_features`](../../tree/11_target_compile_features) 240 | 241 | 可以针对 target 要求编译 feature(即指定要使用 C/C++ 的什么特性): 242 | 243 | ```cmake 244 | target_compile_features(libanswer INTERFACE cxx_std_20) 245 | ``` 246 | 247 | 和直接设置 `CMAKE_CXX_STANDARD` 的区别: 248 | 249 | 1. `CMAKE_CXX_STANDARD` 会应用于所有能看到这个变量的 target,而 `target_compile_features` 只应用于单个 target 250 | 2. `target_compile_features` 可以指定更细粒度的 C++ 特性,例如 `cxx_auto_type`、`cxx_lambda` 等。 251 | 252 | ### [`12_testing`](../../tree/12_testing) 253 | 254 | #### CTest 255 | 256 | 要使用 CTest 运行 CMake 项目的测试程序,需要在 `CMakeLists.txt` 添加一些内容: 257 | 258 | ```cmake 259 | # CMakeLists.txt 260 | cmake_minimum_required(VERSION 3.14) # 提高了 CMake 版本要求 261 | project(answer) 262 | 263 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 264 | include(CTest) 265 | endif() 266 | ``` 267 | 268 | ```cmake 269 | # answer/CMakeLists.txt 270 | if(BUILD_TESTING) 271 | add_subdirectory(tests) 272 | endif() 273 | ``` 274 | 275 | ```cmake 276 | # answer/tests/CMakeLists.txt 277 | add_executable(test_some_func test_some_func.cpp) 278 | add_test(NAME answer.test_some_func COMMAND test_some_func) 279 | ``` 280 | 281 | `BUILD_TESTING` 是 `include(CTest)` 之后添加的一个 cache 变量,默认 `ON`,可通过 `-D` 参数修改。 282 | 283 | 在命令行运行所有 `answer` 模块的测试程序: 284 | 285 | ```shell 286 | $ ctest --test-dir build -R "^answer." 287 | ``` 288 | 289 | #### FetchContent 290 | 291 | 除了使用 `find_package` 找到系统中安装的第三方库,也可通过 CMake 3.11 新增的 FetchContent 功能下载使用第三方库: 292 | 293 | ```cmake 294 | include(FetchContent) 295 | 296 | FetchContent_Declare( 297 | catch2 # 建议使用全小写 298 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 299 | GIT_TAG v3.0.0-preview3) 300 | 301 | FetchContent_MakeAvailable(catch2) 302 | 303 | target_link_libraries(test_some_func} PRIVATE Catch2::Catch2WithMain) 304 | ``` 305 | 306 | `FetchContent_MakeAvailable` 要求 CMake 3.14,如果要支持更旧版本,或者需要更细粒度的控制,可以使用如下替代: 307 | 308 | ```cmake 309 | FetchContent_GetProperties(catch2) 310 | if(NOT catch2_POPULATED) 311 | FetchContent_Populate(catch2) 312 | add_subdirectory(${catch2_SOURCE_DIR} ${catch2_BINARY_DIR}) 313 | endif() 314 | ``` 315 | 316 | #### Macro & Function 317 | 318 | 当需要多次重复同一段 CMake 脚本时,可以定义宏或函数: 319 | 320 | ```cmake 321 | macro(answer_add_test TEST_NAME) 322 | add_executable(${TEST_NAME} ${ARGN}) # ${ARGN} 类似于 C/C++ 中的 __VA_ARGS__ 323 | add_test(NAME answer.${TEST_NAME} COMMAND ${TEST_NAME}) 324 | target_link_libraries(${TEST_NAME} PRIVATE libanswer) 325 | target_link_libraries(${TEST_NAME} PRIVATE Catch2::Catch2WithMain) 326 | endmacro() 327 | 328 | answer_add_test(test_check_the_answer test_check_the_answer.cpp) 329 | answer_add_test(test_another_function test_another_function.cpp) 330 | ``` 331 | 332 | 宏和函数的区别与 C/C++ 中的宏和函数的区别相似。 333 | 334 | ### [`13_back_to_makefile`](../../tree/13_back_to_makefile) 335 | 336 | 调用 CMake 命令往往需要传很多参数,并且 CMake 生成、CMake 构建、CTest 的命令都不太相同,要获得比较统一的使用体验,可以在外面包一层 Make: 337 | 338 | ```makefile 339 | WOLFRAM_APPID := 340 | 341 | .PHONY: build configure run test clean 342 | 343 | build: configure 344 | cmake --build build 345 | 346 | configure: 347 | cmake -B build -DWOLFRAM_APPID=${WOLFRAM_APPID} 348 | 349 | run: 350 | ./build/answer_app 351 | 352 | test: 353 | ctest --test-dir build -R "^answer." 354 | 355 | clean: 356 | rm -rf build 357 | ``` 358 | 359 | 从而方便在命令行调用: 360 | 361 | ```shell 362 | $ make build WOLFRAM_APPID=xxx 363 | $ make test 364 | $ make run 365 | $ make clean 366 | ``` 367 | 368 | ### 补充 369 | 370 | CMake 是代码,应该格式化,格式化工具:。 371 | 372 | ## 直播时的 Q&A 373 | 374 | 由于直播时候录屏里没有录到腾讯会议的聊天记录窗口,导致看回放时看不到原问题,这里列出一下。 375 | 376 | ### CMake 是怎么找到 `.cpp` 文件依赖的头文件的? 377 | 378 | 它没有自己去找 `.cpp` 文件的依赖,而是在生成的 `Makefile` 里面,使用 GCC 等编译器的 `-M` 参数生成 `.d` 依赖文件,这个在其它使用 Make 管理构建的项目里应该也是常见用法。 379 | 380 | ### 模块依赖图是人工画的还是自动生成的? 381 | 382 | 是人工画的,用的是 VSCode 插件 [Draw.io Integration](https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio)。 383 | 384 | ### 在 `CMakeLists.txt` 里定义变量并检查要求不为空,会导致 VSCode 乱报错吗? 385 | 386 | VSCode 装了 [CMake Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) 插件的情况下,修改 `CMakeLists.txt` 后它会自动执行 CMake Configure,如果有要求非空的 cache 变量,它会报错,这时候直接去修改 `build/CMakeCache.txt`,给对应 cache 变量赋值即可。 387 | 388 | 也可以手动运行一次 `cmake` 命令,用 `-D` 参数传入变量,此后只要不删除 `build/CMakeCache.txt`,就不会再报错。 389 | 390 | ### FetchContent 每次编译都要重新编译一遍依赖库吗? 391 | 392 | 不会,`cmake --build build` 是会增量编译的。另外只要不删除 `build/_deps`,即使删了 `build/CMakeCache.txt`,之后重新 configure 也不会重新下载依赖。 393 | 394 | ### 能来个 VSCode 插件推荐列表吗? 395 | 396 | 我自己在用的: 397 | 398 |
399 | 400 | 点击展开 401 | 402 | - Better C++ Syntax 403 | - Bookmarks 404 | - C/C++ 405 | - C/C++ GNU Global(适合阅读修改 Linux 内核用) 406 | - CMake 407 | - CMake Tools 408 | - cmake-format 409 | - Code Runner 410 | - crates 411 | - Diff 412 | - Draw.io Integration 413 | - Error Lens 414 | - Even Better TOML 415 | - GitHub Copilot 416 | - GitLens 417 | - Hex Editor 418 | - hexdump for VSCode 419 | - Include Autocomplete 420 | - LaTeX Workshop 421 | - Live Preview 422 | - Markdown All in One 423 | - Markdown Preview Enhanced 424 | - Marp for VS Code 425 | - Rainbow CSV 426 | - Remote - SSH 427 | - Run on Save 428 | - rust-analyzer 429 | - SVG Viewer 430 | - Tabnine 431 | - Task Explorer 432 | - Todo Tree 433 | - WakaTime 434 | 435 |
436 | 437 | ## 扩展阅读 438 | 439 | ### Makefile 440 | 441 | - 跟我一起写 Makefile 442 | - https://seisman.github.io/how-to-write-makefile/introduction.html 443 | 444 | ### CMake 445 | 446 | - Modern CMake 447 | - https://cliutils.gitlab.io/modern-cmake/ 448 | - More Modern CMake 449 | - https://hsf-training.github.io/hsf-training-cmake-webpage/ 450 | - C++Now 2017: Daniel Pfeifer "Effective CMake" 451 | - https://www.youtube.com/watch?v=bsXLMQ6WgIk 452 | - https://github.com/boostcon/cppnow_presentations_2017/blob/master/05-19-2017_friday/effective_cmake__daniel_pfeifer__cppnow_05-19-2017.pdf 453 | - Effective Modern CMake 454 | - https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1 455 | - CMake Documentation 456 | - https://cmake.org/cmake/help/latest/index.html 457 | -------------------------------------------------------------------------------- /answer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(WOLFRAM_APPID 2 | "" 3 | CACHE STRING "WolframAlpha APPID") 4 | 5 | if(WOLFRAM_APPID STREQUAL "") 6 | message(SEND_ERROR "WOLFRAM_APPID must not be empty") 7 | endif() 8 | 9 | add_library(libanswer INTERFACE) 10 | target_include_directories(libanswer 11 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) 12 | target_compile_definitions(libanswer INTERFACE WOLFRAM_APPID="${WOLFRAM_APPID}") 13 | target_compile_features(libanswer INTERFACE cxx_std_20) 14 | target_link_libraries(libanswer INTERFACE wolfram) 15 | 16 | if(BUILD_TESTING) 17 | add_subdirectory(tests) 18 | endif() 19 | -------------------------------------------------------------------------------- /answer/include/answer/answer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace answer { 8 | namespace v1 { 9 | int find_the_ultimate_answer() { 10 | return 42; 11 | } 12 | } // namespace v1 13 | 14 | namespace v2 { 15 | std::string find_the_ultimate_answer() { 16 | return wolfram::simple_query(WOLFRAM_APPID, "what is the ultimate answer?"); 17 | } 18 | 19 | namespace impl { 20 | template 21 | auto to_string(T &&t) { 22 | if constexpr (requires { std::to_string(t); }) { 23 | return std::to_string(std::forward(t)); 24 | } else if constexpr (requires { std::string(t); }) { 25 | return std::string(std::forward(t)); 26 | } 27 | } 28 | } // namespace impl 29 | 30 | template 31 | requires requires(T &&t, U &&u) { 32 | impl::to_string(std::forward(t)); 33 | impl::to_string(std::forward(u)); 34 | } 35 | auto check_the_answer(T &&given, U &&expected) { 36 | return impl::to_string(std::forward(given)) == impl::to_string(std::forward(expected)); 37 | } 38 | } // namespace v2 39 | 40 | using namespace v2; 41 | } // namespace answer 42 | -------------------------------------------------------------------------------- /answer/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare( 4 | catch2 5 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 6 | GIT_TAG v3.0.0-preview3) 7 | 8 | FetchContent_MakeAvailable(catch2) 9 | 10 | macro(answer_add_test TEST_NAME) 11 | add_executable(${TEST_NAME} ${ARGN}) 12 | add_test(NAME answer.${TEST_NAME} COMMAND ${TEST_NAME}) 13 | target_link_libraries(${TEST_NAME} PRIVATE libanswer) 14 | target_link_libraries(${TEST_NAME} PRIVATE Catch2::Catch2WithMain) 15 | endmacro() 16 | 17 | answer_add_test(test_check_the_answer test_check_the_answer.cpp) 18 | -------------------------------------------------------------------------------- /answer/tests/test_check_the_answer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace answer; 6 | 7 | // 使用 Catch2 编写测试用例 8 | 9 | TEST_CASE("Can compare string and string", "[check_the_answer]") { 10 | REQUIRE(check_the_answer("Hello", "Hello") == true); 11 | REQUIRE(check_the_answer("Hello", "world") == false); 12 | REQUIRE(check_the_answer("13", std::string("13")) == true); 13 | } 14 | 15 | TEST_CASE("Can compare string and integer", "[check_the_answer]") { 16 | REQUIRE(check_the_answer("13", 13) == true); 17 | REQUIRE(check_the_answer("13", 14) == false); 18 | REQUIRE(check_the_answer(13, "13") == true); 19 | REQUIRE(check_the_answer(13, std::string("13")) == true); 20 | REQUIRE(check_the_answer(13, std::string("14")) == false); 21 | } 22 | -------------------------------------------------------------------------------- /assets/ccmake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stdrc/modern-cmake-by-example/03b8e6078a00d3b7cd1a223905989ab0cde3d555/assets/ccmake.png -------------------------------------------------------------------------------- /assets/dependency-graph.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /assets/dependency-graph.svg: -------------------------------------------------------------------------------- 1 |
answer_app
answer_app
libanswer
libanswer
wolfram
wolfram
curl_wrapper
curl_wrapper
CURL::libcurl
CURL::libcurl
maybe other project
maybe other project
maybe other module
maybe other module
maybe other system lib
maybe other system l...
Viewer does not support full SVG 1.1
-------------------------------------------------------------------------------- /curl_wrapper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(CURL REQUIRED) 2 | 3 | add_library(curl_wrapper STATIC curl_wrapper.cpp) 4 | target_include_directories(curl_wrapper 5 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) 6 | 7 | # curl_wrapper PRIVATE 地依赖 CURL 库 8 | target_link_libraries(curl_wrapper PRIVATE ${CURL_LIBRARIES}) 9 | # 视频中的写法是直接使用 `CURL::libcurl`,这里已更新成更好的写法, 10 | # `CURL_LIBRARIES` 是 `find_package(CURL REQUIRED)` 时设置的变量, 11 | # 见 https://cmake.org/cmake/help/latest/module/FindCURL.html 12 | -------------------------------------------------------------------------------- /curl_wrapper/curl_wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "curl_wrapper/curl_wrapper.hpp" 2 | 3 | #include 4 | 5 | namespace curl_wrapper { 6 | std::string http_get_string(const std::string &url) { 7 | const auto curl = curl_easy_init(); 8 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 9 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); 10 | const auto write_func = [](char *ptr, size_t size, size_t nmemb, void *userdata) { 11 | auto &result = *static_cast(userdata); 12 | result.append(ptr, size * nmemb); 13 | return size * nmemb; 14 | }; 15 | using WriteFunction = size_t (*)(char *, size_t, size_t, void *); 16 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, static_cast(write_func)); 17 | std::string result = ""; 18 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result); 19 | curl_easy_perform(curl); // 暂时不考虑请求失败的情况 20 | curl_easy_cleanup(curl); 21 | return result; 22 | } 23 | 24 | std::string url_encode(const std::string &s) { 25 | const auto curl = curl_easy_init(); 26 | auto enc = curl_easy_escape(curl, s.c_str(), s.size()); 27 | std::string result = enc; 28 | curl_free(enc); 29 | curl_easy_cleanup(curl); 30 | return result; 31 | } 32 | } // namespace curl_wrapper 33 | -------------------------------------------------------------------------------- /curl_wrapper/include/curl_wrapper/curl_wrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace curl_wrapper { 6 | std::string http_get_string(const std::string &url); 7 | std::string url_encode(const std::string &s); 8 | } // namespace curl_wrapper 9 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | int main(int argc, const char *argv[]) { 6 | for (;;) { 7 | std::cout << "What is the ultimate answer?" << std::endl; 8 | std::string answer; 9 | std::cin >> answer; 10 | auto expected_answer = answer::find_the_ultimate_answer(); 11 | if (answer::check_the_answer(answer, expected_answer)) { 12 | std::cout << "Correct!" << std::endl; 13 | break; 14 | } 15 | } 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /wolfram/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(wolfram STATIC alpha.cpp) 2 | target_include_directories(wolfram PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) 3 | 4 | # wolfram PRIVATE 地依赖 curl_wrapper 5 | target_link_libraries(wolfram PRIVATE curl_wrapper) 6 | -------------------------------------------------------------------------------- /wolfram/alpha.cpp: -------------------------------------------------------------------------------- 1 | #include "wolfram/alpha.hpp" 2 | 3 | #include 4 | 5 | namespace wolfram { 6 | const std::string API_BASE = "https://api.wolframalpha.com/v1/result"; 7 | 8 | std::string simple_query(const std::string &appid, const std::string &query) { 9 | // 使用 curl_wrapper 库提供的对 CURL 的 C++ 封装 10 | using curl_wrapper::url_encode; 11 | using curl_wrapper::http_get_string; 12 | const auto url = API_BASE + "?appid=" + url_encode(appid) + "&i=" + url_encode(query); 13 | return http_get_string(url); 14 | } 15 | } // namespace wolfram 16 | -------------------------------------------------------------------------------- /wolfram/include/wolfram/alpha.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace wolfram { 6 | std::string simple_query(const std::string &appid, const std::string &query); 7 | } 8 | --------------------------------------------------------------------------------