C++编译之(3)-camke/CMakeLists.txt的编译使用教程

引言

上一节介绍了前面我们介绍了make/Makefile来对c++项目进行编译,我们继续以该项目为例讲解;

C++编译之(1)-g++单/多文件/库的编译
C++编译之(2)-make及makefile编译过程

我们先看看上一节的实战的目录结构如下:

- mutilFilesDemo
  - include // 头文件目录
    - HelloTools.h
    - Prints.h
  - libs // 库子项目目录
    - ToolLibs.h
    - ToolLibs.cpp
    - Makefile  // 子项目Makefile
  - src // 源码目录
    - module // 源码模块
      - Prints.cpp // Prints类
    - HelloTools.cpp // HelloTools类
  - main.cpp // main类
  - Makefile  // 主项目Makefile

make与Makefile,cmake与CMakeLists.txt

make用来编译c++项目,make命令根据Makefile中配置的编译链接关系;由于Makefile文件的制作是个大工程,因此出现了cmake工具cmake根据CMakeLists.txt来执行cmake命令;

使用CMake编写跨平台工程的流程如下:

(1)编写源文件

(2)编写CMakeLists.txt

(3)由CMake根据CMakeLists.txt来生成相应的makefile文件

(4)使用make并根据makefile调用gcc来生成相应的可执行文件

CMake是一个可以跨平台的编译工具,可以用简单的语句来描述所有平台的编译过程。他能够输出各种各样的 makefile 或者工程文件。和make与makefile类似,我们在使用CMake时同样也需要一个文件来提供规则,这个文件就是CMakeLists

CMakeLists.txt

编写CMakeLists.txt最常用的功能就是调用其他的.h头文件和.so/.a库文件,将.cpp/.c/.cc文件编译成可执行文件或者新的库文件。

# 指定cmake最小版本
cmake_minimum_required(VERSION 3.4.1)

# 本CMakeLists.txt的project名称
# 会自动创建两个变量,PROJECT_SOURCE_DIR和PROJECT_NAME 
# ${PROJECT_SOURCE_DIR}:本CMakeLists.txt所在的文件夹路径
# ${PROJECT_NAME}:本CMakeLists.txt的project名称
# CMAKE_BINART_DIR, PROJECT_BINARY_DIR, <projectName>_BINARY_DIR:这三个变量的含义一样。
project(xxx)

# 获取路径下所有的.cpp/.c/.cc文件,并赋值给变量中
aux_source_directory(路径 变量)

# 给文件名/路径名或其他字符串起别名,用${变量}获取变量内容
set(变量 文件名/路径/...)

# 添加编译选项
add_definitions(编译选项)

# 打印消息
message(消息)

# 编译子文件夹的CMakeLists.txt
add_subdirectory(./subProject) 

# 将.cpp/.c/.cc文件生成可执行文件
add_executable(main main.cpp)

# 将.cpp/.c/.cc文件生成.a静态库 // add_library(库文件名称 STATIC 文件)
# 注意,库文件名称通常为libxxx.so,在这里只要写xxx即可
# 生成静态库(默认) 
add_library(util STATIC util.cpp)
# 生成动态库
add_library(util SHARED util.cpp)

# 规定.h头文件路径
include_directories(路径)

# 规定.so/.a库文件路径
link_directories(路径)

# 对add_library或add_executable生成的文件进行链接操作
# 注意,库文件名称通常为libxxx.so,在这里只要写xxx即可
target_link_libraries(可执行文件名称 链接的库文件名称)
# 如果是链接的动态库,则运行的时候需要把它拷贝到可执行文件名称同目录下

如何设置DGB调试模式请看这里

CMakeLists的编写过程的方法

1. 指定 cmake 的最小版本

cmake_minimum_required(VERSION 3.4.1)

2.设置项目名

project(myProject)

它会引入两个变量 myProject_BINARY_DIR 和 myProject_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR

3.设置编译类型

// 将.cpp/.c/.cc文件生成可执行文件
add_executable(main main.cpp)
// 生成静态库(默认) 
add_library(util STATIC util.cpp tools.cpp)
// 生成动态库
add_library(util SHARED util.cpp tools.cpp)

不加STATIC|SHARED 默认为生成静态库;此外,生成的库,默认会加前缀lib,及后缀*.a|*.so

4.搜索所有cpp文件

aux_source_directory(. SRC_LIST)
add_library(demo ${
    
    SRC_LIST})

发现一个目录下所有的源代码文件,并将列表存储再一个变量中(不会递归遍历目录)

5.自定义搜索规则

file(GLOB SRC_LIST "*.cpp" "protocol/*.cpp")
add_library(demo ${
    
    SRC_LIST})
#或者
file(GLOB SRC_LIST "*.cpp")
file(GLOB SRC_PROTOCOL_LIST "protocol/*.cpp")
add_library(demo ${
    
    SRC_LIST} ${
    
    SRC_PROTOCOL_LIST})
#或者
aux_source_directory(. SRC_LIST)
aux_source_directory(protocol SRC_PROTOCOL_LIST)
add_library(demo ${
    
    SRC_LIST) ${
    
    SRC_PROTOCOL_LIST)

6.指定查找的库文件

find_library( # Sets the name of the path variable.
              log-lib
 
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

查找到指定的预编译库,并将它的路径存储在变量中。默认的搜索路径为 cmake 包含的系统库。

类似的命令还有 find_file()、find_path()、find_program()、find_package()。

7.设置包含的目录

include_directories(
    ${
    
    CMAKE_CURRENT_SOURCE_DIR}
    ${
    
    CMAKE_CURRENT_BINARY_DIR}
    ${
    
    CMAKE_CURRENT_SOURCE_DIR}/include
)

Linux 下还可以通过如下方式设置包含的目录

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}")

8.设置链接库搜索目录

link_directories(
    ${
    
    CMAKE_CURRENT_SOURCE_DIR}/libs
)

Linux 下还可以通过如下方式设置包含的目录

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/libs")

9.设置target需要链接的库

target_link_libraries( # 目标库
                       demo
 
                       # 目标库需要链接的库
                       # log-lib 是上面 find_library 指定的变量名
                       ${
    
    log-lib} )
target_link_libraries(demo libface.a) # 链接libface.a
target_link_libraries(demo libface.so) # 链接libface.so

注意,库文件名称通常为libxxx.so,在这里只要写xxx即可,如需要明确指定可加上前后缀

10.使用变量

# set 直接设置变量的值
set(SRC_LIST main.cpp test.cpp)
add_executable(demo ${
    
    SRC_LIST})

# set 追加设置变量的值
set(SRC_LIST main.cpp)
set(SRC_LIST ${
    
    SRC_LIST} test.cpp)
add_executable(demo ${
    
    SRC_LIST})

# list 追加或者删除变量的值
set(SRC_LIST main.cpp)
list(APPEND SRC_LIST test.cpp)
list(REMOVE_ITEM SRC_LIST main.cpp)
add_executable(demo ${
    
    SRC_LIST})

11.添加编译选项

功能1

add_definitions的功能和C/C++中的#define是一样的

# 添加一个宏定义,设置后,代码中即可使用 `TEST_DEBUG`这个宏,等同在代码中增加 #define TEST_DEBUG
add_definitions(-DTEST_DEBUG)

于是cpp代码中,就可以使用TEST_DEBUG这个宏了

#ifdef TEST_DEBUG
...
#else 
...
#endif

问题是,由谁来驱动定义这个宏呢?通过结合options指令可以实现

# 设置一个宏选项默认值ON,
# option选项 不会影响到cpp代码(不会添加#define宏)
option(TEST_DEBUG "test"  ON)
if(TEST_DEBUG )
	message("itis" ${TEST_IT_CMAKE})
	add_definitions(-DTEST_DEBUG )
endif()

最后,在执行cmake时,就可以设置该选项了,并传递该值下去;

cmake .. -DTEST_DEBUG=1

通过cmake-gui也可以很方便的配置选项,如下:
在这里插入图片描述

功能2
add_definitions("-Wall -g")
#没加之前
gcc -c main.c -o test
#添加之后,相当于
gcc -g -Wall -c main.c -o tes

12.复杂项目,子项目

# 添加subProjectDir子目录
add_subdirectory(subProjectDir)

subProjectDir目录的CMakeLists.txt可以这样写:

aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library(subFuns ${
    
    DIR_LIB_SRCS})

13.其他

  1. 常用的预定义变量
变量 说明
PROJECT_SOURCE_DIR 工程根目录
PROJECT_BINARY_DIR 运行cmake命令的目录,通常是${PROJECT_SOURCE_DIR}/build
PROJECT_NAME 返回通过project命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR 当前处理的CMakeLists.txt所在的路径
CMAKE_CURRENT_BINARY_DIR target 编译目录
CMAKE_CURRENT_LIST_DIR CMakeLists.txt的完整路径
CMAKE_CURRENT_LIST_LINE 当前所在的行
CMAKE_MODULE_PATH 定义自己cmake模块所在的路径。SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
EXECUTABLE_OUTPUT_PATH 重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH 重新定义目标链接库的存放位置
  1. 环境变量

    1. 使用环境变量$ENV{name}
    2. 写入环境变量 set(ENV{name} value)
  2. cmake命令的常用参数

Cmake命令行使用:
-G 指明生成的Makefile格式
-D 添加参数
-S 指明源码位置
-B 指明输出路径
例如:
cmake -G “MinGW Makefiles” -S “源码路径” -B “输出路径”
或者
cmake -G “MinGW Makefiles” -D CMAKE_TOOLCHAIN_FILE=“编译工具链路径” -S “源码路径” -B “输出路径”
注意:Cmake是不支持中文的,无论是GUI还是命令行,都严禁出现中文字符.
  1. Do’s and Don’ts
    不好的 CMake 的用法
        不要使用全局函数,例如 link_directories,include_libraries,add_definitions 等,请你忘记它们
        不要滥用 PUBLIC,除非有依赖传递,否则请你使用 PRIVATE 替换 PUBLIC
        不要使用 GLOB 来添加文件
        不要直接链接文件,而是链接目标
        链接时千万不要跳过 PUBLIC/PRIVATE,这会导致未来的链接都没有关键字

    良好的 CMake 用法
        把 CMake 视作代码,保持它的整洁和可读性
        围绕 target 构建你的 CMake。将需要的信息打包在 target 里,然后链接那个目标
        导出你的接口
        写 Config.cmake 文件,这是一个库作者应该做的,可以方便别人使用你的库
        使用 ALAS 目标,以保持使用一致性
        将常用的功能提取成函数或者宏,通常函数更好
        使用小写的函数名,全大写是变量
        使用 cmake_policy 或者 range of versions

如何使用CMakeLists.txt 完成一次编译过程

我们以上一节的项目为例,改造成CMakeLists.txt实现编译。我们首先删除两个Makefile文件,并创建两个CMakeLists.txt,同样是多文件夹、多项目的多层次编译案例,如下所示(其他文件不用改)

- mutilFilesDemo
  - include // 头文件目录
    - HelloTools.h
    - Prints.h
  - libs // 库子项目目录
    - ToolLibs.h
    - ToolLibs.cpp
    - CMakeLists.txt  // 子项目CMakeLists.txt
  - src // 源码目录
    - module // 源码模块
      - Prints.cpp // Prints类
    - HelloTools.cpp // HelloTools类
  - main.cpp // main类
  - CMakeLists.txt  // 主项目CMakeLists

如未看过前面的教程,请点这里看上一节

1、我们首先给出子项目libsCMakeLists.txt的源码

CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)
project(subLibPro)
aux_source_directory(. SRC_LIST)
add_library(ToolLibs STATIC ${
    
    SRC_LIST})

我们进入libs目录,首先创建一个build临时的编译目录,再执行一下cmake

$ cd mutilFilesDemo/libs
$ mkdir build
$ cd build
$ cmake ..
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/mutilFilesDemo/libs/build

执行成功后,在build目录下自动创建了Makefile,有了这个文件,我们可以直接执行make进行编译

# 先看看cmake后,创建了写啥东西
$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile
# 在执行一下make
$ make
Scanning dependencies of target ToolLibs
[ 50%] Building CXX object CMakeFiles/ToolLibs.dir/ToolLibs.cpp.o
[100%] Linking CXX static library libToolLibs.a
[100%] Built target ToolLibs
# 编译成功后,最后再查看一下目录是否生成我们需要的静态库
$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  libToolLibs.a  Makefile

我们看到,成功生成了我们需要的libToolLibs.a

2、接着我们看看如何创建主项目的CMakeLists.txt

直接先贴出代码

cmake_minimum_required(VERSION 3.4.1)

# 项目名称
project(mainPro)

# 设置子项目目录变量-libs
set(SUB_PROJ_LIBS_DIR libs)

# 设置主项目依赖的库名变量-libToolLibs.a
set(MAIN_LIBS ToolLibs)

# 添加子项目
add_subdirectory(${
    
    SUB_PROJ_LIBS_DIR})

# 搜索主项目源码
file(GLOB SRC_LIST "*.cpp" "src/*.cpp" "src/modules/*.cpp")

# 生成可执行目标
add_executable(${
    
    PROJECT_NAME} ${
    
    SRC_LIST})

# 添加库目录
link_directories(${
    
    PROJECT_NAME} ${
    
    SUB_PROJ_LIBS_DIR})

# 添加链接库
target_link_libraries(${
    
    PROJECT_NAME} ${
    
    MAIN_LIBS})

我们在mutilFilesDemo目录下创建一个build目录,用于生成编译文件;同时删除前面子项目编译过程中,创建的build目录

$ cd mutilFilesDemo
$ mkdir build
$ cd mutilFilesDemo/libs
$ rm -rf ./build
$ cd mutilFilesDemo/build
$ cmake ..
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/mutilFilesDemo/build

成功创建Makefile,继续执行make

$ make
Scanning dependencies of target ToolLibs
[ 16%] Building CXX object libs/CMakeFiles/ToolLibs.dir/ToolLibs.cpp.o
[ 33%] Linking CXX static library libToolLibs.a
[ 33%] Built target ToolLibs
Scanning dependencies of target mainPro
[ 50%] Building CXX object CMakeFiles/mainPro.dir/main.cpp.o
[ 66%] Building CXX object CMakeFiles/mainPro.dir/src/HelloTools.cpp.o
[ 83%] Building CXX object CMakeFiles/mainPro.dir/src/modules/Prints.cpp.o
[100%] Linking CXX executable mainPro
[100%] Built target mainPro

非常完美,一点不拖泥带水;子库项目的CMakeLists.txt自动执行,而且自动帮你把项目编译的过程文件全部统一放在了最外层的build目录下,就像我们上一节直接用Makefile编写的一样;而且静态库自动链接上,只需要指定静态库的大致目录即可(甚至不需要指定link_directories,cmake会自动帮你在整个编译的工作目录下即build自动搜素需要的库)

们尝试执行一下mainPro

$ ./mainPro
Hello world!
MAX_NUM+n:110
=================================
使用静态库-add(a,b)
结果为:a+b=500

上一节的结果一模一样

参考文献
C++编译之(1)-g++单/多文件/库的编译
C++编译之(2)-make及makefile编译过程
CMakeLists的基本使用方法
https://zhuanlan.zhihu.com/p/473573789

猜你喜欢

转载自blog.csdn.net/youlinhuanyan/article/details/128754961