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)

Ubuntu下安装最新版本的CMake (ngui.cc)

Download | CMake

10-补充(完结)_哔哩哔哩_bilibili

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.c

    set(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_FLAGSCMAKE_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)

如何你只是想要打印一个变量,那么上述方法已经很好用了!如何你想要打印一些关于某些目标 (或者是其他拥有变量的项目,比如 SOURCESDIRECTORIESTESTS , 或 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。

posted @ 2023-06-11 22:08  zhengcixi  阅读(71)  评论(0编辑  收藏  举报
回到顶部