简介
Linux的make
程序用来自动化编译大型源码,很多时候,我们在Linux下编译安装软件,只需要敲一个make
就可以全自动完成,非常方便。
make
能自动化完成这些工作,是因为项目提供了一个Makefile
文件,它负责告诉make
,应该如何编译和链接程序。
Makefile
相当于Java项目的pom.xml
,Node工程的package.json
,Rust项目的Cargo.toml
,不同之处在于,make
虽然最初是针对C语言开发,但它实际上并不限定C语言,而是可以应用到任意项目,甚至不是编程语言。此外,make
主要用于Unix/Linux环境的自动化开发,掌握Makefile
的写法,可以更好地在Linux环境下做开发,也可以为后续开发Linux内核做好准备。
在本教程中,我们将由浅入深,一步一步学习如何编写Makefile
,完全针对零基础小白,只需要提前掌握如何使用Linux命令。
在Linux环境下,当我们输入make
命令时,它就在当前目录查找一个名为Makefile
的文件,然后,根据这个文件定义的规则,自动化地执行任意命令,包括编译命令。
Makefile
这个单词,顾名思义,就是指如何生成文件。
我们举个例子:在当前目录下,有3个文本文件:a.txt
,b.txt
和c.txt
。
现在,我们要合并a.txt
与b.txt
,生成中间文件m.txt
,再用中间文件m.txt
与c.txt
合并,生成最终的目标文件x.txt
,整个逻辑如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ┌─────┐ ┌─────┐ ┌─────┐ │a.txt│ │b.txt│ │c.txt│ └─────┘ └─────┘ └─────┘ │ │ │ └───┬───┘ │ │ │ ▼ │ ┌─────┐ │ │m.txt│ │ └─────┘ │ │ │ └─────┬─────┘ │ ▼ ┌─────┐ │x.txt│ └─────┘
|
根据上述逻辑,我们来编写Makefile
。
规则
Makefile
由若干条规则(Rule)构成,每一条规则指出一个目标文件(Target),若干依赖文件(prerequisites),以及生成目标文件的命令。
例如,要生成m.txt
,依赖a.txt
与b.txt
,规则如下:
1 2 3
| # 目标文件: 依赖文件1 依赖文件2 m.txt: a.txt b.txt cat a.txt b.txt > m.txt
|
一条规则的格式为目标文件: 依赖文件1 依赖文件2 ...
,紧接着,以Tab开头的是命令,用来生成目标文件。上述规则使用cat
命令合并了a.txt
与b.txt
,并写入到m.txt
。用什么方式生成目标文件make
并不关心,因为命令完全是我们自己写的,可以是编译命令,也可以是cp
、mv
等任何命令。
以#
开头的是注释,会被make
命令忽略。
1
| 注意:Makefile的规则中,命令必须以Tab开头,不能是空格。
|
类似的,我们写出生成x.txt
的规则如下:
1 2
| x.txt: m.txt c.txt cat m.txt c.txt > x.txt
|
由于make
执行时,默认执行第一条规则,所以,我们把规则x.txt
放到前面。完整的Makefile
如下:
1 2 3 4 5
| x.txt: m.txt c.txt cat m.txt c.txt > x.txt
m.txt: a.txt b.txt cat a.txt b.txt > m.txt
|
在当前目录创建a.txt
、b.txt
和c.txt
,输入一些内容,执行make
:
1 2 3
| $ make cat a.txt b.txt > m.txt cat m.txt c.txt > x.txt
|
make
默认执行第一条规则,也就是创建x.txt
,但是由于x.txt
依赖的文件m.txt
不存在(另一个依赖c.txt
已存在),故需要先执行规则m.txt
创建出m.txt
文件,再执行规则x.txt
。执行完成后,当前目录下生成了两个文件m.txt
和x.txt
。
可见,Makefile
定义了一系列规则,每个规则在满足依赖文件的前提下执行命令,就能创建出一个目标文件,这就是英文Make file的意思。
把默认执行的规则放第一条,其他规则的顺序是无关紧要的,因为make
执行时自动判断依赖。
此外,make
会打印出执行的每一条命令,便于我们观察执行顺序以便调试。
如果我们再次运行make
,输出如下:
1 2
| $ make make: `x.txt' is up to date.
|
make
检测到x.txt
已经是最新版本,无需再次执行,因为x.txt
的创建时间晚于它依赖的m.txt
和c.txt
的最后修改时间。
1
| make使用文件的创建和修改时间来判断是否应该更新一个目标文件。
|
修改c.txt
后,运行make
,会触发x.txt
的更新:
1 2
| $ make cat m.txt c.txt > x.txt
|
但并不会触发m.txt
的更新,原因是m.txt
的依赖a.txt
与b.txt
并未更新,所以,make
只会根据Makefile
去执行那些必要的规则,并不会把所有规则都无脑执行一遍。
在编译大型程序时,全量编译往往需要几十分钟甚至几个小时。全量编译完成后,如果仅修改了几个文件,再全部重新编译完全没有必要,用Makefile
实现增量编译就十分节省时间。
当然,是否能正确地实现增量更新,取决于我们的规则写得对不对,make
本身并不会检查规则逻辑是否正确。
伪目标
因为m.txt
与x.txt
都是自动生成的文件,所以,可以安全地删除。
删除时,我们也不希望手动删除,而是编写一个clean
规则来删除它们:
1 2 3
| clean: rm -f m.txt rm -f x.txt
|
clean
规则与我们前面编写的规则有所不同,它没有依赖文件,因此,要执行clean
,必须用命令make clean
:
1 2 3
| $ make clean rm -f m.txt rm -f x.txt
|
然而,在执行clean
时,我们并没有创建一个名为clean
的文件,所以,因为目标文件clean
不存在,每次运行make clean
,都会执行这个规则的命令。
如果我们手动创建一个clean
的文件,这个clean
规则就不会执行了!
如果我们希望make
把clean
不要视为文件,可以添加一个标识:
1 2 3 4
| .PHONY: clean clean: rm -f m.txt rm -f x.txt
|
此时,clean
就不被视为一个文件,而是伪目标(Phony Target)。
大型项目通常会提供clean
、install
这些约定俗成的伪目标名称,方便用户快速执行特定任务。
一般来说,并不需要用.PHONY
标识clean
等约定俗成的伪目标名称,除非有人故意搞破坏,手动创建名字叫clean
的文件。
执行多条命令
一个规则可以有多条命令,例如:
执行cd
规则:
1 2 3 4 5 6
| $ make cd pwd /home/ubuntu/makefile-tutorial/v1 cd .. pwd /home/ubuntu/makefile-tutorial/v1
|
观察输出,发现cd ..
命令执行后,并未改变当前目录,两次输出的pwd
是一样的,这是因为make
针对每条命令,都会创建一个独立的Shell环境,类似cd ..
这样的命令,并不会影响当前目录。
解决办法是把多条命令以;
分隔,写到一行:
1 2
| cd_ok: pwd; cd ..; pwd;
|
再执行cd_ok
目标就得到了预期结果:
1 2 3 4
| $ make cd_ok pwd; cd ..; pwd /home/ubuntu/makefile-tutorial/v1 /home/ubuntu/makefile-tutorial
|
可以使用\
把一行语句拆成多行,便于浏览:
1 2 3 4
| cd_ok: pwd; \ cd ..; \ pwd
|
另一种执行多条命令的语法是用&&
,它的好处是当某条命令失败时,后续命令不会继续执行:
控制打印
默认情况下,make
会打印出它执行的每一条命令。如果我们不想打印某一条命令,可以在命令前加上@
,表示不打印命令(但是仍然会执行):
1 2 3
| no_output: @echo 'not display' echo 'will display'
|
执行结果如下:
1 2 3 4
| $ make no_output not display echo 'will display' will display
|
注意命令echo 'not display'
本身没有打印,但命令仍然会执行,并且执行的结果仍然正常打印。
控制错误
make
在执行命令时,会检查每一条命令的返回值,如果返回错误(非0值),就会中断执行。
例如,不使用-f
删除一个不存在的文件会报错:
1 2 3
| has_error: rm zzz.txt echo 'ok'
|
执行上述目标,输出如下:
1 2 3 4
| $ make has_error rm zzz.txt rm: zzz.txt: No such file or directory make: *** [has_error] Error 1
|
由于命令rm zzz.txt
报错,导致后面的命令echo 'ok'
并不会执行,make
打印出错误,然后退出。
有些时候,我们想忽略错误,继续执行后续命令,可以在需要忽略错误的命令前加上-
:
1 2 3
| ignore_error: -rm zzz.txt echo 'ok'
|
执行上述目标,输出如下:
1 2 3 4 5 6
| $ make ignore_error rm zzz.txt rm: zzz.txt: No such file or directory make: [ignore_error] Error 1 (ignored) echo 'ok' ok
|
make
检测到rm zzz.txt
报错,并打印错误,但显示(ignored)
,然后继续执行后续命令。
对于执行可能出错,但不影响逻辑的命令,可以用-
忽略。
参考源码
可以从GitHub下载源码。
小结
编写Makefile
就是编写一系列规则,用来告诉make
如何执行这些规则,最终生成我们期望的目标文件。
查看官方手册:
编译C程序
C程序的编译通常分两步:
- 将每个
.c
文件编译为.o
文件;
- 将所有
.o
文件链接为最终的可执行文件。
我们假设如下的一个C项目,包含hello.c
、hello.h
和main.c
。
hello.c
内容如下:
1 2 3 4 5 6 7
| #include <stdio.h>
int hello() { printf("hello, world!\n"); return 0; }
|
hello.h
内容如下:
main.c
内容如下:
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h> #include "hello.h"
int main() { printf("start...\n"); hello(); printf("exit.\n"); return 0; }
|
注意到main.c
引用了头文件hello.h
。我们很容易梳理出需要生成的文件,逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ┌───────┐ ┌───────┐ ┌───────┐ │hello.c│ │main.c │ │hello.h│ └───────┘ └───────┘ └───────┘ │ │ │ │ └────┬────┘ │ │ ▼ ▼ ┌───────┐ ┌───────┐ │hello.o│ │main.o │ └───────┘ └───────┘ │ │ └───────┬──────┘ │ ▼ ┌─────────┐ │world.out│ └─────────┘
|
假定最终生成的可执行文件是world.out
,中间步骤还需要生成hello.o
和main.o
两个文件。根据上述依赖关系,我们可以很容易地写出Makefile
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| world.out: hello.o main.o cc -o world.out hello.o main.o
hello.o: hello.c cc -c hello.c
main.o: main.c hello.h cc -c main.c
clean: rm -f *.o world.out
|
执行make
,输出如下:
1 2 3 4
| $ make cc -c hello.c cc -c main.c cc -o world.out hello.o main.o
|
在当前目录下可以看到hello.o
、main.o
以及最终的可执行程序world.out
。执行world.out
:
1 2 3 4
| $ ./world.out start... hello, world! exit.
|
与我们预期相符。
修改hello.c
,把输出改为"hello, bob!\n"
,再执行make
,观察输出:
1 2 3
| $ make cc -c hello.c cc -o world.out hello.o main.o
|
仅重新编译了hello.c
,并未编译main.c
。由于hello.o
已更新,所以,仍然要重新生成world.out
。执行world.out
:
1 2 3 4
| $ ./world.out start... hello, bob! exit.
|
与我们预期相符。
修改hello.h
:
以及hello.c
,再次执行make
:
1 2 3 4
| $ make cc -c hello.c cc -c main.c cc -o world.out hello.o main.o
|
会触发main.c
的编译,因为main.c
依赖hello.h
。
执行make clean
会删除所有的.o
文件,以及可执行文件world.out
,再次执行make
就会强制全量编译:
1 2 3 4 5
| $ make clean && make rm -f *.o world.out cc -c hello.c cc -c main.c cc -o world.out hello.o main.o
|
这个简单的Makefile
使我们能自动化编译C程序,十分方便。
不过,随着越来越多的.c
文件被添加进来,如何高效维护Makefile
的规则?我们后面继续讲解。
参考源码
可以从GitHub下载源码。
小结
在Makefile
正确定义规则后,我们就能用make
自动化编译C程序。
使用隐式规则
我们仍然以上一节的C项目为例,当我们添加越来越多的.c
文件时,就需要编写越来越多的规则来生成.o
文件。
实际上,有的同学可能发现了,即使我们把.o
的规则删掉,也能正常编译:
1 2 3 4 5 6
| world.out: hello.o main.o cc -o world.out hello.o main.o
clean: rm -f *.o world.out
|
执行make
,输出如下:
1 2 3 4
| $ make cc -c -o hello.o hello.c cc -c -o main.o main.c cc -o world.out hello.o main.o
|
我们没有定义hello.o
和main.o
的规则,为什么make
也能正常创建这两个文件?
因为make
最初就是为了编译C程序而设计的,为了免去重复创建编译.o
文件的规则,make
内置了隐式规则(Implicit Rule),即遇到一个xyz.o
时,如果没有找到对应的规则,就自动应用一个隐式规则:
1 2
| xyz.o: xyz.c cc -c -o xyz.o xyz.c
|
make
针对C、C++、ASM、Fortran等程序内置了一系列隐式规则,可以参考官方手册查看。
对于C程序来说,使用隐式规则有一个潜在问题,那就是无法跟踪.h
文件的修改。如果我们修改了hello.h
的定义,由于隐式规则main.o: main.c
并不会跟踪hello.h
的修改,导致main.c
不会被重新编译,这个问题我们放到后面解决。
参考源码
可以从GitHub下载源码。
小结
针对C、C++、ASM、Fortran等程序,make
内置了一系列隐式规则,使用隐式规则可减少大量重复的通用编译规则。
查看官方手册:
当我们在Makefile
中重复写很多文件名时,一来容易写错,二来如果要改名,要全部替换,费时费力。
编程语言使用变量(Variable)来解决反复引用的问题,类似的,在Makefile
中,也可以使用变量来解决重复问题。
以上一节的Makefile
为例:
1 2 3 4 5
| world.out: hello.o main.o cc -o world.out hello.o main.o
clean: rm -f *.o world.out
|
编译的最终文件world.out
重复出现了3次,因此,完全可以定义一个变量来替换它:
1 2 3 4 5 6 7
| TARGET = world.out
$(TARGET): hello.o main.o cc -o $(TARGET) hello.o main.o
clean: rm -f *.o $(TARGET)
|
变量定义用变量名 = 值
或者变量名 := 值
,通常变量名全大写。引用变量用$(变量名)
,非常简单。
注意到hello.o main.o
这个“列表”也重复了,我们也可以用变量来替换:
1 2 3 4 5 6 7 8
| OBJS = hello.o main.o TARGET = world.out
$(TARGET): $(OBJS) cc -o $(TARGET) $(OBJS)
clean: rm -f *.o $(TARGET)
|
如果有一种方式能让make
自动生成hello.o main.o
这个“列表”,就更好了。注意到每个.o
文件是由对应的.c
文件编译产生的,因此,可以让make
先获取.c
文件列表,再替换,得到.o
文件列表:
1 2 3 4 5 6 7 8 9 10
|
OBJS = $(patsubst %.c,%.o,$(wildcard *.c)) TARGET = world.out
$(TARGET): $(OBJS) cc -o $(TARGET) $(OBJS)
clean: rm -f *.o $(TARGET)
|
这样,我们每添加一个.c
文件,不需要修改Makefile
,变量OBJS
会自动更新。
思考:为什么我们不能直接定义OBJS = $(wildcard *.o)
让make
列出所有.o
文件?
内置变量
我们还可以用变量$(CC)
替换命令cc
:
1 2
| $(TARGET): $(OBJS) $(CC) -o $(TARGET) $(OBJS)
|
没有定义变量CC
也可以引用它,因为它是make
的内置变量(Builtin Variables),表示C编译器的名字,默认值是cc
,我们也可以修改它,例如使用交叉编译时,指定编译器:
1 2
| CC = riscv64-linux-gnu-gcc ...
|
自动变量
在Makefile
中,经常可以看到$@
、$<
这样的变量,这种变量称为自动变量(Automatic Variable),它们在一个规则中自动指向某个值。
例如,$@
表示目标文件,$^
表示所有依赖文件,因此,我们可以这么写:
1 2
| world.out: hello.o main.o cc -o $@ $^
|
在没有歧义时可以写$@
,也可以写$(@)
,有歧义时必须用括号,例如$(@D)
。
为了更好地调试,我们还可以把变量打印出来:
1 2 3 4 5
| world.out: hello.o main.o @echo '$$@ = $@' @echo '$$< = $<' @echo '$$^ = $^' cc -o $@ $^
|
执行结果输出如下:
1 2 3 4
| $@ = world.out $< = hello.o $^ = hello.o main.o cc -o world.out hello.o main.o
|
参考源码
可以从GitHub下载源码。
小结
使用变量可以让Makefile
更加容易维护。
查看官方手册:
使用模式规则
前面我们讲了使用隐式规则可以让make
在必要时自动创建.o
文件的规则,但make
的隐式规则的命令是固定的,对于xyz.o: xyz.c
,它实际上是:
1
| $(CC) $(CFLAGS) -c -o $@ $<
|
能修改的只有变量$(CC)
和$(CFLAGS)
。如果要执行多条命令,使用隐式规则就不行了。
这时,我们可以自定义模式规则(Pattern Rules),它允许make
匹配模式规则,如果匹配上了,就自动创建一条模式规则。
我们修改上一节的Makefile
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| OBJS = $(patsubst %.c,%.o,$(wildcard *.c)) TARGET = world.out
$(TARGET): $(OBJS) cc -o $(TARGET) $(OBJS)
%.o: %.c @echo 'compiling $<...' cc -c -o $@ $<
clean: rm -f *.o $(TARGET)
|
当make
执行world.out: hello.o main.o
时,发现没有hello.o
文件,于是需要查找以hello.o
为目标的规则,结果匹配到模式规则%.o: %.c
,于是make
自动根据模式规则为我们动态创建了如下规则:
1 2 3
| hello.o: hello.c @echo 'compiling $<...' cc -c -o $@ $<
|
查找main.o
也是类似的匹配过程,于是我们执行make
,就可以用模式规则完成编译:
1 2 3 4 5 6
| $ make compiling hello.c... cc -c -o hello.o hello.c compiling main.c... cc -c -o main.o main.c cc -o world.out hello.o main.o
|
模式规则的命令完全由我们自己定义,因此,它比隐式规则更灵活。
但是,模式规则仍然没有解决修改hello.h
头文件不会触发main.c
重新编译的问题,这个依赖问题我们继续放到后面解决。
最后注意,模式规则是按需生成,如果我们在当前目录创建一个zzz.o
文件,因为make
并不会在执行过程中用到它,所以并不会自动生成zzz.o: zzz.c
这个规则。
参考源码
可以从GitHub下载源码。
小结
使用模式规则可以灵活地按需动态创建规则,它比隐式规则更灵活。
查看官方手册:
前面我们讲了隐式规则和模式规则,这两种规则都可以解决自动把.c
文件编译成.o
文件,但都无法解决.c
文件依赖.h
文件的问题。
因为一个.c
文件依赖哪个.h
文件必须要分析文件内容才能确定,没有一个简单的文件名映射规则。
但是,要识别出.c
文件的头文件依赖,可以用GCC提供的-MM
参数:
1 2
| $ cc -MM main.c main.o: main.c hello.h
|
上述输出告诉我们,编译main.o
依赖main.c
和hello.h
两个文件。
因此,我们可以利用GCC的这个功能,对每个.c
文件都生成一个依赖项,通常我们把它保存到.d
文件中,再用include
引入到Makefile
,就相当于自动化完成了每个.c
文件的精准依赖。
我们改写上一节的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 30 31
| SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
DEPS = $(SRCS:.c=.d)
TARGET = world.out
$(TARGET): $(OBJS) $(CC) -o $@ $^
%.d: %.c rm -f $@; \ $(CC) -MM $< >$@.tmp; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.tmp > $@; \ rm -f $@.tmp
%.o: %.c $(CC) -c -o $@ $<
clean: rm -rf *.o *.d $(TARGET)
include $(DEPS)
|
变量$(SRCS)
通过扫描目录可以确定为hello.c main.c
,因此,变量$(OBJS)
赋值为hello.o main.o
,变量$(DEPS)
赋值为hello.d main.d
。
通过include $(DEPS)
我们引入hello.d
和main.d
文件,但是这两个文件一开始并不存在,不过,make
通过模式规则匹配到%.d: %.c
,这就给了我们一个机会,在这个模式规则内部,用cc -MM
命令外加sed
把.d
文件创建出来。
运行make
,首次输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $ make Makefile:31: hello.d: No such file or directory Makefile:31: main.d: No such file or directory rm -f main.d; \ cc -MM main.c >main.d.tmp; \ sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.tmp > main.d; \ rm -f main.d.tmp rm -f hello.d; \ cc -MM hello.c >hello.d.tmp; \ sed 's,\(hello\)\.o[ :]*,\1.o hello.d : ,g' < hello.d.tmp > hello.d; \ rm -f hello.d.tmp cc -c -o hello.o hello.c cc -c -o main.o main.c cc -o world.out hello.o main.o
|
make
会提示找不到hello.d
和main.d
,不过随后自动创建出hello.d
和main.d
。hello.d
内容如下:
1
| hello.o hello.d : hello.c
|
上述规则有两个目标文件,实际上相当于如下两条规则:
1 2
| hello.o : hello.c hello.d : hello.c
|
main.d
内容如下:
1
| main.o main.d : main.c hello.h
|
因此,main.o
依赖于main.c
和hello.h
,这个依赖关系就和我们手动指定的一致。
改动hello.h
,再次运行make
,可以触发main.c
的编译:
1 2 3 4 5 6 7
| $ make rm -f main.d; \ cc -MM main.c >main.d.tmp; \ sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.tmp > main.d; \ rm -f main.d.tmp cc -c -o main.o main.c cc -o world.out hello.o main.o
|
在实际项目中,对每个.c
文件都可以生成一个对应的.d
文件表示依赖关系,再通过include
引入到Makefile
,同时又能让make
自动更新.d
文件,有点蛋生鸡和鸡生蛋的关系,不过,这种机制能正常工作,除了.d
文件不存在时会打印错误,有强迫症的同学肯定感觉不满意,这个问题我们后面解决。
参考源码
可以从GitHub下载源码。
小结
利用GCC生成.d
文件,再用include
引入Makefile
,可解决一个.c
文件应该如何正确触发编译的问题。
查看官方手册:
完善Makefile
上一节我们解决了自动生成依赖的问题,这一节我们对项目目录进行整理,把所有源码放入src
目录,所有编译生成的文件放入build
目录:
1 2 3 4 5 6 7
| <project> ├── Makefile ├── build └── src ├── hello.c ├── hello.h └── main.c
|
整理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 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| SRC_DIR = ./src BUILD_DIR = ./build TARGET = $(BUILD_DIR)/world.out
CC = cc CFLAGS = -Wall
SRCS = $(shell find $(SRC_DIR) -name '*.c')
OBJS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS))
DEPS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.d,$(SRCS))
all: $(TARGET)
$(BUILD_DIR)/%.d: $(SRC_DIR)/%.c @mkdir -p $(dir $@); \ rm -f $@; \ $(CC) -MM $< >$@.tmp; \ sed 's,\($*\)\.o[ :]*,$(BUILD_DIR)/\1.o $@ : ,g' < $@.tmp > $@; \ rm -f $@.tmp
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c @mkdir -p $(dir $@) $(CC) $(CFLAGS) -c -o $@ $<
$(TARGET): $(OBJS) @echo "buiding $@..." @mkdir -p $(dir $@) $(CC) -o $(TARGET) $(OBJS)
clean: @echo "clean..." rm -rf $(BUILD_DIR)
-include $(DEPS)
|
这个Makefile
定义了源码目录SRC_DIR
、生成目录BUILD_DIR
,以及其他变量,同时用-include
消除了.d
文件不存在的错误。执行make
,输出如下:
1 2 3 4 5
| $ make cc -Wall -c -o build/hello.o src/hello.c cc -Wall -c -o build/main.o src/main.c buiding build/world.out... cc -o ./build/world.out ./build/hello.o ./build/main.o
|
可以说基本满足编译需求,收工!
参考源码
可以从GitHub下载源码。
小结
除了基础的用法外,Makefile
还支持条件判断,环境变量,嵌套执行,变量展开等各种功能,需要用到时可以查询官方手册。
留言與分享