一、Makefile介绍

  Makefile 可以简单的认为是一个工程文件的编译规则,描述了整个工程文件的编译和链接等相关规则。通俗的讲,Makefile 能够实现大批量文件的简化编译的操作。我们只需要执行 make 命令,工程就会自动编译。每次想要编译工程的时候就执行 make ,省略掉手动编译中的参数选项和命令,很大程度上提高编译的效率

  同时,Makefile 脚本具有较高的复用性,一般我们只要不增加或者是删除工程中的文件,Makefile 基本上不用去修改。

二、Makefile基础语法

2.1、语法编译规则

targets : prerequisites
    command
  • targets:规则的目标可以是一个标签,也可以是要生成的可执行文件,还可以是 Object File(一般称它为中间文件);
  • prerequisites:依赖文件。要生成 targets 需要的文件或者是目标,可以没有,也可以是多个
  • command:make 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行。

【注】规则目标和依赖文件之间要使用冒号分隔开
【注】命令的开始一定要使用Tab键

2.2、变量定义

  Makefile 文件中定义变量的基本语法如下, 变量的名称可以由大小写字母、阿拉伯数字和下划线构成。等号左右的空白符没有明确的要求,因为在执行 make 的时候多余的空白符会被自动的删除。至于值列表,既可以是零项,又可以是一项或者是多项。

变量的名称=值列表        #示例:VALUE_LIST = one two three

  这些变量分为三大类:自定义变量、内置变量、自动变量,其中自定义变量和内置变量较为常用。

  1. 自定义变量
赋值方式含义
简单赋值 ( := )只对当前语句的变量有效
递归赋值 ( = )赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响
条件赋值 ( ?= )如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效
追加赋值 ( += )原变量用空格隔开的方式追加一个新值
##########执行命令:make test###################
###############简单赋值#########################
x:=foo
y:=$(x)b
x:=new
test:
      @echo "y=>$(y)"         #执行结果:y=>foob
      @echo "x=>$(x)"         #执行结果:x=>new

###############递归赋值##########################
x=foo
y=$(x)b
x=new       #该变量会覆盖之前的赋值(递归赋值尽量不要对相同变量使用)
test:
      @echo "y=>$(y)"         #执行结果:y=>newb
      @echo "x=>$(x)"         #执行结果:x=>new
###############条件赋值##########################
x:=foo
y:=$(x)b
x?=new      #该变量上文已定义,故此处赋值无效
test:
      @echo "y=>$(y)"         #执行结果:y=>foob
      @echo "x=>$(x)"         #执行结果:x=>foo
###############追加赋值##########################      
x:=foo
y:=$(x)b
x+=$(y)     #在上文赋值基础上进行追加赋值,以单空格隔开
test:
      @echo "y=>$(y)"         #执行结果:y=>foob
      @echo "x=>$(x)"         #执行结果:x=>foo foob
  1. 自动变量
      自动化变量可以理解为由 Makefile 自动产生的变量。在模式规则(通过匹配模式找字符串)中,规则的目标和依赖的文件名代表了一类的文件。规则的命令是对所有这一类文件的描述。我们在 Makefile 中描述规则时,依赖文件和目标文件是变动的,显然在命令中不能出现具体的文件名称,否则模式规则将失去意义。

在这里插入图片描述

##########执行命令:make test###################
test:test.o test1.o test2.o
         gcc -o $@ $^                # "$@" 代表的是目标文件test
test.o:test.c test.h
         gcc -o $@ $<                # “$^”代表的是所有依赖文件
test1.o:test1.c test1.h
         gcc -o $@ $<                # “$<”代表的是依赖文件中的第一个
test2.o:test2.c test2.h
         gcc -o $@ $<
  1. 环境变量
       系统环境变量可在Makefile中直接使用

2.3、通配符使用(*, %)

  Makefile 是可以使用 shell 命令的,所以 shell 支持的通配符在 Makefile 中也是同样适用的。通配符用来指定一组符合条件的文件名。Makefile 的通配符与 Bash 一致,主要有星号(*)、问号(?)和 […] 。比如, *.o 表示所有后缀名为.o的文件。

通配符含义
*匹配0个或者是任意个字符
匹配任意一个字符
[ ]我们可以指定匹配的字符放在 “[]” 中

【注】:上述三种通配符可以使用在规则的命令中,也可以使用在依赖文件中,但不可直接运用在规则目标中
【注】:"%"字符与“*”类似,也是匹配任意个字符,但是不同于*号,%是可以直接使用在目标Target中,较为常用
【注】:不能以引用变量的方式将通配符用于变量中,若在自定义变量中使用通配符需调用函数 "wildcard",如:OBJ=$(wildcard *.c)
【注】:习惯:*尽量只用在变量定义中,%尽量只用在Target目标和Prerequisites依赖文件中

##########自定义变量中使用通配符###################
OBJ=$(wildcard *.c)
test:$(OBJ)
    gcc -o $@ $^

##########Target中使用 % 符号###################
%.o:%.c
    gcc -o $@ $^
#####"%.o" 把我们需要的所有的 ".o" 文件组合成为一个列表,从列表中挨个取出的每一个文件,"%" 表示取出来文件的文件名(不包含后缀),
#####然后找到文件中和 "%"名称相同的 ".c" 文件,然后执行下面的命令,直到列表中的文件全部被取出来为止。

.
  %标记和系统通配符*的区别呢?
  通俗的讲,*是应用在系统中的,%仅应用在这个Makefile文件中的,看下述代码:

##欲编译一个文件夹下的所有.c文件,你可能会这样写:
%.o:%.c
    gcc -o $@ $<        #结果出错:Make: *** target not found. stop.
##没有-f参数时Make会自动找到makefile中第一个目标中没有通配符的目标进行构造,相当于找不到目标。它的意思并不会自动把文件中所有的文件都编译。

##正确写法
all:$(subst .c,.o,$(wildcard *.c))
 
%.o:%.c
    gcc -o $@ $<

2.4、目标文件搜索(VPATH)

  上述示例中所有的源文件都存放在与 Makefile 相同的目录下。只要依赖的文件存在,并且依赖规则没有问题,执行 make命令整个工程就会按照对我们编写规则去编译,最终会重建目标文件。
  那如果需要的文件是存在于不同的路径下,在编译的时候要去怎么办呢(不改变工程的结构)?这就用到了 Makefile 中为我们提供的目录搜索文件的功能。常见的搜索的方法的主要有两种:一般搜索VPATH和选择搜索vpath。

  • 一般搜索VPATH:VPATH 是Makefile 中的一种特殊环境变量,使用时需要指定文件的路径,多个路径之间要使用空格或者是冒号隔开,表示在多个路径下搜索所有的文件

  • 选择搜索vpath:vpath 是关键字,搜索的时候不仅需要加上文件的路径,还需要加上相应限制的条件,过滤出一部分再去寻找

#VPATH——存在多个路径时的,搜索指定目标路径下的所有文件
VPATH := src:car        #等价写法 VPATH=src car
#vpath——存在多个路径时的,在目标src和car路径下搜索文件test.c
vpath test.c src car     #等价写法 vpath test.c src : car

【注】:无论定义了多少路径,make 执行的时候会先搜索当前路径下的文件,当前目录下没有我们要找的文件,才去 VPATH 的路径中去寻找。如果当前目录下有我们要使用的文件,那么 make 就会使用我们当前目录下的文件。

2.5、伪目标(.PHONY)

   .PHONY这个目标的所有依赖被作为伪目标。伪目标是这样一个目标:当使用 make 命令行指定此目标时,这个目标所在的规则定义的命令、无论目标文件是否存在都会被无条件执行伪目标的存在使得Makefile并不会创建目标文件,只是想去执行这个目标下面的命令,其好处有两点:一是避免我们的 Makefile 中定义的只执行的命令的目标和工作目录下的实际文件出现名字冲突;二是提高执行 make 时的效率,特别是对于一个大型的工程来说,提高编译的效率也是我们所必需的。
  将一个目标声明称伪目标的方法是将它作为特殊的目标\.PHONY的依赖,如下示例。无论当前目录下是否存在 clean 这个文件,当我们执行 make clean 后 rm 都会被执行。而且当一个目标被声明为伪目标之后,make 在执行此规则时不会去试图去查找隐含的关系去创建它。这样同样提高了 make 的执行效率,同时也不用担心目标和文件名重名而使我们的编译失败。

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

  如果不使用伪目标,而当前目录下存在clean文件,则会出现提示clean一是最新的情况。

2.6、条件判断

          在这里插入图片描述

#######条件判断的使用方式#######
ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2'

ifdef VARIABLE-NAME
############# ifeq #############
libs_for_gcc= -lgnu
normal_libs=
ifeq($(CC),gcc)      # CC是系统环境变量
    libs=$(libs_for_gcc)
else
    libs=$(normal_libs)
endif

foo:$(objects)
    $(CC) -o foo $(objects) $(libs)    

############# ifdef #############
foo=
all:
ifdef foo
    @echo yes
else
    @echo  no    #打印的结果:no
endif

2.7、常用函数

  函数的调用和变量的调用很像。引用变量的格式为$(变量名),函数调用的格式如下。其中,function 是函数名,arguments 是函数的参数,多个参数之间要用逗号分隔开。而参数和函数名之间使用空格分开。调用函数的时候要使用字符“\$”,后面可以跟小括号也可以使用花括号。

$(<function> <arguments>)    或者是     ${<function> <arguments>}

  常见函数如下:

################################################## 字符串处理函数 ##################################################
### 1. 模式字符串替换函数,函数使用格式:   $(patsubst <pattern>,<replacement>,<text>)
### 函数说明:函数功能是查找 text 中的单词是否符合模式 pattern,如果匹配的话,则用 replacement 替换。返回值为替换后的新字符串
### 执行 make 命令,我们可以得到的值是 : 1.o 2.o 3.o,这些都是替换后的值。
OBJ=$(patsubst %.c,%.o,1.c 2.c 3.c)
all:
    @echo $(OBJ)

### 2.字符串替换函数,函数使用格式:         $(subst <from>,<to>,<text>)
### 函数说明:函数的功能是把test中的 from 替换成 to,返回值为替换后的新字符串。
### 执行 make 命令,我们得到的值是: fEEt on the strEEt
OBJ=$(subst ee,EE,feet on the street)
all:
    @echo $(OBJ)

### 3.过滤函数,函数使用格式:              $(filter <pattern>,<text>)
### 函数说明:函数的功能是过滤出 text 中符合模式 pattern 的字符串,可以有多个 pattern 。返回值为过滤后的字符串。
### 执行 make 命令,我们得到的值是: 1.c 2.o
OBJ=$(filter %.c %.o,1.c 2.o 3.s)
all:
    @echo $(OBJ)

### 4.反过滤函数,函数使用格式:            $(filter-out <pattern>,<text>)
### 函数说明:函数的功能是功能和 filter 函数正好相反,但是用法相同。去除符合模式  pattern 的字符串,保留符合的字符串。返回值是保留的字符串。
### 执行 make 命令,打印的结果是: 3.s
OBJ=$(filter-out 1.c 2.o ,1.o 2.c 3.s)
all:
    @echo $(OBJ)

### 5.查找字符串函数,函数使用格式:        $(findstring <find>,<in>)
### 函数说明:函数的功能是查找  in 中的 find ,如果我们查找的目标字符串存在。返回值为目标字符串,如果不存在就返回空。
### 执行 make 命令,得到的返回的结果就是 : a
OBJ=$(findstring a,a b c)
all:
    @echo $(OBJ)

################################################## 文件名操作函数 ##################################################
### 1.获取匹配模式文件名函数,命令使用格式: $(wildcard PATTERN)
### 函数说明:函数的功能是列出当前目录下所有符合模式的 PATTERN 格式的文件名。返回值为空格分隔并且存在当前目录下的所有符合模式 PATTERN 的文件名。
### 执行 make 命令,可以得到当前函数下所有的 ".c "".h" 结尾的文件。这个函数通常跟的通配符 "*" 连用,
OBJ=$(wildcard *.c  *.h)
all:
    @echo $(OBJ)

### 2.添加前缀名函数,函数使用格式:        $(addperfix <prefix>,<names>)
### 函数说明:函数的功能是把前缀 prefix 加到 names 中的每个单词的前面。返回值为添加上前缀的文件名序列。
### 执行 make 命令,我们可以得到值是 : src/foo.c src/hacks 。我们可以使用这个函数给我们的文件添加路径。
OBJ=$(addprefix src/, foo.c hacks)
all:
    @echo $(OBJ)  

### 3.添加后缀名函数,函数使用格式:        $(addsuffix <suffix>,<names>)
### 函数说明:函数的功能是把后缀 suffix 加到 names 中的每个单词后面。返回值为添加上后缀的文件名序列。
### 执行 make 后我们可以得到: sec/foo.c.c hack.c。我们可以看到如果文件名存在后缀名,依然会加上。
OBJ=$(addsuffix .c,src/foo.c hacks)
all:
    @echo $(OBJ)  

### 4.取文件函数,函数使用格式:            $(notdir <names>) 
### 函数说明:函数的功能是从文件名序列 names 中取出非目录的部分。非目录的部分是最后一个反斜杠之后的部分。返回值为文件非目录的部分。
### 执行 make 命令,我们可以得到的值是: foo.c hacks
OBJ=$(notdir src/foo.c hacks)
all:
    @echo $(OBJ)

### 5.取目录函数,函数使用格式:           $(dir <names>)
### 函数说明:函数的功能是从文件名序列 names 中取出目录部分,如果没有 names 中没有 "/" ,取出的值为 "./" 。返回值为目录部分,指的是最后一个反斜杠之前的部分。如果没有反斜杠将返回“./”。
### 执行 make 命令,我们可以得到的值是: src/ ./。提取文件 foo.c 的路径是 "src/" 和文件 hacks 的路径 "./"。
OBJ=$(dir src/foo.c hacks)
all:
    @echo $(OBJ)

### 6.取绝对路径,函数使用格式:            $(abspath <names>)
### 函数说明:函数的功能是打印出names中的文件绝对路径,当有多个文件时,绝对路径之间以空格隔开
### 执行 make 命令,我们可以得到的值是:/pri/home/file1 /pri/home/hacks
OBJ=$(abspath file1.txt hacks)
all:
    @echo $(OBJ)

################################################## 其它操作函数 ##################################################
### 1.执行shell命令函数,函数使用格式:    $(shell some_command)
### 函数说明:函数的功能是用于执行操作系统中的shell命令
### 执行 make 命令,我们可以得到的值是 :8
VAR=32
OBJ=$(shell (expr $(VAR)/4))
all:
    @echo $(OBJ)

2.8、命令执行(\)

  在 Makefile 中书写在同一行中的多个命令属于一个完整的 shell 命令行,书写在独立行的一条命令是一个独立的 shell 命令行。因此:在一个规则的命令中命令行 “cd” 改变目录不会对其后面的命令的执行产生影响。就是说之后的命令执行的工作目录不会是之前使用“cd”进入的那个目录。如果欲达到这个目的,就不能把“cd”和其后面的命令放在两行来书写。而应该把这两个命令放在一行上用分号隔开。这样才是一个完整的 shell 命令行。
  如果想把一个完整的shell命令行书写在多行上,需要使用反斜杠 (\)来对处于多行的命令进行连接,表示他们是一个完整的shell命令行。例如上例我们也可以这样书写:

foo:bar/lose
    cd bar;gobble lose >../foo

foo:bar.lose
    cd bar; \
    gobble lose > ../foo

三、-e传参错误

  Makefile不要使用make -e传递变量,会导致莫名问题,直接使用make var1=value传递

  • 加-e传参:当存在系统环境变量与Makefile中的自定义变量名字相同时,make命令行定义的变量不会覆盖同名的环境变量,make将使用系统环境变量去覆盖Makefile中的这些同名变量的定义值,容易导致莫名问题
  • 不加-e传参:无论是否存在系统环境变量与Makefile中的变量同名,make命令行定义的变量都将覆盖同名的环境变量,且被修改的环境变量只在make执行过程有效
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐