CMake学习
本文相关的源代码可从gitee上下载:https://gitee.com/mylayfolk/cmake-learning/tree/master/CMake_Study
参考
前言 - 《CMake菜谱(CMake Cookbook中文版)》 - 书栈网 · BookStack
The Architecture of Open Source Applications (Volume 1)CMake (aosabook.org)
CMake Reference Documentation — CMake 3.26.4 Documentation
CMake 从入门到精通 - 凌逆战 - 博客园 (cnblogs.com)
cmkae命令set_target_properties - yooooooo - 博客园 (cnblogs.com)
CMake Tutorial — CMake 3.27.0-rc1 Documentation
简介
CMake is a tool to manage building of source code. Originally, CMake was designed as a generator for various dialects of Makefile
, today CMake generates modern buildsystems such as Ninja
as well as project files for IDEs such as Visual Studio and Xcode.
CMake是一个管理编译源码构建的系统。最初,CMake被设计为各种Makefile的生成器,如今CMake产生各种现代构建系统。
安装CMake
Ubuntu下可以直接使用apt install命令安装,但是安装的版本比较低:
apt install cmake
或者直接从cmake官网下载最新版本,官网:Download | CMake
我下载的是cmake-3.26.4-linux-x86_64版本,下载完成后,先解压到/usr目录:
$ ls /usr/ -al
drwxr-xr-x 6 root root 4096 Jun 7 08:00 cmake-3.26.4-linux-x86_64/
$ ls /usr/cmake-3.26.4-linux-x86_64/ -al
drwxr-xr-x 2 root root 4096 May 18 10:57 bin/
drwxr-xr-x 3 root root 4096 May 18 10:57 doc/
drwxr-xr-x 4 root root 4096 May 18 10:57 man/
drwxr-xr-x 10 root root 4096 May 18 10:57 share/
然后创建软链接:
$ ln -s /usr/cmake-3.26.4-linux-x86_64/bin/* /usr/bin/
$ ls /usr/cmake-3.26.4-linux-x86_64/bin/
ccmake* cmake* cmake-gui* cpack* ctest*
$ ll /usr/bin/ccmake
lrwxrwxrwx 1 root root 41 Jun 7 08:03 /usr/bin/ccmake -> /usr/cmake-3.26.4-linux-x86_64/bin/ccmake*
$ ll /usr/bin/cmake
lrwxrwxrwx 1 root root 40 Jun 7 08:03 /usr/bin/cmake -> /usr/cmake-3.26.4-linux-x86_64/bin/cmake*
$ ll /usr/bin/cmake-gui
lrwxrwxrwx 1 root root 44 Jun 7 08:03 /usr/bin/cmake-gui -> /usr/cmake-3.26.4-linux-x86_64/bin/cmake-gui*
$ ll /usr/bin/cpack
lrwxrwxrwx 1 root root 40 Jun 7 08:03 /usr/bin/cpack -> /usr/cmake-3.26.4-linux-x86_64/bin/cpack*
$ ll /usr/bin/ctest
lrwxrwxrwx 1 root root 40 Jun 7 08:03 /usr/bin/ctest -> /usr/cmake-3.26.4-linux-x86_64/bin/ctest*
学习CMake
第一个CMake
$ tree
.
├── CMakeLists.txt
└── main.c
CMakeLists.txt文件内容:
cmake_minimum_required(VERSION 3.5)
PROJECT (LESSON00)
SET(SRC_LIST main.c)
MESSAGE(STATUS "Project name " ${PROJECT_NAME})
MESSAGE(STATUS "This is BINARY dir" ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir" ${PROJECT_SOURCE_DIR})
ADD_EXECUTABLE(lesson00 ${SRC_LIST})
main.c文件内容:
#include <stdio.h>
int main(void)
{
printf("This is first lesson!\n");
return 0;
}
执行CMake并编译:
$ cmake .
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Project name LESSON00
-- This is BINARY dir /root/cmake/CMake_Study/Study00
-- This is SOURCE dir /root/cmake/CMake_Study/Study00
-- Configuring done (1.0s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/CMake_Study/Study00
$ make
[ 50%] Building C object CMakeFiles/lesson00.dir/main.c.o
[100%] Linking C executable lesson00
[100%] Built target lesson00
$ ./lesson00
This is first lesson!
基本语法介绍
基本规则
-
变量使用
${}
方式取值,但是在IF控制语句中是直接使用变量名 -
指令(参数1 参数2...)
参数使用括弧括起,参数之间使用空格或分号分开。 以ADD_EXECUTABLE
指令为例,如果存在另外一个func.c源文件就要写成:ADD_EXECUTABLE(LESSON00 main.c func.c)
或者ADD_EXECUTABLE(LESSON00 main.c;func.c)
-
指令是大小写无关的,参数和变量是大小写相关的。但推荐全部使用大写指令
基本指令
这里将后面也会用到的一些命令都放在这里统一说明。
-
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])
功能:设置项目所需最低的cmake版本,如果运行的cmake版本低于所需的最低
<min>
版本,则会报错并停止。
参数<min>
:最低版本号
注意:这个函数应该在CMakeLists.txt
文件的最开头的地方,在调用project()命令之前。 -
project(<PROJECT-NAME> [<language-name>...])
功能:设置工程的名字和支持的语言,并将其存放在变量
<PROJECT_NAME>
中,如果是在CMakeLists.txt顶层调用,还会存放工程名在CMAKE_PROJECT_NAME
变量。
参数<PROJECT-NAME>
:工程的名字
可选参数<language-name>
:不指定默认支持所有语言。
举例:
PROJECT (LESSON0)
:指定了工程的名字,并且支持所有语言—建议
PROJECT (LESSON0 CXX)
:指定了工程的名字,并且支持语言是C++
PROJECT (LESSON0 C CXX)
:指定了工程的名字,并且支持语言是C和C++ -
预定义变量
PROJECT_BINARY_DIR
:编译工程的构建目录预定义变量
PROJECT_SOURCE_DIR
:当前工程最上层目录预定义变量
EXECUTABLE_OUTPUT_PATH
:目标二进制可执行文件的存放位置预定义变量
LIBRARY_OUTPUT_PATH
:库文件的默认输出路径 -
set(<variable> <value>... [PARENT_SCOPE])
功能:将普通变量、缓存变量、环境变量设置给定值。
参数
<variable>
:变量名参数
<value>
:变量的值举例:
set(SRC_LIST main.c)
:SRC_LIST变量包含了main.cset(CMAKE_C_STANDARD 11)
:指定C语言标准 -
message([<mode>] "message text" ...)
功能:向终端输出用户自定义的信息,如果给定多条字符串消息,它们被连接成一条消息,并且没有字符串之间的分隔符。
<mode>
可选参数:消息的类型,影响消息的处理方式- FATAL_ERROR:CMake错误,停止处理和生成
- SEND_ERROR:CMake错误,继续处理,但提供生成
- WARNING:CMake告警,继续处理
- NOTICE(默认值):重要消息打印到标准输出
- STATUS:项目用户可能感兴趣的主要消息。 理想情况下应该简洁,不超过一行。
- VERBOSE:面向项目用户的详细信息。
-
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] [source1] [source2 ...])
功能:将源文件目录加到生成可执行文件的目录中。
参数
<name>
:生成的可执行文件名可选参数
[source1]
:构建目标可执行文件的源文件列表 -
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])
功能:将子目录添加到生成系统中。可以指定源文件的子目录,也可以指定生成中间文件的存放目录。
参数
source_dir
:指定子文件中CMakeLists.txt目录和源代码目录,该目录下的CMakeLists.txt文件用于编译该文件夹下的源码。可选参数
binary_dir
:指定存放输出文件的目录可选参数
EXCLUDE_FROM_ALL
:排除子目录包含在构建系统。举例:
ADD_SUBDIRECTORY(src bin)
:将src子目录加入工程,并指定输出为bin目录。 -
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [<source>...])
功能:枸橘源文件构建动态库、静态库
参数
<name>
:构建的库名,最终生成的库名lib<name>.a
/lib<name>.so
可选参数
[STATIC | SHARED | MODULE]
:生成的库类型,STATIC生成静态库,SHARED生成动态库,可选参数
[<source>...]
:生成库的源文件列表 -
set_target_properties(target1 target2 ... PROPERTIES prop1 value1 prop2 value2 ...)
功能:设置目标target的属性,命令语法:列出想要更改的所有目标,然后提供想要设置的值,可以使用该命令设置任何需要的键值对,然后使用
get_property()
或get_target_property()
命令提取。 -
aux_source_directory(<dir> <variable>)
功能:搜集指定目录
<dir>
下所有源文件,然后存放到<variable>
变量中。 -
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
功能:添加给定目录下的为头文件搜索目录。
-
find_library (<VAR> name1 [path1 path2 ...])
功能:在指定目录下查找指定库,并把库的绝对路径存放到变量里,默认查找动态库。
参数
<VAR>
:变量名称参数
name1
:库名称参数
[path1 path2 ...]
:库的路径 -
target_link_libraries(<target> ... <item>... ...)
功能:把目标文件与库文件进行链接
参数
<target>
:目标文件参数
<item>
:库名 -
add_compile_options(<option> ...)
功能:添加编译选项属性,这些属性编译当前目录和子目录时使用
-
add_definitions(-DFOO -DBAR ...)
功能:向C/C++编译器添加-D定义的宏
-
option(<variable> "<help_text>" [value])
功能:提供用户可选择的bool类型选项
参数
<variable>
:option的名字参数
"<help_text>"
:描述option可选参数
[value]
:option的值,ON或者OFF,默认为OFF -
add_dependencies(<target> [<target-dependency>]...)
功能:定义
<target>
依赖的其他<target-dependency>
,确保在编译本target之前,其他的target已经被构建。 -
link_libraries([item1 [item2 [...]]] [[debug|optimized|general] <item>] ...)
功能:将库链接到以后添加的所有目标
-
if(<condition>) <commands> elseif(<condition>) # optional block, can be repeated <commands> else() # optional block <commands> endif() ##### IF(var),如果变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或<var>_NOTFOUND 时,表达式为真。 IF(NOT var ),与上述条件相反。 IF(var1 AND var2),当两个变量都为真是为真。 IF(var1 OR var2),当两个变量其中一个为真时为真。 IF(COMMAND cmd),当给定的 cmd 确实是命令并可以调用是为真。 IF(EXISTS dir)或者 IF(EXISTS file),当目录名或者文件名存在时为真。 IF(file1 IS_NEWER_THAN file2),当 file1 比 file2 新,或者 file1/file2 其中有一个不存在时为真,文件名请使用完整路径。 IF(IS_DIRECTORY dirname),当 dirname 是目录时,为真。 IF(variable MATCHES regex) IF(string MATCHES regex)
-
foreach(<loop_var> <items>) <commands> endforeach()
其中
<items>
是以分号或空格分隔的项目列表。记录foreach匹配和匹配之间的所有命令endforeach而不调用。 一旦endforeach评估,命令的记录列表中的每个项目调用一次<items>
。在每次迭代开始时,变量loop_var将设置为当前项的值。 -
while(<condition>) <commands> endwhile()
while和匹配之间的所有命令 endwhile()被记录而不被调用。 一旦endwhile()如果被评估,则只要为
<condition>
真,就会调用记录的命令列表。
注意事项
SET(SRC_LIST main.c)
可以写成SET(SRC_LIST "main.c")
,如果源文件名中含有空格,就必须要加双引号。ADD_EXECUTABLE(hello main)
后缀可以不写,会自动去找.c和.cpp,最好不要这样写,可能会有这两个文件main.cpp和main。
外部构建
目的:将源文件和生成的中间文件分开
$ tree
.
├── build
│ └── rebuild.sh
├── CMakeLists.txt
└── main.c
$ cd build/
[root@ubuntu] ~/cmake/CMake_Study/Study00/build
$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Project name LESSON00
-- This is BINARY dir/root/cmake/CMake_Study/Study00/build
-- This is SOURCE dir/root/cmake/CMake_Study/Study00
-- Configuring done (0.7s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/CMake_Study/Study00/build
$ make
[ 50%] Building C object CMakeFiles/lesson00.dir/main.c.o
[100%] Linking C executable lesson00
[100%] Built target lesson00
[root@ubuntu] ~/cmake/CMake_Study/Study00/build
$ ./lesson00
This is first lesson!
生成的文件都存放在:/root/cmake/CMake_Study/Study00/build
指定源文件和生成文件路径
$ tree
.
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.c
外层CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
PROJECT(LESSON00)
ADD_SUBDIRECTORY(src bin)
src下的CMakeLists.txt:
ADD_EXECUTABLE(lesson00 main.c)
执行、测试:
$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.6s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/CMake_Study/Study00/build
$ ll
drwxr-xr-x 4 root root 4096 Jun 10 21:05 ./
drwxr-xr-x 5 root root 4096 Jun 10 20:42 ../
drwxr-xr-x 3 root root 4096 Jun 10 21:05 bin/
-rw-r--r-- 1 root root 14245 Jun 10 21:05 CMakeCache.txt
drwxr-xr-x 5 root root 4096 Jun 10 21:05 CMakeFiles/
-rw-r--r-- 1 root root 1810 Jun 10 21:05 cmake_install.cmake
-rw-r--r-- 1 root root 4597 Jun 10 21:05 Makefile
$ ls bin/
CMakeFiles/ cmake_install.cmake Makefile
[root@ubuntu] ~/cmake/CMake_Study/Study00/build
$ make
[ 50%] Building C object bin/CMakeFiles/lesson00.dir/main.c.o
[100%] Linking C executable lesson00
[100%] Built target lesson00
[root@ubuntu] ~/cmake/CMake_Study/Study00/build
$ ls bin/
CMakeFiles/ cmake_install.cmake lesson00* Makefile
$ ./bin/lesson00
This is first lesson!
可以看出,编译生成的可执行文件在build/bin目录下。
src下的CMakeLists.txt:
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
ADD_EXECUTABLE(lesson00 main.c)
EXECUTABLE_OUTPUT_PATH:定义可执行文件输出路径变量
LIBRARY_OUTPUT_PATH:定义库输出路径变量
静态库和动态库构建
目录结构:
$ tree
.
├── build # 存放临时生成的文件
├── CMakeLists.txt # 顶层CMakeLists.txt
├── lib # 存放生成的库
└── libsrc # 生成库的源文件
├── CMakeLists.txt
├── func.c
└── func.h
顶层CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
PROJECT(FUNC)
ADD_SUBDIRECTORY(libsrc bin)
libsrc目录下的CMakeLists.txt,同时生成动态库和静态库:
SET(LIBFUNC_SRC func.c)
# 对 源文件变量 生成动态库 func_shared
add_library(func_shared SHARED ${LIBFUNC_SRC})
# 对 源文件变量 生成静态库 func_static
add_library(func_static STATIC ${LIBFUNC_SRC})
# 设置最终生成的库的名称
set_target_properties(func_shared PROPERTIES OUTPUT_NAME "func")
set_target_properties(func_static PROPERTIES OUTPUT_NAME "func")
# 指定动态库版本, VERSION:指代动态库版本 SOVERSION:指代API版本
SET_TARGET_PROPERTIES(func_shared PROPERTIES VERSION 1.2 SOVERSION 1)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
执行,生成动态库:
$ cd build/
$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.9s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/CMake_Study/GenLib/build
输出文件:
$ make
[ 25%] Building C object bin/CMakeFiles/func_shared.dir/func.c.o
[ 50%] Linking C shared library /root/cmake/CMake_Study/GenLib/lib/libfunc.so
[ 50%] Built target func_shared
[ 75%] Building C object bin/CMakeFiles/func_static.dir/func.c.o
[100%] Linking C static library /root/cmake/CMake_Study/GenLib/lib/libfunc.a
[100%] Built target func_static
$ ll ../lib/
-rw-r--r-- 1 root root 1654 Jun 11 00:12 libfunc.a
lrwxrwxrwx 1 root root 12 Jun 11 00:12 libfunc.so -> libfunc.so.1*
lrwxrwxrwx 1 root root 14 Jun 11 00:12 libfunc.so.1 -> libfunc.so.1.2*
-rwxr-xr-x 1 root root 8112 Jun 11 00:12 libfunc.so.1.2*
对库进行链接
调用上面的生成的库进行测试,目录结构如下:
$ tree
.
├── bin # 存放生成的可执行文件
├── build # 存放临时生成的文件
├── CMakeLists.txt # 顶层CMakeLists.txt
├── module # 库和头文件
│ ├── include
│ │ └── func.h
│ └── lib
│ ├── libfunc.a
│ ├── libfunc.so
│ ├── libfunc.so.1
│ └── libfunc.so.1.2
└── src # main函数源文件
└── main.c
顶层CMakeLists.txt中的内容:
cmake_minimum_required(VERSION 3.10)
project(cmake_study)
# 输出bin文件路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 将源代码添加到变量
set(src_list ${PROJECT_SOURCE_DIR}/src/main.c)
# 添加头文件搜索路径
include_directories(${PROJECT_SOURCE_DIR}/module/include)
# 在指定路径下查找库,并把库的绝对路径存放到变量里
find_library(FUNC_LIB func HINTS ${PROJECT_SOURCE_DIR}/module/lib)
# 执行源文件
add_executable(main ${src_list})
# 把目标文件与库文件进行链接
target_link_libraries(main ${FUNC_LIB})
执行,编译,生成可执行文件:
$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.6s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/CMake_Study/TestLib/build
$ make
[ 50%] Building C object CMakeFiles/main.dir/src/main.c.o
[100%] Linking C executable /root/cmake/CMake_Study/TestLib/bin/main
[100%] Built target main
$ readelf -d ../bin/main
Dynamic section at offset 0xe08 contains 26 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libfunc.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000f (RPATH) Library rpath: [/root/cmake/CMake_Study/TestLib/module/lib]
$ ./../bin/main
./../bin/main: error while loading shared libraries: libfunc.so.1: cannot open shared object file: No such file or directory
$ export LD_LIBRARY_PATH=../module/lib/
$ ./../bin/main
Generate library!
同一个目录下多个源文件
目录结构:
$ tree
.
├── build # 存放临时生成文件
├── CMakeLists.txt
├── main.c
├── test.c
└── test.h
CMakeLists.txt文件的内容:
cmake_minimum_required(VERSION 3.10)
project(cmake_study)
add_executable(cmake_study
main.c
test.c)
各个源文件内容:
$ more main.c
#include "test.h"
int main(void)
{
func(100);
return 0;
}
$ more test.h
#ifndef TEST_H
#define TEST_H
void func(int data);
#endif
$ more test.c
#include <stdio.h>
void func(int data)
{
printf("data is %d\n", data);
}
如果源文件太多,可以使用aux_source_directory
命令将当前目录下的源文件存放到列表变量里,然后在add_executable
里调用列表变量,修改CMakeLists.txt文件如下:
cmake_minimum_required(VERSION 3.10)
project (cmake_study)
# 将当前目录下的源代码,收集到变量src_path
aux_source_directory(. src_path)
add_executable(cmake_study ${src_path})
aux_source_directory
缺点:会把指定目录下的所有源文件都加入。
不同目录下多个源文件
目录结构:
tree
.
├── build # 存放临时文件
├── CMakeLists.txt # 顶层CMakeLists.txt
├── module0 # module0源文件
│ ├── module0.c
│ └── module0.h
├── module1 # module1源文件
│ ├── module1.c
│ └── module1.h
└── src # main函数
└── main.c
顶层CMakeLists.txt文件的内容:
cmake_minimum_required(VERSION 3.10)
project(cmake_study C)
# 添加 头文件 的搜索路径
include_directories(module0 module1)
# 将路径的源文件收集到变量列表
aux_source_directory(module0 src_path)
aux_source_directory(module1 src_path)
add_executable(cmake_study
src/main.c ${include_path} ${src_path})
各个源文件内容:
$ more module0/module0.h
#ifndef MODULE0_H
#define MODULE0_H
void module0_func(int data);
#endif
$ more module0/module0.c
#include <stdio.h>
void module0_func(int data)
{
printf("Module0 data is %d\n", data);
}
$ more module1/module1.h
#ifndef MODULE1_H
#define MODULE1_H
void module1_func(int data);
#endif
$ more module1/module1.c
#include <stdio.h>
void module1_func(int data)
{
printf("Module1 data is %d\n", data);
}
$ more src/main.c
#include "module0.h"
#include "module1.h"
int main(void)
{
module0_func(100);
module1_func(200);
return 0;
}
源文件和头文件分开
目录结构:
$ tree
.
├── bin # 存放输出的文件
├── build # 存放中间生成文件
├── CMakeLists.txt # 顶层CMakeLists.txt
├── include # 头文件目录
│ ├── module0.h
│ └── module1.h
└── src # 源文件目录
├── CMakeLists.txt # 子目录CMakeLists.txt
├── main.c
├── module0.c
└── module1.c
顶层CMakeLists.txt内容:
cmake_minimum_required (VERSION 3.10)
project (cmake_study)
# 定义变量, 存放可执行文件输出路径
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 向当前工程添加存放源文件的子目录
add_subdirectory(src)
src子目录CMakeLists.txt:
include_directories(../include)
aux_source_directory(. SRC_LIST)
add_executable(main ${SRC_LIST})
执行,编译,生成文件在bin目录:
$ cd bin/
$ ls
main*
$ ./main
Module0 data is 100
Module1 data is 200
添加编译和控制选项
添加编译选项
CMAKE_C_COMPILER
:指定C编译器
CMAKE_C_FLAGS
:添加C文件编译选项,也可以通过add_definitions
命令添加
在cmake脚本中,设置编译选项(配置编译器)有如下三种方法:
-
add_compile_options
命令add_compile_options(-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes)
-
add_definitions
命令add_definitions("-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes")
-
set命令修改
CMAKE_CXX_FLAGS
或CMAKE_C_FLAGS
set(CMAKE_C_FLAGS "-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes")
使用这三种方式在有的情况下效果是一样的,但请注意它们还是有区别的:
add_compile_options
命令和add_definitions
添加的编译选项是针对所有编译器的(包括c和c++编译器),
set命令设置CMAKE_C_FLAGS
或 CMAKE_CXX_FLAGS
变量则是分别只针对c和c++编译器的。
添加控制选项
适用情况:在编译代码时仅编译一些指定的代码,可以使用cmake的option选项,主要有两种情况:
- 本来要生成多个bin或库文件,现在只想生成部分指定的bin或库文件
- 对于同一个bin文件,只编译其中部分代码(使用宏控制)
(1)第一种情况举例:假设工程会生成两个bin文件
$ tree
.
├── bin # 存放生成的二进制文件
├── build # 存放中间文件
├── CMakeLists.txt
└── src
├── CMakeLists.txt
├── main1.c # main1可执行文件
└── main2.c # main2可执行文件
顶层CMakeLists.txt内容如下:
cmake_minimum_required(VERSION 3.10)
project(cmake_study)
# 描述选项
option(MYDEBUG "enable debug compilation" OFF)
# 设置输出bin的地址
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 添加源文件的子目录
add_subdirectory(src)
src目录下的CMakeLists.txt内容如下:
cmake_minimum_required(VERSION 3.10)
project(cmake_study)
# 执行源文件
add_executable(main1 main1.c)
if (MYDEBUG)
add_executable(main2 main2.c) # 执行源文件
else()
message(STATUS "Currently is not in debug mode")
endif()
main1.c和main2.c内容如下:
$ more src/main1.c
#include <stdio.h>
int main(void)
{
printf("main1 func\n");
return 0;
}
$ more src/main2.c
#include <stdio.h>
int main(void)
{
printf("main2 func\n");
return 0;
}
生成、编译、执行:
# 默认执行编译main1.c
$ cmake .. && make && ./../bin/main1
main1 func
# 指定MYDEBUG为ON,同时编译main2.c
$ cmake .. -DMYDEBUG=ON && make && ./../bin/main2
main2 func
(2)第二种情况,对于同一个bin,仅编译部分代码。
$ tree
.
├── bin
├── build
├── CMakeLists.txt
└── main.c
假设main.c内容如下:
#include <stdio.h>
int main(void)
{
#ifdef FUNC1
printf("main1 func\n");
#endif
#ifdef FUNC2
printf("main2 func\n");
#endif
return 0;
}
顶层CMakeLists.txt内容如下:
cmake_minimum_required(VERSION 3.10)
project(cmake_study)
# 设置输出bin文件的地址
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 设置选项WWW1和WWW2,默认关闭
option(FUNC1 "print one message" OFF)
option(FUNC2 "print another message" OFF)
if (FUNC1)
add_compile_options(-DFUNC1)
endif ()
if (FUNC2)
add_compile_options(-DFUNC2)
endif ()
# 执行源文件
add_executable(main main.c)
各种情况下的生成、编译、执行:
$ cmake .. -DFUNC1=ON -DFUNC2=OFF && make && ./../bin/main
main1 func
$ cmake .. -DFUNC1=OFF -DFUNC2=ON && make && ./../bin/main
main2 func
$ cmake .. -DFUNC1=ON -DFUNC2=ON && make && ./../bin/main
main1 func
main2 func
调试CMake
参考:调试 · Modern CMake (modern-cmake-cn.github.io)
打印变量
通常我们使用的打印语句如下:
message(STATUS "MY_VARIABLE=${MY_VARIABLE}")
然而,通过一个内置的模组 CMakePrintHelpoers
可以更方便的打印变量:
include(CMakePrintHelpers)
cmake_print_variables(MY_VARIABLE)
如何你只是想要打印一个变量,那么上述方法已经很好用了!如何你想要打印一些关于某些目标 (或者是其他拥有变量的项目,比如 SOURCES
、DIRECTORIES
、TESTS
, 或 CACHE_ENTRIES
- 全局变量好像因为某些原因缺失了) 的变量,与其一个一个打印它们,你可以简单的列举并打印它们:
cmake_print_properties(
TARGETS my_target
PROPERTIES POSITION_INDEPENDENT_CODE
)
跟踪运行
你可能想知道构建项目的时候你的 CMake 文件究竟发生了什么,以及这些都是如何发生的?用 --trace-source="filename"
就很不错,它会打印出你指定的文件现在运行到哪一行,让你可以知道当前具体在发生什么。
例子:
cmake -S . -B build --trace-source=CMakeLists.txt
$ cmake -S . -B build --trace-source=CMakeLists.txt
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(9): cmake_minimum_required(VERSION 3.1...3.21 )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(13): project(ModernCMakeExample VERSION 1.0 LANGUAGES CXX )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(29): add_library(MyLibExample simple_lib.cpp simple_lib.hpp )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(34): add_executable(MyExample simple_example.cpp )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(38): target_link_libraries(MyExample PRIVATE MyLibExample )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(40): message(STATUS PROJECT_VERSION=${PROJECT_VERSION} )
-- PROJECT_VERSION=1.0
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(41): include(CMakePrintHelpers )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(42): cmake_print_variables(PROJECT_VERSION )
-- PROJECT_VERSION="1.0"
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(48): enable_testing()
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(49): add_test(NAME MyExample COMMAND MyExample )
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/share/Modern-CMake-zh_CN/examples/simple-project/build
以debug模式构建
对于单一构建模式的生成器 (single-configuration generators),你可以使用参数 -DCMAKE_BUILD_TYPE=Debug
来构建项目,以获得调试标志 (debugging flags)。
如果你使用了 debug 模式构建,你就可以在上面运行调试器了,比如 gdb。