Linux工具之make工具和makefile文件基础

为什么要使用make工具和makefile文件

  我们使用gcc命令编译一个helloWorld程序是非常简单的,因为他只有一个文件。但是当多个文件编译成一个可执行文件时,就需要链接器进行目标文件链接。但是当我们的工程非常庞大,设计的源文件非常多的时候,一个个编译不仅操作繁琐,也会导致没有改变的文件重复编译,浪费了大量的时间。
  make辅助编译工具,在编译之前会比较所有文件的时间戳。它会选择性地编译被修改过的文件,而不是全部编译一遍,这极大地节省了编译花费的时间。对于没有被修改的文件的目标文件,由于链接器工作是最后将目标文件链接到一起,所以不会被其他文件影响。

怎么使用make工具

  调用make命令即可调用make工具

make
	#调用该命令之后,会报错
	#make: *** No targets specified and no makefile found.  Stop.
	#原因是我们没有指定make工具如何去工作,而指导工作是通过makefile文件来实现的

什么是makefile

  makefile是描述整个工程编译、链接等规则的文件。makefile文件的命名必须是makefile或者是Makefile

touch makefile
make
	#报错:make: *** No targets.  Stop.
	#原因:虽然找到了makefile文件,但是是空文件,没有包含任何规则。

makefile的语法

语法格式,注意所有的行前空隙都使用tab生成的!不能用空格:
	目标:依赖
		命令

举例:

all:
	gcc hello.c -o hello

目标:all
依赖:空
命令:gcc hello.c -o hello
上面的例子也可以写成:

all:hello.o
	gcc hello.o -o hello
hello.o:hello.c
	gcc -c hello.c

目标:allhello.o
依赖:hello.ohello.c
命令:gcc hello.c -o hello.ogcc -c hello.c
  因为all依赖hello.o文件,所以要先执行gcc hello.c -o hello.o得到hello.o文件,然后才可以执行gcc hello.c -o hello。所以输入make命令后执行顺序与上述依赖的相互关系相同,而不是makefile文件中从上到下的顺序。

利用makefile文件执行make命令

#假设已经写好makefile文件如上面的第二个例子
make all #制定编译目标all,也可以不指定获取的编译目标,就默认获取第一个编译目标

  我们会发现,执行上述makefile后,会得到.o的中间文件,我们当然可以手动地删除它们(rm命令),但是每次都要操作是非常麻烦的。我们可以在makefile文件中加入如下命令:

clean:
	rm -rf *.o

  然后执行命令make clean,就可以快速删掉.o文件。这里使用的是通配符结合rm命令删除匹配的文件。

makefile的伪目标

  思考如下问题:如果在当前目录下,也有一个clean文件,那么执行make clean会发生什么?
在这里插入图片描述
  我们可以发现,当前路径下有与makefile目标名同名的文件时,make命令会报错,除了在调用make命令前删除相关文件外,还有什么方法呢?这就会用到makefile的伪目标。
  伪目标模式:.PHONY:目标名,在可能出现同名文件的目标名的上一行添加这个语句就能避免同名文件报错了。

-PHONY:clean
clean:
	rm -rf *.o

Makefile变量与变量赋值

  • 变量可以在很多地方使用,如目标,依赖,或者命令
  • 变量的复制可以使用:=?=+=:=
  • 变量的使用:通过$(变量名)来完成变量的使用

示例1:

使用:=来立刻赋值:

var:=aaa
var:=$(var1)bbb
var1:=ccc
all:
	echo $(var2)

  使用:=来给变量赋值,是立刻赋值,在执行var:=aaa的同时变量值已经确定,所以最后打印出来应该是aaabbb而非cccbbb

示例2:

使用=来延迟赋值:

var=aaa
var=$(var1)bbb
var1=ccc
all:
	echo $(var2)

  使用=来给变量赋值,是延迟赋值,使用他来赋值的话引用变量时会引用这个被引用的变量最终被赋予的值,所以最后打印出来应该是cccbbb而非aaabbb

示例3

使用?=来缺省赋值

var:=aaa
var?=ccc
var:=$(var1)bbb
all:
	echo &(var2)

示例4

使用+=来追加赋值

var:=aaa
var=$(var1)bbb
var1+=ccc
var1+=ddd
all:
	echo $(var2)

  使用?=来给变量赋值,是缺省赋值,意思是如果该变量在此之前没有被赋值,就将其缺省为赋予的值。所以会打印aaabbb,而非cccbbb
  此处,相当于直接:=进行赋值(此处用到了\符号作多行书写):

var:=aaa\
		ccc\
		bbb

makefile的自动化变量

  不用定义且会随着上下文变化而变化的变量。

  • %@:表示所有目标
  • $<:表示第一个依赖文件,如果依赖模式是%,那么它就表示一系列文件。(%为通配符,类似于Linux上的*
  • $^表示所有依赖。

问题引入

  我们通过下面的程序来体会以下这些概念:
下面有如下的文件

//hello.c文件如下
#include<stdio.h>
#include"hello.h"
void hello()
{
	printf("hello world!\n);
}
//hello.h文件如下
#ifndef _HELLO_
#define _HELLO_
void hello(void);
#endif
//main.c文件如下
#include<stdio.h>
#include"hello.h"
void main(void)
{
	hello();
}

makefile文件如下

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

clean:
	rm -rf *.o hello

  通过make命令,我们可以编译得到可执行文件,但是如果我们要另外追加一些include的文件,那么我们就要在makefile文件中重新修改依赖和下方的命令,这在非常多文件的时候是非常麻烦的。

使用变量来优化makefile

变量来替代文件名

  尝试如下的使用方法:

var:= hello.o main.o
hello:$(var)
	gcc $(var) -o hello
hello.o:hello.c
	gcc -c hello.c -o hello.o
main.o:main.c
	gcc -c main.c -o main.o

  可以发现,以后如果需要再增加目标文件依赖,可以直接修改var变量而不是修改两次下方的语句,这减少了我们的工作量。

自动化变量$<$@来替代同名依赖和目标

  观察下述makefile文件:

var:= hello.o main.o
hello:$(var)
	gcc $(var) -o hello
%.o:%.c
	gcc -c $< -o $@

  可以发现我们利用自动化变量,将两组分别同名的依赖和目标整合到了一个地方。

自动化变量$^来替代所有的依赖

  观察下述makefile文件:

var:= hello.o main.o
hello:$(var)
	gcc $^ -o hello
%.o:%.c
	gcc -c $< -o $@

  有个疑问:直接使用$(var)也差不多啊,为啥要引入$^

makefile的函数

wildcard函数

wildcard:通配符的意思
格式:$(wildcard 包含通配符的字符串)
功能:展开展开指定的目录
举例:
/home/test的目录下有一个a.c的C源文件和一个test的文件夹,在/home/test/test文件夹下有一个b.c文件。我们在/home/test目录下创建makefile文件如下:

var:=$(wildcard ./*.c ./test/*.c)
all:
	@echo $(var)

  此处shell命令前加上@后,使用make命令就不会再终端显示@之后的shell命令。此处就不会显示echo这个命令。我们执行这个makefile文件得到结果:
在这里插入图片描述
可以发现:

  1. echo命令没有被显示出来
  2. wildcard关键字之后的通配符路径被展开成了匹配到的c源文件。

notdir函数

格式:$(notdir 路径字符串)
功能:提取路径字符串中的文件名
  假设当前路径下只有test.c的C源文件

dir:=$(wildcard ./*.c)
var2:=$(notdir $(dir))
var:=$(notdir ./test.o)
chartest:
    echo $(dir)
    echo $(var)
    echo $(var2)

  执行上述文件,会输出如下结果:
在这里插入图片描述
可以发现:var2变量提取了通配符结果./test.c中的文件名部分test.c

dir函数

格式:$(dir 文件名字符串)$
功能:提取文件名字符串中的路径名(也就是最后一个 /之前的所有字符)

dir:=$(wildcard ./*.c)
#var2:=$(notdir $(dir))
var:=$(notdir ./test.o)
var3:=$(dir $(dir))
chartest:
    echo $(dir)
    echo $(var)
    #echo $(var2)
    echo $(var3)

  执行上述文件,会输出如下结果:
在这里插入图片描述
可以发现:

  1. #后的makefile语句没有被执行,其实#是makefile的注释符号
  2. echo语句之前的#并没有起作用,所以可以知道:缩进部分的语句会被完全当作shell语句处理,而不是makefile语句。
  3. var3变量提取了通配符结果./test.c中的路径部分./

patsubst函数

格式:$(patsubst 源字符,目标字符,字符列表
功能:替换字符列表中的源字符到目标字符

var2:=$(notdir $(dir))
var:=$(notdir ./test.o)
#var3:=$(dir $(dir))
var4:=$(patsubst %.c,%.o,$(var2))
chartest:
    echo $(dir)
    echo $(var)
    echo $(var2)
    @echo $(var3)
    echo $(var4)

  执行上述文件,会输出如下结果:
在这里插入图片描述
可以发现:var2中的.c部分被替换成.o并存入到var4中(var2没有被改变)。

使用$(var:源字符串=目标字符串)来替换字符

格式:$(var:a=b)
功能:把字符串中的源字符串替换成目标字符串

dir:=$(wildcard ./*.c)
var2:=$(notdir $(dir))
var:=$(notdir ./test.o)
#var3:=$(dir $(dir))
#var4:=$(patsubst %.c,%.o,$(var2))
var5:=$(dir:%.c=%.o)
chartest:
    echo $(dir)
    echo $(var)
    echo $(var2)
    @echo $(var3)
    @echo $(var4)
    echo $(var5)

  执行上述文件,会输出如下结果:
在这里插入图片描述
可以发现:变量dir中的.c被替换成了.o

foreach函数

格式:$(foreach <var>,<list>,<text>)
功能:把参数<list>中的单词逐一取出放到参数<var>指定的变量中,然后在执行<text>所包含的表达式。每一次<text>会返回一个字符串。

dir:=$(wildcard ./*.c)
#var2:=$(notdir $(dir))
var:=$(notdir ./test.o)
var3:=$(dir $(dir))
#var4:=$(patsubst %.c,%.o,$(var2))
#var5:=$(dir:%.c=%.o)
var6:=$(foreach n,$(var3),$(wildcard $(n)*.c))
chartest:
    echo $(dir)
    echo $(var)
    @echo $(var2)
    echo $(var3)
    @echo $(var4)
    @echo $(var5)
    echo $(var6)

  执行上述文件,会输出如下结果:
在这里插入图片描述
可以发现:

  1. foreach执行的过程为从var3中取出一个值放入变量n中,执行语句$(wildcard $(n)*.c)。然后再取出下一个值,执行相应语句直到穷尽var3中的值。
  2. 可以发现,dir函数不会去除重复路径,如./test2.c ./test.c在dir函数处理后变成./ ./,所以在此后的var6中,会执行两次,每一次都会因为*.c的通配符,匹配到两个test2.c test.c文件名。故而总共输入两组重复的文件名。

makefile相关的资料手册

  限于篇幅原因,本文只列出最基本的一些用法。读者可以在此基础上自行查找一些相关的资料手册进行进一步的学习。此处推荐一本《跟我一起写makefile》,写得很不错。

设置vim编辑器的tab缩进宽度

#先打开vim配置文件
vi /etc/vim/vimrc	#etc文件夹放配置文件,rc结尾的文件一般是配置文件

再在最后一行加入语句(这里的配置原理同PATH的.bashrc文件原理类似):

set tabstop=4

  这里我们设置tab缩进宽度为4。同样地,也可以加入语句set number,就可以在打开的时候就设置显示每一行的行号了。set shiftwidth=4表示换行的自动缩进长度设为4个空格。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值