最近更新于 2024-05-05 14:19
在实际开发中,仅仅通过使用 gcc 命令对程序进行编译是非常低效的,原因主要有以下两点:
(1)程序往往是由多个源文件组成(某些程序项目可能成千上万个源码),源文件的个数越多,gcc 命令就会越长。此外,各种编译规则也会加大 gcc 命令的复杂度,所以在开发调试程序的过程中,通过输入 gcc 命令来编译是很麻烦的。
(2)在程序的整个开发过程中,调试的工作量往往会占据过半。在调试的过程中,每次调试可能只会修改部分源文件。而使用 gcc 命令编译会把那些没有被修改的源文件重新编译,这样会造成过多不必要的开销(有些工程项目完整编译一次可能就要花掉几个小时的时间)。
通过 cmake 来控制编译器则可以很好的解决,需要根据工程自己写一套编译的具体规则,例如,说明在哪里提取某个源码,在哪里调用某个库,在哪里引用某个头文件……规则写成以后,后面就可以通过执行 cmake 命令根据写好的规则来控制整个工程的构建,而不是每次编译都输入一个成千上万个字符长度的命令。构建的时候还会比较源码和生成目标文件的时间,判断是否修改了某些文件,只有修改的才重新编译,也解决了上面提到的问题。
文档
官方文档:https://cmake.org/documentation
中文参考资料:https://www.bookstack.cn/read/CMake-Cookbook/README.md
部分基础使用
编译所在系统判断
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") message("Linux") elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") message("Windows") else () message("Unknow System!") endif ()
编译器判断
if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") message("GNU gcc/g++") elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") message("Clang") elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC") message("MSVC") else () message("Unknow Compiler") endif ()
GNU gcc/g++ 我自用的参数
注:如果是编译库,删除 -pie-no
sanitizer 调试参数
-fsanitize=address -fsanitize=leak -fsanitize=undefined
C Release
set(CMAKE_C_FLAGS_RELEASE "-no-pie -O3")
C Debug
set(CMAKE_C_FLAGS_DEBUG "-std=c17 -no-pie -Wall -Werror=return-type -Werror=address -Werror=sequence-point -Werror=format-security -Wextra -pedantic -Wimplicit-fallthrough -Wsequence-point -Wswitch-unreachable -Wswitch-enum -Wstringop-truncation -Wbool-compare -Wtautological-compare -Wfloat-equal -Wshadow=global -Wpointer-arith -Wpointer-compare -Wcast-align -Wcast-qual -Wwrite-strings -Wdangling-else -Wlogical-op -Wconversion -g -O0 -DDEBUG")
C++ Release
set(CMAKE_CXX_FLAGS_RELEASE "-no-pie -O3")
C++ Debug
set(CMAKE_CXX_FLAGS_DEBUG "-std=c++17 -no-pie -Wall -Werror=return-type -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point -Werror=format-security -Wextra -pedantic -Wimplicit-fallthrough -Wsequence-point -Wswitch-unreachable -Wswitch-enum -Wstringop-truncation -Wbool-compare -Wtautological-compare -Wfloat-equal -Wshadow=global -Wpointer-arith -Wpointer-compare -Wcast-align -Wcast-qual -Wwrite-strings -Wdangling-else -Wlogical-op -Wconversion -g -O0 -DDEBUG")
自动获取源码文件名
aux_source_directory(源码所在文件夹名 用于存放源码文件名的变量)
也可以使用 SET() 手动设置一个含有源码路径名的变量
将源码编译生成可执行文件
set(EXECUTABLE_OUTPUT_PATH 目标生成路径) add_executable(要生成的可执行文件的名字 ${存放源码文件名的变量}})
设置项目名称
project(项目名称)
设置搜索头文件的路径
include_directories(头文件路径)
链接库文件到可执行文件中
target_link_libraries(可执行文件名 库名)
在一个大工程内,某个模块引用另外一个模块库,即设置的要使用的那个库的 project 名字
搜寻模块
使用示例见后面 示例-2 ,其中的模块使用的是 示例-1 中编译的静态库和动态库 – 更多使用可查阅官方文档
这里顺便记录一些我经常用到的第三方库寻找模块的写法
# SQLite3 find_package(SQLite3 REQUIRED) target_link_libraries (可执行文件名 ${SQLite3_LIBRARIES})
# wiringPi find_path(WIRINGPI_INCLUDE_DIRS NAMES wiringPi.h) find_library(WIRINGPI_LIBRARIES NAMES wiringPi) target_link_libraries(可执行文件名 ${WIRINGPI_LIBRARIES})
# OpenCV find_package(OpenCV REQUIRED) target_link_libraries(可执行文件名 ${OpenCV_LIBS})
# pthread set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) target_link_libraries(可执行文件名 PRIVATE Threads::Threads)
# dlib find_package(dlib REQUIRED) target_link_libraries(可执行文件名 dlib::dlib)
生成库
set(LIBRARY_OUTPUT_PATH 目标生成路径) add_library(静态库名 STATIC 源码路径名)
set(LIBRARY_OUTPUT_PATH 目标生成路径) add_library(动态库名 SHARED 源码路径名)
设置工程中子模块路径
add_subdirectory(路径)
向终端输出信息
message(mode "信息内容")
mode 取值:
- 留空 重要消息;
- STATUS 非重要消息;
- WARNING 警告, 但会继续执行;
- AUTHOR_WARNING 警告 (dev), 会继续执行;
- SEND_ERROR 错误, 继续执行,但是会跳过生成的步骤;
- FATAL_ERROR 错误, 终止所有处理过程;
cmake –build . –target install 安装部署
简单使用就是,默认安装到 /usr/local 下
install(TARGETS 目标......)
比如后面 示例-1 的工程要安装,可以像下面这样写。demo 是可执行文件,默认放到 /usr/local/bin。sub 是动态库,默认放到 /usr/local/lib。工程中还有一个静态库 add,因为静态库是链接的时候就已经放入可执行文件了,也就没必要安装到系统中。
这样安装后执行 demo,可能会出现 demo: error while loading shared libraries: libsub.so: cannot open shared object file: No such file or directory 的错误,这是因为 demo 搜索不到 sub 库,/usr/local/lib 不是系统默认的搜索路径(或者放入系统的 /lib 库,不建议)。临时设置动态库搜索路径变量 LD_LIBRARY_PATH,执行 export LD_LIBRARY_PATH=/usr/local/lib:LD_LIBRARY_PATH。永久设置,向 /etc/ld.so.conf 中追加 /usr/local/lib 这个库路径,然后执行 sudo ldconfig 刷新。
install(TARGETS demo sub)
除上面提到的,其它部分预置变量
CMAKE_SOURCE_DIR 工程根目录路径 CMAKE_CURRENT_SOURCE_DIR CMakeLists.txt所在目录路径 CMAKE_SYSTEM 系统名字并带版本号 CMAKE_SYSTEM_PROCESSOR 处理器名称 CMAKE_CXX_COMPILER 编译器路径 $ENV{shell变量名} 获取shell变量的内容 CMAKE_SIZEOF_VOID_P void*的sizeof大小,用于判断位宽
示例-1
工程结构
工程源码
使用
modules/static 下是加法运算的实现,将用作编译为静态库,编译后会放到 lib 下(libadd.a)
modules/shared 下是减法运算的实现,将用作编译为动态库,编译后会放到 lib 下(libsub.so)
samples 下是调用库进行使用,main() 所在
示例-2
工程结构
工程源码
使用