本文是根据项目30daymakecppserver进行理解cmake的用法,以下是项目地址
yuesong-feng/30dayMakeCppServer: 30天自制C++服务器,包含教程和源代码
day13
sudo apt-get install clang-tidy
chmod +x /home/felix/Linux\ study/30dayMakeCppServer/code/day13/build_support/cpplint.py
sudo apt-get install clang-format
rm -rf build
mkdir build
cd build
cmake ..
make
set()
set
是 CMake 中最常用的命令之一,用于定义和修改变量。- 它可以设置简单的变量值、列表、缓存变量以及修改编译器选项、路径等。
- 通过适当的选项(如
CACHE
),可以控制变量的作用域,允许用户在命令行或 GUI 中修改这些值 -
cmake_minimum_required(VERSION 3.10) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(BUILD_SHARED_LIBS ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Expected directory structure. set(PINE_BUILD_SUPPORT_DIR "${CMAKE_SOURCE_DIR}/build_support")#设定 build_support 目录 存放编译相关工具或脚本 #设定 Clang 可能的路径 方便 CMake 自动查找 Clang set(PINE_CLANG_SEARCH_PATH "/usr/local/bin" "/usr/bin" "/usr/local/opt/llvm/bin" "/usr/local/opt/llvm@8/bin" "/usr/local/Cellar/llvm/8.0.1/bin") # 编译器设置 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wextra -std=c++17 -pthread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter -Wno-attributes") #TODO: remove set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIC") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fPIC") set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} -fPIC") # Output directory. 目标文件存放目录 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
project()
<project_name>
: 项目的名称(必需)。<languages>
: 一个可选参数,指定项目中使用的编程语言,通常包括C
、CXX
(C++)、Fortran
、CUDA
等。如果不指定,CMake 会根据实际的源代码文件类型自动推断。
project(Pine
VERSION 0.1
DESCRIPTION "pine" #描述
LANGUAGES C CXX
)
详细解析 CMake 语法:
file(GLOB_RECURSE pine_sources ${PROJECT_SOURCE_DIR}/src/*.cpp)
add_library(pine_shared SHARED ${pine_sources})
该 CMake 代码用于在 CMake 构建系统中定义一个共享库(Shared Library)。下面我们逐步解析它的语法和功能。
file()
file(GLOB_RECURSE pine_sources ${PROJECT_SOURCE_DIR}/src/*.cpp)
作用:
file(GLOB_RECURSE …)用于递归地查找指定目录下符合某个模式的所有文件,并将它们的路径存储到变量 pine_sources 中。
语法解析:
-
file(GLOB_RECURSE <变量名> <搜索路径>)
:-
GLOB_RECURSE
:递归查找所有匹配的文件。 -
<变量名>
:存储搜索结果的变量(这里是pine_sources
)。 -
<搜索路径>
:文件搜索路径,可以包含通配符(*.cpp
)。
-
在这行代码中:
-
file(GLOB_RECURSE pine_sources ${PROJECT_SOURCE_DIR}/src/*.cpp)
-
PROJECT_SOURCE_DIR
:CMake 变量,表示 CMakeLists.txt 所在的项目根目录。 -
src/*.cpp
:匹配src
目录下及其子目录中的所有.cpp
源文件。 - 例如,假设
src
目录结构如下:
那么src/ ├── main.cpp ├── utils/ │ ├── helper.cpp │ ├── math.cpp ├── core/ ├── engine.cpp
pine_sources
变量会包含:src/main.cpp src/utils/helper.cpp src/utils/math.cpp src/core/engine.cpp
-
⚠️ 注意:
GLOB_RECURSE
会递归查找 所有子目录,但 CMake 不推荐 在大项目中使用GLOB
,因为它不会在新文件添加时自动更新,可能导致不稳定的构建行为。
add_library()
add_library(pine_shared SHARED ${pine_sources})
作用:
add_library()
用于创建一个库目标(Library Target)。
语法解析:
add_library(<库名> <库类型> <源文件列表>)
-
<库名>
:目标库的名称(这里是pine_shared
)。 -
<库类型>
:-
SHARED
:创建 共享库(动态库),即.so
(Linux/macOS)或.dll
(Windows)。 -
STATIC
:创建 静态库,即.a
(Linux/macOS)或.lib
(Windows)。 -
MODULE
:创建 模块库,用于动态加载的插件(通常用于 CMake 插件或特殊用途)。
-
-
<源文件列表>
:库的源代码文件(这里是pine_sources
,即file(GLOB_RECURSE …)
找到的所有.cpp
文件)。
示例:
如果 pine_sources
变量包含:
src/main.cpp
src/utils/helper.cpp
src/utils/math.cpp
src/core/engine.cpp
那么 add_library(pine_shared SHARED ${pine_sources})
等价于:
add_library(pine_shared SHARED
src/main.cpp
src/utils/helper.cpp
src/utils/math.cpp
src/core/engine.cpp
)
这会告诉 CMake 编译这些 .cpp
文件并生成一个共享库,例如:
-
Linux/macOS:
libpine_shared.so
-
Windows:
pine_shared.dll
共享库:
在 C++ 和其他编程语言中,共享库(Shared Library) 是一种动态链接库,允许多个程序共享相同的代码,提高代码复用性和减少可执行文件的体积。常见的共享库格式有:
- Linux/macOS:
libXXX.so
- Windows:
XXX.dll
总结
这两行 CMake 代码的作用是:
-
查找
src
目录及其子目录中 所有.cpp
源文件 并存储在变量pine_sources
中。 -
使用这些
.cpp
文件 生成一个 共享库pine_shared
。
如果你还需要将 .h
头文件包含到编译过程中,可以使用:
target_include_directories(pine_shared PUBLIC ${PROJECT_SOURCE_DIR}/include)
这样 CMake 就会在编译时查找 include
目录中的头文件。
🚀 如果你的项目较大,建议手动列出源文件,而不是使用 GLOB_RECURSE
,这样能避免因 CMake 缓存未更新而导致的问题。
add_custom_targe:
命令定义的自定应目标,可以使用特定的名称来调用,并执行相应的命令。
add_custom_target(build-tests COMMAND ${CMAKE_CTEST_COMMAND} --show-only)
add_custom_target(check-tests COMMAND ${CMAKE_CTEST_COMMAND} --verbose)
🔹 作用:
build-tests
:调用ctest --show-only
,列出所有测试,但不会执行。check-tests
:调用ctest --verbose
,执行所有测试,并输出详细信息。
📌 这样就可以使用 make check-tests
来运行所有测试。
在CMake中,通过add_custom_target
命令定义的自定义目标,可以使用特定的名称来调用,并执行相应的命令。
总cmake代码:
########################################
foreach (pine_test_source ${PINE_TEST_SOURCES})
# Create a human readable name.
get_filename_component(pine_test_filename ${pine_test_source} NAME)
string(REPLACE ".cpp" "" pine_test_name ${pine_test_filename})
# Add the test target separately and as part of "make check-tests".
add_executable(${pine_test_name} EXCLUDE_FROM_ALL ${pine_test_source})
add_dependencies(build-tests ${pine_test_name})
add_dependencies(check-tests ${pine_test_name})
target_link_libraries(${pine_test_name} pine_shared)
# Set test target properties and dependencies.
set_target_properties(${pine_test_name}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
COMMAND ${pine_test_name}
)
endforeach(pine_test_source ${PINE_TEST_SOURCES})
foreach ()
foreach (pine_test_source ${PINE_TEST_SOURCES})
file(GLOB PINE_TEST_SOURCES "${PROJECT_SOURCE_DIR}/test/*.cpp")
作用:
- 遍历
PINE_TEST_SOURCES
变量中的每个测试.cpp
文件,并为每个测试文件创建独立的可执行目标。
get_filename_component(....... NAME)
get_filename_component(pine_test_filename ${pine_test_source} NAME)
作用:提取 .cpp
文件的文件名(不包含路径)。----pine_test_source表示。.cpp文件名的路径
string(REPLACE ".cpp" "" ...)
:
去掉 .cpp
后缀,得到可执行文件的名字。
📌 示例: 假设 test/
目录下有 server_test.cpp
,经过这段代码后:
pine_test_filename = "server_test.cpp"
pine_test_name = "server_test"
最终 server_test
作为目标可执行文件的名称。
add_executable()
add_executable(${pine_test_name} EXCLUDE_FROM_ALL ${pine_test_source})
🔹 作用:
add_executable(<name> EXCLUDE_FROM_ALL <sources>)
- 创建可执行文件
<name>
,但不默认编译,只有make server_test
才会编译该文件。 EXCLUDE_FROM_ALL
表示不会被make all
自动构建。
- 创建可执行文件
📌 示例:
add_executable(server_test EXCLUDE_FROM_ALL server_test.cpp)
这样不会影响 make all
,但可以单独 make server_test
。
add_dependencies()
仅用于构建顺序的依赖,而不是链接依赖,用于管理目标之间的构建顺序依赖
add_dependencies(build-tests ${pine_test_name})
add_dependencies(check-tests ${pine_test_name})
让 build-tests
和 check-tests
依赖于所有测试可执行文件
🔹 作用:
1.make build-tests
会确保所有测试可执行文件被创建。2.make check-tests
也会先构建测试可执行文件,然后运行测试。- 3.(
make check-tests
不会自动执行所有测试可执行文件,它只是构建(编译)所有测试可执行文件,但不会真正运行它们。) - 4.执行以上命令时会先构建依赖的文件,然后再执行make "自定义名称"---make build-tests
add_test()可以明确指定了要运行的可执行文件-----上面3情况
target_link_libraries()
target_link_libraries(${pine_test_name} pine_shared)
作用:
- 让测试可执行文件链接
pine_shared
共享库(add_library(pine_shared SHARED ...)
)。 - 这样测试代码可以调用共享库
pine_shared
中的所有函数。
set_target_properties()
set_target_properties(${pine_test_name}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
COMMAND ${pine_test_name}
)
作用:
RUNTIME_OUTPUT_DIRECTORY
:设置测试可执行文件的输出目录,默认为${CMAKE_BINARY_DIR}/bin
。COMMAND ${pine_test_name}
:定义测试的执行命令(通常用于ctest
,但这里没有完整定义)。
总结:
这个 CMake 代码的核心作用是:
查找 test/ 目录下所有 .cpp 文件。
构建有意义的可执行文件名(去掉 .cpp 后缀)。
遍历每个 .cpp 文件:
单独构建成可执行文件,而不是整个项目的一个模块。
添加共享库依赖,确保测试程序能访问 pine_shared 代码。
设置可执行文件的输出目录 为 bin/,方便管理。
定义 make check-tests 目标:
让 make check-tests 编译所有测试程序。
但 当前代码不会自动运行测试,需要手动 add_test() 或修改 check-tests 目标。
🚀 最终效果:
make server_test 只编译 server_test.cpp 并生成 bin/server_test。
make check-tests 会编译所有 test/*.cpp 代码,但不会自动执行它们(需要额外修改)。
所有测试可执行文件存放在 bin/ 目录,方便运行和调试。
add_subdirectory()
add_subdirectory(src)
add_subdirectory(test)
这两行 CMake 代码的作用是将 src
和 test
目录作为子目录添加到 CMake 构建系统,并让 CMakeLists.txt 继续在这些子目录中查找新的 CMakeLists.txt
文件。
-
查找
src/
和test/
目录 下的CMakeLists.txt
。 -
递归构建:
-
src/CMakeLists.txt
负责编译源代码(生成库或可执行文件)。 -
test/CMakeLists.txt
负责编译测试代码(通常会生成测试可执行文件)。
-
-
使主 CMakeLists.txt 能够管理整个项目的构建。
include_directories()
set(PINE_SRC_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/src/include)
set(PINE_TEST_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/test/include)
include_directories(${PINE_SRC_INCLUDE_DIR} ${PINE_TEST_INCLUDE_DIR})
include_directories(...)
告诉编译器去这些目录查找头文件。- 作用:
- 编译
src
源代码时,可以使用#include "my_header.h"
,而不需要指定完整路径。 - 编译
test
目录下的测试代码时,同样能找到test/include/
内的头文件。 -
PROJECT_SOURCE_DIR 是 CMake 内置变量,表示 CMakeLists.txt 所在的项目根目录。
- 编译
CMake 内置的编译标志变量
变量名称 | 作用 |
---|---|
CMAKE_CXX_FLAGS | C++ 代码的全局编译选项 |
CMAKE_C_FLAGS | C 代码的全局编译选项 |
CMAKE_CXX_FLAGS_DEBUG | Debug 模式下的 C++ 代码编译选项 |
CMAKE_CXX_FLAGS_RELEASE | Release 模式下的 C++ 代码编译选项 |
CMAKE_CXX_FLAGS_RELWITHDEBINFO | Release + Debug 信息模式下的编译选项 |
CMAKE_CXX_FLAGS_MINSIZEREL | 最小化大小的 Release 模式编译选项 |
CMAKE_BINARY_DIR内置变量----CMAKE_BINARY_DIR
默认是执行 cmake ..
命令时所在的 构建目录
大总结:
总结
项目初始化:设置基本信息和C++标准。
CMake运行目录检查:避免从错误的目录运行CMake。
工具和Clang配置:设置工具路径、查找并配置 clang-format、clang-tidy、cpplint。
编译器设置:配置编译器和链接器选项。
输出目录设置:设置目标文件、库文件的存放目录。
头文件目录设置:配置头文件的包含路径。
添加子目录:添加源代码和测试代码目录。
代码质量工具:
格式化:使用 clang-format 进行代码格式化。
格式化检查:检查代码是否符合格式要求。
静态分析:使用 cpplint 进行代码风格检查。
高级分析:使用 clang-tidy 进行潜在问题分析。
cmake_minimum_required(VERSION 3.10)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(BUILD_SHARED_LIBS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(Pine
VERSION 0.1
DESCRIPTION "pine"
LANGUAGES C CXX
)
# People keep running CMake in the wrong folder, completely nuking their project or creating weird bugs.
# This checks if you're running CMake from a folder that already has CMakeLists.txt.
# Importantly, this catches the common case of running it from the root directory.
file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" PATH_TO_CMAKELISTS_TXT)
if (EXISTS "${PATH_TO_CMAKELISTS_TXT}")
message(FATAL_ERROR "Run CMake from a build subdirectory! \"mkdir build ; cd build ; cmake .. \" \
Some junk files were created in this folder (CMakeCache.txt, CMakeFiles); you should delete those.")
endif ()
# Expected directory structure.
set(PINE_BUILD_SUPPORT_DIR "${CMAKE_SOURCE_DIR}/build_support")#设定 build_support 目录 存放编译相关工具或脚本
#设定 Clang 可能的路径 方便 CMake 自动查找 Clang
set(PINE_CLANG_SEARCH_PATH "/usr/local/bin" "/usr/bin" "/usr/local/opt/llvm/bin" "/usr/local/opt/llvm@8/bin" "/usr/local/Cellar/llvm/8.0.1/bin")
######################################################################################################################
# DEPENDENCIES
######################################################################################################################
# CTest
# enable_testing()
# 查找 clang-format 并设置格式化
# clang-format
if (NOT DEFINED CLANG_FORMAT_BIN)
# attempt to find the binary if user did not specify
find_program(CLANG_FORMAT_BIN
NAMES clang-format clang-format-8
HINTS ${PINE_CLANG_SEARCH_PATH})
endif ()
if ("${CLANG_FORMAT_BIN}" STREQUAL "CLANG_FORMAT_BIN-NOTFOUND")
message(WARNING "Pine/main couldn't find clang-format.")
else ()
message(STATUS "Pine/main found clang-format at ${CLANG_FORMAT_BIN}")
endif ()
# 查找 clang-tidy 并启用编译命令导出
# clang-tidy
if (NOT DEFINED CLANG_TIDY_BIN)
# attempt to find the binary if user did not specify
find_program(CLANG_TIDY_BIN
NAMES clang-tidy clang-fidy-8
HINTS ${PINE_CLANG_SEARCH_PATH})
endif ()
if ("${CLANG_TIDY_BIN}" STREQUAL "CLANG_TIDY_BIN-NOTFOUND")
message(WARNING "Pine/main couldn't find clang-tidy.")
else ()
# Output compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
message(STATUS "Pine/main found clang-fidy at ${CLANG_TIDY_BIN}")
endif ()
# 代码检查工具 cpplint
# cpplint
find_program(CPPLINT_BIN
NAMES cpplint cpplint.py
HINTS ${PINE_BUILD_SUPPORT_DIR})
if ("${CPPLINT_BIN}" STREQUAL "CPPLINT_BIN-NOTFOUND")
message(WARNING "Pine/main couldn't find cpplint.")
else ()
message(STATUS "Pine/main found cpplint at ${CPPLINT_BIN}")
endif ()
######################################################################################################################
# COMPILER SETUP
######################################################################################################################
# 编译器设置
# Compiler flags.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wextra -std=c++17 -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter -Wno-attributes") #TODO: remove
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIC")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fPIC")
set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} -fPIC")
set(GCC_COVERAGE_LINK_FLAGS "-fPIC")
message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
message(STATUS "CMAKE_CXX_FLAGS_DEBUG: ${CMAKE_CXX_FLAGS_DEBUG}")
message(STATUS "CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}")
message(STATUS "CMAKE_SHARED_LINKER_FLAGS: ${CMAKE_SHARED_LINKER_FLAGS}")
# Output directory. 目标文件存放目录
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# Includes. 设置并包含项目的头文件目录,确保编译时可以正确找到头文件
#设定 PINE_SRC_INCLUDE_DIR 变量,指向 源代码头文件目录(通常存放 .h 头文件)。
#PROJECT_SOURCE_DIR 是 CMake 内置变量,表示 CMakeLists.txt 所在的项目根目录。
set(PINE_SRC_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/src/include)
set(PINE_TEST_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/test/include)
include_directories(${PINE_SRC_INCLUDE_DIR} ${PINE_TEST_INCLUDE_DIR})
#添加子目录
add_subdirectory(src)
add_subdirectory(test)
######################################################################################################################
# MAKE TARGETS
######################################################################################################################
##########################################
# "make format"
# "make check-format"
##########################################
string(CONCAT PINE_FORMAT_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}/src,"
"${CMAKE_CURRENT_SOURCE_DIR}/test,"
)
# 代码格式化 make format
# runs clang format and updates files in place.
add_custom_target(format ${PINE_BUILD_SUPPORT_DIR}/run_clang_format.py
${CLANG_FORMAT_BIN}
${PINE_BUILD_SUPPORT_DIR}/clang_format_exclusions.txt
--source_dirs
${PINE_FORMAT_DIRS}
--fix
--quiet
)
#cpplint 静态检查
# runs clang format and exits with a non-zero exit code if any files need to be reformatted
add_custom_target(check-format ${PINE_BUILD_SUPPORT_DIR}/run_clang_format.py
${CLANG_FORMAT_BIN}
${PINE_BUILD_SUPPORT_DIR}/clang_format_exclusions.txt
--source_dirs
${PINE_FORMAT_DIRS}
--quiet
)
##########################################
# "make cpplint"
##########################################
file(GLOB_RECURSE PINE_LINT_FILES
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/test/*.cpp"
)
# Balancing act: cpplint.py takes a non-trivial time to launch,
# so process 12 files per invocation, while still ensuring parallelism
add_custom_target(cpplint echo '${PINE_LINT_FILES}' | xargs -n12 -P8
${CPPLINT_BIN}
--verbose=2 --quiet
--linelength=120
--filter=-legal/copyright,-build/include_subdir,-readability/casting
)
###########################################################
# "make clang-tidy" target
###########################################################
# runs clang-tidy and exits with a non-zero exit code if any errors are found.
# note that clang-tidy automatically looks for a .clang-tidy file in parent directories
add_custom_target(clang-tidy
${PINE_BUILD_SUPPORT_DIR}/run_clang_tidy.py # run LLVM's clang-tidy script
-clang-tidy-binary ${CLANG_TIDY_BIN} # using our clang-tidy binary
-p ${CMAKE_BINARY_DIR} # using cmake's generated compile commands
)
# add_dependencies(check-clang-tidy pine_shared) # needs gtest headers, compile_commands.json
#总结:
#这段 CMake 代码为项目配置了以下目标:
# 代码格式化:使用 clang-format 格式化代码并应用到指定目录。
# 代码格式化检查:检查代码是否符合格式要求,并在不符合时返回错误。
# 静态检查:使用 cpplint 进行静态代码分析,确保代码符合规定的编程风格。
# 高级静态分析:使用 clang-tidy 对代码进行深入分析,查找潜在问题和优化建议