首先什么是cmake,cmake和makefil的区别是啥,cmake是一个配置文件,其可以自动生成项目构建工具,比如makefile,ninja,wmake等,他支持跨平台(windows/unix-like)比如cmake generators根据我们指定的option可以生成unix 下的makefile,也可以生成visio studio XXX下的makefile等等
makefile就是一个文件叫做makefile,makefile是由多个
target: prerequisites
command
组成,其中
g++ XXX.cpp -o XXX.o
当target是label的时候
我们target是default,表明我们执行make命令默认运行的行
default:
g++ main.cpp -o main.o
假如我们用cmake代替如下,其中build用来放编译后的文件
root@zhr-workstation:~/test/make# tree
.
├── build
└── main.cpp
1 directory, 1 file
root@zhr-workstation:~/test/make# cat CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2) #cmake最小版本号
project(test) #设置项目名称
add_executable(${PROJECT_NAME} main.cpp) #这个语句的意思是创建可执行文件的名字,名字就是project名字,会自动的将PROJECT_NAME转换成test,因为我们上面定义了,后面的main.cpp表明我们指定的编译源文件
root@zhr-workstation:~/test/make# cmake -S . -B ./build/
-- The C compiler identification is GNU 11.2.0
-- The CXX compiler identification is GNU 11.2.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
-- Generating done
-- Build files have been written to: /root/test/make/build
最后看一下是否生成makefile文件
root@zhr-workstation:~/test/make# cd build/
root@zhr-workstation:~/test/make/build# ls
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile
运行makefile文件
root@zhr-workstation:~/test/make/build# make
[ 50%] Building CXX object CMakeFiles/test.dir/main.cpp.o
[100%] Linking CXX executable test
[100%] Built target test
root@zhr-workstation:~/test/make/build# ls
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile test
root@zhr-workstation:~/test/make/build# ./test
hey zzz
下载安装就没有什么好说的,cmake支持二进制安装,支持apt安装,比如
apt-get install cmake
二进制安装就需要访问官网,官网连接在此
cmake的流程如下
CMakeLists.txt(Cmake配置文件)
|
|
↓
cmake(调用cmake命令行工具)
|
|
↓
Makefile(根据配置文件生成makefile)
|
|
↓
make(make工具根据makefile调用gcc/g++生成可执行文件)
|
|
↓
可执行文件
假如源文件就一个main.cpp需要编译,那么我们的cmake配置文件可以写成如下格式
cmake_minimum_required(VERSION 3.10.2) #cmake最小版本号
project(test) #设置项目名称
add_executable(${PROJECT_NAME} main.cpp) #这个语句的意思是创建可执行文件的名字,名字就是project名字,会自动的将PROJECT_NAME转换成test,因为我们上面定义了,后面的main.cpp表明我们指定的编译源文件
我们有以下文件,其中main.cpp包含adder.h,且adder.h定义的函数定义在adder.cpp中他们都在同一级目录
root@zhr-workstation:~/test/make# tree
.
├── adder.cpp
├── adder.h
├── build
├── CMakeLists.txt
└── main.cpp
1 directory, 4 files
main.cpp如下
root@zhr-workstation:~/test/make# cat main.cpp
#include
#include "adder.h"
int main(){
std::cout << add(72.1f, 73.8f) << '\n';
return 0;
}
adder.h如下
root@zhr-workstation:~/test/make# cat adder.h
float add(float, float);
adder.cpp如下
root@zhr-workstation:~/test/make# cat adder.cpp
float add(float a, float b){
return (a+b);
}
CMakeLists.txt如下,就是直接加了个adder.cpp去编译,因为他们都在同一目录下
root@zhr-workstation:~/test/make# cat CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2) #cmake最小版本号
project(test) #设置项目名称
add_executable(${PROJECT_NAME} main.cpp adder.cpp) #这个语句的意思是创建可执行文件的名字,名字就是project名字,会自动的将PROJECT_NAME转换成test,因为我们上面定义了,后面的main.cpp表明我们指定的编译源文件
cmake
root@zhr-workstation:~/test/make# cmake -S . -B build/
-- The C compiler identification is GNU 11.2.0
-- The CXX compiler identification is GNU 11.2.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
-- Generating done
-- Build files have been written to: /root/test/make/build
make编译
root@zhr-workstation:~/test/make# cd build/
root@zhr-workstation:~/test/make/build# make
[ 33%] Building CXX object CMakeFiles/test.dir/main.cpp.o
[ 66%] Building CXX object CMakeFiles/test.dir/adder.cpp.o
[100%] Linking CXX executable test
[100%] Built target test
root@zhr-workstation:~/test/make/build#
运行
root@zhr-workstation:~/test/make/build# ./test
145.9
为了正式点,我们创建一个include文件夹专门用来放头文件,一个ADD文件夹专门用于放ADD函数的cpp文件,如下,注意我们ADD目录下新增了一个CMakeLists.txt文件,因为这个ADD目录放的是库文件,库文件要编译链接到main.cpp中,所以我们在这个要编译的库文件中也定义cmake文件让其生成Makefile,如果不在ADD这个库目录中新增makefile,那么其就链接不到main.cpp中,换句话说main.cpp就用不能使用add函数
root@zhr-workstation:~/test/make# tree
.
├── ADD
│ ├── adder.cpp
│ └── CMakeLists.txt
├── build
├── CMakeLists.txt
├── include
│ └── ADD
│ └── adder.h
└── main.cpp
main.cpp如下,主要是改了include的内容
root@zhr-workstation:~/test/make# cat main.cpp
#include
#include "include/ADD/adder.h"
int main(){
std::cout << add(72.1f, 73.8f) << '\n';
return 0;
}
我们项目根目录的CMakeLists.txt如下,注意add_subdirectory指定的是库文件目录名字,target_link_libraries后面的adder是库的名字,定义在我们的库文件目录的CMakeLists.txt中
cmake_minimum_required(VERSION 3.10.2) #cmake最小版本号
project(test) #设置项目名称
add_executable(${PROJECT_NAME} main.cpp) #这个语句的意思是创建可执行文件的名字,名字就是project名字,会自动的将PROJECT_NAME转换成test,因为我们上面定义了,后面的main.cpp表明我们指定的编译源文件
add_subdirectory(ADD) #指定库目录名称,名称相对路径,指定后会去ADD这个目录中找对应的cmakelists file
target_link_libraries(${PROJECT_NAME} adder) #将我们子目录里面设置的库名字链接到我们项目中,adder是我们ADD库目录中定义的库名称,假如我们不止一个library,那么我们可以一次性link多个,直接在这后面加即可
我们库目录ADD的CMakeLists.txt如下
add_library(adder adder.cpp) #设置库的名字(adder),且指定要编译的对应库文件(adder.cpp)
然后我们在项目根目录cmake
root@zhr-workstation:~/test/make# cmake -S . -B build/
-- The C compiler identification is GNU 11.2.0
-- The CXX compiler identification is GNU 11.2.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
-- Generating done
-- Build files have been written to: /root/test/make/build
make编译
root@zhr-workstation:~/test/make# cd build/
root@zhr-workstation:~/test/make/build# make
[ 25%] Building CXX object ADD/CMakeFiles/adder.dir/adder.cpp.o
[ 50%] Linking CXX static library libadder.a
[ 50%] Built target adder
[ 75%] Building CXX object CMakeFiles/test.dir/main.cpp.o
[100%] Linking CXX executable test
[100%] Built target test
运行
root@zhr-workstation:~/test/make/build# ./test
145.9
我们上面的例子都是假设库文件在内部,假如库文件在网上呢?我们需要将其link到我们的项目中
在此之前我们要先讲一下gitignore
一般来说每个Git项目中都需要一个.gitignore文件,这个文件的作用就是告诉Git哪些文件不需要添加到版本管理中,换句话说本地修改完项目后,上传到github等版本管理服务中,本地哪些文件不上传过去。
我们C++项目中想把一个项目上传到github中当然不想上传cmake编译后的文件(一些cmake缓存,CMake形成makefile的中间文件),也不想上传C++编译后的文件(什么动态链接库,静态链接库,可执行文件,对象文件等等),我么不还要忽略build的目录,所以我们要在项目的main中加上 .gitignore
我们的gitigore如下
root@zhr-workstation:~/test/make# cat .gitignore
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
#ignore cmake temp file
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
#ignore directory
[Bb][Uu][Ii][Ll][Dd]/
然后我们引入第三方的库
先git init初始化
root@zhr-workstation:~/test/make# git init
使用git submodule引入github的第三方库,这里我们引入GLFW
GLFW是配合 OpenGL 使用的轻量级工具程序库,缩写自 Graphics Library Framework(图形库框架)。GLFW 的主要功能是创建并管理窗口和 OpenGL 上下文,同时还提供了处理手柄、键盘、鼠标输入的功能
root@zhr-workstation:~/test/make# git submodule add https://github.com/glfw/glfw.git external/glfw
这里放入external目录中,这个目录专门用于放置第三方库
项目的根目录自动的生成了一个gitmodules
root@zhr-workstation:~/test/make# cat .gitmodules
[submodule "external/glfw"]
path = external/glfw
url = https://github.com/glfw/glfw.git
然后我们可以将这个第三方modules commit到我们github上,但是我没有配置账号信息(git config)所以就不执行了
git commit -am "add submodule glfw"
因为GLFW有自己维护的CMakeLists.txt文件,所以我们只需要在跟目录的CMakeLists.txt文件加入add_subdirectory(external/glfw)
然后cmake发现错误,然后翻开external/glfw里面的CMakeLists.txt发现有一些option我们可以在cmake的时候关掉(_DXXX=OFF),比如GLFW_BUILD_DOCS,然后发现缺少X11的依赖,直接下载即可
假设我们想用glwf里面的函数,我们要链接这个库,所以我们要将其链接进来,首先glwf的cmake定义库名称在make/external/glfw/src的CMakeLists.txt中,所以我们要在根的cmake里加上这个需要链接的目录,再链接,最后根目录的CMakeLists.txt如下
cmake_minimum_required(VERSION 3.10.2) #cmake最小版本号
project(test) #设置项目名称
add_executable(${PROJECT_NAME} main.cpp) #这个语句的意思是创建可执行文件的名字,名字就是project名字,会自动的将PROJECT_NAME转换成test,因为我们上面定义了,后面的main.cpp表明我们指定的编译源文件
add_subdirectory(ADD) #指定库目录名称,名称相对路径,指定后会去ADD这个目录中找对应的cmakelists file
add_subdirectory(external/glfw) #执行cmake的时候执行这个子目录的cmakelists文件
target_link_directories(${PROJECT_NAME} #指定链接的库的目录,因为这个目录下的CMakeLists.txt定义了库名称也就是add_library
PRIVATE external/glfw/src
)
target_link_libraries(${PROJECT_NAME} adder glfw) #将我们子目录里面设置的库名字链接到我们项目中,adder是我们ADD库目录中定义的库名称,glfw因为定义了他的target_link_directories所以也找得到了
我们想将gtest集成进我们的项目中,并且用cmake构建,假设我们项目目录为xer
,我们先下载gtest到目录中(googletest就是下载后的代码,URI是我们一个库的代码)
root@zhr-workstation:~/xer# ls
googletest URI
然后我们编译gtest,然后将编译后的文件copy到我们的库中
root@zhr-workstation:~/xer# cd googletest/
root@zhr-workstation:~/xer/googletest# mkdir build
root@zhr-workstation:~/xer/googletest# cd build
root@zhr-workstation:~/xer/googletest/build# cmake ..
root@zhr-workstation:~/xer/googletest/build# make
root@zhr-workstation:~/xer/googletest/build# cd ../googletest
root@zhr-workstation:~/xer/googletest/googletest# cd include
root@zhr-workstation:~/xer/googletest/googletest/include# cp -r gtest /usr/include/
首先看一下项目的目录
root@zhr-workstation:~/xer# ls
googletest URI
再看一下我们URI库的结构
root@zhr-workstation:~/xer/URI# tree
.
├── include
│ └── URI.h
├── src
│ └── URI.cpp
└── test
└── src
└── URITest.cpp
test目录就是我们的gtest文件
首先我们先写项目根目录xer下的CMakeLists.txt
文件
cmake_minimum_required(VERSION 3.8)
set(This xer) # 设置This变量
project($(This) C CXX) #项目名称xer,支持C/C++
set(CMAKE_C_STANDARD 99) #c99标准
set(CMAKE_CXX_STANDARD 11) #c++11标准
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
enable_testing()
# google test
set(BUILD_GTEST ON CACHE BOOL "Build the googletest subproject" FORCE)
set(BUILD_GMOCK OFF CACHE BOOL "Build the googlgmock subproject" FORCE)
add_subdirectory(googletest)
# URI
add_subdirectory(URI)
因为项目没有写完就一个库(URI)
然后我们在URI库目录下写CMakeLists.txt
文件
cmake_minimum_required(VERSION 3.8)
set(This URI)
set(Headers
include/URI.h
)
set( Sources
src/URI.cpp
)
add_library(${This} STATIC ${Sources} ${Headers}) #This就是我们指定的这个库名字,后面的STATIC代表静态编译,后面2个跟的编译源文件
set_target_properties(${This} PROPERTIES
FOLDER Libraries
)
target_include_directories(${This} PUBLIC include)
注意target_include_directories(${This} PUBLIC include)
,有了他之后我们URI目录下的源代码可以直接include
目录include下的头文件,因为这个CMakeLists.txt
文件直接定义于URI目录下,在执行add_library()
编译sources文件的时候如果源文件include了头文件,可以直接找本目录下include目录下的头文件,此时目录如下所示
注意我们将一个A库的include下头文件用
target_include_directories
共享出去后,link此库的文件B.cpp(B.cpp目录下的cmake中target_link_libraries
了A库(A库add_library
))可以直接使用这个头文件(相当于B.cpp已经在A库的include目录之下)
root@zhr-workstation:~/xer/URI# tree
.
├── CMakeLists.txt
├── include
│ └── URI.h
├── src
│ └── URI.cpp
└── test
└── src
└── URITest.cpp
然后我们要给gtest文件添加CMakeLists.txt
,我们将文件添加到test下
set(This URITest)
set(Sources src/URITest.cpp)
add_executable(${This} ${Sources})
#set_property(${This} PROPERTIES
# FOLDER Tests
#)
target_link_libraries(${This} PUBLIC
gtest_main #链接gtest
URI #链接我们自己需要测试的库
)
add_test(
NAME ${This}
COMMAND ${This}
)
此时目录如下所示
root@zhr-workstation:~/xer/URI# tree
.
├── CMakeLists.txt
├── include
│ └── URI.h
├── src
│ └── URI.cpp
└── test
├── CMakeLists.txt
└── src
└── URITest.cpp
gtest文件URITest.cpp
如下
#include
#include //因为URI目录下的CMakeLists.txt文件定义了从include下找(target_include_directories)
TEST(URITest, Placeholder){
URI::URI uri;
ASSERT_TRUE(false); //一定会报错
}
此时我们cmake和build
root@zhr-workstation:~/xer/build# cmake ..
-- The C compiler identification is GNU 11.3.0
-- The CXX compiler identification is GNU 11.3.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
-- Found Python: /usr/bin/python3.10 (found version "3.10.6") found components: Interpreter
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE
-- Configuring done
-- Generating done
-- Build files have been written to: /root/xer/build
root@zhr-workstation:~/xer/build# make
[ 12%] Building CXX object googletest/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o
[ 25%] Linking CXX static library ../../lib/libgtest.a
[ 25%] Built target gtest
[ 37%] Building CXX object googletest/googletest/CMakeFiles/gtest_main.dir/src/gtest_main.cc.o
[ 50%] Linking CXX static library ../../lib/libgtest_main.a
[ 50%] Built target gtest_main
[ 62%] Building CXX object URI/CMakeFiles/URI.dir/src/URI.cpp.o
[ 75%] Linking CXX static library libURI.a
[ 75%] Built target URI
[ 87%] Building CXX object URI/test/CMakeFiles/URITest.dir/src/URITest.cpp.o
[100%] Linking CXX executable URITest
[100%] Built target URITest
然后运行gtest
root@zhr-workstation:~/xer/build# ctest -C debug
Test project /root/xer/build
Start 1: URITest
1/1 Test #1: URITest ..........................***Failed 0.00 sec
0% tests passed, 1 tests failed out of 1
Total Test time (real) = 0.00 sec
The following tests FAILED:
1 - URITest (Failed)
Errors while running CTest
Output from these tests are in: /root/xer/build/Testing/Temporary/LastTest.log
Use "--rerun-failed --output-on-failure" to re-run the failed cases verbosely.
发现单元测试报错,我们看具体那里错了
root@zhr-workstation:~/xer/build# URI/test/URITest
Running main() from /root/xer/googletest/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from URITest
[ RUN ] URITest.Placeholder
/root/xer/URI/test/src/URITest.cpp:6: Failure
Value of: false
Actual: false
Expected: true
[ FAILED ] URITest.Placeholder (0 ms)
[----------] 1 test from URITest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] URITest.Placeholder
1 FAILED TEST