Makefile详解

HarderHeng Lv5

一、Makefile是什么

makefile定义了一些规则来制定项目如何进行编译。

makefile中包含了一系列的**目标(Targets)规则(Rules)**。

目标通常是文件名,并且大概率是.o文件。紧跟着的生成这个文件的规则,通常是其源文件和编译的命令。编译的命令也可以是能够命令行执行的命令。

除了目标和规则,makefile也可以用来指定编译器的某些行为例如使用哪一个编译器或者编译器优化等级等等。

二、Makefile基础

1、目标

  • makefile文件直接命名为makefile才能让make工具识别。
  • makefile的编写规则为目标+编写规则,在目标名后面加上就代表这是一个目标的条目
1
2
3
all:
@echo "HelloWorld"\
#目标名为all,在构建目标时执行echo命令,加上@后在命令行里就不会打印多余的内容

image-20240409213927459

  • 如果在同一个makefile中定义了多个目标,然后直接在命令行中执行make命令,就会执行makefile中的第一个目标。当然也可以自己指定要构建的目标。

    2、依赖

    依赖是包含在规则中的一部分,如果一个目标依赖于另一个目标,那么就要依次先构建依赖的关系目标,才能构建当前的目标。

    1
    2
    3
    4
    5
    all: test
    @echo "HelloWorld"
    test:
    @echo "test"
    #在构建目标all时,会先构建其先决条件test目标

    image-20240409214548703

    3、规则

    一条目标的条目其实也就是一个整个规则,由目标依赖命令组成。其中依赖必须在构建该目标之前就已经满足,这个依赖也可以是其他的目标,也就是会被先构建出来才会构建原本的目标。规则的命令需要能够生成出目标,才能被其他目标作为依赖。

三、Makefile的特性

1、不重复构建

如果在makefile中以文件作为构建的目标,在执行make的时候就会将对应的文件构建出来,这些文件会一直存在,如果在下一次的构建中这些文件的源文件没有被修改,那么就不会重复构建这些文件。

1
2
3
4
5
6
7
8
9
10
#makefile
all: main.o helloworld.o
gcc -o main main.o helloworld.o #编译链接出main文件
@echo "Make all"
main.o: main.c #依赖源文件main.c
gcc -c main.c #编译出main.o
@echo "Make main.o"
helloworld.o: helloworld.c #依赖源文件helloworld.c
gcc -c helloworld.c #编译出helloworld.o
@echo "Make helloworld.o"
1
2
3
4
5
6
//main.c
extern void helloworld();
int main(){
helloworld();
return 0;
}
1
2
3
4
5
//helloworld.c
#include<stdio.h>
void helloworld(){
printf("HelloWorld");
}

image-20240409222123641

执行了两次make,可以看到后一次make并没有构建目标main.ohelloworld.o,因为前一次make已经生成了这两个文件,并且在后一次make时会自动识别main.chelloworld.c是否发生了更新时间的变化,也就是是否进行了更改,如果.c源文件没有被更改,make就认为要构建的目标main.ohelloworld.o也是最新的不需要进行重新构建。

那为什么all会被重新构建呢?

因为构建目标是all而不是gcc的目标main,main文件已经存在了但是目标all并不存在,关键在于要构建的目标是否存在。

2、假目标

有时候我们构建出一个目标后,这个目标已经存在那么在进行重新构建时就不会执行任何该目标下的命令。但是有时我们需要该目标下除了构建这个目标的命令之外的命令,就需要想办法暂时屏蔽掉不重复构建的特性,这时候就可以用到假目标。

不使用假目标的效果

image-20240409224219548

  • 假目标就是为了避免所要构建的目标和已经存在的文件重名的情况。
1
2
3
4
5
6
7
8
9
10
11
#makefile
.PHONY: main.o
all: main.o helloworld.o
gcc -o main main.o helloworld.o #编译链接出main文件
@echo "Make all"
main.o: main.c #依赖源文件main.c
gcc -c main.c #编译出main.o
@echo "Make main.o"
helloworld.o: helloworld.c #依赖源文件helloworld.c
gcc -c helloworld.c #编译出helloworld.o
@echo "Make helloworld.o"

使用假目标后,可以进行同一目标的重复构建,此时main.o的构建被单纯的当成一个目标而并非是一个文件,并不会检查此时文件的存在性。

image-20240409224235265

3、变量

makefile中的变量有点像c语言中的宏定义,只进行字符串的替换。

1
2
SRC = folder
$(SRC)

直接定义变量,然后使用变量时就直接$( )进行引用。

一些常用的能简化Makefile编写的自定义变量:

  • TARGET:目标,可以是各种目标比如TARGET_1,TARGET_2。
  • OBJECTS:可执行的.o文件,也可以加上一些标识。
  • SOURCE:源文件地址。
  • INCLUDE:头文件地址。

预定义变量

在make中有一些预定义变量,除了作为自己可以直接使用的变量,有的地方的隐式规则也可能会调用这些预定义变量。

  • CC:C语言编译器
  • CFLAGS:C语言默认使用的选项
  • CXX:Cpp语言编译器
  • CXXFLAGS:Cpp语言默认使用的选项

一般优化等级、调试等等的选项都可以写在上述的两种FLAGS中。

自动变量

对一条规则来说,需要有目标依赖

  • $@:表示目标文件
  • $^:表示所有依赖的文件
  • $<:表示第一个依赖文件

这三个内置变量在一条规则内使用则可以直接代表这些意义。

1
2
main: main.o test.o
$(CC) $^ -o $@

上面的例子就是说用所有的依赖文件作为编译器参数,来生成目标文件main

变量赋值

这里有三种赋值方式。

  • = 这种赋值方式会在调用变量时进行变量的赋值计算

通配符

make支持shell中的通配符。

  • *.c:所有后缀为.c的文件,常用来匹配文件路径。
  • %.c:匹配任何字符,常用于模式匹配。
  • ?:匹配一个单个的字符。
  • []:用于匹配括号内的任意单一字符。

4、隐式规则

隐晦规则会使得make进行自动推导。

要使用一个.o文件时,如果没有定义生成.o文件的规则,则自动寻找这个.o文件对应的.c文件,进行自动的生成。

自动推导时,就会使用预定义的变量。

5、模式匹配

模式匹配常和通配符一起使用,表示一系列的规则,来自动化的包含文件或者生成文件。

模式规则

1
2
%.o: %.c
gcc -c $< -o $@

上述的规则用来匹配一个.o.c文件,对每一个需要用到的.o文件都适用这个模式,根据下面的命令进行生成。

Wildcard关键字

1
objects:= $(wildcard *.o)

上述的赋值语句使用了wildcard关键字,用来展开后面的通配符。

6、文件搜寻

make执行命令时默认在当前的目录下工作,如果要在其他的目录中寻找文件,则

  • Title: Makefile详解
  • Author: HarderHeng
  • Created at : 2024-04-13 16:58:28
  • Updated at : 2025-03-06 20:53:39
  • Link: https://harderheng.life/2024/04/13/Makefile详解/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments