手写Makefile

前言

在软件开发过程中,经常会用到各种IDE,也就是集成开发环境。我们只需要把源代码、头文件添加进来,再点击软件的“build”按钮,就可以完成编译。整个过程都是IDE帮我们完成的。我们本身对编译过程并不了解。在以后的工作种会逐步发现,IDE的使用会越来越少。项目都是基于脚本编译的,大部分开源项目都是基于cmake或者makefile。cmake这里就不作介绍了。他本质也是生成makefile。

基于这种情况,我们需要了解makefile的机制,也许实际应用中你不会用到,但是多了解一些总是有好处的。本文的目的就是快速写出自己的makefile,makefile其他相关特性请自行了解。话不多说,直接开始!

开始

 我们先新建一个main.c。

f9c1a911260c4571b1dd3272fde4e393.png

只有一个文件,那我们直接编译。会生成a.out。

gcc main.c -o main

那我们如何使用makefile编译呢。

Makefile规则

上面我们编译时指定了-o main。也就是我们想生成可执行程序main。可执行程序就是我们的目标。而我们的目标又是依赖main.c。这里就引入了目标和依赖的概念。

aa37c100464642b1add52fdde77cd5c9.png目标的生成会有依赖,这个依赖也可能是一个目标。一直这么层层找下去,生成对应的依赖目标。所有的依赖都有了之后,就可以执行目标锁对应的command。

target ... : prerequisites ...
			command

 

 看到这里可能比较难理解,那我们先写一个简单的makefile看看。

main:main.o
    gcc main.o -o main


main.o:main.c
    gcc -c main.c -o main.o

main是我们的目标。main可执行程序依赖main.o,然后main.o又依赖main.c。然后我们执行make,就可以编译出main可执行程序。这样一个简单的makefile就写好了。

我们实际的项目不可能只有一个源文件。我们现在新增a.c、b.c。

68fba9a095de4c84a0e4a4c307e3b587.png

我们现在来实现第二版makefile。

main:main.o a.o b.o
    gcc main.o a.o b.o -o main


main.o:main.c
    gcc -c main.c -o main.o


a.o:a.c
    gcc -c a.c -o a.o

b.o:b.c
    gcc -c b.c -o b.o

执行make,可以看到编译过程。 

4b5b2a29d3954bc79f73ece2ccb8cc7a.png

现在才3个源文件,如果100个呢,这样的makefile会非常庞大、不灵活。

我们继续优化。第三版makefile。

obj = main.o a.o b.o

main:$(obj)
    gcc $^ -o $@


main.o:main.c
    gcc -c main.c -o main.o


a.o:a.c
    gcc -c a.c -o a.o

b.o:b.c
    gcc -c b.c -o b.o

obj是一个变量,变量的内容就是main.o a.o b.o。$(obj)就是使用这个变量。所以等效如下:

main:$(obj)等效 main:main.o a.o b.o,这样要新增源文件,只需要修改obj变量,和添加一个源文件规则即可。

$^表示所有的依赖目标,也就是main.o a.o b.o。$@表示目标main。这是makefile的自动变量,先记住就好。

所以当前的makefile还不够执行,因为没增加一个文件还需要增加new.o:new.c的规则。

第四版makefile。

obj = main.o a.o b.o

main:$(obj)
    gcc $^ -o $@

%.o:%.c
    gcc -c $^ -o $@

我们使用%通配符。当寻找main.o的时候,就会先找到main.c ,然后通过命令生成main.o。简单说就是先找main.o,然后发现%.o:%.c规则符合。所以就会展开成main.o:main.c,利用这个规则,就不需要每个文件都写一个规则了。即使要新增一个文件c.c。那么只需要修改obj = main.o a.o b.o c.o就可以了。

如果每增加一个文件都要修改obj变量,也很麻烦。

第五版makefile。

src = $(wildcard *.c)

obj = $(patsubst %.c,%.o,$(src))

main:$(obj)
    gcc $^ -o $@

%.o:%.c
    gcc -c $^ -o $@

$(wildcard *.c)就是查找当前目录下的所有.c文件。使用了*通配符。patsubst就是将所有的.c文件替换成.o,然后返回给obj变量。这两个函数都是makefile内置的函数。这样我们随便增加源文件,都不需要修改makefile了。

不过我们生成的中间文件(.o)都和源文件混在一起了。我们希望将所有的.o文件,存放到某个目录。

tmp = objs

src = $(wildcard *.c)

obj = $(addprefix $(tmp)/,$(patsubst %.c,%.o,$(src)))

main:$(obj)
    gcc $^ -o $@

$(tmp)/%.o:%.c
    gcc -c $^ -o $@

addprefix就是添加一个前缀。应为我们想将.o文件都存放到objs里,main需要依赖objs的.o文件。所以需要写成main:$(obj),展开就是main: objs/main.o objs/a.o objs/b.o。

objs/main.o和匹配规则$(tmp)/%.o匹配上。%就相当于main。然后%.c,就是main.c。目标的%内容会替换到依赖的%d。mian.c就在当前文件下,所以可以找到。

执行make,生成的.o文件都存放到objs里了(objs需要提前手动创建)。 

81852bb91aae4a4b81558c5fcabf0b9c.png

 我们现在将所有的源文件放到src目录下。

1c9c34150f284ef387d21ac8bf0a8015.png

 第六版makefile。

tmp = objs
d_src = src
src = $(wildcard $(d_src)/*.c)

obj = $(addprefix $(tmp)/,$(patsubst %.c,%.o,$(notdir $(src))))
$(info $(obj))
main:$(obj)
        gcc $^ -o $@

$(tmp)/%.o:$(d_src)/%.c
        gcc -c $^ -o $@

notdir去掉路径信息,所以obj的值还是objs/main.o objs/a.o objs/b.o。注意匹配规则改成了$(tmp)/%.o:$(d_src)/%.c。相比之前加了d_src。因为源文件放到了src目录下,如果不加$(d_src)/%.c那么编译时就会找不到对应的.c文件。

make: *** No rule to make target 'objs/a.o', needed by 'main'.  Stop.

到这里就是比较比较完成的makefile了。

第六版makefile。

之前在做项目的时候,源代码有很多,并不是放在同一个目录下的。例如在src下又添加了一个test文件夹,文件夹下有t1.c和t2.c。

9dc3a1778dda4e8fb7dea04ee8f5d032.png

 makefile如下。这个makefile编译的时候是有问题的。

tmp = objs
d_src = src
src = $(wildcard $(d_src)/*.c)
src += $(wildcard $(d_src)/test/*.c)

obj = $(addprefix $(tmp)/,$(patsubst %.c,%.o,$(notdir $(src))))
$(info $(obj))
main:$(obj)
        gcc $^ -o $@

$(tmp)/%.o:$(d_src)/%.c
        gcc -c $^ -o $@
objs/a.o objs/b.o objs/main.o objs/t1.o objs/t2.o
gcc -c src/a.c -o objs/a.o
gcc -c src/b.c -o objs/b.o
gcc -c src/main.c -o objs/main.o
make: *** No rule to make target 'objs/t1.o', needed by 'main'.  Stop.

src目录下的源文件都可以编译,但是找不到objs/t1.o的依赖规则。因为我们的依赖规则写的是$(tnp)/%.o:$(d_src)/%.c。所以只会在src目录下查找t1.c,因为t1.c是在src/test目录下,所以找不到然后报错。我们可以增加一条匹配规则就可以编过了。到那时这样也比较麻烦。

$(tmp)/%.o:$(d_src)/test/%.c
    gcc -c $^ -o $@

make提供了vpath的功能,可以让make时在当前目录找不到的情况下去vpath指定的路径去找。

终极版makefile。

tmp = objs
d_src = src
src = $(wildcard $(d_src)/*.c)
src += $(wildcard $(d_src)/test/*.c)

vpath %.c src src/test


obj = $(addprefix $(tmp)/,$(patsubst %.c,%.o,$(notdir $(src))))
$(info $(obj))
main:$(obj)
    gcc $^ -o $@

$(tmp)/%.o:%.c
    gcc -c $^ -o $@

 vpath指定了src src/test目录,所以make时会从这些目录去查找。那么匹配规则就可以改成$(tmp)/%.o:%.c。

这样的好处就是你的源文件目录结构可以是多层嵌套的,并且可以将生成的中间文件存放到objs目录。网上搜索make将生成的.o文件存放到指定目录,99.9%的文章都是以源代码在src目录下举例说明的,对于多层目录结构并不使用。我也是研究了很久才实现的。可以说是全网描述最清楚的文档。

自此,终极版makefile已经满足入门的大部分需求了。具体的特性可以另行学习。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王涛的专栏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值