首次接触 CMake,见识了 CMakeLists.txt 的强大后,赶紧整理出来分享一下。
参考资料:《Cmake 3.6 W3Cschool参考手册》
本文讲述了一个 CMake 项目,在从单文件到多文件,单目录到多子目录的代码演化过程中,CMakeLists.txt 脚本的编写变化。
本文内容都是在 Linux 系统上操作的,如果在 VS Code 和 Visual Status 2019 上操作的话,也是一样的。
单文件的CMake项目
例子1
创建如下 CMake 项目
目录结构
[root@localhost cmaketest]# tree
.
├── CMakeLists.txt
└── main.cpp
0 directories, 2 files
[root@localhost cmaketest]#
创建CMake项目
如上,新建一个文件夹存放 CMake 项目,这里是 cmaketest 目录。在目录下新建两个文件:main.cpp、CMakeLists.txt 。
在 main.cpp 中写上如下代码(当然你也可以写其它的,心随你动,哈哈)
#include <iostream>
int Func_Add(int num1, int num2)
{
int nSum = num1 + num2;
return nSum;
}
int main()
{
int numSum = Func_Add(2, 3);
std::cout << "2 + 3 = " << numSum << std::endl;
return 0;
}
在 CMakeLists.txt 中写上如下命令
cmake_minimum_required (VERSION 3.8)
PROJECT(calcSum)
ADD_EXECUTABLE(calcSum main.cpp)
这样一个简单的 CMake 项目就新建好了。
CMakeLists.txt 中命令解释:
- cmake_minimum_required (VERSION 3.8)
设置项目所需的 cmake 最低版本为 cmake 3.8 版本。 - PROJECT(calcSum)
为整个项目设置一个名字,这里起名为 calcSum。 - ADD_EXECUTABLE(calcSum main.cpp)
添加一个目标可执行文件 calcSum 到项目中,这个可执行文件是使用后面指定的 main.cpp 文件生成的。
构建和编译CMake项目
在项目路径下新建一个 build 目录,用来存放构建和编译过程中生成的文件。
进入 build 目录后编译验证如下:
[root@localhost cmaketest]# mkdir build
[root@localhost cmaketest]# cd build/
[root@localhost build]# cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- 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.7s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fenghx/myCMakePro/cmaketest/build
[root@localhost build]# make
[ 50%] Building CXX object CMakeFiles/calcSum.dir/main.cpp.o
[100%] Linking CXX executable calcSum
[100%] Built target calcSum
[root@localhost build]# ls
calcSum CMakeCache.txt CMakeFiles cmake_install.cmake Makefile
[root@localhost build]# ./calcSum
2 + 3 = 5
[root@localhost build]#
这就是单个文件的 CMake 项目中 CMakeLists.txt 的编写。
多个文件的CMake项目
我们开发一个项目,代码文件肯定不只有一个 main.cpp 那么简单。肯定会有很多源文件。这里先说一下有两个文件:main.cpp、util.h 的 CMake 项目。
例子2
目录结构
[root@localhost cmaketest]# tree
.
├── build
├── CMakeLists.txt
├── main.cpp
└── util.h
0 directories, 3 files
[root@localhost cmaketest]#
修改项目
将第一个例子中 main.cpp 中的 Func_Add() 函数的代码提取到 util.h 中,main.cpp 中保留剩下的。其它不变。如下
util.h 文件中的代码:
#ifndef __UTIL_H__
#define __UTIL_H__
#include <iostream>
int Func_Add(int num1, int num2)
{
int nSum = num1 + num2;
return nSum;
}
#endif
main.cpp 文件中的代码:
#include "util.h"
int main()
{
int numSum = Func_Add(2, 3);
std::cout << "2 + 3 = " << numSum << std::endl;
return 0;
}
编译项目
现在,不用修改 CMakeLists.txt 文件,直接编译的话也是OK的:
[root@localhost cmaketest]# rm -rf build/*
[root@localhost cmaketest]# cd build/
[root@localhost build]# cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- 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.4s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fenghx/myCMakePro/cmaketest/build
[root@localhost build]# make
[ 50%] Building CXX object CMakeFiles/calcSum.dir/main.cpp.o
[100%] Linking CXX executable calcSum
[100%] Built target calcSum
[root@localhost build]# ls
calcSum CMakeCache.txt CMakeFiles cmake_install.cmake Makefile
[root@localhost build]# ./calcSum
2 + 3 = 5
原因想必大家也都知道,在预处理的时候,会将 util.h 头文件中的内容整合到 main.cpp 中,这就相当于和例子1一样了。
但是我们日常开发时,很多时候都不会将函数的实现,即函数体放到头文件中,头文件中只放函数的声明,.cpp 源文件中才放函数的实现。即如下。
例子3
目录结构:
[root@localhost cmaketest]# tree
.
├── build
├── CMakeLists.txt
├── main.cpp
├── util.cpp
└── util.h
1 directory, 4 files
[root@localhost cmaketest]#
修改项目
新增一个 util.cpp 文件如下:
#include "util.h"
int Func_Add(int num1, int num2)
{
int nSum = num1 + num2;
return nSum;
}
头文件 util.h 中代码修改为:
#ifndef __UTIL_H__
#define __UTIL_H__
#include < iostream >
int Func_Add(int num1, int num2);
#endif
编译项目
[root@localhost cmaketest]# rm -rf build/*
[root@localhost cmaketest]# cd build/
[root@localhost build]# cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- 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.5s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fenghx/myCMakePro/cmaketest/build
[root@localhost build]# make
[ 50%] Building CXX object CMakeFiles/calcSum.dir/main.cpp.o
[100%] Linking CXX executable calcSum
CMakeFiles/calcSum.dir/main.cpp.o:在函数‘main’中:
main.cpp:(.text+0x13):对‘Func_Add(int, int)’未定义的引用
collect2: 错误:ld 返回 1
make[2]: *** [calcSum] 错误 1
make[1]: *** [CMakeFiles/calcSum.dir/all] 错误 2
make: *** [all] 错误 2
[root@localhost build]#
可见,我们构建项目后,使用 make 命令编译时,会报错:main.cpp:(.text+0x13):对‘Func_Add(int, int)’未定义的引用。
这是由于,我们在 CMakeLists.txt 中语句 ADD_EXECUTABLE(calcSum main.cpp) 只指定了一个依赖源文件 main.cpp, 但是现在的话,要想生成 calcSum 可执行文件,需要依赖 main.cpp、util.cpp 两个源文件才可以。
修改CMakeLists.txt
cmake_minimum_required (VERSION 3.8)
PROJECT(calcSum)
ADD_EXECUTABLE(calcSum main.cpp util.cpp)
上面我们将 util.cpp 源文件也指定在依赖项中,用空格分割。
再编译项目
[root@localhost cmaketest]# cd build/
[root@localhost build]# cmake ..
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fenghx/myCMakePro/cmaketest/build
[root@localhost build]# make
[ 33%] Building CXX object CMakeFiles/calcSum.dir/util.cpp.o
[ 66%] Linking CXX executable calcSum
[100%] Built target calcSum
[root@localhost build]# ./calcSum
2 + 3 = 5
[root@localhost build]#
发现编译成功了。ADD_EXECUTABLE() 的第一个参数是目标可执行文件的名字,后面的为依赖源文件,当编译需要依赖多个源文件时,可以将依赖的源文件加在后面,源文件和源文件之间用空格分割。
单个子目录的CMake项目
例子4
我们的项目不可能所有的文件都放在同一个主目录下,实际开发中项目都会分很多子目录,比如下面的
目录结构
[root@localhost cmaketest]# tree
.
├── build
├── CMakeLists.txt
├── main.cpp
└── src
├── util.cpp
└── util.h
2 directories, 4 files
[root@localhost cmaketest]#
修改项目
在项目主目录下新建一个 src 目录,然后将文件 util.h 和 util.cpp 文件移动到 src 目录下。
编译项目
[root@localhost cmaketest]# rm -rf build/*
[root@localhost cmaketest]# cd build/
[root@localhost build]# cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- 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.5s)
CMake Error at CMakeLists.txt:5 (ADD_EXECUTABLE):
Cannot find source file:
util.cpp
Tried extensions .c .C .c++ .cc .cpp .cxx .cu .mpp .m .M .mm .ixx .cppm .h
.hh .h++ .hm .hpp .hxx .in .txx .f .F .for .f77 .f90 .f95 .f03 .hip .ispc
CMake Error at CMakeLists.txt:5 (ADD_EXECUTABLE):
No SOURCES given to target: calcSum
CMake Generate step failed. Build files cannot be regenerated correctly.
[root@localhost build]#
发现在执行 cmake 命令构建项目时就报错了:
CMake Error at CMakeLists.txt:5 (ADD_EXECUTABLE):
Cannot find source file:
util.cpp
无法找到依赖的源文件 util.cpp,这是由于将 util.cpp 文件移动到 src 后,并没有在 CMakeLists.txt 中修改依赖源文件的路径,所以 cmake 编译器在主目录下查找时无法找到。
修改CMakeLists.txt文件
cmake_minimum_required (VERSION 3.8)
PROJECT(calcSum)
ADD_EXECUTABLE(calcSum main.cpp src/util.cpp)
构建项目
[root@localhost cmaketest]# cd build/
[root@localhost build]# cmake ..
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fenghx/myCMakePro/cmaketest/build
[root@localhost build]#
发现可以成功建立了。接着我们执行 make 编译项目。
编译项目
[root@localhost build]# make
[ 33%] Building CXX object CMakeFiles/calcSum.dir/main.cpp.o
/home/fenghx/myCMakePro/cmaketest/main.cpp:1:18: 致命错误:util.h:没有那个文件或目录
#include "util.h"
^
编译中断。
make[2]: *** [CMakeFiles/calcSum.dir/main.cpp.o] 错误 1
make[1]: *** [CMakeFiles/calcSum.dir/all] 错误 2
make: *** [all] 错误 2
[root@localhost build]#
这里又报错了,无法找到 util.h 头文件。可想而知和一开始找不到 util.cpp 是一样。我们试着将头文件加到依赖项中,看行不行。
修改CMakeLists.txt文件
cmake_minimum_required (VERSION 3.8)
PROJECT(calcSum)
ADD_EXECUTABLE(calcSum main.cpp src/util.cpp src/util.h)
重新编译
[root@localhost cmaketest]# cd build/
[root@localhost build]# cmake ..
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fenghx/myCMakePro/cmaketest/build
[root@localhost build]# make
[ 33%] Building CXX object CMakeFiles/calcSum.dir/main.cpp.o
/home/fenghx/myCMakePro/cmaketest/main.cpp:1:18: 致命错误:util.h:没有那个文件或目录
#include "util.h"
^
编译中断。
make[2]: *** [CMakeFiles/calcSum.dir/main.cpp.o] 错误 1
make[1]: *** [CMakeFiles/calcSum.dir/all] 错误 2
make: *** [all] 错误 2
[root@localhost build]#
发现这样还是无法找到 util.h 头文件,那应该如何才能让编译器找到这个头文件呢,无非就是告诉编译器这个头文件的路径,这就需要用到:include_directories() 了
- include_directories(路径名称)
添加一个头文件路径到项目构建中去。
修改CMakeLists.txt文件
cmake_minimum_required (VERSION 3.8)
PROJECT(calcSum)
INCLUDE_DIRECTORIES(src/)
ADD_EXECUTABLE(calcSum main.cpp src/util.cpp)
如上,我们将相对路径 src/ 添加到了项目的构件中,然后再编译项目。
编译项目
[root@localhost cmaketest]# cd build/
[root@localhost build]# cmake ..
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fenghx/myCMakePro/cmaketest/build
[root@localhost build]# make
[ 33%] Building CXX object CMakeFiles/calcSum.dir/main.cpp.o
[ 66%] Building CXX object CMakeFiles/calcSum.dir/src/util.cpp.o
[100%] Linking CXX executable calcSum
[100%] Built target calcSum
[root@localhost build]# ./calcSum
2 + 3 = 5
[root@localhost build]#
发现此时就已经可以编译生成可执行文件了。
子目录中有很多的源文件的CMake项目
如果子目录中有很多的 .cpp 源文件,单单把这么多的源文件都写到依赖项中,就很费劲了,密密麻麻可读性也不好,而且如果要新建 .cpp 源文件的话,岂不是每次添加都得编辑一下 CMakeLists.txt 文件,太麻烦了,有没有办法解决这个事情呢?
有办法:
- aux_source_directory(路径名称 变量名称)
搜索指定目录下的所有源文件,并将他们保存在给定的变量中。
修改CMakeLists.txt文件
cmake_minimum_required (VERSION 3.8)
PROJECT(calcSum)
INCLUDE_DIRECTORIES(src/)
AUX_SOURCE_DIRECTORY(src/ SUB_SOURCE_FILE)
ADD_EXECUTABLE(calcSum main.cpp ${SUB_SOURCE_FILE})
如上,AUX_SOURCE_DIRECTORY() 会搜索 src/ 目录下的所有源文件,将它们保存到指定的变量 SUB_SOURCE_FILE 中。之后我们直接在 ADD_EXECUTABLE() 中使用这个变量就可以了,使用变量的方法为:==${变量名}==。
编译项目
[root@localhost cmaketest]# rm -rf build/*
[root@localhost cmaketest]# cd build/
[root@localhost build]# cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- 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.5s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fenghx/myCMakePro/cmaketest/build
[root@localhost build]# make
[ 33%] Building CXX object CMakeFiles/calcSum.dir/main.cpp.o
[ 66%] Building CXX object CMakeFiles/calcSum.dir/src/util.cpp.o
[100%] Linking CXX executable calcSum
[100%] Built target calcSum
[root@localhost build]# ./calcSum
2 + 3 = 5
[root@localhost build]#
可见,在使用 make 编译的时候确实找到 util.cpp 源文件了。这样就不用我们每次都要添加依赖的源文件了。是不是很帅。
多个子目录的CMake项目
再来想想,项目中一般不会只有一个子目录,往往有很多个子目录,如果子目录很多,那只使用 AUX_SOURCE_DIRECTORY() 的话显然要写很多行,并且变量也要定义很多,不太好,那该怎么办呢?有没有传递多个目录的方法呢?
有,使用 file() 文件操作命令。
- file(GLOB 变量名 路径1 路径2 .....)
按照一定规则搜索给定路径列表中的所有文件,将文件名都保存在给定的变量中。
修改CMakeLists.txt文件
cmake_minimum_required (VERSION 3.8)
PROJECT(calcSum)
INCLUDE_DIRECTORIES(src/)
FILE(GLOB SOURCE_FILES
${PROJECT_SOURCE_DIR}/*.cpp
${PROJECT_SOURCE_DIR}/src/*.cpp
)
ADD_EXECUTABLE(calcSum ${SOURCE_FILES})
这里的变量 PROJECT_SOURCE_DIR 是 CMake 中的一个变量,代表的是当前项目的根目录。上面命令是将根目录下的所有 .cpp 文件和 src/ 目录下的所有 .cpp 文件都搜索出来,存放到变量 SOURCE_FILES 中,这样,在命令 ADD_EXECUTABLE() 中就可以直接使用变量 SOURCE_FILES ,而不需要编写依赖的源文件了。
编译项目
[root@localhost cmaketest]# rm -rf build/*
[root@localhost cmaketest]# cd build/
[root@localhost build]# cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- 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.4s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fenghx/myCMakePro/cmaketest/build
[root@localhost build]# make
[ 33%] Building CXX object CMakeFiles/calcSum.dir/main.cpp.o
[ 66%] Building CXX object CMakeFiles/calcSum.dir/src/util.cpp.o
[100%] Linking CXX executable calcSum
[100%] Built target calcSum
[root@localhost build]# ./calcSum
2 + 3 = 5
[root@localhost build]#
发现真的是可以的,完美解决不爽的问题。
需要指定编译器版本的CMake项目
修改项目
如果将 main.cpp 改为如下:
#include "util.h"
int main()
{
auto numSum = Func_Add(2, 3);
std::cout << "2 + 3 = " << numSum << std::endl;
return 0;
}
我用 auto 自动识别类型定义了变量 numSum,来接受 Func_Add() 返回的值。
编译项目
[root@localhost cmaketest]# cd build/
[root@localhost build]# make
[ 33%] Building CXX object CMakeFiles/calcSum.dir/main.cpp.o
/home/fenghx/myCMakePro/cmaketest/main.cpp: 在函数‘int main()’中:
/home/fenghx/myCMakePro/cmaketest/main.cpp:5:7: 错误:‘numSum’不是一个类型名
auto numSum = Func_Add(2, 3);
^
/home/fenghx/myCMakePro/cmaketest/main.cpp:6:29: 错误:‘numSum’在此作用域中尚未声明
std::cout << "2 + 3 = " << numSum << std::endl;
^
make[2]: *** [CMakeFiles/calcSum.dir/main.cpp.o] 错误 1
make[1]: *** [CMakeFiles/calcSum.dir/all] 错误 2
make: *** [all] 错误 2
[root@localhost build]#
这里就会报错,原因是 auto 关键字是 C++11 标准里面的,而在不指定 C++ 标准版本的情况下,编译器是不会执行 C++11 标准的。
那如何来设置 C++11 标准呢?有办法,如下:
- set(CMAKE_CXX_STANDARD 11)
设置C++标准为 C++11
修改CMakeLists.txt文件
cmake_minimum_required (VERSION 3.8)
SET(CMAKE_CXX_STANDARD 11)
PROJECT(calcSum)
INCLUDE_DIRECTORIES(src/)
FILE(GLOB SOURCE_FILES
${PROJECT_SOURCE_DIR}/*.cpp
${PROJECT_SOURCE_DIR}/src/*.cpp
)
ADD_EXECUTABLE(calcSum ${SOURCE_FILES})
编译项目
[root@localhost build]# rm -rf ./*
[root@localhost build]# ls
[root@localhost build]# cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- 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.4s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fenghx/myCMakePro/cmaketest/build
[root@localhost build]# make
[ 33%] Building CXX object CMakeFiles/calcSum.dir/main.cpp.o
[ 66%] Building CXX object CMakeFiles/calcSum.dir/src/util.cpp.o
[100%] Linking CXX executable calcSum
[100%] Built target calcSum
[root@localhost build]# ./calcSum
2 + 3 = 5
[root@localhost build]#
如上,完美解决问题。
需要连接动态库的CMake项目
另外,如果你要链接动态库,那么就要使用 target_link_libraries()
- target_link_libraries(目标可执行文件名 动态库名)
指定要链接给目标可执行文件的动态库
更多的 CMakeLists.txt 编写技巧,请参考《Cmake 3.6 W3Cschool参考手册》