gcc、make与cmake
1.gcc
gcc是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。
当你的程序只有一个源文件时,直接就可以用gcc命令编译它。
gcc hello.c -o hello
,也可以gcc -o hello hello.c
- 编译成功后会生成一个可执行文件hello,在命令行中输入 ./hello运行程序
或者分步骤
1 | gcc - 选项 源文件名(依赖文件) -o 目标文件名 |
- 预处理:
gcc -E hello.c -o hello.i
- 编译:
gcc -S hello.i -o hello.s
- 汇编:
gcc -c hello.s -o hello.o
- 链接:
gcc hello.o -o hello
但是当你的程序包含很多个源文件时,用gcc命令逐个去编译时,就很容易混乱而且工作量大。
2.make和makefile
2.1.make
make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式,通过调用makefile文件中用户指定的命令来进行编译和链接的。
make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。
makefile命令中就包含了调用gcc(也可以是别的编译器)去编译某个源文件的命令。
makefile在一些简单的工程完全可以人工手下,但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改。
2.2.makefile语法
一个简单的makefile文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29# C++ 简单项目的 Makefile 示例
# 编译器
CXX = g++
# 编译选项
CXXFLAGS = -Wall -std=c++17
# 源文件
SRC = main.cpp utils.cpp
# 对应的目标文件(.o)
OBJ = $(SRC:.cpp=.o)
# 最终生成的可执行文件名
TARGET = my_program
# 默认目标
all: $(TARGET)
# 链接目标文件生成可执行文件
$(TARGET): $(OBJ)
$(CXX) $(OBJ) -o $(TARGET)
# 编译每一个 .cpp 文件为 .o 文件
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $<
# 清理命令:删除中间文件和可执行文件
clean:
rm -f $(OBJ) $(TARGET)
2.2.1.基础结构
1 | 目标: 依赖1 依赖2 ... |
- 目标(Target):通常是你想生成的文件,比如可执行文件 main。
- 依赖(Dependencies):是生成目标所依赖的文件,比如源代码文件 .cpp。
- 命令(Command):是生成目标所需要执行的 shell 命令(注意前面要用Tab,不是空格!)
2.2.2.makefile变量
自定义变量
在makefile文件中定义的变量
make工具传给makefile的变量1
2
3
4
5
6定义变量:
变量名=变量值
引用变量:
$(变量名)
或
${变量名}注意:
- 变量是大小写敏感的
- 变量一般都在makefile的头部定义
- 变量几乎可在makefile的任何地方使用
- makefile变量名可以以数字开头
简单(即时)变量
变量的值即刻确定,在定义的时候就已经确定了。延时变量
变量要使用到的的时候才会确定,在定义等于时并不存在
常用的变量的定义如下:1
2
3
4
5:= # 即时变量
= # 延时变量
?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句
\+= # 附加, 它是即时变量还是延时变量取决于前面的定义
?=: # 如果这个变量在前面已经被定义了,这句话就会不会起效果,系统环境变量
make工具解析makefile前,读取系统环境变量并设置为makefile的变量- 预定义变量(自动变量)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26@目标名∗∗∗∗< 依赖文件列表中第一个文件
$^ 依赖文件列表中除去重复文件的部分
CC C编译器的名称,默认值为cc
CFLAGS C编译器的选项
AR 归档维护程序的程序名,默认值为ar
ARFLAGS 归档维护程序的选项
AS 汇编程序的名称,默认值为as
ASFLAGS 汇编程序的选项
CPP C预编译器的名称,默认值为$(CC)-E
CPPFLAGS C预编译器的选项
CXX C++编译器的名称,默认值为g++
CXXFLAGS C++编译器的选项
LIBS:告诉链接器要链接哪些库文件
LDFLAGS:gcc 等编译器会用到的一些优化参数,也可以在里面指定库文件的位置。
CFLAGS: 指定头文件(.h文件)的路径
CFLAGS部分参数如下:
-S 只是编译不汇编,生成汇编代码
-E 只进行预编译,不做其他处理
-g 在可执行程序中包含标准调试信息
-o file 把输出文件输出到file里
-v 打印出编译器内部编译各过程的命令行信息和编译器的版本
-I dir 在头文件的搜索路径列表中添加dir目录
-L dir 在库文件的搜索路径列表中添加dir目录
-static 链接静态库
-llibrary 连接名为library的库文件
2.2.3.通配符
1 | %.o:表示所用的.o文件 |
2.2.4.伪目标
一些目标并不是真正的文件,而是操作,比如 clean
2.2.5.函数
在Makefile中可以使用它内部的函数来处理文本,从而让我们的命令或规则更加智能。函数调用之后,函数的返回值可以当作变量来使用。
- 函数的调用语法
1
2
3$(<function><arguments>)
或者
${<function><arguments>} - 字符串替换与分析函数patsubst
函数 patsubst 语法如下:1
2
3
4
5
6
7
8
9
10$(patsubst <pattern>,<replacement>,<text>)
查找text中以空白符分隔的单词是否符合模式,如果匹配的话,就可以使用进行替换。
实例
files2 = a.c b.c c.c d.c e.c abc
dep_files = $(patsubst %.c,%.d,$(files2))
all:
@echo dep_files = $(dep_files)
结果dep_files = a.d b.d c.d d.d e.d abc - findstring
函数 findstring 语法如下:1
2$(findstring <FIND>,<IN>)
功能:从字符串中查找指定的字符串,找到就返回,没找到返回空。 - filter、filter-out
函数 filter 语法如下:函数filter-out 语法如下:1
2$(filter <pattern...>,<text>) # 在text中取出符合patten格式的值
功能:以模式过滤 字符串中的内容,保留符合模式的内容,可有多个模式。1
2$(filter-out <pattern...>,text) # 在text中取出不符合patten格式的值
功能:以模式过滤 字符串中的内容,保留不符合模式的内容,可有多个模式。 - 文件名称处理函数wildcard
函数Wildcard语法如下:1
2$(wildcard <pattern...>)
功能:这个函数 wildcard 会以 这个格式,去寻找所有存在的文件,返回存在文件的名字,文件名以空格分隔。若不存在任何符合此格式的文件,则返回空 make控制函数
- info
函数info语法如下:1
2$(info <text>)
功能:这个函数会标准输出打印文本 ,用于输出调试信息。 - warning
函数warning语法如下:1
2$(warning <text>)
功能:这个函数会向标准输出打印文本 ,用于输出警告信息。make继续执行。 error
函数error语法如下:1
2$(error <text>)
功能:这个函数会向标准错误输出打印文本 ,用于输出指明错误信息。make停止执行foreach
函数foreach语法如下:1
2$(foreach <var>,<list>,<text>)
功能:把参数中的单词逐一取出放到参数所指定的变量中,然后再执行 所包含的表达式。每一次 会返回一个字符串,循环过程中, 的所返回的每个字符串会以空格分隔,最后当整个循环结束时,text所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。
- info
3.cmake和CmakeList.txt
Cmake就可以根据一个叫CMakeLists.txt文件makefile文件给上面那个make用。当然cmake还有其他功能,就是可以跨平台生成对应平台能用的makefile,你不用再自己去修改了。
分别使用makefile和CmakeLists.txt构建项目
3.1.CmakeLists.txt文件编写
编写CMakeLists.txt最常用的功能就是调用其他的头文件(.h .hpp )、动态链接库(.so)、静态链接库(.a),将源文件.cpp .c .cc 编译成目标可执行或目标可链接库文件。CMake是makefile的上层工具,用于跨平台构建环境,生成可移植的makefile,并简化自己动手写makefile时的巨大工作量。*
一般,项目工程文件中:
- bin 文件夹存放 编译好的可执行二进制文件
- include 文件夹存放头文件
- src 文件夹存放源代码
- lib 文件夹存放编译好的库文件或者要调用的库文件
CMake支持大写、小写、混合大小写的命令。如果在编写CMakeLists.txt文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意
通常一个CMakeLists.txt需按照下面的流程:1
2
3
4
5
6
7
8
9
10
11project(xxx) #必须
add_subdirectory(子文件夹名称) #父目录必须,子目录不必
add_library(库文件名称 STATIC 文件) #通常子目录(二选一)
add_executable(可执行文件名称 文件) #通常父目录(二选一)
include_directories(路径) #必须
link_directories(路径) #必须
target_link_libraries(库文件名称/可执行文件名称 链接的库文件名称) #必须
注释
CMake 使用#
进行行注释,可以放在任何位置。
CMake 使用#[[ ]]
形式进行多行注释。变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30PROJECT_NAME:项目名称
PROJECT_SOURCE_DIR:工程的根目录
PROJECT_BINARY_DIR :执行cmake命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
CMAKE_CURRENT_SOURCE_DIR:当前CMakeLists.txt文件所在目录
CMAKE_CURRENT_BINARY_DIR:target 编译目录
CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE:当前所在的行
CMAKE_INSTALL_PREFIX:工程安装目录,所有生成和调用所需的可执行程序,库文件,头文件都会安装到该路径下,Unix/Linux下默认为/usr/local,windows下默认为C:\Program Files
CMAKE_MODULE_PATH:设置搜索CMakeModules模块(.cmake)的额外路径,用来定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
CMAKE_C_FLAGS:设置C编译选项
CMAKE_CXX_FLAGS:设置C++编译选项
CMAKE_C_COMPILER:设置C编译器
CMAKE_CXX_COMPILER:设置C++编译器
CMAKE_BUILD_TYPE:build类型(Debug,Release,…),CMAKE_BUILD_TYPE=Debug;
CMAKE_COMMAND:也就是CMake可执行文件本身的全路径
CMAKE_DEBUG_POSTFIX:Debug版本生成目标的后缀,通常可以设置为"d"字符
CMAKE_GENERATOR:编译器名称,例如”UnixMakefiles”, “Visual Studio 7”等
BUILD_SHARED_LIBS:指定编译成静态库还是动态库
EXECUTABLE_OUTPUT_PATH:设置编译后可执行文件目录
LIBRARY_OUTPUT_PATH:设置生成的库文件目录
CMAKE_INCLUDE_CURRENT_DIR:自动添加CMAKE_CURRENT_BINARY_DIR和CMAKE_CURRENT_SOURCE_DIR到当前处理的CMakeLists.txt,set (CMAKE_INCLUDE_CURRENT_DIR ON)
CMAKE_MAJOR_VERSION:cmake 主版本号,比如 3.4.1 中的 3
CMAKE_MINOR_VERSION:cmake 次版本号,比如 3.4.1 中的 4
CMAKE_PATCH_VERSION:cmake 补丁等级,比如 3.4.1 中的 1
CMAKE_SYSTEM:系统名称,比如 Linux-2.6.22
CMAKE_SYSTEM_NAME:不包含版本的系统名,比如 Linux
CMAKE_SYSTEM_VERSION:系统版本,比如 2.6.22
CMAKE_SYSTEM_PROCESSOR:处理器名称,比如 i686
UNIX:在所有的类 UNIX 平台下该值为 TRUE,包括 OS X 和 cygwin
WIN32:在所有的 win32 平台下该值为 TRUE,包括 cygwinCMake变量使用${}方式取值(使用)
,但是在if控制语句中是直接使用变量名
环境变量使用$ENV{}
方式取值,使用SET(ENV{VAR} VALUE)
赋值cmake_minimum_required()
指定使用的 cmake 的最低版本project()
定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。1
2
3
4
5
6project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])add_executable()
:定义工程会生成一个可执行程序
add_executable(可执行程序名 源文件名称)
- 这里的可执行程序名和project中的项目名没有任何关系
- 源文件名可以是一个也可以是多个,如有多个可用空格或;间隔
1
add_executable(app add.c div.c main.c mult.c sub.c)
假设这五个源文件需要反复被使用,每次都直接将它们的名字写出来确实是很麻烦,此时我们就需要定义一个变量,将文件名对应的字符串存储起来,在cmake里定义变量需要使用set。
set
定义变量1
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
- VAR:变量名
- VALUE:变量值
1
2
3
4
5
6# 方式1: 各个源文件之间使用空格间隔
# set(SRC_LIST add.c div.c main.c mult.c sub.c)
# 方式2: 各个源文件之间使用分号 ; 间隔
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
add_executable(app ${SRC_LIST})
- C++程序的时候,可能会用到C++11、C++14、C++17、C++20等新特性,那么就需要在编译的时候在编译命令中制定出要
使用哪个标准
,还是使用set
1
2
3
4
5
6#增加-std=c++11
set(CMAKE_CXX_STANDARD 11)
#增加-std=c++14
set(CMAKE_CXX_STANDARD 14)
#增加-std=c++17
set(CMAKE_CXX_STANDARD 17) - 指定
可执行程序的输出路径
(EXECUTABLE_OUTPUT_PATH),通过set
设定1
2set(HOME /home/robin/Linux/Sort)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin) 在 CMake 中使用
aux_source_directory
命令可以查找
某个路径下的所有源文件
,命令格式为:1
aux_source_directory(< dir > < variable >)
- dir:要搜索的目录
variable:将从dir目录下搜索到的源文件列表存储到该变量中
例子
1
2
3
4
5
6cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
add_executable(app ${SRC_LIST})
get_filename_componet
获取文件路径file
可以搜索文件(当然,除了搜索以外通过 file 还可以做其他事情)1
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
- GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
搜索当前目录的src目录下所有的源文件,并存储到变量中
1
2file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
包含头文件
,在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。在CMake中设置要包含的目录也很简单,通过一个命令就可以搞定了,他就是include_directories
1
include_directories(headpath)
制作静态库
add_library(库名称 STATIC 源文件1 [源文件2] ...)
在Linux中,静态库名字分为三部分:lib+库名字+.a
,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
例如:要将src目录中的源文件编译成静态库,然后再使用1
2
3
4
5cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc STATIC ${SRC_LIST})这样最终就会生成对应的静态库文件libcalc.a
制作动态库
add_library(库名称 SHARED 源文件1 [源文件2] ...)
在Linux中,动态库名字分为三部分:lib+库名字+.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。1
2
3
4
5cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc SHARED ${SRC_LIST})这样最终就会生成对应的动态库文件libcalc.so。
指定动态库、静态库的输出路径
通过set
命令给EXECUTABLE_OUTPUT_PATH
宏设置了一个路径
由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH
宏了,而应该使用LIBRARY_OUTPUT_PATH
,这个宏对应静态库文件和动态库文件都适用。链接静态库
link_libraries(<static lib> [<static lib>...])
- 参数1:指定出要链接的静态库的名字,可以是全名 libxxx.a,也可以是掐头(lib)去尾(.a)之后的名字 xxx
参数2-N:要链接的其它静态库的名字
link_directories(<lib path>)
1
2
3
4
5
6
7
8
9
10$ tree
.
├── build
├── CMakeLists.txt
├── include
│ └── head.h
├── lib
│ └── libcalc.a # 制作出的静态库的名字
└── src
└── main.cpp1
2
3
4
5
6
7
8
9
10
11cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
add_executable(app ${SRC_LIST})
链接动态库
target_link_libraries
1
2
3
4target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)- target:指定要加载动态库的文件的名字,(谁需要)
- PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLICpthread、calc都是可执行程序app要链接的动态库的名字。
1
2
3
4
5
6
7cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC_LIST})
# 指定可执行程序要链接的动态库名字
target_link_libraries(app pthread)
日志
,message
1
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
- (无) :重要消息
- STATUS :非重要消息
- WARNING:CMake 警告, 会继续执行
- AUTHOR_WARNING:CMake 警告 (dev), 会继续
- SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
- FATAL_ERROR:CMake 错误, 终止所有处理过程
- 控制语句,条件判断
if
1
2
3
4
5
6
7if(<condition>)
<commands>
elseif(<condition>) # 可选快, 可以重复
<commands>
else() # 可选快
<commands>
endif() - 循环
foreach
- 循环
while
3.2.例子
见下一篇文章