文章先简单介绍了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 实操演练
说了一大堆,可能懵逼了!别急,接下来我会来说如何运用上述知识编译代码,以及如何生成静态/动态链接库!
我们先构建一个目录结构
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!按照下面的操作就可以编译成功并且生成一个可执行文件啦
方式一:直接式
方式二:连接静态库并生成可执行文件
先生成静态链接库
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/
注意报错信息!一定要输入完整!
方式三:链接动态库并生成可执行文件
# 先生成动态链接库
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的预备基本知识也准备的差不多了!
二、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}目录
2.3 CMake常用变量
CMAKE_C_FLAGS
CMAKE_CXX_FLAGS
CMAKE_BUILD_TYPE
--
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
工程名_BINARY_DIR
- 这三个变量指代的内容是一致
- 如果是 in source buid,指的就是工程顶层目录
- 如果是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不采用连接库形式
构建如上目录
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)
输出结果如上图所示,并且在lib目录下生成了动态链接库.