深入浅出CMake(一):基础篇

2019-03-15 15:47:43 浏览数 (1)

什么是 CMake?

CMake 是一个跨平台的编译构建工具,用来自动化生成 Makefile 之类的构建文件的。

一般在 unix 类系统上开发,我们用 gcc 或者 g 编译源码。

代码语言:javascript复制
g   hello.cpp  world.cpp 

针对很小的工程,处理的源码文件就这么几个,我们完全手写编译脚步就好了。

但是如果工程量变大,情况就变得复杂了,我们就需要用 make 工具,并编写 Makefile 记录好源码与依赖之间的关系。

make 配合 Makefile 使用,威力很大,但是手写代码很烦恼,而 cmake 似乎更现代化,它能够自动生成 Makefile,并且逻辑似乎更清晰。

你可以简单地认为,cmake 的使用比手写 Makefile 更简单。

当然,我并不是说 cmake 比 make 更高级,更好,只是相对于新手而言,它是很友好的,我们都希望把精力花在编写具体的业务代码上,而不是炫技一般编写复杂的 Makefile 文件,我决定学习 CMake 也是看到 OpenCV 改用它编译了,另外 Android 的源码好像也是它,这也让我不得不去学习它。

至于 CMake 和其它编译工具孰好孰坏,这里我不做评价。

一个最简单的 CMake 例子

要构建一个 CMake 编译系统,首先需要在代码根目录创建一个 CMakeLists.txt 文件,这个文件是用来描述构建过程的,可以看做是一个高级版的 Makefile 文件。

假设整个工程只有 hello.cpp 这个文件.

代码语言:javascript复制
#include <iostream>

using namespace std;


int main(int argc,char** argv)
{
    cout << "Hello for cmake!" << endl;
    return 0;
}

如果要编译的话,我们可以这样编写 CMakeLists.txt 。

代码语言:javascript复制
cmake_minimum_required(VERSION 2.8.11)
project(HELLO)

add_executable(hello hello.cpp)

3 行代码就搞定了。

cmake_minimum_required()这句代码是指定 cmake 的最低版本 project()这句代码是指定工程的名称 add_executable()这个指示生成的可执行文件,上面的例子是编译 hello.cpp 生成 hello 这个可执行文件

CMakeLists.txt 写好后,在当前目录执行 cmake .命令,.代表在当前目录执行操作,如果 CMakeLists.txt 在别处,需要将路径添加在 cmake 后面。

代码语言:javascript复制
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.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/frank/exercises/cmake

命令完成后,我们可以发现当前目录多了 1 个名为 CMakeFiles 的文件,也多了一个 Makefile 文件。

我们再执行 make 命令,最终可以发现生成了可执行文件 hello.

代码语言:javascript复制
[ 50%] Building CXX object CMakeFiles/hello.dir/hello.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello

在终端执行 ./hello,可以看见屏幕打印了

代码语言:javascript复制
Hello fo cmake!

这正式 hello.cpp 中的输出。

这是最简单的 cmake 编译例子,但实际工作中是不会有这么简单的,实际工作中会涉及到很多的源文件,还有很多的动态的库,静态的库。

下面讲解一个稍微复杂但完整的例子。

一个稍微复杂的 CMake 例子

假设 hello.cpp 需要调用一个 so 中的 api,这个 so 库是由 world.cpp 编译生成的。

这个应该可以代表大多数开发场景,因为 C/C 工程开发中,避免不了要调用其他的 so 或者是 .a 文件,也要和 .h 头文件打交道。

代码语言:javascript复制
#ifndef __WORLD_H__
#define __WORLD_H__

void say_hello();

#endif

上面是 world.h 的代码,它位于工程中 ./include文件夹。

代码语言:javascript复制
#include <iostream>
#include "include/world.h"


void say_hello()
{
    std::cout << "I'm frank909! blog is frank909.blog.csdn.net." << std::endl;
}

上面是 world.cpp 的代码。

代码语言:javascript复制
#include <iostream>
#include "include/world.h"

using namespace std;


int main(int argc,char** argv)
{
    cout << "Hello for cmake!" << endl;
    say_hello();
    return 0;
}

上面是 hello.cpp 修改后的代码,它直接调用了 say_hello()方法,这个时候我们需要仔细考虑怎么写 CMakeLists.txt 文件了。

代码语言:javascript复制
cmake_minimum_required(VERSION 2.8.11)
project(HELLO)

include_directories("include")
add_library(world world.cpp)

add_executable(hello hello.cpp)
target_link_libraries(hello world)

include_directories()这句代码指定了头文件地址 add_library()的作用是生成库文件,默认是 .a 的文件,也就是静态库,如何生成动态库接下来的部分会讲。 target_lingk_libraries()这句代码的意思也很容易懂,那就是为可执行文件 hello 链接 libworld.a 这个库。

执行 cmake .操作可以看到会正常生成 MakeFile 文件。

再执行 make动作,可以看见目标正常生成。

代码语言:javascript复制
Scanning dependencies of target world
[ 25%] Building CXX object CMakeFiles/world.dir/world.cpp.o
[ 50%] Linking CXX static library libworld.a
[ 50%] Built target world
Scanning dependencies of target hello
[ 75%] Building CXX object CMakeFiles/hello.dir/hello.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello

生成了两个文件 libworld.a 和 hello.

我们执行 ./hello 可以看到如下结果。

代码语言:javascript复制
Hello for cmake!
I'm frank909! blog is frank909.blog.csdn.net.

以上的例子就基本覆盖了我们日常开发的模式。

指定头文件路径

指定动态库或者是静态库的路径,然后链接。

我们已经具备自己编写简单的 CMakeLists.txt 的能力了,但为了更好的理解和灵活运用我们还需要学习一些关于 cmake 的基本概念。

cmake 中的 target

cmake 的主要工作大多是为了操作各种各样的 target,cmake 把二进制可执行文件和库都称为 target。

target 就是指 cmake 要编译的目标。

可执行文件

add_executable()指定了可执行文件,它是 unix 上 a.out 之类的文件和 windows 平台 .exe 文件

动态库和静态库都使用 add_library()命令生成,也就是 unix 平台上的 .so 和 .a 文件,window 上的 .DLL 文件等等。

add_library()默认生成 .a 文件,如果要生成 .so 文件用 add_library(test SHARED test.cpp)这样的形式,要指定 SHARED 模型,才会生成 libtest.so 文件。 默认的效果等同于 add_library(test STATIC test.cpp)生成的是 libtest.a 文件。

链接库

link_libraries(hello test)cmake 通过 link_libraries() 命令指定了目标间的依赖关系,示例代码中 hello 是可执行文件,test 是库。

指定头文件

include_directories()指定了编译系统的头文件地址

处理好了头文件、库的生成和链接、可执行文件的生成,cmake 就基本 OK 了。

如果,遇到更复杂的情况的话,还是需要深入了解 CMake 构建机制,我就陆续编写更详细的内容。

0 人点赞