Makefile基础语法 看这一篇就够了

在这里插入图片描述

Makefile 基础语法

Makefile 是一种用于自动化构建过程的脚本文件,它通过定义目标文件和依赖关系来告诉 make 工具如何构建项目。Makefile 的语法和结构可以非常灵活,适用于各种复杂的构建场景。本教程将详细介绍 Makefile 的基本概念、语法以及如何编写高效的 Makefile 文件。

Makefile 意义:

  • 自动化构建:
    Makefile 可以自动构建项目,减少手动编译和链接的时间消耗。
    通过简单的 make 命令,就可以自动处理项目的编译和链接过程。
  • 依赖管理:
    Makefile 能够追踪文件之间的依赖关系,只对那些真正需要更新的文件进行编译。
    当源文件发生变化时,Makefile 可以确保只重新编译受影响的部分,而不是整个项目。
  • 提高效率:
    自动化构建过程减少了人为错误的机会,同时也节省了大量的时间。
    开发者可以专注于编写代码而不是编译过程。
  • 可配置性和可扩展性:

Makefile 可以轻松地配置不同的构建选项,如编译器标志、调试信息等。
可以很容易地添加新的目标或规则来适应项目的增长和发展。

1. Makefile 基础

1.1 规则格式

Makefile 文件中的每一条规则都由三个部分组成:

  1. 目标(target): 目标文件或要完成的任务。
  2. 依赖(dependencies): 完成目标所需的文件或其他目标。
  3. 命令(commands): 用于生成目标的一系列 shell 命令。

1.2 示例

target: dependencies
    command1
    command2

1.3 默认目标

Makefile 中的第一个目标会被当作默认目标。如果没有显式指定目标,make 会构建第一个列出的目标。

1.4 变量

Makefile 支持变量定义,可以简化重复的文本。它们用于存储和管理数据,比如文件名、目录路径、编译器选项等。

CC = gcc # 定义编译器
CFLAGS = -Wall -g # 定义编译选项
OBJS = foo.o bar.o baz.o # 定义目标依赖

1.5 自动变量

自动变量 $@, $<, $^, $+ 等用于引用目标或依赖。

  1. $@
    • 含义:表示规则的目标文件名。当 Makefile 正在执行某个规则时,$@ 就会被替换为目标文件的名称。
    • 用途
      • 构建命令:在构建命令中使用 $@ 来指定输出文件
      • 依赖检查:在依赖检查时 $@ 用来标识当前规则的目标文件
  2. $^
    • 含义:在 Makefile 中,自动变量 $^ 代表的是规则中所有依赖文件的集合。它包含了所有依赖文件的名称,但去除了重复的依赖文件。这与 $+ 不同,后者保留所有依赖文件,即使有重复也会列出多次
    • 用途
      • 构建命令:在构建命令中使用 $^ 来引用所有依赖文件
      • 依赖检查:在依赖检查时 $^ 用来标识所有依赖文件
  3. $<
  • 含义:在 Makefile 中,自动变量 < 代表的是规则中第一个依赖文件的名称。当 M a k e f i l e 正在执行某个规则时, < 代表的是规则中第一个依赖文件的名称。当 Makefile 正在执行某个规则时, <代表的是规则中第一个依赖文件的名称。当Makefile正在执行某个规则时,< 就会被替换为第一个依赖文件的名称。
    • 用途
      • 构建命令:在构建命令中使用 $< 来引用第一个依赖文件
      • 依赖检查:在依赖检查时 $< 用来标识第一个依赖文件
        示例
    CC = gcc
    CFLAGS = -Wall -g
    all: hello
    
    hello: main.o util.o
         $(CC) $(CFLAGS) -o $@ $^
    
    %.o: %.c
         $(CC) $(CFLAGS) -c -o $@ $<
    
    clean:
         rm -f *.o hello
    

解释
- 规则 hello 的目标文件是 hello,依赖项是 main.o 和 util.o。
- 在构建 hello 时, @ 就会被替换为 h e l l o , @ 就会被替换为 hello, @就会被替换为hello,^ 就会被替换为 main.o util.o
- 在构建 .o 文件时, @ 会被替换为 . o 文件名,例如 m a i n . o 或 u t i l . o 。 − 在构建 . o 文件时 , @ 会被替换为 .o 文件名,例如 main.o 或 util.o。 - 在构建 .o 文件时, @会被替换为.o文件名,例如main.outil.o在构建.o文件时,^ 不会被使用,因为每个 .o 文件只有一个依赖文件。
- 在构建 .o 文件时,$< 会被替换为 .c 文件名,例如 main.c 或 util.c。

  1. $%
    • 含义:自动变量 $% 代表的是规则中目标成员的名称,通常用于处理归档成员(archive member)的情况,即库文件中的成员文件。这个变量在处理静态库(例如 .a 文件)时非常有用。
    • 用途
      • 构建命令:在构建命令中使用 $% 来引用归档库中的成员文件
      • 依赖检查:在依赖检查时 $% 用来标识归档库中的成员文件
    • 示例
      AR = ar
      ARFLAGS = rcs
      LIB = libfoo.a
      
      all: $(LIB)
      
      $(LIB): foo.o bar.o
        $(AR) $(ARFLAGS) $@ $^
      
      %.o: %.c
           $(CC) $(CFLAGS) -c -o $@ $<
      
      clean:
           rm -f *.o $(LIB)
      
    • 解释
      • 规则 $(LIB) 的目标文件是 libfoo.a,依赖项是foo.o bar.o
      • 在构建 $(LIB) 时,$% 就会被替换为库中的成员文件名,例如 foo.o bar.o
      • 在构建 .o 文件时,$% 不会被使用,因为每个 .o 文件只有一个依赖文件
  2. $?
    • 含义:自动变量 $? 代表的是所有比目标文件更新的依赖文件的集合。它包含了所有比目标文件更新的依赖文件的名称,并且这些文件之间是以空格分隔的。
    • 用途
      • 构建命令:在构建命令中使用 $? 来引用所有比目标文件更新的依赖文件。
      • 依赖检查:在依赖检查时 $? 用来标识所有比目标文件更新的依赖文件。
    • 示例
      CC = gcc
      CFLAGS = -Wall -g
      
      all: myapp
      
      myapp: main.o util.o
           $(CC) $(CFLAGS) -o $@ $?
      
      %.o: %.c
           $(CC) $(CFLAGS) -c -o $@ $<
      
      clean:
           rm -f *.o myapp
      
    • 解释
    • 当首次运行 make 时,所有 .c 文件都会比 myapp 新,因此 $? 包含 main.o util.o
    • $(CC) $(CFLAGS) -o $@ $? 会链接 main.o util.o生成 myapp
    • 如果之后修改了 main.c 文件,但 util.c 未修改,则 $? 只包含 main.o,因此只有 main.o 会被重新编译,然后与 util.o 一起链接生成 myapp
    • 如果 util.c 也被修改了,那么 $? 将包含 main.o util.o,这两个文件都将被重新编译,然后链接生成 myapp

1.6 通配符

在 Makefile 中,通配符被用来匹配文件名的一部分或全部,以便于构建规则的定义更加灵活。这里有几个常用的通配符及其用途:

  • %:在模式规则中用于匹配单个文件名的部分。它通常用于目标或依赖项的模板中。
    • 示例
    %.o: %.c
        gcc -c $< -o $@
    
    • 解释:这条规则会匹配所有.c文件,并将它们编译成相应的 .o 文件。当你运行 make foo.o,make 将会尝试找到匹配 foo.o: foo.c 的规则。
  • *:匹配任意数量的字符(包括零字符)。
    • 示例
    objects = $(wildcard *.o)
    all: $(objects)
    
    • 解释:这里 $(wildcard *.o) 会扩展为当前目录下所有的.o文件。
  • ?:匹配单个字符。

1.7 条件执行

Makefile 中,条件执行允许您根据变量的值来控制某些规则或命令的执行。这可以通过几个条件指令来实现,主要包括 ifeq, ifneq, ifdef, 和 ifndef

1. ifeq 和 ifnq
  • ifeq 用于测试两个表达式的值是否相等,如果相等则执行 then 后面的语句块,否则跳过。
  • ifneq 则相反,如果两个表达式的值不相等,则执行 then 后面的语句块。
2. 基本语法
ifeq (value1, value2)
    # TEXT-IF-TRUE
else
    # TEXT-IF-FALSE
endif
3. 示例
ifeq ($(DEBUG), 1)
    CFLAGS += -g
else
    CFLAGS += -O2
endif
4. ifdef 和 ifndef
  • ifdef 用于检查变量是否已经被定义,如果已定义则执行 then 后面的语句块。
  • ifndef 则检查变量是否未被定义,如果没有定义则执行 then 后面的语句块。
5. 基本语法
ifdef variable
    # TEXT-IF-DEFINED
else
    # TEXT-IF-NOT-DEFINED
endif
6. 示例
ifdef DEBUG
    CFLAGS += -DDEBUG
endif

1.8 函数

在 Makefile 中,函数是用来处理变量值的工具,可以让构建过程更加灵活和动态。以下是一些常用的 Makefile 函数及其用途:

字符串处理函数
  • subst:
    • 功能:字符串替换函数
    • 语法$(subst FROM,TO,TEXT)
    • 示例
      STR := hello world
      NEW_STR := $(subst l,X,$(STR))
      
    • 解释:STR 变量被设置为 “hello world”,然后使用 subst 函数将其中的字母 l 替换为 X,因此,最终 NEW_STR 的值将是 “heXXo worXd”,并将结果赋值给 NEW_STR 变量。
  • patsubst:
    • 功能:模式字符串替换函数
    • 语法$(patsubst PATTERN,REPLACEMENT,TEXT)
      • PATTERN %.c,表示匹配任何以 .c 结尾的文件。
      • REPLACEMENT %.o,表示将匹配到的 .c 替换成 .o
      • TEXT FILES 变量,包含了一组 .c 文件。
    • 示例
      FILES := foo.c bar.c baz.c
      OBJECTS := $(patsubst %.c,%.o,$(FILES))
      
    • 解释patsubst 函数会遍历 FILES 变量中的每个元素,如果元素匹配 PATTERN(即以 .c 结尾),则用 REPLACEMENT 替换该元素。因此,OBJECTS 变量的值将会是:foo.o bar.o baz.o
  • wildcard:
    • 功能:扩展通配符,返回匹配的文件列表。
    • 语法$(wildcard PATTERN)
    • 示例
      SOURCES := $(wildcard *.c)
      
    • 解释:返回当前目录下所有.c文件
  • notdir:
    • 功能:从文件路径中移除目录部分,只保留文件名。
    • 语法$(notdir FILE)
    • 示例
      FILE := /path/to/file.txt
      NAME := $(notdir $(FILE))
      
    • 解释:当 notdir 函数执行时,它会从给定的路径中移除目录部分,只保留文件名。因此,最终 NAME 的值将是 “file.txt”。
  • dir:
    • 功能:返回文件路径的目录部分
    • 语法:$(dir FILE)
    • 示例
      FILE := /path/to/file.txt
      DIR := $(dir $(FILE))
      
    • 解释dir 函数执行时,它会从给定的路径中移除文件名部分,只保留目录路径。因此,最终 DIR 的值将是 “/path/to/”。
  • addsuffix:
    • 功能:给列表中的每个元素添加后缀
    • 语法$(addsuffix SUFFIX,LIST)
    • 示例
      SUFFIX := .o
      LIST := foo bar baz
      OBJECTS := $(addsuffix $(SUFFIX),$(LIST))
      
    • 解释:在这里,SUFFIX 是要添加到列表每个元素末尾的后缀,LIST 是要处理的列表。addsuffix 函数执行时,它会将 SUFFIX 的值 “.o” 添加到 LIST 中每个元素的末尾。因此,最终 OBJECTS 的值将是:foo.o bar.o baz.o
  • addprefix:
    • 功能:给列表中的每个元素添加前缀。
    • 语法$(addprefix PREFIX,LIST)
    • 示例
      PREFIX := obj/
      LIST := foo.o bar.o baz.o
      OBJECTS := $(addprefix $(PREFIX),$(LIST))
      
    • 解释:在这里,PREFIX 是要添加到列表每个元素的前缀,LIST 是要处理的列表。addprefix 函数执行时,它会将 prefix 的值 “obj/” 添加到 LIST 中每个元素的末尾。因此,最终 OBJECTS 的值将是:obj/foo obj/bar obj/baz
  • filter:
    • 功能:从列表中选择匹配的元素。
    • 语法$(filter PATTERN,...)
    • 示例
      FILES := foo.c bar.c foo.h bar.h
      CS := $(filter %.c,$(FILES))
      
    • 解释:当 filter 函数执行时,它会从 FILES 变量中筛选出所有匹配模式 %.c 的元素。因此,最终 CS 的值将是:foo.c bar.c
  • filter-out:
    • 功能:从列表中排除匹配的元素。
    • 语法$(filter-out PATTERN,...)
    • 示例
      FILES := foo.c bar.c foo.h bar.h
      NON_CS := $(filter-out %.c,$(FILES))
      
    • 解释:当 filter-out 函数执行时,它会从 FILES 变量中筛选出所有匹配模式不是 %.c 的元素。因此,最终 NON_CS 的值将是:foo.h bar.h
其他函数
  • sort:
    • 功能:对列表排序。
    • 语法$(sort LIST)
    • 示例
      FILES := foo.c bar.c baz.c extra.c
      SORTED_FILES := $(sort $(FILES))
      
    • 解释:sort 函数会对 FILES 变量中的元素进行排序,SORTED_FILES量将包含按字母顺序排序后的文件名列表,结果为:bar.c baz.c extra.c foo.c
  • strip:
    • 功能:删除变量值中的空白字符。
    • 语法$(strip TEXT)
    • 示例
      TEXT := hello world
      STRIPPED := $(strip $(TEXT))
      
    • 解释:当 strip 函数执行时,它会去除 TEXT 变量值中的前导和尾随空白字符。由于 “hello world” 本身没有前导或尾随空白字符,因此最终 STRIPPED 的值仍然是 “hello world”。
  • call:
    • 功能:调用宏或函数。
    • 语法$(call macro-name, arguments...)
    • 示例
      define MY_MACRO
          @echo "Hello, $(1)"
      endef
      
      $(call MY_MACRO,World)
      
    • 解释:这段代码定义了一个宏 MY_MACRO,它接受一个参数(这里用 $(1) 表示),并打印一条包含该参数的消息。

当 call 函数执行时,它会调用 MY_MACRO 宏,并将 “World” 作为参数传递。因此,最终会打印出:Hello, World

  • foreach :
    • 功能:用于迭代变量名。
    • 语法$(foreach var, list, command)
      • var: 用于迭代的变量名。
      • list: 需要迭代的列表。
      • command: 对每个元素执行的命令。
    • 示例
      SOURCES := foo.c bar.c baz.c
      OBJECTS :=
      
      define foreach
          $(eval $(1) := $(foreach $(2),$(3),$(4)))
      endef
      
      $(call foreach, OBJECTS, %.c, %.o, $(SOURCES))
      
      all:
          @echo "Sources: $(SOURCES)"
          @echo "Objects: $(OBJECTS)"
      
    • 输出
      Sources: foo.c bar.c baz.c
      Objects: foo.o bar.o baz.o
      
    • 解释:这表明 foreach 函数成功地将每个 .c 文件名转换成了对应的 .o 文件名。
    • 注意事项
      • foreach 函数不是内置的 Makefile 函数,而是通过宏定义来实现的。
      • 在实际使用中,您可能需要根据具体需求调整 foreach 函数中的命令。

1.10 清理规则

在 Makefile 中,清理规则(clean rule)是一种常见的做法,用于删除中间文件和编译产物,以便重新编译项目或保持工作目录的整洁。

基本语法
.PHONY: clean
clean:
    rm -f *.o my_program
详解
  • .PHONY 标记:
    • clean 目标通常标记为 .PHONY,这意味着即使没有与之对应的目标文件存在,make 也会执行这个目标。这是因为清理规则通常不需要与任何具体的文件关联,而是要删除一系列文件。通常clean放在makefile文件末尾。
  • 清理命令:
    • rm -f 命令用于删除文件。-f 选项表示强制删除,即使文件不存在也不会产生错误。
    • *.o 表示删除所有 .o 文件。
    • my_program 表示删除名为 my_program 的可执行文件。
示例
SOURCES := foo.c bar.c baz.c
OBJECTS := $(addsuffix .o, $(basename $(SOURCES)))

all: my_program

my_program: $(OBJECTS)
    gcc -o $@ $(OBJECTS)

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

.PHONY: clean
clean:
    rm -f $(OBJECTS) my_program
解释
1. 定义源文件和目标文件:
SOURCES := foo.c bar.c baz.c
OBJECTS := $(addsuffix .o, $(basename $(SOURCES)))
2. 定义 all 目标:
all: my_program

运行 make 时,默认会执行 all 目标,这里指向 my_program。

3. 定义 my_program 目标
my_program: $(OBJECTS)
    gcc -o $@ $(OBJECTS)

这个目标定义了如何从目标文件列表 OBJECTS 创建可执行文件 my_program。

4. 定义 .o 文件的规则:
%.o: %.c
    gcc -c $< -o $@

这个规则定义了如何从 .c 文件创建对应的 .o 文件。

5. 定义清理规则:
.PHONY: clean
clean:
    rm -f $(OBJECTS) my_program

这个清理规则定义了如何删除所有的 .o 文件和可执行文件 my_program。

运行示例
$ make
gcc -c foo.c -o foo.o
gcc -c bar.c -o bar.o
gcc -c baz.c -o baz.o
gcc -o my_program foo.o bar.o baz.o
$ ls
foo.c  foo.o  bar.c  bar.o  baz.c  baz.o  my_program  Makefile
$ make clean
rm -f foo.o bar.o baz.o my_program
$ ls
foo.c  bar.c  baz.c  Makefile
  • 33
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值