Makefile和Cmake入门
B站于仕琪老师课程 + 动手实操 学习记录
Makefile入门
两个函数的实现在两个不同的 Cpp 文件中: factorial.cpp 和 printhello.cpp。
两个函数在头文件 functions.h 中进行声明。
主函数中调用这两个函数,使用 #include
包含头文件 functions.h 。
这个时候有三个 cpp 文件,一个.h文件,如何执行进行编译呢?
最直接的方法是使用如下编译指令
g++ main.cpp factorial.cpp printhello.cpp -o main.out
编译成功,并且可以运行,如下:
这里的 .cpp 文件只有三个,直接这样编译没有问题,但是如果有很多文件,这样一起编译的话会花费很多时间,这个时候可以选择逐个编译,如下:
g++ mian.cpp -c
的意思是只编译,不链接。 编译完成之后,可以看到生成了很多 .o 文件。
将这些 .o 文件链接在一起生成可执行文件就可以执行程序了,如下:
为了减少编译时间,选择了逐个编译。如果修改了某个 cpp文件,只需要对修改的cpp文件执行编译即可,没有修改的文件不需要再次编译。
但是问题是,如果文件非常多,每次手动输入这些命令也是很麻烦的。
这个时候,可以把这些编译命令写到脚本文件中,这个脚本文件有一个固定的格式,那就是makefile。
makefile 会很多写法,最简单makefile的写法如下:
## VERSION 1
hello: main.cpp printhello.cpp factorial.cpp
g++ -o hello main.cpp printhello.cpp factorial.cpp
说明:
- #开头表示注释
- 第一行 hello 是目标(可执行文件),目标的生成依赖于main.cpp printhello.cpp factorical.cpp三个文件
- 第二行
g++ -o
前面必须要有一个tab
符号,不能是空格,表示使用g++ -o
命令生成目标 hello。第一行只是说了目标文件的生成依赖什么,但是没有说怎么生成,这里就是说基于所以依赖的文件,以何种方式生成目标文件。
写完 makefile 文件之后,直接使用make
命令,该命令会自动寻找当前文件夹下面的makefile(或Makefile)文件。如果makefile文件的名字不是不是makefile,可以使用 -f 选项指定文件名称,如下所示:
make -f filename
使用make
命令后,makefile文件的执行过程如下,
执行过程中,寻找目标文件hello,如果没有就生成该文件。
执行完成之后,在没有任何改动的情况下个,再次执行make,会出现make: "hello" is up to date
。
也就是说,执行一次make之后,目标文件hello所以依赖的三个 cpp文件没有修改的话,再次使用make是不会重新编译的。具体的实现是根据文件的保存时间判断的,只有hello的保存时间早于三个cpp文件的任意一个,使用make命令才会再次编译全部文件。
这个makefile文件的问题同样也是,文件很多的时候,编译时间很长,只修改其中一个文件之后,使用make命令,会编译全部文件。
所以给出第二个版本的makefile文件,如下:
## VERSION 2
# 定义变量
CXX = g++
TARGET = hello
OBJ = main.o printhello.o factorial.o
# 编译过程 (和version1一样,只是这里使用变量表示)
$(TARGET) : $(OBJ)
$(CXX) -o $(TARGET) $(OBJ)
main.o: main.cpp
$(CXX) -c main.cpp
printhello.o: printhello.cpp
$(CXX) -c printhello.cpp
factorial.o: factorial.cpp
$(CXX) -c factorial.cpp
使用make命令后,执行过程如下:
执行过程中,从头开始,先去找第一个TARGET文件,没有就要生成,因为生成该文件依赖OBJ文件,所以这个时候就要去往下找OBJ对应的文件,找到之后,在回头生成TARGET文件。
这个时候,如果修改了cpp文件,再次编译的时候,只会对修改过的cpp文件进行编译, 而不是全部编译。
如下,只有main.cpp的时间戳是晚于hello文件的,所以只对main.cpp再次编译,然后链接形成hello文件。
这个版本的makefile已经是可以用的,当然还有更好的写法,如下:
## VERSION 3
CXX = g++
TARGET = hello
OBJ = main.o printhello.o factorial.o
CXXFLAGS = -c -Wall # 编译选项,-Wall的的意思是报出所有的warning
$(TARGET) : $(OBJ)
$(CXX) -o $@ $^ # $@代表$(TARGET),$^代表$(OBJ)
%.o: %.cpp
$(CXX) $(CXXFLAGS) $< -o $@
# %表示通配符 $< 表示所依赖的文件的第一个($^是全部)
.PHONY: clean
clean:
rm -f *.o $(TARGET)
# clean是一个新的目标,不需要依赖对象,执行rm -f命令,强制删除所有.o 文件和 $(TARGET) 文件
使用make clean
会在目标文件中寻找clean目标,然后执行,如下:
.PHONY: clean
的作用:
因为clean文件(目标文件)的生成不依赖于任何文件(冒号后面的内容为空),所以如果当前文件夹中已经存在一个 clean 文件的话,就会出现make: 'clean' is up to date
,clean文件存在,不需要生成。
.PHONY: clean
表示有一个不存在的目标依赖clean,这个时候就会启动 clean 的生成。此时,就算有clean的情况下,make clean
命令也能够正常执行预定的功能。
(phony的意思是:伪造的,假的)
version3 的makefile文件执行过程如下:
因为编译选项中添加了 -Wall ,所以报出了程序中所有的warning。
虽然v3已经很不错了,但是还是在OBJ的地方还是出现了文件名,文件很多的情况下,还是要自己一个个去写,所以最后一个版本的makefile写法,如下。
## VERSION 4
CXX = g++
TARGET = hello
SRC = $(wildcard *.cpp) # 当前目录下的所有CPP文件都放到变量SRC中
OBJ = $(patsubst %.cpp, %.o, $(SRC)) # 替换 把所有的.cpp替换为 .o
CXXFLAGS = -c -Wall
$(TARGET) : $(OBJ)
$(CXX) -o $@ $^
%.o: %.cpp
$(CXX) $(CXXFLAGS) $< -o $@
.PHONY: clean
clean:
rm -f *.o $(TARGET)
Cmake的使用过程
makefile的配置是和和当前的系统强相关的,编译器、路径等都是不同的。如果编写一个跨平台的内容,是很不方便的。
Cmake就可以很好的解决这个问题。
Cmake基本过程:
- 编写一个Cmake文件,文件名称为CMakeLists.txt
- 执行
cmake .
命令 - 执行
make
命令
CMakeLists.txt文件内容如下:
具体执行过程如下:
这个时候的一个问题是,使用cmake生成了很多文件,如果如果想要删除这些文件,需要一个一个的删除,很麻烦,这个时候是可以建一个文件夹的,这个文件夹的名称一般为build
。
需要注意的时候,这个时候使用的是cmake ..
。原因如下:
.
是当前目录,..
是上一级目录,cmake
其实就是在一个指定的目录下面寻找CMakeLists.txt文件,我们这里是进入了build文件夹,而CMakeLists.txt是在上一级文件夹,所以需要使用cmake ..
。
这个时候,如果想要删除,直接删除build文件夹就可以了。(删除文件夹的命令:rm -rf build/
)
(这也是为什么好多工具有一个build文件夹的原因。)