一文读懂Makefile

一、工程构建

        有这样一个小工程,通过键盘输入两个整数,然后计算它们的和并打印输出。这个工程中main.c、input.c和calcu.c这三个C文件,以及input.h和calcu.h这两个头文件。其中main.c是主体,input.c负责接收键盘输入,calcu.c进行两个数相加运算。为了方便,将代码贴出如下:

"main.c"

#include <stdio.h>
#include "input.h"
#include "calca.h"

int main(int argc, char *argv[]) {
    int a, b, num;

    input_int(&a, &b);
    num = calcu(a, b);
    printf("%d + %d = %d\r\n", a, b, num);
}

-------------------------------------------------------------------------------
"input.c"

#include <stdio.h>
#include "input.h"

void input_int(int *a, int *b) {
    printf("input two num:");
    scanf("%d %d", a, b);
    printf("\r\n");
}

-------------------------------------------------------------------------------
"calcu.c"

#include <stdio.h>
#include "calcu.h"

int calcu(int a, int b) {
    return (a + b);
}

-------------------------------------------------------------------------------
"input.h"

#ifndef _INPUT_H
#define _INPUT_H

void input_int(int *a, int *b);
#endif

-------------------------------------------------------------------------------
"calcu.h"

#ifndef _CALCU_H
#define _CALCU_H

int calcu(int a, int b);
#endif

        现在我们对其进行编译并执行:

二、Makefile语法

2.1 Makefile规则格式

        Makefile里面是由一系列规则组成的,这些规则的格式如下: 

目标1 ... : 依赖 ...
<TAB>命令1
<TAB>命令2
<TAB>...

目标2 ... : 依赖 ...
<TAB>命令1
<TAB>命令2
<TAB>..

......

指令1:
<TAB>命令1
<TAB>命令2
<TAB>...

指令2:
<TAB>命令1
<TAB>命令2
<TAB>...

......

        根据上述规则,本工程Makefile编写如下:

main:main.o input.o calcu.o
	gcc -o main main.o input.o calcu.o 

main.o:main.c
	gcc -c main.c
input.o:input.c
	gcc -c input.c
calcu.o:calcu.c
	gcc -c calcu.c

clean:
	rm *.o
	rm main

        Makefile编写好后就可以使用make命令来编译工程了。 

2.2 Makefile变量

2.2.1  赋值符“=”

        使用变量来对2.1中Makefile进行改写:

object = main.o input.o calcu.o

main:$(object)
	gcc -o main $(object)

main.o:main.c
	gcc -c main.c
input.o:input.c
	gcc -c input.c
calcu.o:calcu.c
	gcc -c calcu.c

clean:
	rm *.o
	rm main

        在使用“=”时,我们要知道"=" 赋值的变量的真实值取决于他所引用的变量的最后一次有效值。 

2.2.2  赋值符“:=”

        ":=" 赋值的变量只会使用定义时的值。

2.2.3 赋值符“?=”

        上述代码的意思是,如果变量curname前面没有被赋值,那么此变量就是“leafye”,如果在前面已经赋值过了,就是用前面的值。

2.2.4 变量追加“+=” 

        当需要对已经定义好的变量添加一些字符串时,就需要用到“+=”。

 2.3 Makefile模式规则

        在2.1中我们对本工程的Makefile文件进行了编写。该Makefile的第4~9行是将对应的.c源文件编译为.o文件,如果工程中C文件很多的话,一条条规则编写显然是不可取的。为此,我们可以使用Makefile里的模式规则来将所有的.c文件编译为对应的.o文件。
        模式规则中,使用“%”来进行文件名的匹配。例如“%.c”就表示所有的以.c结尾的文件。当“%”出现在目标中的时候,目标中“%”所代表的值决定了依赖中“%”的值,使用方法如下:

%.o:%.c
<TAB>命令

        因此,2.1中的Makefile可以改为如下形式:

object = main.o input.o calcu.o

main:$(object)
	gcc -o main $(object)

%.o:%.c
	#命令

clean:
	rm *.o
	rm main

2.4 Makefile自动化变量 

        上面讲的规则模式中,目标和依赖都是一系列文件,每一次对模式规则进行解析的时候都会是不同的目标和依赖文件,而命令只有一行,如何通过一行命令来从不同的依赖文件中生成对应的目标?自动化变量就是来完成这个功能的。自动化变量会把模式中所定义的一系列文件自动地挨个取出,直至所有符合模式的文件都被取完。自动化变量只会吃现在规则的命令中。常用的自动化变量如下表所示。

        我们用自动化变量来完成2.3中的Makefile,最终代码如下:

object = main.o input.o calcu.o

main:$(object)
	gcc -o main $(object)

%.o:%.c
	gcc -c $<

clean:
	rm *.o
	rm main

2.5 Makefile伪目标

        有时候我们需要编写一个规则来执行一些命令,但这个规则不是用来创建文件的,比如在前面的Makefile文件中有如下代码用来清理工程的功能。

clean:
	rm *.o
	rm main

        当我们输入“make clean”以后,后面的“rm *.o” 和“rm main”总是会执行。但是,当目录下有个名为“clean”的文件时,执行“make clean”以后,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的命令不会被执行,设想的清理工程的功能就无法完成。为了避免这个问题,可以将clean声明为伪目标,声明方式如下:

.PHONY:clean

        使用伪目标修改的Makefile文件如下:

object = main.o input.o calcu.o

main:$(object)
	gcc -o main $(object)

%.o:%.c
	gcc -c $<

.PHONY:clean

clean:
	rm *.o
	rm main

2.6 Makefile条件判断

        Makefile条件判断的语法有如下两种:

<条件关键字>
    <条件为真时执行的语句>
endif

        以及

<条件关键字>
    <条件为真时执行的语句>
else
    <条件为假时执行的语句>
endif

        其中条件关键字有4个:ifeq、ifneq、ifdef和ifndef,这4个关键字分为两对,ifeq与ifneq、ifdef和ifndef。 先来看ifeq和ifneq,ifeq用来判断是否相等,ifneq判断是否不相等,ifeq用法如下:

ifeq(<参数1>,<参数2>)
ifeq'<参数1>','<参数2>'
ifeq"<参数1>","<参数2>"

        上述用法都是用来比较“参数1”和“参数2”是否相同,如果相同就为真。ifneq则相反。 

        ifdef和ifndef的用法如下:

ifdef<变量名>

        如果“变量名”的值非空,那么表达式为真,否则表达式为假。ifndef则相反。

2.7 Makefile函数使用 

        Makefile支持函数,我们可以直接使用。接下来介绍几个常用的函数,其他的函数大家可以参考《跟我一起写Makefile》这份文档。

2.7.1 函数patsubst

        函数patsubst用来完成模式字符串替换,使用方法如下:

$(patsubst<pattern>,<replacement>,<text>)

        此函数查找字符串<text>中的单词是否符合模式<pattern>,如果匹配就用<replacement>来替换掉,<pattern>可以使用通配符"%",表示任意长度的字符串,函数的返回值就是替换后的字符串。 如果<replacement>中也包含"%",那么<replacement>中的"%"将是<pattern>中的那个"%"所代表的字符串,比如:

$(patsubst %.c,%.o,a.c b.c c.c)

        将字符串“a.c b.c c.c”中的所有符合“%.c”的字符串,替换为“%.o”,替换完成以后的字符串为“a.o b.o c.o”。 

2.7.2 函数wildcard

        通配符“%”只能用在规则中,只有在规则中它才会展开,如果在变量定义和函数使用时,通配符不会自动展开,这个时候就要用到函数wildcard,使用方法如下:

$(wildcard PATTERN···)

        比如:

$(wildcard *.c)

        上面的代码是用来获取当前目录下所有的.c文件,类似“%”。  

猜你喜欢

转载自blog.csdn.net/weixin_46773333/article/details/129650175