{"id":18275896,"url":"https://github.com/davideuler/cmake-tutorial","last_synced_at":"2026-02-11T01:03:04.194Z","repository":{"id":138208864,"uuid":"548747719","full_name":"davideuler/cmake-tutorial","owner":"davideuler","description":"cmake tutorial of a simple project","archived":false,"fork":false,"pushed_at":"2022-10-10T06:02:49.000Z","size":8,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-02T19:18:42.011Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"CMake","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davideuler.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-10T05:50:11.000Z","updated_at":"2022-10-10T05:54:19.000Z","dependencies_parsed_at":"2023-07-25T06:15:28.842Z","dependency_job_id":null,"html_url":"https://github.com/davideuler/cmake-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/davideuler/cmake-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davideuler%2Fcmake-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davideuler%2Fcmake-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davideuler%2Fcmake-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davideuler%2Fcmake-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davideuler","download_url":"https://codeload.github.com/davideuler/cmake-tutorial/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davideuler%2Fcmake-tutorial/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266456245,"owners_count":23931383,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T12:14:25.944Z","updated_at":"2026-02-11T01:03:04.153Z","avatar_url":"https://github.com/davideuler.png","language":"CMake","funding_links":[],"categories":[],"sub_categories":[],"readme":"这篇文章主要介绍 CMake 的使用，看完这篇文章后，CMake 的绝大多数使用方法你都能掌握。本篇文章采用循序渐进的方法带你一步步逐渐进阶 CMake，通过多个示例，告诉你如何使用 CMake 解决常见的构建系统问题。\n\n\n\nstep0环境配置\n开始前说明一下，我的环境是 Windows10 + CMake + MinGW，MinGW 就是 GCC 的 Windows 移植版本。\n\n环境安装我就不介绍了，不是这篇文章的重点，知乎有很多相关教程，这里提供相关工具下载链接：\n\n构建工具：Download | CMake\n\n编译工具：Downloads - MinGW-w64\n\n需要注意的是，CMake 和 MinGW 安装好后，要手动添加到环境变量。\n\n\n\nstep1构建最小项目\n最基本的项目是将一个源代码文件生成可执行文件。对于这么简单的项目，只需要一个三行的 CMakeLists.txt 文件即可，这是本篇教程的起点。在 step1 目录中创建一个 CMakeLists.txt 文件，如下所示：\n\n```\ncmake_minimum_required(VERSION 3.15)\n\n# set the project name\nproject(Tutorial)\n\n# add the executable\nadd_executable(Tutorial tutorial.cpp)\n```\n\ncmake_minimum_required  指定使用 CMake 的最低版本号，project 指定项目名称，add_executable 用来生成可执行文件，需要指定生成可执行文件的名称和相关源文件。\n\n注意，此示例在 CMakeLists.txt 文件中使用小写命令。CMake 支持大写、小写和混合大小写命令。tutorial.cpp 文件在 step1 目录中，可用于计算数字的平方根。\n\n```\n// tutorial.cpp\n\n#include \u003ccmath\u003e\n#include \u003ccstdlib\u003e\n#include \u003ciostream\u003e\n#include \u003cstring\u003e\n\nint main(int argc, char* argv[])\n{\n    if (argc \u003c 2) {\n        std::cout \u003c\u003c \"Usage: \" \u003c\u003c argv[0] \u003c\u003c \" number\" \u003c\u003c std::endl;\n        return 1;\n    }\n\n    // convert input to double\n    const double inputValue = atof(argv[1]);\n\n    // calculate square root\n    const double outputValue = sqrt(inputValue);\n    std::cout \u003c\u003c \"The square root of \" \u003c\u003c inputValue\n              \u003c\u003c \" is \" \u003c\u003c outputValue\n              \u003c\u003c std::endl;\n    return 0;\n}\n```\n\n构建、编译和运行\n\n现在就可以构建和运行我们的项目了，就是先运行 cmake 命令来构建项目，然后使用你选择的编译工具进行编译。\n\n先从命令行进入到 step1 目录，并创建一个构建目录 build，接下来，进入 build 目录并运行 CMake 来配置项目，并生成构建系统：\n\n```\nmkdir build\ncd build\ncmake -G\"MinGW Makefiles\" ..\n```\n\n构建系统是需要指定 CMakeLists.txt 所在路径，此时在 build 目录下，所以用 .. 表示 CMakeLists.txt 在上一级目录。\n\nWindows 下，CMake 默认使用微软的 MSVC 作为编译器，我想使用 MinGW 编译器，可以通过 -G 参数来进行指定，只有第一次构建项目时需要指定。\n\n此时在 build 目录下会生成 Makefile 文件，然后调用编译器来实际编译和链接项目：\n\ncmake --build .\n--build 指定编译生成的文件存放目录，其中就包括可执行文件，. 表示存放到当前目录，\n\n在 build 目录下生成了一个 Tutorial.exe 可执行文件，试着执行它：\n\n\u003e Tutorial.exe 5\nThe square root of 5 is 2.23607\n该程序计算 5 的平方根，从输出结果看已经得到了正确的结果。\n\n此时目录结构为：\n```\nstep1/\n    build/\n    CMakeLists.txt\n    tutorial.cpp\n```\n\n外部构建与内部构建\n\n这里创建了一个 build 目录存放编译产物，可以避免编译产物与代码文件混在一起，这种叫做外部构建。\n\n还有一种内部构建，即直接在项目根目录下进行构建系统与编译，这时构建和编译命令就更改为：\n\n```\ncmake -G\"MinGW Makefiles\" .\ncmake --build .\n```\n\n内部构建会使得项目文件很混乱，一般直接用外部构建即可。\n\n\n\nstep2优化 CMakeLists.txt 文件\nset 与 PROJECT_NAME\n\n这是之前见过的 CMakeLists.txt 文件：\n\n```\ncmake_minimum_required(VERSION 3.15)\n\n# set the project name\nproject(Tutorial)\n\n# add the executable\nadd_executable(Tutorial tutorial.cpp)\n```\n\n\n指定了项目名后，后面可能会有多个地方用到这个项目名，如果更改了这个名字，就要改多个地方，比较麻烦，那么可以使用 PROJECT_NAME 来表示项目名。\n\n```\nadd_executable(${PROJECT_NAME} tutorial.cpp)\n```\n\n生成可执行文件需要指定相关的源文件，如果有多个，那么就用空格隔开，比如：\n\n```\nadd_executable(${PROJECT_NAME} a.cpp b.cpp c.cpp)\n```\n\n我们也可以用一个变量来表示这多个源文件：\n\n```\nset(SRC_LIST a.cpp b.cpp c.cpp)\nadd_executable(${PROJECT_NAME} ${SRC_LIST})\n```\n\nset 命令指定 SRC_LIST 变量来表示多个源文件，用 ${var_name} 获取变量的值。\n\n于是原来的 CMakeLists.txt 文件就可以变成如下所示：\n\n```\ncmake_minimum_required(VERSION 3.15)\n\n# set the project name\nproject(Tutorial)\n\nSET(SRC_LIST tutorial.cpp)\n\n# add the executable\nadd_executable(${PROJECT_NAME} ${SRC_LIST})\n```\n\n这样看起来就很简洁。\n\n添加版本号和配置头文件\n\n我们可以在 CMakeLists.txt 为可执行文件和项目提供一个版本号。首先，修改 CMakeLists.txt 文件，使用 project 命令设置项目名称和版本号。\n\n```\ncmake_minimum_required(VERSION 3.15)\n\n# set the project name and version\nproject(Tutorial VERSION 1.0)\n\nconfigure_file(TutorialConfig.h.in TutorialConfig.h)\n```\n\n然后，配置头文件将版本号传递给源代码：\n\n```\nconfigure_file(TutorialConfig.h.in TutorialConfig.h)\n```\n\n由于 TutorialConfig.h 文件会被自动写入 build 目录，因此必须将该目录添加到搜索头文件的路径列表中。将以下行添加到 CMakeLists.txt 文件的末尾：\n\n```\ntarget_include_directories(${PROJECT_NAME} PUBLIC\n                           ${PROJECT_BINARY_DIR}\n                           )\nPROJECT_BINARY_DIR 表示当前工程的二进制路径，即编译产物会存放到该路径，此时PROJECT_BINARY_DIR 就是 build 所在路径。\n```\n\n然后创建 TutorialConfig.h.in 文件，包含以下内容：\n\n```\n// the configured options and settings for Tutorial\n#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@\n#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@\n```\n\n当使用 CMake 构建项目后，会在 build 中生成一个  TutorialConfig.h 文件，内容如下：\n\n```\n// the configured options and settings for Tutorial\n#define Tutorial_VERSION_MAJOR 1\n#define Tutorial_VERSION_MINOR 0\n```\n\n下一步在 tutorial.cpp 包含头文件 TutorialConfig.h，最后通过以下代码打印出可执行文件的名称和版本号。\n\n```\n    if (argc \u003c 2) {\n      // report version\n      std::cout \u003c\u003c argv[0] \u003c\u003c \" Version \" \u003c\u003c Tutorial_VERSION_MAJOR \u003c\u003c \".\"\n                \u003c\u003c Tutorial_VERSION_MINOR \u003c\u003c std::endl;\n      std::cout \u003c\u003c \"Usage: \" \u003c\u003c argv[0] \u003c\u003c \" number\" \u003c\u003c std::endl;\n      return 1;\n    }\n```\n\n指定 C++ 标准\n\n接下来将 step1/tutorial.cpp 源码中的 atof 替换为 std::stod，这是 C++11 的特性，并删除 #include\u003ccstdlib\u003e。\n\n```\nconst double inputValue = std::stod(argv[1]);\n```\n\n在 CMake 中支持特定 C++标准的最简单方法是使用 CMAKE_CXX_STANDARD 标准变量。在 CMakeLists.txt 中设置 CMAKE_CXX_STANDARD 为11，CMAKE_CXX_STANDARD_REQUIRED 设置为True。确保在 add_executable 命令之前添加 CMAKE_CXX_STANDARD_REQUIRED 命令。\n\n```\ncmake_minimum_required(VERSION 3.15)\n\n# set the project name and version\nproject(${PROJECT_NAME} VERSION 1.0)\n\n# specify the C++ standard\nset(CMAKE_CXX_STANDARD 11)\nset(CMAKE_CXX_STANDARD_REQUIRED True)\n```\n\n需要注意的是，如果你的gcc编译器版本够高，也可以不用指定 C++ 版本为 11。从 GCC 6.1 开始，当不指定任何版本 C++ 标准时，默认版本是 C++ 14，如果你想用 C++17 的语言，还是需要指定的。\n\n修改完成后，需要对代码进行重新编译 cmake --build .，此时可以不用进行项目构建。\n\n此时目录结构为：\n\n```\nstep2/\n    build/\n    CMakeLists.txt\n    tutorial.cpp\n    TutorialConfig.h.in\n```\n\nstep3添加库\n现在我们将向项目中添加一个库，这个库包含计算数字平方根的实现，可执行文件使用这个库，而不是编译器提供的标准平方根函数。\n\n我们把库放在名为 MathFunctions 的子目录中。此目录包含头文件 MathFunctions.h 和源文件 mysqrt.cpp。源文件有一个名为 mysqrt 的函数，它提供了与编译器的 sqrt 函数类似的功能，MathFunctions.h 则是该函数的声明。\n\n在 MathFunctions 目录下创建一个 CMakeLists.txt 文件，并添加以下一行：\n\n```\n# MathFunctions/CMakeLists.txt\nadd_library(MathFunctions mysqrt.cpp)\n```\n\n表示添加一个叫 MathFunctions 的库文件。\n\nCMake 中的 target 有可执行文件和库文件，分别使用 add_executable 和 add_library 命令生成，除了指定生成的可执行文件名/库文件名，还需要指定相关的源文件。\n\n此时文件结构为：\n```\nstep3/\n    build/\n    MathFunctions/\n        CMakeLists.txt\n        MathFunctions.h\n        mysqrt.cpp\n    CMakeLists.txt\n    tutorial.cpp\n    TutorialConfig.h.in\n```\n\n为了使用 MathFunctions  这个库，我们将在顶级 CMakeLists.txt 文件中添加一个 add_subdirectory(MathFunctions) 命令指定库所在子目录，该子目录下应包含 CMakeLists.txt 文件和代码文件。\n\n可执行文件要使用库文件，需要能够找到库文件和对应的头文件，可以分别通过 target_link_libraries 和 target_include_directories 来指定。\n\n使用 target_link_libraries 将新的库文件添加到可执行文件中，使用 target_include_directories 将 MathFunctions 添加为头文件目录，添加到 Tutorial 目标上，以便 mysqrt.h 可以被找到。\n\n顶级 CMakeLists.txt 的最后几行如下所示：\n\n```\n# add the MathFunctions library\nadd_subdirectory(MathFunctions)\n\n# add the executable\nadd_executable(${PROJECT_NAME} tutorial.cpp)\n\ntarget_link_libraries(${PROJECT_NAME} PUBLIC MathFunctions)\n\n# add the binary tree to the search path for include files\n# so that we will find TutorialConfig.h\ntarget_include_directories(${PROJECT_NAME} PUBLIC\n                           ${PROJECT_BINARY_DIR}\n                           ${PROJECT_SOURCE_DIR}/MathFunctions\n                           )\n```\n\nMathFunctions 库就算添加完成了，接下来就是在主函数使用该库中的函数，先在 tutorial.cpp 文件中添加头文件：\n\n```\n#include \"MathFunctions.h\"\n```\n\n然后使用 mysqrt 函数即可：\n```\nconst double outputValue = mysqrt(inputValue);\n```\n\nstep4将库设置为可选项\n现在将 MathFunctions 库设为可选的，虽然对于本教程来说，没有必要这样做，但对于较大的项目来说，这种情况很常见。\n\n第一步是向顶级 CMakeLists.txt 文件添加一个选项。\n\n```\noption(USE_MYMATH \"Use tutorial provided math implementation\" ON)\n```\n\noption 表示提供用户可以选择的选项。命令格式为：option(\u003cvariable\u003e \"description [initial value])。\n\nUSE_MYMATH 这个选项缺省值为 ON，用户可以更改这个值。此设置将存储在缓存中，以便用户不需要在每次构建项目时设置该值。\n\n下一个更改是使 MathFunctions 库的构建和链接成为条件。于是创建一个 if 语句，该语句检查选项 USE_MYMATH 的值。\n\n```\nif(USE_MYMATH)\n  add_subdirectory(MathFunctions)\n  list(APPEND EXTRA_LIBS MathFunctions)\n  list(APPEND EXTRA_INCLUDES ${PROJECT_SOURCE_DIR}/MathFunctions)\nendif()\n\n# add the executable\nadd_executable(${PROJECT_NAME} tutorial.cpp)\n\ntarget_link_libraries(${PROJECT_NAME} PUBLIC ${EXTRA_LIBS})\n\n# add the binary tree to the search path for include files\n# so that we will find TutorialConfig.h\ntarget_include_directories(${PROJECT_NAME} PUBLIC\n                           ${PROJECT_BINARY_DIR}\n                           ${EXTRA_INCLUDES}\n                           )\n```\n\n在 if 块中，有 add_subdirectory 命令和 list 命令，APPEND表示将元素MathFunctions追加到列表EXTRA_LIBS中，将元素 ${PROJECT_SOURCE_DIR}/MathFunctions 追加到列表EXTRA_INCLUDES中。EXTRA_LIBS 存储 MathFunctions 库，EXTRA_INCLUDES 存储 MathFunctions 头文件。\n\n变量EXTRA_LIBS用来保存需要链接到可执行程序的可选库，变量EXTRA_INCLUDES用来保存可选的头文件搜索路径。这是处理可选组件的经典方法，我将在下一步介绍现代方法。\n\n接下来对源代码的进行修改。首先，在 tutorial.cpp 中包含 MathFunctions.h 头文件：\n\n```\n#ifdef USE_MYMATH\n    #include \"MathFunctions.h\"\n#endif\n```\n\n然后，还在 tutorial.cpp 中，使用 USE_MYMATH 选择使用哪个平方根函数：\n\n```\n#ifdef USE_MYMATH\n  const double outputValue = mysqrt(inputValue);\n#else\n  const double outputValue = sqrt(inputValue);\n#endif\n```\n\n因为源代码使用了 USE_MYMATH 宏，可以用下面的行添加到 tutorialconfig.h.in 文档中：\n\n// TutorialConfig.h.in\n#cmakedefine USE_MYMATH\n现在使用 cmake 命令构建项目，并运行生成的 Tutorial 可执行文件。\n\n```\nbuild\u003e cmake -G\"MinGW Makefiles\" ..\nbuild\u003e cmake --build .\nbuild\u003e Tutorial.exe 8\nComputing sqrt of 8 to be 4.5\nComputing sqrt of 8 to be 3.13889\nComputing sqrt of 8 to be 2.84378\nComputing sqrt of 8 to be 2.82847\nComputing sqrt of 8 to be 2.82843\nComputing sqrt of 8 to be 2.82843\nComputing sqrt of 8 to be 2.82843\nComputing sqrt of 8 to be 2.82843\nComputing sqrt of 8 to be 2.82843\nComputing sqrt of 8 to be 2.82843\nThe square root of 8 is 2.82843\n\n```\n\n\n默认调用 mysqrt 函数，也可以在构建项目时指定 USE_MYMATH 的值为 OFF：\n\n```\n\u003e cmake -DUSE_MYMATH=OFF ..\n\u003e make --build .\n```\n\n此时会调用自带的 sqrt 函数。\n\n\nstep5添加库的使用要求\n现在将 MathFunctions 库设为可选的，虽然对于本教程来说，没有必要这样做，但对于较大的项目来说，这种情况很常见。\n\n```\ntarget_compile_definitions()\n\ntarget_compile_options()\n\ntarget_include_directories()\n\ntarget_link_libraries()\n```\n\n现在重构一下 step4 中的代码，使用更加现代的 CMake 方法来包含 MathFunctions 库的头文件。\n\n首先声明，链接 MathFunctions 库的任何可执行文件/库文件都需要包含 MathFunctions 目录作为头文件路径，而 MathFunctions 本身不需要包含，这被称为 INTERFACE 使用要求。\n\nINTERFACE是指消费者需要、但生产者不需要的那些东西。在MathFunctions/CMakeLists.txt 最后添加：\n\n```\n# MathFunctions/CMakeLists.txt\ntarget_include_directories(MathFunctions\n          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}\n          )\n```\n\nCMAKE_CURRENT_SOURCE_DIR 表示 MathFunctions 库所在目录。\n\n现在我们已经为 MathFunctions 指定了使用要求 INTERFACE，那么可以从顶级 CMakeLists.txt 中删除EXTRA_INCLUDES变量的相关使用：\n\n```\nif(USE_MYMATH)\n  add_subdirectory(MathFunctions)\n  list(APPEND EXTRA_LIBS MathFunctions)\n  list(APPEND EXTRA_INCLUDES ${PROJECT_SOURCE_DIR}/MathFunctions)   # 删除此行\nendif()\n\n...\n\n# add the binary tree to the search path for include files\n# so that we will find TutorialConfig.h\ntarget_include_directories(${PROJECT_NAME} PUBLIC\n                           ${PROJECT_BINARY_DIR}\n                           ${EXTRA_INCLUDES}   # 删除此行\n                           )\n```\n\n现在只要是链接了 MathFunctions 库，就会自动包含 MathFunctions 所在目录的头文件，简洁而优雅。\n\n这里补充两点知识：\n\n1、使用要求除了 INTERFACE，还有PRIVATE 和 PUBLIC。INTERFACE表示消费者需要生产者不需要，PRIVATE表示消费者不需要生产者需要，PUBLIC 表示消费者和生产者都需要。\n\n2、这里使用 add_library 命令生成的 MathFunctions 库其实是静态链接库。动态库和静态库的区别是：静态库在链接阶段会被链接到最终目标中（比如可执行程序），缺点是同一个静态库如果被不同的程序引用，那么内存中会存在这个静态库函数的多份拷贝。动态库在链接阶段不会被拷贝最终目标中，程序在运行阶段才会加载这个动态库。所以多个程序就算引用了同一个动态库，内存中也只存在一份动态库函数的拷贝。\n\n\n\nstep6build 目录介绍\n在文本中，我都是创建了一个 build 用来存放 cmake 构建和编译的产物，这里简单说下里面有些什么东西。\n\n```\nbuild/\n    CMakeCache.txt\n    CMakeFiles/\n    cmake_install.cmake\n    Makefile\n    Tutorial.exe\n    TutorialConfig.h\n    MathFunctions/\n```\n\n其中 Makefile 是 cmake 根据顶级 CMakeLists.txt 生成的构建文件，通过该文件可以对整个项目进行编译。\n\nTutorial.exe 就是生成的可执行文件，通过该文件运行程序。\n\nTutorialConfig.h 是用于配置信息的头文件，是 cmake 根据 TutorialConfig.h.in 文件自动生成的。\n\n还有个 MathFunctions 文件夹：\n\n```\nMathFunctions/\n    CMakeFiles/\n    cmake_install.cmake\n    Makefile\n    libMathFunctions.a\n```\n\n其中 Makefile 是 cmake 根据 MathFunctions 目录下的 CMakeLists.txt 生成的构建文件。\n\nlibMathFunctions.a 则是 MathFunctions 静态链接库，可执行文件会通过这个库调用 mysqrt 函数。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavideuler%2Fcmake-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavideuler%2Fcmake-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavideuler%2Fcmake-tutorial/lists"}