什么是 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 后面。
-- 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
,可以看见屏幕打印了
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
文件夹。
#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 文件了。
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
动作,可以看见目标正常生成。
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 构建机制,我就陆续编写更详细的内容。