初学者在Linux中编写代码的时候,都会了解到需要使用 gcc 1.c -o app 把 .c源文件 变成可执行文件。但是如果是一个由上百个.c文件构成的项目,我们还得一个个去变成可执行文件么?
因此工程管理器应运而生。工程管理器是一个能够管理较多的文件,并且能根据文件时间自动检测出更新过的文件而减少编译的工作量,同时通过读入 Makefile 文件来执行大量的编译工作。
Makefile是一种用于自动化构建和编译软件项目的工具。它通常用于管理大型项目中的源代码文件,以及定义项目的编译、链接和其他构建过程。它通常是使用文本编辑器编写的,其语法基于一种称为"make"的构建工具的规范。Makefile的语法相对简单,但可以非常灵活,可以根据项目的需要进行定制和扩展。
总的来说,Makefile是工程管理器在软件项目中非常重要的一部分,它可以帮助开发人员自动化构建过程,提高开发效率,减少错误和提高可维护性。
默认的情况下,make 命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是 GNU 的 make 识别的。有另外一些 make 只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的 make 都支持“makefile”和“Makefile”这两种默认文件名。
寻找顺序:“GNUmakefile” > “makefile” > “Makefile(推荐)”
但是你可以指定执行某一个的MakeFile,使用make的 “-f” 和 “–file” 参数即可。比如:make -f Make.Linux 或 make --file Make.AIX
Target(目标) : prerequisites(依赖)
Command(命令)
eg;
app:main.o fun.o
gcc main.o fun.o -o app
main.o:main.c
gcc -c main.c -o main.o -I ./inc
fun.o:fun.c
gcc -c fun.c -o fun.o -I ./inc
#1.第一行即“app”为终极目标,下面的所有目标都是为了生成这个终极目标而编写
#2.第一行的规则没有先后顺序
#3.当时间不对时,需要将时间调整正确之后才能使用 make 命令。
#4.makefile根据时间信息判断是否执行编译(目标文件与最终生成文件进行时间对比)。
#5.每个规则中的目标,都可以是一个文件,也可以是一个标签,标签作为第一个会一直执行。标签不是实际的文件;
#6.每一个规则中的目标,不一定要有依赖。
#7.每一个规则,不一定非得有命令列表。
#8.每个规则中可以有多条命令规则,但是前面都得需要加 Tab 键。
1 ) 读入所有的 Makefile 文件。
2 ) 读入 include 的 Makefile 文件。
3 ) 初始化文件中的变量。
4 ) 推导隐晦规则,并分析所有规则。
5 ) 为所有的目标文件创建依赖关系链。
6 ) 根据依赖关系,决定哪些目标要重新生成。
7 ) 执行生成命令。
注:①-⑤步为第一个阶段,⑥ -⑦为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么 make 会把其展开在使用的位置。但 make 并不会马上展开,make 使用的是拖延战术,如果变量出现在依赖关系的规则中,仅当这条依赖将要使用了,变量才会在其内部展开。
Makefile 里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
显式规则说明了如何生成一个或多的的目标文件。这是由 Makefile 的编写人员明显指出的。包括要生成的文件,文件的依赖文件,生成的命令。
使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有“%”字符。“%”的表示一个或多个任意字符。在依赖目标中同样可以使用“%”,只是依赖目标中的“%”的取值,取决于其目标。
有一点需要注意的是,“%”的展开发生在变量和函数的展开之后,变量和函数的展开发生在 make 载入 Makefile 时,而模式规则中的“%”则发生在运行时。
app:main.o fun.o
gcc main.o fun.o -o app
%.o:%.c
gcc -c $< -o $@ -I ./inc
当同级目录下具有标签名一致文件时使用伪目标。为目标不是一个真正的目标,仅仅是为了执行其所有规则下面的命令,不应该让 make 来判断他是否存在,或者是否应该被生成。
规则:.PHONY:后面声明是 伪目标
clean:
rm *.o app
#这样在执行make clean命令的时候就不会运行名为clean的makefile文件而发生冲突
makefile中声明变量需要赋予初值,在使用的时候要在前面加上 $ 符号(最好同时使用 () 或者 {} 将变量名包裹)。如果你要是用 $ 字符,则需要用 $$ 来替代。变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。
Makefile 中定义的变量切皆为字符串。
eg: 单字符: $A多字符:$(AA) 或者 ${AA}
#符号 =
#功能 延迟展开赋值
#例子
B=$A
A=10
all:
echo $B #输出为10
#符号 :=
#功能 立即展开赋值
#例子
B:=$A
A=10
all:
echo $B #无输出,因为B为空
#符号 ?=
#功能 条件赋值(经常出现在make传参)
#例子
A=30
A?=10
all:
echo $A #输出为30
#符号 +=
#功能 追加赋值
#例子
A=30
A+=10
all:
echo $A #输出为30 10,因为是字符串
#符号 $@
#功能 代替目标名
#符号 $^
#功能 代替依赖
#符号 $<
#功能 依赖集合中的第一个
#符号 @
#功能 make 在执行命令前不要将命令显示在标准输出上
在 Makefile 使用 include 关键字可以把别的 Makefile 包含进来,这很像 C 语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include 的语法是:
include ***.mk
如果没有找到或是不能读取文件,make会产生警告并且继续载入其他文件,当完成 makefile的读取,make 会再次寻找这些文件,如果还是不行,make 才会出现一条致命信息。你可以在 include 前加一个减号“-”,例如 -include filename 来让 make 不理那些文件。
上面的例子中已经提及了,即 # + 文本
#语法:
ifxxx (arg1,arg2)
#do true
else
#do false
endif
其他形式
ifxxx "arg1" "arg2"
ifxxx 'arg1' 'arg2'
ifxxx "arg1" 'arg2'
ifxxx 'arg1' "arg2"
ifxxx | 功能 |
---|---|
ifeq | 判断参数是否相等,相等为 true,否则为 false |
ifneq | 判断参数是否不相等,不相等为 true,否则为 false |
ifdef | 判断变量是否有值,有值则为 ture,否则为 false |
ifndef | 判断变量是否没有值,没有值则为 ture,否则为 false |
#判断语句不在规则中
B=30
C=20
ifeq ($B,$C)
A=10
else
A=20
endif
all:
echo $A
#标签在判断语句中
B=30
C=30
ifeq ($B,$C)
all:
echo $B
else
all:
echo $C
endif
#判断语句在命令行中
B=30
C=20
all:
ifeq ($B,$C)
echo $B
else
echo $C
endif
1 ) 条件判断语句根据条件的值来决定 make 的执行。
2 ) 条件判断可以比较两个不同变量或者变量和常量。
3 ) 条件判断在预处理阶段有效,在执行阶段无效。
4 ) 条件判断不能控制规则中命令的执行过程。
5 ) 条件判断语句之前可以有空格,但是不能有 Tab 字符。
6 ) 在条件判断语句中不要使用自动变量(@,$@,$^ , $<)。
7 ) 一条完整的条件语句必须位于同一个 makefile 中。
例子:
src=$(wildcard ./src/*.c)
返回值:目录下的所有 .c 文件(内容包含路径)
例子:
allFileName==$(notdir $(src))
返回值:每一个文件的非目录部分(即文件名)
例子:
file=$(patsubst %.h,%.o,$(allFileName))
返回值:把所有的文件名后缀从 .h 换成了 .o
例子:
sources=file1.c file2.c file3.h file4.h
src=$(filter-out %.h, $(sources))
返回值:过滤掉所有的 .h 文件
目标:
从 inc 文件夹中取出 .h 头文件 与 src1、src2 文件夹中的 .c 源文件一起参与编译,并且将 .o 文件生成到 obj 目录下。最后在当前目录下生成可执行文件 app 。在后续编译中可以自行选择过滤 .o 文件或者 .c 文件。
INC=./inc/
obj=./obj/
CC=gcc
mode="1"
m="1"
src1c=$(wildcard ./src1/*.c)
src2c=$(wildcard ./src2/*.c)
obj1=$(patsubst ./src1/%.c,./obj/%.o,$(src1c))
obj2=$(patsubst ./src2/%.c,./obj/%.o,$(src2c))
file1=$(filter-out ./obj/$(nf),$(obj1))
file2=$(filter-out ./obj/$(nf),$(obj2))
file3=$(filter-out ./src1/$(nf),$(src1c))
file4=$(filter-out ./src2/$(nf),$(src2c))
obj3=$(patsubst ./src1/%.c,./obj/%.o,$(file3))
obj4=$(patsubst ./src2/%.c,./obj/%.o,$(file4))
#如果m=1代表过滤.o 其他代表过滤.c
ifeq ($(m),$(mode))
app:$(file1) $(file2)
$(CC) $^ -o $@
else
app:$(obj3) $(obj4)
$(CC) $^ -o $@
endif
$(obj)%.o:./src1/%.c
$(CC) -c $^ -o $@ -I $(INC)
$(obj)%.o:./src2/%.c
$(CC) -c $^ -o $@ -I $(INC)
clean:
rm ./obj/*.o
.PHONY:clean