Makefile:一个简单的入门

本文最后更新于:2022年10月13日 下午

为什么使用Makefile

Makefile可以使构建更方便、减少不必要的构建

想象以下情景,目标文件main的依赖关系如下:

那么可以使用如下命令行进行编译:

1
gcc main.c stack.c maze.c -o main

那么会出现以下状况:

  • 即使是仅仅改变了某一个文件的一行代码,那么所有的.o文件都需要重新生成,然后通过链接得到目标文件,这个开销是巨大的
    • 一个极端情景:一个大型的软件项目往往由上千个源文件组成,全部编译一遍需要几个小时
  • 如果有多个.o文件,我们修改了某一个文件(比如.h或者.c文件),那么会影响多个.o文件,因此我们需要重新编译这些.o文件,那么管理他们的依赖是十分麻烦的

Makefile应运而生

Makefile格式

在目录下必须有一个Makefile文件,该文件名大小写敏感,里面内容类似下面:

1
2
<target> : <prerequisites> 
[tab] <commands> #hello,thank you

其中:

  • target是目标文件
  • prerequisites是前置条件
  • 如果要更新目标,那么需要更新所有前置条件
  • target和prerequisites可以有多个,用空格分开
  • command前面必须是tab,这些command将会被shell执行
  • #是注释

一个简单的Makefile程序:

1
2
3
4
5
6
7
8
9
10
11
blah: blah.o

cc blah.o -o blah # Runs third

blah.o: blah.c

cc -c blah.c -o blah.o # Runs second

blah.c:

echo "int main() { return 0; }" > blah.c # Runs first

在下面的情况下,执行make会更新目标文件:

  • 当前目录没有目标文件
  • 前置条件需要更新
  • 前置条件修改时间晚于目标文件(目标文件依赖于前置条件,而前置条件被修改了,那么目标文件需要更新)

在编译过程中,经常有一些过程文件,比如二进制文件,那么我们可以清除这些二进制文件,保留源文件,以便于下次make

make规则的一个示例:

1
2
3
4
clean:
@echo "cleanning project"
-rm main *.o
@echo "clean completed"
  • @表示这行语句不被输出,如果不加@那么会输出这行语句本身
  • -表示这行语句的执行错误也要忽略。一般rm命令会加-,因为要删除的文件或许不存在

至此我们发现Makefile中已经有了很多目标文件了,比如main和clean,那么如果我们要指定构建target,那么只需要在命令行中输入make target即可;假设没有指定,那么会默认执行Makefile文件的第一个目标

如果文件夹中存在clean这个文件名,那么再次执行make clean并不会执行,因为它认为该文件已存在。为了防止这种情况发生,可以通过.PHONY声明clean是”伪目标”

1
2
3
4
5
6
.PHONY: clean

clean:
@echo "cleanning project"
-rm main *.o
@echo "clean completed"

Makefile中约定俗成的名字:

all,执行主要的编译工作,通常用作缺省目标。
install,执行编译后的安装工作,把可执行文件、配置文件、文档等分别拷到不同的安装目录。
clean,删除编译生成的二进制文件。
distclean,不仅删除编译生成的二进制文件,也删除其它生成的文件,例如配置文件和格式转换后的文档,执行make distclean之后应该清除所有这些文件,只留下源文件。

变量与规则

1
2
3
4
foo = $(bar)
bar = Huh?
all:
@echo $(foo)
  • 最终会打印出Huh?
  • 这种赋值方式,在make遇到变量定义时不会立即展开
  • 如果想要立即展开,可以使用a:=b的形式
  • +=可以追加

Makefile中有一些特殊变量,比如:

  • $@表示规则中的目标文件(target)
  • $<表示规则中第一个prerequisites
  • $?表示规则中所有比目标文件新的prerequisites
  • $^规则中的所有prerequisites

使用这些变量,可以对Makefile进行改写

版本1:

1
2
main: main.o stack.o maze.o
gcc main.o stack.o maze.o -o main

版本2:

1
2
main: main.o stack.o maze.o
gcc $^ -o $@

常见变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
AR
静态库打包命令的名字,缺省值是ar。
ARFLAGS
静态库打包命令的选项,缺省值是rv。
AS
汇编器的名字,缺省值是as
ASFLAGS
汇编器的选项,没有定义。
CC
C编译器的名字,缺省值是cc。
CFLAGS
C编译器的选项,没有定义。
CXX
C++编译器的名字,缺省值是g++。
CXXFLAGS
C++编译器的选项,没有定义。
CPP
C预处理器的名字,缺省值是$(CC) -E。
CPPFLAGS
C预处理器的选项,没有定义。
LD
链接器的名字,缺省值是ld。
LDFLAGS
链接器的选项,没有定义。
TARGET_ARCH
和目标平台相关的命令行选项,没有定义。
OUTPUT_OPTION
输出的命令行选项,缺省值是-o $@。
LINK.o
把.o文件链接在一起的命令行,缺省值是$(CC) $(LDFLAGS) $(TARGET_ARCH)
LINK.c
把.c文件链接在一起的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS)
$(TARGET_ARCH)
LINK.cc
把.cc文件(C++源文件)链接在一起的命令行,缺省值是$(CXX) $(CXXFLAGS)
$(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
COMPILE.c
编译.c文件的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c。
COMPILE.cc
编译.cc文件的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c。
RM
删除命令的名字,缺省值是rm -f。

make -p查看Makefile的隐藏规则

依赖关系

  • gcc -M main.c可以列出目标文件和源文件的依赖关系,包括系统头文件
  • gcc -MM *.c在上面的基础上不列出系统头文件

一个内核驱动的Makefile实例

  • -j $(nrpoc) :指定处理器数量
  • -C $(LINUX_KERNAL_PATH) :在内核源码路径执行Makefile
  • M=$(CURRENT_PATH) modules :以内核为基础编译模块的时候,指定目录查找模块源码
  • all:make的缺省目标
  • @表示这行语句不被输出,如果不加@那么会输出这行语句本身
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    TARGET=rootkit
    OBJ=$(TARGET).o
    MODULE=$(TARGET).ko
    obj-m+=$(OBJ)

    EXTRA_CFLAGS+=-g -O0 # 编译参数
    CURRENT_PATH:=$(shell pwd) #当前路径
    LINUX_KERNAL:=$(shell uname -r) #内核版本号
    LINUX_KERNAL_PATH:=/lib/modules/$(LINUX_KERNAL)/build


    all:rootkit

    rootkit:
    make -j $(nrpoc)-C $(LINUX_KERNAL_PATH) M=$(CURRENT_PATH) modules
    install:
    # 安装模块
    @sudo insmod $(CURRENT_PATH)/$(MODULE)
    # 卸载模块
    @sudo rmmod $(CURRENT_PATH)/$(MODULE)

    clean:
    make -C $(LINUX_KERNAL_PATH) M=$(CURRENT_PATH) clean

    .PHONY:all install clean rootkit

Makefile:一个简单的入门
http://gls.show/p/b7f87e4d/
作者
郭佳明
发布于
2022年9月12日
许可协议