文章先简单介绍了gcc/g++相关知识,从而引入cmake的知识,能够帮助读者更好理解cmake。

一、CMake预备知识

1.1 环境搭建

安装GCC,GDB(便捷安装指令)

sudo apt update
sudo apt install build-essential gdb
#查看下载的版本信息
gcc --version
g++ --version
gdb --version

安装cmake

sudo apt install cmake
#查看下载的信息
cmake --version

1.2  gcc/g++相关知识

程序的一般编译过程如下所示:

预处理->编译->汇编->链接

如需要编译c++文件时我们可以进行如下操作:

方式一:

# -E 编译器对输入文件进行预处理
g++ -E test.cpp -o test.i
# 会生成一个.i扩展名文件
# -S 编译器将c++代码变为了汇编语言
g++ -S test.i -o test.s
# 会生成一个.s扩展名文件
# -c 编译器将汇编语言转换为机器语言
g++ -c test.s -o test.o
#会生成一个.o扩展名文件
# -o 指定生成可执行文件的名称/如果不使用会默认生成可执行文件a.out
g++ test.o -o test_exec

方式二:

gcc/g++ 非常便捷,因为只要输入下面的指令,它可以自动完成预处理、编译、汇编、链接

g++ test.cpp -o test_exec

gcc/g++也不只有如上几个可选参数

-O :用来对代码进行优化。就比如说自己的代码中有一个循环,循环中有一个常数运算如x=(333*444)/2,如果通过g++直接来生成的可执行文件,这个可执行文件每次走循环都会算一次,效率很低,但如果加入-O那么就会自动优化代码,让他除去多余的运算,效率变高。可以通过time ./test_exec来进行验证

-l(小写L):用来指定程序要链接的库,比如我生成了一个libfunc.so/libfunc.a的链接库,lib*.so/.a这个*里面的内容就是库名,只需要输入以下语句就可完成链接:

g++ test.cpp -o test_exec -lfunc

注意:在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接 ,否则需要通过-L来告诉编译器库的目录在哪!

-L:指定库文件所在目录

比如我的链接库在/home/mm/test/lib中,就可以通过如下参数指定库文件目录:

g++ -L /home/mm/test/lib -lfunc -o test_exec

-I(大写i):指定头文件搜索目录。头文件如果不在usr/include 中,编译器会不知道去哪找头文件,这时候需要-I /XXX来指定头文件搜索目录。

还有其他一些参数可以自己去查阅资料学习。

标红的参数对后面cmake的一些函数息息相关。

1.3 链接库

静态链接:在链接阶段,直接把函数所需要的而二进制代码包含到可执行文件中去,所以在不需要依赖库,可以独立执行。

动态链接:在编译的时候不直接拷贝可执行代码,而是通过 记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定的代码时,去共享执行内存中已经加载的动态库可执行代码,最终达到运行时连接的目的。

1.4 实操演练

说了一大堆,可能懵逼了!别急,接下来我会来说如何运用上述知识编译代码,以及如何生成静态/动态链接库!

我们先构建一个目录结构




cmake 编译ios应用 cmake编译教程_c++


main.cpp

#include <iostream>
#include "func.h"
using namespace std;
int main()
{
    cout<<"hello main"<<endl;
    funcp();
}

func.h

#include <iostream>
using namespace std;
void funcp();

func.cpp

#include "func.h"
void funcp()
{
    cout<<"hello funcp"<<endl;
}

这是一个非常简单的demo!按照下面的操作就可以编译成功并且生成一个可执行文件啦

方式一:直接式


cmake 编译ios应用 cmake编译教程_c++_02


方式二:连接静态库并生成可执行文件

先生成静态链接库


cmake 编译ios应用 cmake编译教程_学习_03


g++ -c src/func.cpp -o lib/func.o -I include/
cd lib
ar rs libfunc.a func.o

然后输入以下指令进行编译!

g++ src/main.cpp -o bin/main_exec1 -lfunc -L lib/ -I include/


cmake 编译ios应用 cmake编译教程_linux_04


注意报错信息!一定要输入完整!

方式三:链接动态库并生成可执行文件


cmake 编译ios应用 cmake编译教程_vscode_05


# 先生成动态链接库
g++ src/func.cpp -o lib/libfun.so -fPIC -shared -I include/
# 编译代码
g++ src/main.cpp -o bin/main_exec2 -lfunc -I include/ -L lib/

./bin/main_exec2


cmake 编译ios应用 cmake编译教程_学习_06


至此三种编译的方式都已经实现!CMake的预备基本知识也准备的差不多了!

二、CMake基础

2.1 语法特性

基本语法格式:指令(参数1 参数2 ...)

参数使用括弧()括起/参数之间用空格分隔(不是用,)

变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名

2.2 重要指令

这些指令的参数都很多,详细可以参考cmake的官方文档,这里我只说明一些常用的的

cmake_minimum_required()-指定CMake的最小版本要求

语法: cmake_minimum_required(VERSION versionNumber [FATAL_ERROR])

# 一般使用语法如下
cmake_minimum_required(VERSION 2.8)
project()-定义工程名称,并可指定工程支持的语言

语法: project(projectname [CXX] [C] [Java])

project(DEMO2)
#上面的命令会自动生成变量,比如PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR变量
set()-设置变量的值

语法:set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

# 下面语句就是相当于src被func.cpp func1.cpp func2.cpp赋值
set(src func.cpp func1.cpp func2.cpp)
message()-打印信息或者变量值
message("PROJECT_BINARY_DIR=${PROJECT_BINARY_DIR}")

该语句会在执行cmake指令时打印出信息

include_directories ()-向工程添加头文件的搜索路径(可以多个)

语法: include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)

在一般引用头文件时我们会直接include "func.h",但是往往需要include “XXX/XXX/func.h",可以通过include_directories()来简化头文件的引用!

这一步相当于之前我们所说的gcc/g++编译时的参数-I(大写i)

include_directories(XXX/XXX)
#这样就可以直接#include "func.h",而不需要include "XXX/XXX/func.h"
link_directories()-向工程添加链接库的搜索路径(可以多个)

语法: link_directories(dir1 dir2 ...)

这一步想当于gcc/g++编译时的-L参数,来指定链接库搜索路径

# 例如链接库存放在目录/usr/lib/mylib和/usr/lib/mylib1中
link_directories(/usr/lib/mylib /usr/lib/mylib1)
add_library()-来生成静态/动态链接库

语法: add_library(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)

如果我们想指定lib*.so(动态库)、lib*.a(静态库)的生成目录,可以通过set()函数来更改LIBRARY_OUTPUT_PATH的值

# 将如将test.cpp test1.cpp生成链接库并放到project目录下的lib文件夹中
set(SRC test.cpp test1.cpp)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(func SHARED ${SRC})

SHARED,动态库

STATIC,静态库

MODULE,在使用dyld的系统有效,如果不支持dyld,则被当做SHARED对待。

EXCLUDE_FROM_ALL参数的意思是这个不会被默认构建,除非有其他的组件依赖或者手工构建。

find_library()-用于库的路径查找

该指令如果找到库则会将结果(绝对路径+库全名)存储到变量<VAR>中

语法:

find_library(

<VAR>

name | NAMES name1 [name2 ...] [NAMES_PER_DIR]

[HINTS [path | ENV var]... ]

...)

name:库名/*.a/*.so,库名优先搜索动态链接库

HINTS:是在搜索系统路径之前先搜索HINTS指定的路径

PATHS:是先搜索系统路径,然后再搜索PATHS指定的路径。

其他参数请参考官方文档

find_library(var NAMES opencv_core PATHS /opt/opencv3.1/lib/)
find_package()-搜索库的头文件和库文件的目录

如果找到XXX库就把头文件路径和库文件路径赋值给下面两个语句中的 ${XXX_INCLUDE_DIRS}、 ${XXX_LIBRARIES}。随后可以通过include_directories和target_link_libraries()来进行连接。

以c++的thread库为例

find_package(Boost REQUIRED system thread timer chrono)
add_executable(threads main.cpp)
target_link_libraries(threads ${Boost_LIBRARIES})
add_compile_options()-添加编译参数
# 如果我们想让编译器编译时有-wall -std=c++11 -O2等参数,我们可以用该函数指定
add_compile_options(-Wall -std=c++11 -O2)
add_executable()-生成可执行文件

语法:add_executable(exename source1 source2 ... sourceN)

# 将main.cpp生成main_exec的可执行文件
add_executable(main_exec main.cpp)
target_link_libraries()-为target添加需要连接的共享库

语法: target_link_libraries(target library1 library2...)

target:该参数必须是add_executable()或add_library之类的命令生成的

该语句类似于g++/gcc编译语句中的-l(小写L)参数

注意对于link_libraries()作用范围是全局的,而target_link_libraries()作用范围是给target的

# 将func动态链接库连接到可执行文件main
# 一般会配合link_directories使用
target_link_libraries(main func)
add_subdirectory()-向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制 存放的位置

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

该语句可以完成CMakeLists.txt的层层嵌套,可以将外层目录的CMakeLists.txt与内层目录的CMakelists.txt联系起来。

# 添加src子目录,src中需有一个CMakeLists.txt
add_subdirectory(src)
aux_source_directory()-发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指 令临时被用来自动构建源文件列表

语法: aux_source_directory(dir VARIABLE)

# 将当前目录下所有的源代码文件定义为SRC变量
aux_source_directory(. SRC)
install()-指定在安装时运行的规则(在执行make install时调用)

可以用来安装很多内容,可以包括目标二进制、动态库、静态库以及文件、目录、脚本

install(TARGETS myrun mylib mystaticlib
       RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
       LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
       ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

可执行二进制myrun安装到${CMAKE_INSTALL_BINDIR}目录,动态库libmylib.so安装到${CMAKE_INSTALL_LIBDIR}目录,静态库libmystaticlib.a安装到${CMAKE_INSTALL_LIBDIR}目录


cmake 编译ios应用 cmake编译教程_c++_07


2.3 CMake常用变量

CMAKE_C_FLAGS

CMAKE_CXX_FLAGS

CMAKE_BUILD_TYPE

--

CMAKE_BINARY_DIR

PROJECT_BINARY_DIR

工程名_BINARY_DIR

  1. 这三个变量指代的内容是一致
  2. 如果是 in source buid,指的就是工程顶层目录
  3. 如果是out of source,比如在项目根目录新建build,然后cd build&&cmake ..,指的是工程编译发生的目录

--

CMAKE_SOURCE_DIR

PROJECT_SOURCE_DIR

工程名_SOURCE_DIR

这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。 --

CMAKE_C_COMPILER:指定C编译器

CMAKE_CXX_COMPILER:指定C++编译器

EXECUTABLE_OUTPUT_PATH:可执行文件输出的存放路径

LIBRARY_OUTPUT_PATH:库文件输出的存放路径

CMAKE_CURRENT_SOURCE_DIR:这是当前处理的CMakeLists.txt所在的目录

三、CMake实操演练

3.1不采用连接库形式


cmake 编译ios应用 cmake编译教程_linux_08


构建如上目录

main.cpp

#include <iostream>
#include "person.h"
#include "funcp.h"
using namespace std;

int main(){
    person p1("三三",23);
    funcp(p1);
    cout<<"hello main!"<<endl;
    return 0;
}

funcp.cpp

#include "funcp.h"

void funcp(person &p1){
    cout<<"name:"<<p1.name<<" age:"<<p1.age<<endl;
}

funcp.h

#pragma once
#include <iostream>
#include "person.h"
using namespace std;

void funcp(person &p1);

person.cpp

#include "person.h"


person::person(string name,int age)
{
    this->name=name;
    this->age=age;
}

person.h

#pragma once
#include <string>
using namespace std;
class person{
public:
string name;
int age;
person(string name,int age);
~person(){};
};

外层CMakeLists.txt如下:

# 设置cmake最小版本为3.0
cmake_minimum_required(VERSION 3.0)
# 设置工程名
project(demo5)

# 相当于gcc/g++编译器的-I用来指定头文件搜索目录
include_directories(include)
# 相当于gcc/g++编译器的-L用来指定连接库搜索目录
link_directories(lib)
# 设置可执行文件输出路径
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
# 向下包含src,src必须也要有CMakeLists.txt
add_subdirectory(src)

src目录下CMakeLists.txt如下:

# 给SRC变量赋值
set(SRC main.cpp funcp.cpp person.cpp)
# 生成可执行文件,用变量时记得${xx}
add_executable(main_exec ${SRC})

一切就绪后,在demo5目录下:

cd build
cmake ..
make

然后会在/bin目录下生成一个可执行文件main_exec

然后可以通过./bin/main_exec运行

3.2采用连接库形式

只需要修改两个cmakelists.txt文件就好

外层CMakeLists.txt如下:

# 设置cmake最小版本为3.0
cmake_minimum_required(VERSION 3.0)
# 设置工程名
project(demo5)

# 相当于gcc/g++编译器的-I用来指定头文件搜索目录
include_directories(include)
# 设置连接库输出的路径
set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 用来生成func的动态连接库
add_library(func SHARED src/funcp.cpp src/person.cpp)
# 相当于gcc/g++编译器的-L用来指定连接库搜索目录
link_directories(lib)
#设置可执行文件输出路径
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
# 向下包含src,src必须也要有CMakeLists.txt
add_subdirectory(src)

src目录下CMakeLists.txt如下:

# 生成可执行文件,这里可以看到只需要main.cpp就可以生成可知性文件
add_executable(main_exec1 main.cpp)
# 该可知性文件在运行时会装载动态链接库
target_link_libraries(main_exec1 func)


cmake 编译ios应用 cmake编译教程_c++_09


输出结果如上图所示,并且在lib目录下生成了动态链接库.


cmake 编译ios应用 cmake编译教程_vscode_10