cmake

文章目录

  • 前言
  • makefile简单入门
  • cmake下载
  • cmake流程原理
  • cmake使用实例
    • 简单使用
    • 编译同一目录下多个文件
    • 库文件分布于不同的目录下
    • 当库文件在外部(github etc...)
  • cmake & gtest
    • 下载编译gtest
    • cmake关联gtest

前言

首先什么是cmake,cmake和makefil的区别是啥,cmake是一个配置文件,其可以自动生成项目构建工具,比如makefile,ninja,wmake等,他支持跨平台(windows/unix-like)比如cmake generators根据我们指定的option可以生成unix 下的makefile,也可以生成visio studio XXX下的makefile等等

makefile简单入门

makefile就是一个文件叫做makefile,makefile是由多个

target: prerequisites
	command

组成,其中

  • target:代表一个label(all,default,clean等make后面跟的操作),或者Object File
  • prerequisite:就是生成前面target(当target是可执行文件的时候)所需要的文件
  • command:就是我们的shell命令,比如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下载

下载安装就没有什么好说的,cmake支持二进制安装,支持apt安装,比如

apt-get install cmake

二进制安装就需要访问官网,官网连接在此

cmake流程原理

cmake的流程如下

CMakeLists.txt(Cmake配置文件)
|
|
↓
cmake(调用cmake命令行工具)
|
|
↓
Makefile(根据配置文件生成makefile)
|
|
↓
make(make工具根据makefile调用gcc/g++生成可执行文件)
|
|
↓
可执行文件

cmake使用实例

简单使用

假如源文件就一个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

当库文件在外部(github etc…)

我们上面的例子都是假设库文件在内部,假如库文件在网上呢?我们需要将其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所以也找得到了

cmake & gtest

下载编译gtest

我们想将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/

cmake关联gtest

首先看一下项目的目录

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

你可能感兴趣的:(unix,linux,c++)