引言
上一节介绍了前面我们介绍了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.其他
- 常用的预定义变量
变量 | 说明 |
---|---|
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 | 重新定义目标链接库的存放位置 |
-
环境变量
- 使用环境变量
$ENV{name}
- 写入环境变量
set(ENV{name} value)
- 使用环境变量
-
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还是命令行,都严禁出现中文字符.
- 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、我们首先给出子项目libs
的CMakeLists.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