Makefile入门
文章目录
- Makefile入门
- 一、Makefile入门
- 1.1 编译工具及构建工具介绍:
- 1.2 编译的四个阶段:
- 1.3 Makefile的认知:
- 1.3.1 什么是Makefile:
- 1.3.2 Makefile的规则与示例:
- 二、Makefile的基本语法:
- 2.1 通配符:
- 2.2 伪目标:
- 2.3 注释和换行符:
- 2.4 变量赋值和预定义变量:
- 2.5变量的替换引用:
- 2.6 Makefile的常用函数:
- 2.6.1 Makefile函数的格式:
- 2.6.2 wildcard 通配符:
- 2.6.3 shell函数:
- 2.6.4 patsubst替换函数:
- 2.6.5 subst替换函数:
- 2.6.6 dir函数:
- 2.6.7 notdir函数:
- 2.6.8 suffix函数:
- 2.6.9 basename函数:
- 2.6.10 addsuffix函数:
- 2.6.11 addprefix函数:
- 2.6.12 foreach函数:
- 2.7 条件判断语句:
- 2.7.1 条件判断ifeq 语句:
- 2.7.1.1 ifeq 语句的语法:
- 2.7.1.2 ifeq 语句的实例:
- 2.7.2 条件判断ifneq语句:
- 2.7.2.1 ifneq语句的语法:
- 2.7.2.2 ifneq语句的实例:
- 2.7.3 条件判断ifdef语句:
- 2.7.3.1 ifdef语句的语法:
- 2.7.3.2 ifdef语句的实例:
- 2.7.4 条件判断ifndef语句:
- 2.7.4.1 ifndef语句的语法:
- 2.7.4.2 ifndef语句的实例:
- 2.7.4.2 ifndef语句的实例:
一、Makefile入门
1.1 编译工具及构建工具介绍:
在之前的课程,都是直接使用gcc对代码进行编译,这对简单的工程是可以的,但当我们遇到复杂的工程时,每次用gcc等编译工具去操作就会显得很低效。因此make工具就出现了, make的出现是为了解决手动编译和链接大型工程的问题,它可以避免重复的工作,提高效率,保证正确性。make工具就根据makefile中的命令进行编译和链接的。但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改,因此更高级的一些构建系统或者工具工具像cmake、qmake、ninja和auto make就出现了,它们可以根据一些配置文件来自动化编译和链接软件项目。
cmake是一个跨平台的构建系统,它可以根据CMakeLists.txt中的指令来生成不同平台和工具的工程文件,例如Makefile、Visual Studio解决方案、Ninja文件等。cmake可以支持多种语言和多种架构,它还提供了一些高级功能,如测试、打包、安装等。
qmake是一个用于Qt项目的构建系统,它可以根据.pro或.pri中的指令来生成Makefile或其他形式的工程文件。
ninja是一个小巧而快速的构建工具,它可以根据ninja.build中的规则来执行编译和链接命令。ninja主要关注于性能和效率,它可以利用多核处理器和并行处理来加速构建过程。ninja通常不需要用户直接编写配置文件,而是由其他构建系统(如cmake)来生成
auto make是一个用于生成Makefile.in文件的工具,Makefile.in是一种用于auto conf的配置文件格式,auto conf是一个用于生成configure脚本的工具。configure脚本是一个用于检测系统环境并生成最终的Makefile文件的脚本Makefile.am是一种用于auto make的配置文件格式,它包含了一些指令和变量,用于定义程序或库的源文件、目标文件、依赖关系和编译选项等。
make是一个经典而通用的构建工具,它可以根据Makefile中的规则来执行编译和链接命令。make可以支持多种平台和工具,它还提供了一些高级功能,如条件判断、函数调用、模式匹配。
1.2 编译的四个阶段:
回顾下编译的四个过程:预处理(Pre-Processing)、编译(Compiling)、汇编 (Assembliang)、链接(Linking)
1.3 Makefile的认知:
1.3.1 什么是Makefile:
相信在Linux系统中经常会用到make这个命令来编译程序,而执行make命令所依赖的文件便是Makefile文件,make命令通过Makefile文件编写的内容对程序进行编译。make命令根据文件更新的时间戳来决定哪些文件需要重新编译这可以避免编译已经编译过的,没有改变的文件,从而提升编译效率。
1.3.2 Makefile的规则与示例:
一个简单的 Makefile 文件包含一系列的“规则”,其样式如下:
目标(target)…: 依赖()…
<tab>命令(command)
当“依赖文件”比“目标文件”更加新时,或者目标文件还没有生成时,就会执行“命令”
Makefile一个示例:
debug :@echo "hello world"
如果我们要编译下面一个简单的例子:
#include <stdio.h>int main()
{printf("Hello World!");return 0;
}
Makefile修改如下:
debug :@echo "hello world"hello : hello.cgcc hello.c -o hello
执行命令make hello可以生成 hello文件, 执行make debug可以输出“hello world”:
Makefile修改如下:
debug :@echo "hello world" hello : hello.c@gcc hello.c -o helloclean :@rm hello
执行命令make clean可以删除hello程序:
以上述为例,介绍一下详细规则:
- 目标(target)通常是要生成的文件的名称,可以是可执行文件(比如上例中的hello就是要生成的可执行文件名)或OBJ 文件, 也可以是一个执行的动作名称(如上述例子中的clean)。
- 依赖(prerequiries)是用来产生目标的材料(比如源文件 ) ,一个目标经常有几个依赖。
- 命令(command)是生成目标时执行的动作,一个规则可以含有几个命令,每个命令占一 行。
- 每个命令行前面必须是一个Tab字符,即命令行第一个字符是Tab。通常当一个依赖发生了变化,规则就会调用命令产生新的目标。
- 但是并非所有目标都有依赖,像上述例子中的“clean”,它只负责清除文件。
- 每一个Makefile文件也可以包含规则外的其他文件。
- 对于上面的Makefile,执行"make"后仅当hello.c文件比hello文件新时,才会执行gcc hello.c -o hello从而生成hello文件,如果没有hello文件时,这个命令也会被执行。运行“make clean”时,由于clean目标没有依赖,它的命令“rm -f hello”就会被强制执行。
二、Makefile的基本语法:
2.1 通配符:
- 当一个目标文件所依赖的依赖文件有很多时,需要写很多条规则,因此可以使用通配符只需要写一行来代替多行的规则。
首先举一个没有使用通配符的例子:共有两个.c文件,分别为a.c,b.c:
/* a.c */
#include <stdio.h>int main()
{func_b();return 0;
}
/* b.c */
#include <stdio.h>void func_b()
{printf("This is function b");
}
# 这是一个 Makefile 文件,用于编译程序。
# 该 Makefile 包含两个源文件 a.c 和 b.c,以及一个目标文件 test。
# 编译命令为:
# make test
# 运行命令为:
# ./testtest:a.o b.ogcc -o test a.o b.oa.o : a.cgcc -c -o a.o a.cb.o : b.cgcc -c -o b.o b.c
上述例子中的目标文件“test”有两个依赖“a.o”,“b.o”,不难看出,生成a.o,b.o的规则有很多相似之处,因此可以使用通配符来改进Makefile。改进后的Makefile如下:
# 这是一个Makefile文件,用于编译程序。test:a.o b.ogcc -o test $^%.o : %.cgcc -c -o $@ $<
其中用到的通配符的解释:
- %.o:表示所有.o文件
- %.c:表示所有.c文件
- $@:表示目标
- $<:表示第一个依赖文件
- $^:表示所有依赖文件
2.2 伪目标:
如果一个目标和一个实际文件同名,那么make会认为该目标已经是最新的,不需要重新生成,也不会执行其命令。通过将目标声明为伪目标 .PHONY: hello,可以避免这种情况,强制执行其命令。
debug :@echo "hello world" hello :@gcc hello.c -o helloclean :@rm hello.PHONY : hello
2.3 注释和换行符:
- 采用
#
进行一行注释 - 采用
\
作为续行符
2.4 变量赋值和预定义变量:
- Makefile中的变量赋值运算符有四种,分别是
=
、:=
、?=
和+=
,$
符号表示取变量的值,当变量名多于一个字符时,使用"( )
":
= 表示延迟展开赋值,即变量的值是在使用时才确定,可能会受到后面的赋值影响。例如,VAR_A = A,VAR_B = $(VAR_A) B,VAR_A = AA,那么最后VAR_B的值是AA B,而不是A B。
:= 表示直接赋值,即变量的值是在定义时就确定,不会受到后面的赋值影响。例如,VAR_A := A, VAR_B := $(VAR_A) B,VAR_A := AA,那么最后VAR_B的值是A B,而不是AA B。
?=表示条件赋值,即只有当变量没有被赋值时,才使用等号后面的值作为变量的值。例如,VAR ?= new_value,如果VAR在之前没有被赋值,那么VAR的值就为new_value,否则保持原来的值不变。
+= 表示追加赋值,即将等号后面的值追加到变量原来的值之后,形成一个新的值。例如,VAR += new_value,如果VAR在之前没有被赋值,那么VAR的值就为new_value,如果VAR在之前被赋值为old_value,那么VAR的值就为old_value new_value
$
符的其他用法:
$^
表示所有的依赖文件
$@
表示生成的目标文件
$<
代表第一个依赖文件
2.5变量的替换引用:
$(var:a=b)或${var:a=b} #变量的替换引用的语法格式
表示把变量var的值中的a后缀替换成b后缀。例如:
src := a.c b.c c.c
obj := $(src:c=o)
把变量src的值中的.c后缀替换成.o后缀,赋值给变量obj,结果是:
obj := a.o b.o c.o
# 这是一个 Makefile 文件,用于编译和运行 C 语言程序。TARGET = hello # 定义一个变量 TARGET,其值为 hello
CC := gcc # 定义一个变量 CC,其值为 gcc
TARGET1 := $(CC) $(TARGET) # 定义一个变量 TARGET1,其值为 $(CC) $(TARGET)
CC ?= g++ # 定义一个变量 CC,其值为 g++,如果 CC 未定义,则使用 g++
CC += -g # 追加参数 -g 到 CC 变量中SRC = hello.c # 定义一个变量 SRC,其值为 hello.c
OBJ = $(SRC:.c=.o) # 定义一个变量 OBJ,其值为 hello.c 编译成的 .o 文件debug :@echo "hello world" @echo $(TARGET1)@echo $(OBJ)hello : $(OBJ) # 定义一个规则,其目标为 hello,依赖于 $(OBJ) 文件 @$(CC) hello.c -o \$(TARGET) clean :@rm hello.PHONY : hellos
2.6 Makefile的常用函数:
2.6.1 Makefile函数的格式:
Makefile函数的基本格式是: ( f u n c t i o n a r g u m e n t s ) ∗ ∗ ,或者是 ∗ ∗ (function arguments)**,或者是** (functionarguments)∗∗,或者是∗∗{function arguments},其中,function 是函数名,arguments 是函数的参数,参数之间要用逗号分隔开,参数和函数名之间使用空格分开。调用函数的时候要使用字符“$”,后面可以跟小括号或者大括号。
2.6.2 wildcard 通配符:
Makefile中的wildcard 是一个函数,用于扩展通配符,返回与通配符匹配的文件列表。通配符是一种特殊的字符,可以表示多个文件名或目录名,常见的通配符有 *
和 ?
,分别表示任意长度的任意字符和单个任意字符。格式如下:
$(wildcard arguments)
比如*.c 表示所有以 .c 结尾的文件名,a?.txt 表示所有以 a 开头,中间有一个任意字符,以 .txt 结尾的文件名。例如:
# 这是一个Makefile文件,用于编译当前目录下的所有.c文件。files = $(wildcard *.c)all: @echo files = $(files)
这段Makefile代码的主要功能是:
- 扫描当前目录:使用
wildcard
函数扫描当前目录下所有的.c文件,并将这些文件名存储在变量files
中。 - 输出文件列表:定义了一个名为
all
的目标,当用户执行make all
命令时,会输出变量files
的值,即当前目录下所有.c文件的列表。
2.6.3 shell函数:
函数 shell 是 make 与外部环境的通讯工具。函数 shell 的执行结果和在控制台上执行‘command arguments ’的结果相似。不过如果‘ command arguments ’的结果含有换行符(和回车符),则在函数 shell 的返回结果中将把它们处理为单个空格,若返回结果最后是换行符(和回车符)则被去掉。格式如下:
$(shell 命令)
这里是一个简单的例子,使用shell
函数来获取当前目录下的文件列表,并将结果赋值给变量:
# 这是一个Makefile文件,用于列出当前目录下的文件。
FILES := $(shell ls) all: @echo "Current directory files:" $(FILES)
2.6.4 patsubst替换函数:
在Makefile中,patsubst
函数用于模式替换,它的原型如下:
$(patsubst 模式,替换文本,文本)
这个函数会在“文本”中查找与“模式”匹配的部分,并将其替换为“替换文本”。模式可以使用通配符,如%
表示任意数量的字符。
下面是一个使用patsubst
函数的实例:
# 假设我们有一个变量,包含了文件名列表,文件名以.c结尾
SRCS := a.c b.c c.c # 假设文件名列表为a.c b.c c.c # 我们想要将这些文件名中的.c替换为.o,以得到对象文件列表
OBJS := $(patsubst %.c,%.o,$(SRCS)) # 使用patsubst函数替换.c为.o all: @echo OBJS = $(OBJS)
在这个例子中,SRCS
变量包含了三个以.c结尾的文件名。我们使用patsubst
函数将这些文件名中的.c替换为.o,并将结果赋值给OBJS
变量。执行这个Makefile得出结果:
2.6.5 subst替换函数:
在Makefile中,subst
函数用于字符串替换,它的原型如下:
$(subst 从,到,文本)
这个函数会在“文本”中查找所有的“从”字符串,并将其替换为“到”字符串。与patsubst
不同,subst
不使用模式匹配,而是进行简单的字符串替换。
下面是一个使用subst
函数的实例:
# 假设我们有一个变量,包含了一些文本
TEXT := hello world, welcome to the makefile world # 我们想要将所有的"world"替换为"Makefile"
REPLACED_TEXT := $(subst world,Makefile,$(TEXT)) all: @echo "Original text: $(TEXT)" @echo "Replaced text: $(REPLACED_TEXT)"
在这个例子中,TEXT
变量包含了一段文本。我们使用subst
函数将这段文本中所有的“world”字符串替换为“Makefile”,并将结果赋值给REPLACED_TEXT
变量。最后,我们打印出原始文本和替换后的文本。
2.6.6 dir函数:
在Makefile中,dir
函数用于处理文件名和路径,提取目录部分。以下是这个函数的原型和实例:
$(dir <names...>)
<names...>
:一个或多个文件名。- 返回值:文件名中的目录部分。
# 定义包含完整路径的文件名变量
FULL_PATHS := /path/to/file1.txt /another/path/to/file2.txt # 使用dir函数提取目录部分
DIRS := $(dir $(FULL_PATHS)) # 打印结果
all: @echo "Full paths: $(FULL_PATHS)" @echo "Directories: $(DIRS)"
这段代码的主要功能是:
- 定义一个包含多个文件完整路径的变量
FULL_PATHS
。 - 使用
dir
函数从这些完整路径中提取目录部分,并将结果存储在变量DIRS
中。 - 通过定义一个
all
目标,打印出原始的完整路径和提取出的目录部分,以便用户查看和验证结果。
通过这种方式,代码展示了如何在 Makefile 中处理文件路径,并提取目录部分,这对于文件管理和路径操作非常有用。
2.6.7 notdir函数:
在Makefile中,notdir
函数用于处理文件名和路径,去除目录部分。以下是这个函数的原型和实例:
$(notdir <names...>)
<names...>
:一个或多个文件名。- 返回值:文件名中的非目录部分。
# 定义包含完整路径的文件名变量
FULL_PATHS := /path/to/file1.txt /another/path/to/file2.txt # 使用notdir函数去除目录部分
FILES := $(notdir $(FULL_PATHS)) # 打印结果
all: @echo "Full paths: $(FULL_PATHS)" @echo "Files: $(FILES)"
这段 Makefile 代码的主要功能是:
- 定义一个包含文件完整路径的变量
FULL_PATHS
。 - 使用
notdir
函数从FULL_PATHS
中提取文件名,去除路径部分,并将结果存储在FILES
变量中。 - 通过定义
all
目标,打印FULL_PATHS
和FILES
变量的值,以便用户查看文件的完整路径和去除路径后的文件名。
简而言之,这段代码的目的是展示如何从文件的完整路径中提取文件名,并通过打印结果验证操作的正确性。
2.6.8 suffix函数:
Makefile的suffix
函数用于获取文件名中的后缀部分。以下是关于suffix
函数的原型和如何在Makefile中使用它的一个简单示例:
$(suffix <names...>)
<names...>
:一个或多个文件名。- 返回值:文件名中的后缀部分。
# 定义包含文件名和后缀的变量
FILES := file1.txt file2.c file3.o # 使用suffix函数提取文件后缀
SUFFIXES := $(suffix $(FILES)) # 打印结果
all: @echo "File suffixes: $(SUFFIXES)"
这段 Makefile 代码的主要功能是提取并显示一组文件名的后缀。具体步骤如下:
- 定义一个包含多个文件名的变量
FILES
。 - 使用
suffix
函数提取这些文件名的后缀,并将结果存储在变量SUFFIXES
中。 - 通过定义一个
all
目标,使用@echo
命令打印出提取到的文件后缀。
通过这段代码,用户可以方便地获取一组文件的后缀信息,并将其显示在终端上。
2.6.9 basename函数:
basename函数格式:
$(basename names...)
功能:从文件名序列中取出各个文件名的前缀部分。
返回值:返回文件名序列的前缀序列,如果文件没有前缀,则返回空字串。
dep = $(basename src/foo.c src-1.0/bar hacks.o) all:@echo dep = $(dep)
2.6.10 addsuffix函数:
$(addsuffix suffix,names...)
参数**‘ names… ’是一系列的文件名**,文件名之间用空格隔开; suffix 是 一个后缀名。将 suffix( 后缀 ) 的值附加在每一个独立文件名的后面,完成后将 文件名串联起来,它们之间用单个空格隔开。
# 这是一个Makefile文件,用于给cat和dog两个文件生成.c文件dep = $(addsuffix .c,cat dog) # 生成.c文件列表all:@echo $(dep)
2.6.11 addprefix函数:
在Makefile中,addprefix
函数用于给字符串列表中的每一个字符串添加前缀。这个函数在处理文件名列表、变量等场景下非常有用。
$(addprefix <prefix>,<names>...)
<prefix>
:要添加到每个字符串前面的前缀。<names>
:一个或多个字符串,每个字符串都会添加前缀。
假设我们有一个文件名列表,想要给每个文件名前面添加路径src/
,可以使用addprefix
函数实现:
# 定义文件名列表
FILES := file1.c file2.c file3.c # 使用addprefix给文件名添加前缀
SRC_FILES := $(addprefix src/,$(FILES)) # 打印结果
all: @echo $(SRC_FILES)
在这个例子中,SRC_FILES
变量会被赋值为src/file1.c src/file2.c src/file3.c
,实现了给文件名列表添加前缀的目的。s
2.6.12 foreach函数:
在GNU Make中,foreach
函数是一个用于迭代处理的函数,它允许你对列表中的每个元素执行一系列操作。不过需要注意的是,foreach
并不是Makefile的内置函数,而是GNU Make提供的扩展功能。
$(foreach <var>,<list>,<text>)
<var>
:迭代过程中当前元素的变量名。<list>
:要进行迭代的列表。<text>
:对于列表中每个元素要执行的文本(通常是一系列命令或Makefile语句)。
以下是一个使用foreach
函数的示例,该示例将遍历一个文件名列表,并为每个文件名添加后缀.bak
。
# 定义文件名列表
FILES := file1 file2 file3 # 使用foreach为文件名添加后缀
BACKUP_FILES := $(foreach f,$(FILES),$(f).bak) # 打印结果
all: @echo $(BACKUP_FILES)
在这个例子中,BACKUP_FILES
变量会被赋值为file1.bak file2.bak file3.bak
,这是通过将.bak
后缀添加到FILES
列表中的每个文件名来实现的。
当你运行make
命令时,它将执行all
目标,并打印出带有.bak
后缀的文件名列表。
2.7 条件判断语句:
2.7.1 条件判断ifeq 语句:
在Makefile中,ifeq
语句用于进行条件判断,它比较两个参数是否相等,如果相等,则执行随后的代码块。这是Makefile中常用的条件语句之一,用于根据不同的条件来调整构建过程。
2.7.1.1 ifeq 语句的语法:
ifeq (<arg1>, <arg2>)
# 如果<arg1>等于<arg2>,则执行这里的命令
endif
<arg1>
和<arg2>
是要进行比较的两个参数,它们可以是变量、字符串或者数值。- 如果
<arg1>
和<arg2>
相等,那么ifeq
和endif
之间的命令将会被执行。 endif
是条件语句的结束标志。
2.7.1.2 ifeq 语句的实例:
以下是一个使用ifeq
语句的示例,该示例根据编译器的类型来设置不同的编译选项。
CC=gcc # 检查编译器是否为gcc
ifeq ($(CC),gcc) CFLAGS=-O2
else CFLAGS=-O0
endif all: $(CC) $(CFLAGS) -o myprogram myprogram.c
在这个示例中,如果变量CC
的值是gcc
,那么CFLAGS
将被设置为-O2
,否则CFLAGS
将被设置为-O0
。然后,使用这些设置来编译程序。
2.7.2 条件判断ifneq语句:
在Makefile中,ifneq
语句是用于条件判断的关键字,它用于比较两个参数是否不相等。如果两个参数不相等,那么ifneq
和endif
之间的代码块将被执行。
2.7.2.1 ifneq语句的语法:
ifneq (<arg1>, <arg2>)
# 如果<arg1>不等于<arg2>,则执行这里的命令
endif
<arg1>
和<arg2>
是要进行比较的两个参数。- 如果
<arg1>
和<arg2>
不相等,那么ifneq
和endif
之间的命令将会被执行。
2.7.2.2 ifneq语句的实例:
下是一个使用ifneq
语句的示例,该示例根据操作系统的类型来设置不同的编译选项。
# 获取操作系统类型
OS=$(shell uname -s) # 检查操作系统是否为Linux
ifneq ($(OS),Linux) CFLAGS=-DNOT_LINUX
else CFLAGS=-DLINUX
endif all: gcc $(CFLAGS) -o myprogram myprogram.c
在这个示例中,OS
变量被设置为当前操作系统的类型。然后,使用ifneq
语句检查OS
是否不等于Linux
。如果OS
的值不是Linux
,那么CFLAGS
将被设置为-DNOT_LINUX
;否则,CFLAGS
将被设置为-DLINUX
。最后,使用这些设置来编译程序。
2.7.3 条件判断ifdef语句:
在Makefile中,ifdef
语句用于检查某个变量是否已被定义。如果变量已定义,那么ifdef
和endif
之间的代码块将被执行。
2.7.3.1 ifdef语句的语法:
ifdef <variable-name>
# 如果<variable-name>已定义,则执行这里的命令
endif
<variable-name>
是要检查的变量名。
2.7.3.2 ifdef语句的实例:
以下是一个使用ifdef
语句的示例,该示例根据是否定义了某个变量来决定是否包含特定的源文件。
# 假设有一个可选的变量FEATURE
# make FEATURE=1将会定义这个变量 ifdef FEATURE
SRCS += feature.c
endif all: gcc $(SRCS) -o myprogram
在这个示例中,如果FEATURE
变量被定义(例如,通过在命令行上执行make FEATURE=1
),那么SRCS
变量将包含feature.c
,否则不包含。然后,使用SRCS
变量中列出的源文件来编译程序。
2.7.4 条件判断ifndef语句:
在Makefile中,ifndef
语句用于检查某个变量是否未被定义。如果变量未定义,那么ifndef
和endif
之间的代码块将被执行。
2.7.4.1 ifndef语句的语法:
ifndef <variable-name>
# 如果<variable-name>未定义,则执行这里的命令
endif
<variable-name>
是要检查的变量名。
2.7.4.2 ifndef语句的实例:
以下是一个使用ifndef
语句的示例,该示例根据是否未定义某个变量来决定是否包含特定的源文件。
# 假设有一个可选的变量DEBUG
# 如果未定义DEBUG,则不包含debug.c源文件 ifndef DEBUG
SRCS := main.c utils.c
else
SRCS := main.c utils.c debug.c
endif all: gcc $(SRCS) -o myprogram
all:
gcc $(SRCS) -o myprogram
在这个示例中,如果`FEATURE`变量被定义(例如,通过在命令行上执行`make FEATURE=1`),那么`SRCS`变量将包含`feature.c`,否则不包含。然后,使用`SRCS`变量中列出的源文件来编译程序。#### 2.7.4 条件判断ifndef语句:在Makefile中,`ifndef`语句用于检查某个变量是否**未**被定义。如果变量未定义,那么`ifndef`和`endif`之间的代码块将被执行。##### 2.7.4.1 ifndef语句的语法:```makefile
ifndef <variable-name>
# 如果<variable-name>未定义,则执行这里的命令
endif
<variable-name>
是要检查的变量名。
2.7.4.2 ifndef语句的实例:
以下是一个使用ifndef
语句的示例,该示例根据是否未定义某个变量来决定是否包含特定的源文件。
# 假设有一个可选的变量DEBUG
# 如果未定义DEBUG,则不包含debug.c源文件 ifndef DEBUG
SRCS := main.c utils.c
else
SRCS := main.c utils.c debug.c
endif all: gcc $(SRCS) -o myprogram
在这个示例中,如果DEBUG
变量未定义,SRCS
变量将只包含main.c
和utils.c
。如果DEBUG
变量已定义,SRCS
变量还将包含debug.c
。然后,使用SRCS
变量中列出的源文件来编译程序。****