makefile
前言
最近在写底层C代码需要用到makefile来简化编译流程并优化文件目录结构,一直没找到很好的makefile教程(一个通俗易懂的渐进式的教程),通过+c同学终于是找到了他在之前在学校实训的时候写的一篇文章,由于网站只能通过校内网查阅,在此决定分享一下,并留作个人往后学习查阅使用。
正文
原文名字:Makefile的使用
原文作者:DaddyTrap
Makefile简介
Makefile是一个文件名为Makefile的文本文件,用于定义项目的编译规则,以便于整个项目的编译。
(创建方法:touch Makefile
编辑方法:gedit Makefile vim Makefile
…)
如果不使用Makefile,可能我们就需要跟之前一样手打一大串编译命令来编译代码——大一时便是深有体会。g++ main.cpp support1.cpp support2.cpp support3.cpp ...
这样的命令每次都要打一遍,如果是在平时的题目倒还好,如果遇到有数十个cpp和hpp的项目那就不好玩了,而且这样编译,有些依赖关系不清楚也是一个麻烦。
Makefile中就可以定义好各个文件的依赖关系,在之后再需要编译时,只需要执行make命令就可以自动编译了。
在一次make
之后,一般 会生成很多 目标文件(*.o) 和一个可执行文件,当这些文件和源代码都没有被修改时,再次执行make
会提示make: 'bin/your_program' is up to date.
,而当你只修改了一个源代码文件再执行make
时,它也不会重复编译已经最新的文件,而只编译依赖了你的源代码的文件,这对提高编译效率是非常重要的。
编译过程
关于编译过程,本文就不再赘述,仅提供一篇博客(←这是超链接)吧。
Makefile初级教程
规则 的基本语法:
target ... : prerequisites ...
command
...
...
- target 是下面的命令的 目标 ,即下面命令是为了target而生的。这个 目标 可以是*.o文件,也可以是可执行文件
- prerequisites 则是生成该目标所 依赖 的文件,如果找不到依赖的文件,下面的命令就不会执行且会中断 make
- command 就是生成目标文件的命令,一般就是编译命令了,如
g++ main.cpp
等等(注意:命令前面必须有 Tab (‘\t’) 真正 的Tab,这样make才会认为它是指令)
直接看一个例子会更加直观。
假设有下列三个文件和下述的目录结构
// main.cpp
#include <iostream>
#include <string>
#include "support1.hpp"
int main() {
std::string s = "Testing";
support1::PrintItself(s);
return 0;
}
// support1.cpp
#include "support1.hpp"
void support1::PrintItself(std::string s) {
std::cout << s << std::endl;
}
// support.hpp
#include <iostream>
namespace support1 {
void PrintItself(std::string);
}
# 目录结构
user@computer:~/learnmake$ tree
.
├── main.cpp
├── Makefile
├── support1.cpp
└── support1.hpp
main.cpp中include了support1,我们的 目的 是编译出一个可执行文件main
毫无疑问可以使用g++ main.cpp support1.cpp -o main
这样的命令,于是就写成了下面的Makefile
# Makefile version 1
main: main.cpp support1.cpp support1.hpp
g++ main.cpp support1.cpp -o main
- main就是上文说到的target
- 生成它依赖于所有的代码文件(main.cpp support1.cpp support1.hpp)
- 生成它的命令是
g++ main.cpp support1.cpp -o main
这个Makefile简单易懂,但是不行,这不程序员。这样的关系还是不太清晰。我们一般让各个cpp文件生成.o文件,再将它们链接起来。也就是需要下面的命令
g++ support1.cpp -c # 生成support1.o
g++ main.cpp -c # 生成main.o
g++ main.o support1.o -o main # 链接生成可执行文件main
那就按这个改吧
# Makefile version 2
main: main.o support1.o
g++ main.o support1.o -o main
support1.o: support1.hpp support1.cpp
g++ -c support1.cpp
main.o: main.cpp
g++ -c main.cpp
这里就可以再说说 依赖关系 怎么体现了。按顺序来读:
- 需要得到目标main,先看看它所依赖的文件在不在——并没有
- 于是先生成所依赖的文件main.o和support1.o
- main.o依赖main.cpp,在目录中寻找,有则执行命令,无则报错
- support1.o同上
- 依赖文件已经都存在了,生成目标out
- 结束
这样,一个简单的Makefile就完成了,然而它还有很多可以改进的地方
先看下面这一个Makefile
# Makefile version 3
CC := g++
FLAGS := -std=c++11 -w
main: support1.o main.o
$(CC) $(FLAGS) main.o support1.o -o $@
support1.o: support1.hpp support1.cpp
$(CC) $(FLAGS) -c support1.cpp
main.o: main.cpp
$(CC) $(FLAGS) -c main.cpp
clean:
@rm -f *.o
@rm -f *.gch
@rm -f main
突然出现的这些语法,下面我们逐个说说
- 变量(如
CC := g++
、$(CC)
) - 在Makefile中可以使用
"<name> := <content>"
声明变量,并在此后使用$(<name>)
可以使用,类似C/C++中的宏,它是字符串的直接替换。如$(CC) $(FLAGS) -c support1.cpp
会变成g++ -std=c++11 -w -c support1.cpp
。 - 前缀是
$
符号,后面带个括号()
的都是变量,如$@
也是变量,称为 自动化变量,它是目标文件的名字。如目标为main的这一段的命令会变成g++ -std=c++ -w main.o support1.o -o main
- 伪目标(没有依赖文件的目标clean
- clean一段中,target后面没有依赖文件,这称为 伪目标,作用是写了之后就可以使用
make clean
来执行它的命令集了 - 这里的命令集可以使用许多shell命令,但似乎并不是所有都能用
- 命令前面的
@
表示 不显示这条命令 ,和Windows下的.bat类似(下面分别是不使用@
和使用@
的对比)
在Makefile中加入
foo:
echo "Testing"
# 同样地,这是伪目标,可以使用make foo来执行其命令集
user@computer:~/learnmake$ make foo
echo "Testing"
Testing
Makefile中改为
foo:
@echo "Testing"
user@computer:~/learnmake$ make foo
Testing
复杂一点的情况
有时候一个项目的目录并没有如此简单,例如:
.
├── bin
├── build
├── include
├── lib
│ ├── mysql
│ └── mysql++
└── src
- bin是编译生成的可执行文件
- build是编译的中间文件如*.o文件
- include是各种.h或.hpp文件
- lib是一些必要的库文件
- src是.cpp文件
这个时候,事情并不简单。因为现在的代码“身首异处”,所以要把它们的路径告诉编译器才行,我们使用 相对路径 来达到效果。
.表示当前目录,..
表示上一级目录,用类似这样的相对路径来找到需要的文件,当然./
经常可以省略……(于是本文并没有者两个的使用)
以下面的目录结构为目标(假设原本不存在bin目录和build目录):
.
├── bin
│ └── main
├── build
│ ├── main.o
│ └── support1.o
├── include
│ └── support1.hpp
├── Makefile
└── src
├── main.cpp
└── support1.cpp
因为最后的目标 main 在 bin 目录中,所以 target 也应该是 bin/main,而其依赖的两个文件也要写清楚目录了 build/main.o build/support1.o。
而在编译前,应该确认 bin 和 build 都是存在的目录,因此需要在编译前多一条命令 mkdir -p bin
。因为要指明文件目录,于是编译命令变成g++ -std=c++11 -w build/main.o build/support1.o -o bin/main
那么编译生成support1.o的时候命令就是g++ -std=c++11 -w src/support1.cpp -c -o build/support1.o
:-)骗你的
由于在support1.cpp中我们写了#include "support1.hpp"
,按以前的经验,这样写是只能找到同一个文件夹下的文件的,所以需要加一个编译器的参数-I./include
,让它去其他地方找头文件。这样编译命令就完成了,生成.o文件的也类似。那么Makefile就可以写出来了
CC := g++
FLAGS := -std=c++11 -w
bin/main: build/support1.o build/main.o
@mkdir -p bin
$(CC) $(FLAGS) -I./include build/support1.o build/main.o -o $@
build/support1.o: src/support1.cpp
@mkdir -p build
$(CC) $(FLAGS) -I./include -c -o $@ src/support1.cpp
build/main.o: src/main.cpp
@mkdir -p build
$(CC) $(FLAGS) -I./include -c -o $@ src/main.cpp
clean:
@rm -rf build
@rm -rf bin
(可能有朋友注意到这里的support1.hpp去掉了,因为编译的时候就会找support1.hpp,找不到就会编译错误,所以它是否写在依赖规则里并不影响结果,也就可有可无了 订正:在这里只是为了达到使用下文通配符%就包含两个cpp的效果,实际上写这个也是必要的,例如:support1.hpp 被修改后,再次使用make,如果不写,那么make就不会重新编译了)
这样就好了吗?不,不可以。将来要是文件结构有了偏差,写这个Makefile的人是要负责的。如果目录不同了,那么改各个路径要每个都改,而最好的做法应该是只改一处就影响全局。所以应该下面这样会更好。
CC := g++
FLAGS := -std=c++11 -w
INC_DIR := include
SRC_DIR := src
BUILD_DIR := build
BIN_DIR := bin
INCLUDE := -I./$(INC_DIR)
$(BIN_DIR)/main: $(BUILD_DIR)/support1.o $(BUILD_DIR)/main.o
@mkdir -p $(BIN_DIR)
$(CC) $(FLAGS) $(INCLUDE) $(BUILD_DIR)/support1.o $(BUILD_DIR)/main.o -o $@
$(BUILD_DIR)/support1.o: $(SRC_DIR)/support1.cpp
@mkdir -p $(BUILD_DIR)
$(CC) $(FLAGS) $(INCLUDE) -c -o $@ $(SRC_DIR)/support1.cpp
$(BUILD_DIR)/main.o: $(SRC_DIR)/main.cpp
@mkdir -p $(BUILD_DIR)
$(CC) $(FLAGS) $(INCLUDE) -c -o $@ $(SRC_DIR)/main.cpp
clean:
@rm -rf $(BUILD_DIR)
@rm -rf $(BIN_DIR)
对于不同的情况,只要一个一个目标、一个一个依赖来写就好了
下面用的是通配符,并不适用于所有情况,还请各位谨慎使用
然而这个Makefile还可以更加简短并更加完善,如下面代码(出现的新东西,后面细说)
CC := g++
FLAGS := -std=c++11 -w
INC_DIR := include
SRC_DIR := src
BUILD_DIR := build
BIN_DIR := bin
INCLUDE := -I./$(INC_DIR)
$(BIN_DIR)/main: $(BUILD_DIR)/support1.o $(BUILD_DIR)/main.o
@mkdir -p $(BIN_DIR)
$(CC) $(FLAGS) $(INCLUDE) $^ -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
@mkdir -p $(BUILD_DIR)
$(CC) $(FLAGS) $(INCLUDE) -c -o $@ $<
clean:
@rm -rf $(BUILD_DIR)
@rm -rf $(BIN_DIR)
- 两个自动化变量
$^
依赖文件的集合,用空格分隔$<
第一个依赖文件- 其实在
%.o
部分,也可以换成$^
- 还有上文已经提过的 $@目标文件
- 通配符%
- 作用是就是*的作用了……通配……
这里的好处就是当src
目录下再多cpp文件时,生成%.o文件的这一部分不用更改,生成可执行文件那里只要加一个依赖就好了
实际上还有方法让生成可执行文件的规则也自动,但又会聊到更加深的内容了
或许这篇新手向的教程并不让你尽兴,因此如果你想了解更多的Makefile知识,可以参考下面两篇文章
- 跟我一起写Makefile(←这是超链接):写的非常详细,但少了自动化变量的解释
- 补充:自动化变量(←这是超链接)
相关阅读
PowerDesigner16.5汉化破解版安装教程(含安装文件、汉
目录 一、课程推荐 二、软件下载 三、软件安装 四、软件破解 五、软件汉化 一、课程推荐 有读者希望能有相关教程,这里推荐一个
关于魔方地常识 魔方的国际标准配色为:红、黄、蓝、绿、白、橙,其中黄对白、红对橙、蓝对绿 魔方的中心点地颜色两两对应,不可更改
网页orApp鼠标滚动效果在原型设计中如何实现,文章对此作出了分享。前些天要做一个高保真的email应用原型,在做滑动显示邮件列表的时
当我在虚拟机上安装win7系统时,出现了很多问题,在网上找教程,教程有那么多,每个都不一样,按照教程来,却并没有装上系统,让我很繁琐,经过自