Makefile基础学习

Makefile:描述哪些文件需要编译、哪些文件需要重新编译的文件,使用make命令对工程进行编译

本篇文章参考《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.7》的3.3 Makefile基础部分

1.示例

1.1 代码

  • main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
#include "input.h"
#include "calcu.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);
return 0;
}
  • input.c
1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include "input.h"

void input_int(int *a,int *b)
{
printf("input two numbers:");
scanf("%d %d",a,b);
printf("\r\n");
}
  • calcu.c
1
2
3
4
5
6
#include "calcu.h"

int calcu(int a,int b)
{
return a+b;
}
  • input.h
1
2
3
4
5
6
#ifndef _INPUT_H
#define _INPUT_H

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

#endif // !_INPUT_H
  • calcu.h
1
2
3
4
5
6
#ifndef _CALCU_H
#define _CALCU_H

int calcu(int a,int b);

#endif // !_CALCU_H

1.2 编译运行

  • gcc手动编译
1
$ gcc main.c calcu.c input.c -o main
  • Makefile编译
1
2
3
4
5
6
7
8
9
10
11
12
main: main.o input.o calcu.o
gcc -o 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

使用make命令编译工程,./main执行可执行程序

1
2
$ make
$ ./main

二、Makefile语法

2.1 Makefile规则格式

1
2
3
4
目标...: 依赖文件集合...
命令1
命令2
...

make 命令会为 Makefile 中的每个以 TAB 开始的命令创建一个 Shell 进程去执行

  • 具体例子:
1
2
main: main.o input.o calcu.o
gcc -o main.o input.o calcu.o

这条规则的目标是 main,``main.o、input.o、calcu.o是生成main的依赖文件,如果要更新目标main`,就必须先更新它的所有依赖文件,如果依赖文件中的任何一个有更新,那么目标也必须更新,“更新”就是执行一遍规则中的命令列表

  • 1.2Makefile解释如下:

首先更新第一条规则中的 main,第一条规则的目标成为默认目标,只要默认目标更新了那么就认为Makefile 的工作完成。

在第一次编译的时候由于 main 还不存在,因此第一条规则会执行,第一条规则依赖于文件 main.o input.o 、calcu.o这个三个.o 文件,这三个.o 文件目前还都没有,因此必须先更新这三个文件。make 会查找以这三个.o 文件为目标的规则并执行。

main.o为例,发现更新 main.o 的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为gcc–c main.c,不链接编译 main.c只是生成 main.o,其它两个.o 文件同理。

最后一个规则目标是 clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行 clean 的话可以直接使用命令make clean,执行以后就会删除当前目录下所有的.o 文件以及 main,因此 clean 的功能就是清理工程

  • 综上,Make的执行过程如下:

1、make 命令会在当前目录下查找以 Makefile(makefile 其实也可以)命名的文件。

2、当找到 Makefile 文件以后就会按照 Makefile 中定义的规则去编译生成最终的目标文件。

3、当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比目标文件晚)的话就会执行后面的命令来更新目标

2.2 Makefile变量及赋值

2.2.1 变量

Makefile变量都是字符串,类似于C语言中的宏

这里使用变量objects表示main.o input.o calcu.o,引用变量的方式是$(变量名),这里就是$(objects)来使用变量objects

1
2
3
4
5
# "#"号开头表示注释
# Makefile变量使用
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)

2.2.2 赋值

Makefile赋值符有=:=?=以及+=四种,只不过第四种是变量追加

  • 赋值符=
1
2
3
4
5
6
name = lyj
curname = $(name)
name = thee

print:
@echo curname: $(curname)

由于Make 在执行过程中会自动输出命令执行过程,使用@表示不输出命令执行过程

1
2
3
4
5
$ make print
# 输出:thee
# 不加@输出结果如下
# echo curname: thee
# curname: thee

这里输出thee而不是lyj的原因是该赋值符=表示被赋值变量curname会根据赋值变量name的改变而改变即name最终的值(变量真实值取决于它所引用变量最后一次有效值)

  • 赋值符:=
1
2
3
4
5
6
name = lyj
curname := $(name)
name = thee

print:
@echo curname: $(curname)
1
2
$ make print
# 输出:lyj

这里输出lyj是因为赋值符:=不会使用后面的定义的变量,只能使用前面已经定义好的(类似于C语言中变量的赋值,赋值结束后就不能更改了)

  • 赋值符?=
1
curname ?= jack

该赋值符?=表示如果变量 curname 前面没有被赋值,那么此变量就赋值为jack,否则该变量的值不变

  • 变量追加+=
1
2
3
4
5
name = lyj
name += thee

print:
@echo name: $(name)
1
2
$ make
# 输出:lyj thee

2.2.3 Makefile模式规则

  • %表示任意长度的非空字符串,类似于通配符,如%.c表示所有以.c结尾的文件

2.2.4 Makefile自动化变量

自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完

  • 常用自动化变量(其中$@/$</$^较常用)
自动化变量 描述
$@ 规则中的目标集合,在模式规则中,如果有多个目标的话,$@表示匹配模式中定义的目标集合
$% 当目标是函数库的时候表示规则中的目标成员名,如果目标不是函数库文件,那么其值为空
$< 依赖文件集合中的第一个文件,如果依赖文件是以模式(即%)定义的,那么$<就是符合模式的一系列的文件集合
$? 所有比目标新的依赖目标集合,以空格分开
$^ 所有依赖文件的集合,使用空格分开,如果在依赖文件中有多个重复的文件,$^会去除重复的依赖文件,只保留一份
$+ $^类似,但是当依赖文件存在重复的话不会去除重复的依赖文件
$* 这个变量表示目标模式中%及其之前的部分,如果目标是 test/a.test.c,目标模式为a.%.c,那么$*就是test/a.test
1
2
3
4
5
6
7
8
9
10
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)

%.o: %.c # 使用模式规则
gcc -c $< # 使用自动化变量

clean:
rm *.o
rm main

这里使用模式规则和自动化变量将1.2Makefile简化如上

2.2.5 Makefile伪目标

Makefile中一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令

使用伪目标主要是为了避免 Makefile 中定义的执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的

例如对于clean这个工程清理的代码,如果工作目录中存在clean的文件,此时执行make clean则会因为没有依赖文件而认定目标是最新的,后面的命令也不会执行,预先设想的清理功能也无法实现,为避免这个问题,将clean声明为伪目标,声明方式如下:

1
.PHONY: clean

这样不管当前工作目录是否存在名为clean的文件,执行make clean都会执行工程清理

2.2.6 Makefile条件判断

  • 语法1
1
2
3
<条件关键字>
<条件为真时执行的语句>
endif
  • 语法2
1
2
3
4
5
<条件关键字>
<条件为真时执行的语句>
else
<条件为假时执行的语句>
endif
  • 条件关键词

ifeq/ifneqifdef/ifndef

  • ifeq使用方法(相同为真) 参数可以为函数返回值

    1
    2
    3
    4
    5
    ifeq (<参数 1>, <参数 2>)
    ifeq '<参数 1>','<参数 2>'
    ifeq "<参数 1>","<参数 2>"
    ifeq "<参数 1>",'<参数 2>'
    ifeq '<参数 1>',"<参数 2>"
  • ifneq使用方法同ifeq(不相同为真)

  • ifdef使用方法

    1
    ifdef <变量名>

    如果”变量名”值非空,那么表达式为真,否则为假,”变量名”也可以是函数返回值

  • ifndef使用方法同ifdef,但是含义与之相反

这里的ifdefifndefC语言中含义类似

2.2.7 Makefile函数使用

Makefile支持函数,但无法自定义函数

函数用法如下:

1
2
3
$(函数名 参数集合)
# 或者是如下:
${函数名 参数集合}

接下来是常用的几个函数

  • subst函数:字符串替换
1
2
$(subst <from>,<to>,<text>)
# 该函数功能是将字符串<text>中的<from>内容替换为<to>,返回被替换后的字符串

例如:

1
2
$(subst thee,lyj,hello thee!)
# 这里将thee替换为lyj,替换后的字符串为hello lyj!
  • patsubst函数:模式字符串替换
1
2
$(patsubst <pattern>,<replacement>,<text>)
# 此函数查找字符串<text>中的单词是否符合模式<pattern>,如果匹配就用<replacement>来替换掉,<pattern>可以使用通配符"%",表示任意长度的字符串,函数返回值就是替换后的字符串。如果<replacement>中也包涵"%",那么<replacement>中的"%"将是<pattern>中的那个"%"所代表的字符串

例如:

1
2
$(patsubst %.c,%.o,a.c b.c c.c d.c)
# 这里将a.c b.c c.c d.c替换为a.o b.o c.o d.o
  • dir函数:获取目录
1
2
$(dir <names...>)
# 此函数用来从文件名序列<names>中提取出目录部分,返回值是文件名序列<names>的目录部分

例如:

1
2
$(dir <src/a.c>)
# 返回目录部分即src/
  • notdir函数:去除文件中的目录部分
1
2
$(notdir <names...>)
# 此函数用与从文件名序列<names>中提取出文件名非目录部分

例如:

1
2
$(dir <src/a.c>)
# 返回非目录部分即a.c
  • foreach函数:循环
1
2
$(foreach <var>,<list>,<text>)
# 把参数<list>中的单词逐一取出来放到参数<var>中,然后再执行<text>所包含的表达式。每次<text>都会返回一个字符串,循环的过程中,<text>中所包含的每个字符串会以空格隔开,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串将会是函数 foreach 函数的返回值
  • wildcard函数

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

1
$(wildcard PATTERN...)

例如:

1
2
$(wildcard *.c)
# 获取当前目录所有.c文件,类似于%