玩转Makefile | 四步教你从零开始写Makefile

 

0. 相关阅读

    玩转Makefile | 系列综述

    玩转Makefile | 基础入门

    玩转Makefile | 四步教你从零开始写Makefile

    玩转Makefile | 企业项目Makefile实例

    玩转Makefile | 编译有共用文件的多个程序

    玩转Makefile | 一次编译多个目标

 

1. 前言

        通过之前章节的学习,我们对Makefile有个基础的认识,现在开始自己动手写Makefile。目前网络上有不少可以自动生成Makefile的工具,但很多项目其实没必要那么复杂,完全可以自己动手写出来。而且对于初学者来说,自己动手写一遍Makefile可以顶看十遍高手写的Makefile,也可以加深对Makefile的理解,将来公司的Makefile有需要修改的时候自己就可以动手搞定,不需要依靠他人,何乐而不为?


2. 源代码介绍
        在本教程中用于示例的代码很简单,仅仅是在main函数中调用了fun1及fun2函数,而fun1及fun2独立写在fun1.c及fun2.c里。代码如下:

 

//main.c
int main()
{
    printf("hello world\n");
    fun1();
    fun2();
}

//fun1.c
void fun1()
{
    printf("this is fun1\n");
}

//fun2.c
void fun2()
{
    printf("this is fun2\n");
}


3. 第一版Makefile
        对于我们的示例代码,不通过Makefile编译其实也很简单:

 

gcc main.c fun1.c fun2.c -o app

        我们知道,Makefile其实就是按规则一条条的执行。所以,我们完全可以把上面那条命令写成Makefile的一个规则。我们的目标是app,按此写法依赖是main.c fun1.c fun2.c,则最终的Makefile如下:

 

app: main.c fun1.c fun2.c
    gcc main.c fun1.c fun2.c -o app

        但这个版本的Makefile有两个很重要的不足:
        1. 对于简单代码还好,而对于大型项目,具有成千上万代码来说,仅用一行规则是完全不够的,即使够的话也需要写很长的一条规则;
        2. 任何文件只要稍微做了修改就需要整个项目完整的重要编译。
        基于此,我们在第一版的基础上优化出第二版。


4. 第二版Makefile

 

        在第二版Makefile中,为了避免改动任何代码就需要重新编译整个项目的问题,我们将主规则的各个依赖替换成各自的中间文件,即main.c --> main.o,fun1.c --> fun1.o,fun2.c --> fun2.o,再对每个中间文件的生成各自写条规则比如对于main.o,规则为:

 

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

        这样做的好处是,当有一个文件发生改动时,只需重新编译此文件即可,而无需重新编译整个项目。完整Makefile如下:

 

app: main.o fun1.o fun2.o
    gcc main.o fun1.o fun2.o -o app

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

fun1.o: fun1.c
    gcc -c fun1.c -o fun1.o

fun2.o: fun2.c
    gcc -c fun2.c -o fun2.o

        第二版Makefile同样具有一些缺陷:
        1. 里面存在一些重复的内容,可以考虑用变量代替;
        2. 后面三条规则非常类似,可以考虑用一条模式规则代替。
        基于此,我们在第二版的基础上优化出第三版。


5. 第三版Makefile
        在第三版Makefile中,我们使用变量及模式规则使Makefile更加简洁。使用的三个变量如下:

 

obj = main.o fun1.o fun2.o
target = app
CC = gcc

        使用的模式规则为::

 

%.o: %.c
        $(CC) -c $< -o $@

        这条模式规则表示:所有的.o文件都由对应的.c文件生成。在规则里,我们又看到了两个自动变量:$<和$@。其实自动变量有很多,常用的有三个:
        $<:第一个依赖文件;
        $@:目标;
        $^:所有不重复的依赖文件,以空格分开

 

obj = main.o fun1.o fun2.o
target = app
CC = gcc

$(target): $(obj)
    $(CC) $(obj) -o $(target)

%.o: %.c
    $(CC) -c $< -o $@

        第三版Makefile依然存在一些缺陷:
        1. obj对应的文件需要一个个输入,工作量大;
        2. 文件数目比较少时还好,文件数目一旦很多的话,obj将很长;
        3. 而且每增加/删除一个文件,都需要修改Makefile。
        基于此,我们在第二版的基础上优化出第四版。


6. 第四版Makefile
        在第四版Makefile中,我们隆重推出了两个函数:wildcardpatsubst
        wildcard:扩展通配符,搜索指定文件。在此我们使用src = $(wildcard ./*.c),代表在当前目录下搜索所有的.c文件,并赋值给src。函数执行结束后,src的值为:main.c fun1.c fun2.c
        patsubst:替换通配符,按指定规则做替换。在此我们使用obj = $(patsubst %.c, %.o, $(src)),代表将src里的每个文件都由.c替换成.o。函数执行结束后,obj的值为main.o fun1.o fun2.o,其实跟第三版Makefile的obj值一模一样,只不过在这里它更智能一些,也更灵活。
        除了使用patsubst函数外,我们也可以使用模式规则达到同样的效果,比如:obj = $(src:%.c=%.o),也是代表将src里的每个文件都由.c替换成.o。
        几乎每个Makefile里都会有一个伪目标clean,这样我们通过执行make clean命令就是将中间文件如.o文件及目标文件全部删除,留下干净的空间。一般是如下写法:

 

.PHONY: clean
clean:
        rm -rf $(obj) $(target)

        .PHONY代表声明clean是一个伪目标,这样每次执行make clean时,下面的规则都会被执行。

 

src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src))
#obj = $(src:%.c=%.o)
target = app
CC = gcc

$(target): $(obj)
    $(CC) $(obj) -o $(target)

%.o: %.c
    $(CC) -c $< -o $@

.PHONY: clean
clean:
    rm -rf $(obj) $(target)


7. 总结
        Makefile其实也并不难,但关键的是一定要自己动手写,这样才会更加加深理解,否则也容易造成眼高手低。如果实在不知道从何下手,可以尝试按上面的教程,一步步写下来,也只需要写四个版本而已,写完了相信就有了初步的理解。

 

        附:本教程对应的源码可从这里获取。

 

        在源代码里,总共有四个Makefile文件,可以通过make -f Makefile1来执行不同的Makefile。

 

---------------

码字不易,点个赞再走呗~

我是良许,世界500强外企 Linux 开发工程师,专业生产 Linux 干货。欢迎关注我的公众号「良许Linux」,回复「1024」获取最新最全的技术资料,回复「入群」进入高手如云技术交流群;提供永久免费 CSDN 资料下载服务。

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

良许Linux

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

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

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

打赏作者

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

抵扣说明:

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

余额充值