Makefile入门

Makefile简介

Makefile是什么?
gcc hello.c -o hello
gcc aa.c bb.c cc.c dd.c …
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,也可以执行操作系统的命令。

make和Makefile是什么关系?
make工具:找出修改过的文件,根据依赖关系,找出受影响的相关文件,最后安装规则单独编译这些文件。
Makefile文件:记录依赖关系和编译规则。

必须要学精Makefile吗?
项目(Uboot、Kernel、…)和底层机制(编译、链接、库加载)通过Makelife进行项目管理,学精Makelife很有必要。

怎么学习Makefile?
Makefile的本质:无论多么复杂的语法,都是为了更好地解决项目文件之间的依赖关系。

Makefile三要素

目标、依赖、命令。

描述三要素的关系
目标:依赖的文件或者是其他的目标
命令1
命令2

实验演示
.PHONY:可以指定伪目标

引入Makefile管理项目

首先打开Linux命令行,我们先创建一个目录,用来存放项目,我这里用的目录名是 study ,创建好后并进入目录:

$ sudo mkdir study
$ cd study

在引入Makefile之前,我们先编写一个小项目,随后再引入Makefile进行管理项目。例如在目录下,我们使用 vi 编辑器创建一个 game.c 文件并写入以下内容:

$ sudo vi game.c

并写入以下内容后保存退出:

#include<stdio.h>

void play()
{
        printf("Play a game!!!\n\r");
}

void stop()
{
        printf(("Stop playing game!!!\r\n");
}

保存退出后,再使用 vi 编辑器创建一个 main.c 文件,用来编写主函数:

$ sudo vi main.c

并写入以下内容后保存退出:

#include<stdio.h>

int main()
{
	play();
	stop();
	return 0;
}

使用 gcc 编译这两个文件:

$ sudo gcc main.c game.c -o play	//用gcc编译生成可执行程序的命令
									//-o选项指定可执行程序的名字为play

编译成功后,我们使用 ls 命令查看目录,可以发现目录下多了一个名为 play 的可执行文件。
查看目录
./play 执行这个可执行程序,成功看到以下内容:

$ ./play
Play a game!!!
Stop playing game!!!

至此一个简单的小项目就编写好了,现在我们开始引入Makefile进行管理项目。首先创建Makefile并打开文件:

$ sudo vi Makefile

然后编写以下内容:

play:main.c game.c					//创建目标play,依赖于main.c文件和game.c文件
        gcc main.c game.c -o play	//指定对应目标执行命令,我们同样使用刚刚用gcc编译生成可执行程序的命令

.PHONY:clean				//定义伪目标,包含目标了clean

clean:						//创建伪目标clean
        rm play				//指定目标执行命令,删除应用程序play

保存退出后我们来到命令行,删除刚刚我们生成的 play 可执行文件,然后执行 make 命令:

$ sudo rm play
$ sudo make

编译成功后,我们使用 ls 命令查看目录,同样可以发现目录下多了名为 play 的可执行文件。查看目录
./play 执行这个可执行程序,同样可以看到以下内容:

$ ./play
Play a game!!!
Stop playing game!!!

到这里我们已经成功引入了Makefile对我们的小项目进行了管理,但是呢现在我们的Makefile还有比较多的问题。比如我们每次编译应用程序 play ,都需要重新编译 main.c 和 play.c ,浪费了许多编译的时间,具体原理我们可以参阅GCC编译器基础
现在我们打开Makefile文件来进行一些修改:

$ sudo vi Makefile
play:main.o game.o
        gcc main.o game.o -o play

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

play.o:play.c
        gcc-c play.c -o play.c

.PHONY:clean

clean:
        rm play

经过改造过后,我们的应用程序 play 不再直接依赖于 game.c,main.c 文件,而是依赖于 game.o,main.o 文件。也就是说如果我们修改了 main.c 文件, game.o 文件是不受影响的,也就不需要重新编译 game.c 文件,只需要编译 main.c 文件就可以了。那么在编译 play 应用程序的时候,我们不用再去编译game.c,main.c 文件,而是直接编译 game.o,main.o 文件。
保存退出后,我们先执行 make clean 命令删除可执行文件 play ,然后执行 make 命令:

$ sudo make clean
rm play
$ sudo make
gcc main.o game.o -o play

编译成功后,我们使用 ls 命令查看目录,可以看到生成了 game.o,main.o 和 play 可执行文件三个文件:
查看目录
./play 执行这个可执行程序,同样成功看到以下内容:

$ ./play
Play a game!!!
Stop playing game!!!

现在我们的Makefile文件已经达到了预期的目的,通过它可以解决这些文件的依赖性问题,从而优化编译的时间。但是现在Makefile只能针对此项目来用,要是我们想对大多数项目使用Makefile文件,就需要把Makefile文件改造成能通用的文件。

Makefile的变量、模式匹配

变量

系统变量

在当前目录下创建一个 Makefile_text 文件进行测试,并编辑以下内容保存退出:

$ sudo vim Makefile_text
.PHONY:all						//定义尾目标all

all:							//目标all
        echo "$(CC)"			//打印系统变量CC,CC一般指代编译器
        echo "$(AS)"			//打印系统变量AS,AS一般指代汇编器
        echo "$(MAKE)"			//打印系统变量MAKE,MAKE一般指代MAKE工具

CCASMAKE这样的系统变量有很多,用的比较少,只需要记住一些常用的就可以了,现在我们来对新编辑的文件使用一下make工具,可以看到:

$ sudo make -f Makefile_text	//-f 指定Make工具找到文件Makefile_text
echo "cc"						//否则Make工具会默认使用原来的makefile文件
cc
echo "as"
as
echo "make"
make

自定义变量

=,延时赋值
:=,立即赋值
?=,空赋值
+=,追加赋值

我们先来看看延时赋值是什么吧,接着打开刚创建的 Makefile_text 文件并编辑以下内容保存退出:

$ sudo vim Makefile_text
A=123
B=$(A)
A=456

.PHONY:all

all:
        echo "$(B)"

再对新更改的文件使用make工具,可以看到:

$ sudo make -f Makefile_text
echo "456"
456

然后我们再来看一下立即赋值是什么:

$ sudo vim Makefile_text
A=123
B:=$(A)
A=456

.PHONY:all

all:
        echo "$(B)"

使用make工具,可以看到:

$ sudo make -f Makefile_text
echo "123"
123

可以看到延时赋值和立即赋值是相反的,要记住它,接下来我们再看一下空赋值:

$ sudo vim Makefile_text
A?=123
A?=456

.PHONY:all

all:
        echo "$(A)"

使用make工具,可以看到:

$ sudo make -f Makefile_text
echo "123"
123

空赋值就是只有当前变量的值是空的时,赋值才有效,最后我们来看追加赋值:

$ sudo vim Makefile_text
A?=123
#B:=$(A)
A+=456

.PHONY:all

all:
        echo "$(A)"

使用make工具,可以看到:

$ sudo make -f Makefile_text
echo "123 456"
123 456

在给变量追加赋值时,追加赋值不会覆盖掉原来的值,而是在原来的值后面加上新的值

自动化变量

$<:第一个依赖文件
$^:全部的依赖文件
$@:目标

同样我们先来看$<,打开 Makefile_text 文件编辑以下内容并保存退出:

$ sudo vim Makefile_text
all:targeta targetb
        echo "$<"

targeta:

targetb:

使用make工具,可以看到:

sudo make -f Makefile_text
echo "targeta"
targeta

$<就代表第一个依赖文件,我们再来看看$^

$ sudo vim Makefile_text
all:targeta targetb
        echo "$^"

targeta:

targetb:

使用make工具,可以看到:

sudo make -f Makefile_text
echo "targeta targetb"
targeta targetb

$^就表示全部的依赖文件,最后我们来看$@

$ sudo vim Makefile_text
all:targeta targetb
        echo "$@"

targeta:

targetb:

使用make工具,可以看到:

sudo make -f Makefile_text
echo "all"
all

$@就表示当前目标
接下来我们就可以修改优化一下我们前面的小项目了,我们打开文件Makefile并修改为以下内容并保存退出:

$ sudo vim Makefile
CC=gcc
TARGET=play
OBJS=main.o game.o

$(TARGET):$(OBJS)
        $(CC) $^ -o $@

main.o:main.c
        $(CC) -c main.c -o main.o

play.o:play.c
        $(CC) -c play.c -o play.c

.PHONY:clean

clean:
        rm play

我们执行 sudo rm play 命令删除可执行文件 play ,然后执行 make 命令:

$ sudo rm play
$ sudo make
gcc main.o game.o -o play

./play 执行这个可执行程序,同样成功看到以下内容,说明我们优化成功,大大提高了可移植性:

$ ./play
Play a game!!!
Stop playing game!!!

模式匹配

%:匹配任意多个非空字符
shell:*通配符

我们继续打开 Makefile_text 文件并修改为以下内容并保存退出:

$ sudo vim Makefile_text
%:
        echo "$@"

使用make工具,可以看到:

$ sudo make -f Makefile_text hsne
echo "hsne"
hsne

可以发现无论你输入啥就打印啥,%的作用就是匹配任意多个非空字符
接下来我们又可以继续改造我们的小项目了,我们打开文件Makefile并修改为以下内容并保存退出:

$ sudo vim Makefile
CC=gcc
TARGET=play
OBJS=main.o game.o

$(TARGET):$(OBJS)
        $(CC) $^ -o $@

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

.PHONY:clean

clean:
        rm play

我们先执行 make clean 命令删除可执行文件 play ,然后执行 make 命令:

$ sudo make clean
rm play
$ sudo make
gcc main.o game.o -o play

./play 执行这个可执行程序,同样成功看到以下内容,说明我们又改造成功了,又提高了可移植性:

$ ./play
Play a game!!!
Stop playing game!!!

默认规则

.o文件默认使用.c文件来进行编译

因此不需要模式匹配也同样可以,例如我们继续打开 Makefile 文件,将模式匹配的内容删除:

$ sudo vim Makefile
CC=gcc
TARGET=play
OBJS=main.o game.o

$(TARGET):$(OBJS)
        $(CC) $^ -o $@

.PHONY:clean

clean:
        rm play

我们执行 make clean 命令删除可执行文件 play ,然后执行 make 命令:

$ sudo make clean
rm play
$ sudo make
gcc main.o game.o -o play

可以发现也是可以正常编译的, ./play 执行这个可执行程序,同样是可以正常执行的:

$ ./play
Play a game!!!
Stop playing game!!!

参考:野火【第一期】Linux系列教学视频之“零基础入门”篇

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

FantasyQin

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

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

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

打赏作者

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

抵扣说明:

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

余额充值