目标:依赖执行语句
$ ls -l
Makefile
main.c
#下面是Makefile的命令
# 这是一个注释
# 目标文件 生成的文件名 : 依赖文件 所需的源文件
main: entry.c bar.cgcc -o main main.c # 编译命令
$ ls -l
Makefile
bar.c
entry.c
main
#增添了一些文件
main: entry.c bar.cgcc entry.c bar.c -o main
$ tree
├── entry.c
├── func
│ ├── bar.c
│ └── bar.h
└── makefile#entry.c 调用了bar.h 的函数
多行注释和不输出命令
正常注释:
#line
#line2
#line3#多行注释 \
这行也被注释 \
这一行也被注释 \
也就是再跟着上一行的地方接上\这个换行符号 \
要保证第一行是正常注释"#"开头
不输出原始命令,只输出具体结果:
通常的 makefile
target:dependnececommand
$ make#输出
command的具体命令 echo "Hello World"
Hello World #实际执行内容
处理方法:
- 加上"@"符号
clean_file:touch clean_fileclean:@rm -f clean_file
#再make clean的时候就不会输出具体内容
- 所有命令之前加上
.SILENT:
都不会输出原始的命令内容
.SILENT:xxxx
xxxx
clean_file:touch clean_fileclean:@rm -f clean_file
运行 make 的时候只会默认执行touch clean_file
而不会执行clean
,也就是说他只会创建文件而不是执行clean
的命令
需要手动执行make clean
来看这个
# 默认目标是构建
all: build# 构建目标
build:@echo "Building the project..."# 这里放构建命令,例如 gcc -o main main.c# 清理目标
clean:@echo "Cleaning the project..."rm -f clean_file main# 代码风格检查目标
tidy:@echo "Running code style check..."# 这里放代码风格检查命令,例如 clang-format --dry-run --Werror *.c# 格式化代码目标
format:@echo "Formatting code..."# 这里放代码格式化命令,例如 clang-format -i *.c
定义变量
.SILENT:
x := dude
y := Hello,Sharon
all:echo $(x)echo ${x}# Bad practice, but worksecho $xecho ${y}echo $(y)echo $y
目标(Targets)
执行所有的 Target
all : Hello World SharonHello:touch Hello.rs
World:touch World.rs
Sharon:touch Sharon.rs
执行多个目标
# $@ 是一个自动变量,包含目标名称
all: hello Sharonhello Sharon:echo $@
自动变量和通配符
* 通配符 wildcard
$?
表示所有比目标更新的依赖文件(即那些被修改过的 .c 文件)**
print: $(wildcard *.c)ls -la $?
$(wildcard *.o)
是一个函数调用 如果不加上$符号,(wildcard *.o)
会被认为是一个普通文本,不是函数调用
thing_wrong := *.o #没有加上wildcard 表示名字为*.o的普通文件 有错误
thing_right := $(wildcard *.o)all: one two three fourone: $(thing_wrong) #执行错误two: *.othree: $(thing_right)four: $(wildcard *.o)
自动变量
Makefile
自动变量
自动变量 | 含义 | 示例代码 | 运行结果(假设目标为 output.txt ,依赖为 input.txt ) |
---|---|---|---|
$@ |
当前目标的名字 | echo $@ |
output.txt |
$< |
第一个依赖文件 | echo $< |
input.txt |
$^ |
所有依赖文件 | echo $^ |
input.txt |
$? |
更新的依赖文件 | echo $? |
如果 input.txt 比 output.txt 新,输出 input.txt |
$* |
目标的主干部分 | echo $* |
output (去掉后缀 .txt ) |
#这里保证 src/input1.txt src/input2.txt存在 否则会报错
output.txt: src/input1.txt src/input2.txt@echo "Target: $@"@echo "第一个依赖文件: $<"@echo "所有依赖文件: $^"@echo "更新的依赖文件: $?"@echo "主干: $*"@echo "依赖文件的目录部分: $(^D)"@echo "依赖文件的文件名部分: $(^F)"cat $^ > $@
#输出如下
目标: output.txt
第一个依赖文件: src/input1.txt
所有依赖文件: src/input1.txt src/input2.txt
更新的依赖文件: src/input1.txt src/input2.txt
主干: output
依赖文件的目录部分: src src
依赖文件的文件名部分: input1.txt input2.txt
Fancy Rules
隐式规则(Implicit Rules)
简写介绍
CC:C 编译器(默认是 gcc)
CC = gcc
CC = clang
CXX: C++ 编译器 (默认是 g++).
CXX = g++ # 默认是 g++
CXX = clang++ # 默认是 g++
CFLAGS: 传递给 C 编译器的额外标志 (例如 -g
表示生成调试信息).
CFLAGS = -g -Wall -O2 -std=c11 # 示例
CXXFLAGS: 传递给 C++ 编译器的额外标志.
CXXFLAGS = -g -Wall -O2 -std=c++17 # 示例
常用选项:
启用调试信息: -g
启用所有警告: -Wall -Wextra
优化级别: -O2
指定标准 (如 C++17): -std=c++17
CPPFLAGS: 传递给 C 预处理器的额外标志.
CPPFLAGS = -DDEBUG -Iinclude # 示例
常用选项:
定义宏: -DDEBUG
添加包含目录: -Iinclude
LDFLAGS: 传递给链接器的额外标志.
LDFLAGS = -Llib -pthread # 示例
常用选项:
添加库目录: -Llib
指定链接选项: -pthread
LOADLIBES 和 LDLIBS: 链接的库.
LDLIBS = -lm -lfoo # 示例
常用选项:
链接数学库: -lm
链接动态库: -lfoo
隐式规则的触发条件
- 目标文件没有显式规则: 如果 Make 发现目标文件 (例如
blah
或blah.o
) 没有对应的显式规则,它会尝试使用隐式规则. - 依赖文件存在: 如果目标文件依赖于某个源文件 (例如
blah.o
依赖于blah.c
),并且该源文件存在,Make 会使用隐式规则来生成目标文件.
常见的隐式规则
编译 C 程序: 如果存在 n.c
文件,Make 会自动生成 n.o
文件.
$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@
编译 C++ 程序: 如果存在 n.cc
或 n.cpp
文件,Make 会自动生成 n.o
文件.
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
示例
CC = gcc # 使用 gcc 作为 C 编译器
CFLAGS = -g # 启用调试信息# 隐式规则 #1: 通过 C 链接器隐式规则生成可执行文件 blah
# 隐式规则 #2: 通过 C 编译隐式规则生成 blah.o,因为 blah.c 存在
blah: blah.o# 生成 blah.c 文件
blah.c:echo "int main() { return 0; }" > blah.c# 清理生成的文件
clean:rm -f blah*
# 编译器设置
更完整的示例
CC = gcc
CXX = g++# 编译选项
CFLAGS = -g -Wall -O2 -std=c11
CXXFLAGS = -g -Wall -O2 -std=c++17
CPPFLAGS = -DDEBUG -Iinclude# 链接选项
LDFLAGS = -Llib -pthread
LDLIBS = -lm -lfoo# 目标
all: my_program# 编译 C 源文件
my_program: main.o utils.o$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@main.o: main.c$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@utils.o: utils.c$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@# 清理
clean:rm -f *.o my_program
删除当前文件夹除了指定文件之外的任何文件
find . -maxdepth 1 -type f ! -name 'Makefile' -exec rm -f {} +
静态模式规则(Static Pattern Rules)
语法
targets...: target-pattern: prereq-patterns ...commands
通常使用通配符去实现目标模式和依赖模式.
目标: foo.o bar.o all.o
目标模式: %.o
,匹配所有 .o
文件.
依赖模式: %.c
,将 %
替换为匹配的主干 (例如 foo
),生成依赖文件 foo.c
.
命令: 编译 .c
文件生成 .o
文件.
手动编写规则的示例
objects = foo.o bar.o all.o
all: $(objects)$(CC) $^ -o all#手动写三次数
foo.o: foo.c$(CC) -c foo.c -o foo.obar.o: bar.c$(CC) -c bar.c -o bar.oall.o: all.c$(CC) -c all.c -o all.oall.c:echo "int main() { return 0; }" > all.c%.c:touch $@clean:rm -f *.c *.o all
静态模式优化后
objects = foo.o bar.o all.o
all: $(objects)$(CC) $^ -o all# 静态模式规则
$(objects): %.o: %.c$(CC) -c $^ -o $@all.c:echo "int main() { return 0; }" > all.c%.c:touch $@clean:rm -f *.c *.o all
流程
- 从
all
开始检查,发现需要foo.o bar.o all.o
. - 往下找到静态模式规则,与
.o
有关,执行它. .o
依赖.c
,往下寻找.c
:- 如果
foo.c
或bar.c
不存在,使用%.c
规则生成空的.c
文件. - 如果
all.c
不存在,使用all.c
规则生成包含int main() { return 0; }
的all.c
文件.
- 如果
- 回到静态模式规则,编译
.c
为.o
. - 回到
all
,链接.o
为可执行文件all
.
静态模式规则和过滤(Static Pattern Rules and Filter)
files = foo.c bar.o baz.h qux.c
c_and_h_files = $(filter %.c %.h, $(files))
从 files 里筛选.c
和.h
文件
$(filter %.o,$(obj_files)): %.o: %.cecho "target: $@ prereq: $<"$(filter %.result,$(obj_files)): %.result: %.rawecho "target: $@ prereq: $<"
从 obj_files 中筛选出所有以 .o 结尾的文件
: %.o: %.c
是一个规则模式,表示每个.o
依赖.c
文件
筛选.result
文件 表示依赖于.raw
文件
模式规则(Pattern Rules)
%.o : %.c$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
将所有的 .c
文件编译成 .o
文件
双冒号:
all: blahblah::echo "hello"blah::echo "hello again"#输出
echo "hello"
hello
echo "hello again"
hello again
对于同一个目标定义多个规则,会运行多次
all: blahblah:echo "hello"blah:echo "hello again"
#output
Makefile:7: 警告:覆盖关于目标“blah”的配方
Makefile:4: 警告:忽略关于目标“blah”的旧配方
echo "hello again"
hello again
单个冒号多个规则会被警告和覆盖,新的规则会覆盖旧的规则
命令与执行(Commands and execution)
命令回显/静默(Command Echoing/Silencing)
all:@echo "This make line will not be printed"echo "But this will"
@
后面的不会被显示,只会执行
双美元符号
make_var = I am a make variable
all:sh_var='I am a shell variable'; echo $$sh_var@echo $(make_var)
定义变量make_var
,第二行echo
这个变量
sh_var
定义在命令行中,要使用他需要在同一行echo
,而且需要$$
进行转义
错误处理
-k
选项:继续执行即使遇到错误:
make -k
假设 Makefile 中有多个目标,其中一个目标的命令失败了,make 会继续执行其他目标,而不是立即停止.
-i 选项:忽略所有命令的错误:make -i
,忽略所有命令的错误,继续执行后续的命令
在命令前添加 -:忽略单个命令的错误:
all: target1 target2 target3target1:echo "Running target1"false # 这个命令会失败target2:echo "Running target2"-false # 这个命令会失败,但错误会被忽略target3:echo "Running target3"
new_contents = "hello:\n\ttouch inside_file"
all:mkdir -p subdirprintf $(new_contents) | sed -e 's/^ //' > subdir/makefilecd subdir && $(MAKE)clean:rm -rf subdir
流程:
new_contents
是一个字符串不会作为命令被使用all
作为默认目标被执行- 创建目录
subdir
,向subdir/makefile
文件写入new_contents
的内容 - 进入
subdir
然后递归调用makefile
- 当前目录的
makefile
内容是
hello:touch inside_file
输出结果
makemkdir -p subdir
printf "hello:\n\ttouch inside_file" | sed -e 's/^ //' > subdir/makefile
cd subdir && make
make[1]: 进入目录“parent_dir/subdir”
touch inside_file
make[1]: 离开目录“parent_dir/subdir”
导出、环境变量和递归 make(Export, environments, and recursive make)
基本术语:
环境变量(Environment Variable):操作系统中的全局变量,可以在当前 shell 会话及其子进程中使用.
1.1 在 Shell 中设置环境变量
# 设置环境变量
export MY_ENV_VAR="I am an environment variable"
# 查看环境变量
echo $MY_ENV_VAR # 输出:I am an environment variable
1.2 设置MY_ENV_VAR
后在 Makefile 中使用环境变量
all:echo $$MY_ENV_VAR # 输出:I am an environment variableecho $(MY_ENV_VAR) # 输出:I am an environment variable
全局变量(Global Variable)
定义:全局变量是指在 Makefile 中定义的变量,可以在整个 Makefile 中使用.
2.1 在 Makefile 中定义全局变量
#这是一个Makefile的全局变量
MY_GLOBAL_VAR="I am a global variable"all:echo $(MY_GLOBAL_VAR) # 输出:I am a global variableecho $$MY_GLOBAL_VAR # 输出为空,因为 MY_GLOBAL_VAR 不是环境变量
2.2 定义后,导出全局变量为环境变量
MY_GLOBAL_VAR="I am a global variable"
export MY_GLOBAL_VARall:echo $(MY_GLOBAL_VAR) # 输出:I am a global variableecho $$MY_GLOBAL_VAR # 输出:I am a global variable
一个具体的例子
new_contents = "hello:\n\techo \$$(cooly)"all:mkdir -p subdirprintf $(new_contents) | sed -e 's/^ //' > subdir/makefile@echo "---MAKEFILE CONTENTS---"@cd subdir && cat makefile@echo "---END MAKEFILE CONTENTS---"cd subdir && $(MAKE)# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport coolyclean:rm -rf subdir#输出
make
mkdir -p subdir
printf "hello:\n\techo \$(cooly)" | sed -e 's/^ //' > subdir/makefile
---MAKEFILE CONTENTS---
hello:echo $(cooly)---END MAKEFILE CONTENTS---
cd subdir && make
make[1]: 进入目录“Parent_dir/subdir”
echo "The subdirectory can see me!"
The subdirectory can see me!
make[1]: 离开目录“Parent_dir/subdir”
流程:
,mkdir -p subdir
- 向
subdir/makefile
输入内容 - 进入
subdir
然后cat makefile
- 进入子文件夹然后递归调用
make
- 因为通过
export
把cooly
导出为env_var
所以在子文件夹可以echo
很重要!!
第三步,启动一个新的 make 进程来执行子目录中的 Makefile,执行完后会自动输入消息进入目录和离开目录.但是命令没有离开目录的操作,为什么会回到原来的目录呢?子目录中完成执行后,它会自动返回到父目录
语法解析
create_ = "target_name:\n\techo \$$(var_name)"
:
\t
制表符,也就是tab
,要和换行符连起来用满足makefile
的格式,智能缩进一个tab
target:dependececommand#否则就多了一个空格
target:dependececommand
- 使用
\$
对$
进行转义字符 然后解析变量
printf "hello:\n\techo \$(cooly)" | sed -e 's/^ //' > subdir/makefile
printf
是输出命令, 输出一个字符串"hello:\n\techo \$(cooly)"
|
管道sed -e 's/^ //'
:sed
是流编辑器,表示对文本处理.-e
表示后面跟着一个编辑命令.s/匹配模式/替换内容/标志
:对应到这里's/^ //'
:匹配模式^
是正则表达式元字符,表示行的开头
sed -e 's/^ // '
匹配行开头后紧跟的一个空格字符
make
的参数
make clean run test
:按照顺序依次clean
run
test
变量(Variables Pt. 2)
不同的定义方式
=
是递归定义,会在后面展开追加的定义:=
是直接定义?=
是如果没有定义那就定义,否则不修改当前的定义
one = one ${later_var}
two = two ${later_var} ${later_var}
three := three ${later_var} ${later_var}${later_var}
four = x_4
four ?= x_5
five ?= x_5
later_var= laterall:@echo $(one)@echo $(two)@echo $(three)@echo $(four)@echo $(five)#output
one later
two later later
three
x_4
x_5
递归定义
one = hello
one = ${one}all:echo $(one)
#output
Makefile:3: *** Recursive variable 'one' references itself (eventually). Stop.
这里一直递归定义one
,会停止运行
一个合理的递归
one = hello
one := ${one} thereall:echo $(one)#output
echo hello there
hello there
只会递归一次把one
从 hello 变成 hello there
行首的字符会被删除,行尾会,比如保留" "
空格
with_spaces = hello
after = $(with_spaces)therenullstring =
space = $(nullstring)all:echo "$(after)"echo start"$(space)"end
#output
echo "hello there"
hello there
echo start" "end
start end
同时使用
foo := start
foo += moreall:echo $(foo)
命令行参数和覆盖(Command line arguments and override)
override option_one = did_override
option_two = not_override
all:echo $(option_one)echo $(option_two)#make 指定参数option_one = hi
make option_one=hi
#output
did_override
not_override
命令列表和定义(List of commands and define)
目标特定的变量(Target-specific variables)
在all
这这个特定的目标里面设置把one
设置为cool
,其他情况one
还是one
all: one = coolall:echo one is defined: $(one)other:echo one is nothing: $(one)
命令
make
等于默认make all
输出
echo one is defined: cool
one is defined: cool
make other
输出
echo one is defined: nothing
one is defined: nothing
特定模式的变量(Pattern-specific variables)
%.c: one = coolblah.c:
echo one is defined: $(one)other:
echo one is nothing: $(one)
Makefiles 的条件部分(Conditional part of Makefiles)
条件 if/else
foo = okall:
ifeq ($(foo), ok)echo "foo equals ok"
elseecho "nope"
endif
检查一个变量是否为空
nullstring =
foo = $(nullstring) # end of line; there is a space hereall:
ifeq ($(strip $(foo)),)echo "foo is empty after being stripped"
endif
ifeq ($(nullstring),)echo "nullstring doesn't even have spaces"
endif
检查一个变量是否已定义
bar =
foo = $(bar)all:
ifdef fooecho "foo is defined"
endif
ifndef barecho "but bar is not"
endif
Makelags
检查make
后面的参数是否出现某一个
all:
# Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case.
ifneq (,$(findstring i, $(MAKEFLAGS)))echo "i was passed to MAKEFLAGS"
endif