常用Makefile
目标:依赖
执行语句
$ ls -l
Makefile
main.c
#下面是Makefile的命令
# 这是一个注释
# 目标文件 生成的文件名 : 依赖文件 所需的源文件
main: entry.c bar.c
gcc -o main main.c # 编译命令
$ ls -l
Makefile
bar.c
entry.c
main
#增添了一些文件
main: entry.c bar.c
gcc entry.c bar.c -o main
$ tree
├── entry.c
├── func
│ ├── bar.c
│ └── bar.h
└── makefile
#entry.c 调用了bar.h 的函数
多行注释和不输出命令
正常注释:
#line
#line2
#line3
#多行注释 \
这行也被注释 \
这一行也被注释 \
也就是再跟着上一行的地方接上\这个换行符号 \
要保证第一行是正常注释"#"开头
不输出原始命令,只输出具体结果:
通常的 makefile
target:dependnece
command
$ make
#输出
command的具体命令 echo "Hello World"
Hello World #实际执行内容
处理方法:
- 加上"@"符号
clean_file:
touch clean_file
clean:
@rm -f clean_file
#再make clean的时候就不会输出具体内容
- 所有命令之前加上
.SILENT:都不会输出原始的命令内容
.SILENT:
xxxx
xxxx
clean_file:
touch clean_file
clean:
@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 works
echo $x
echo ${y}
echo $(y)
echo $y
目标(Targets)
执行所有的 Target
all : Hello World Sharon
Hello:
touch Hello.rs
World:
touch World.rs
Sharon:
touch Sharon.rs
执行多个目标
# $@ 是一个自动变量,包含目标名称
all: hello Sharon
hello 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 four
one: $(thing_wrong) #执行错误
two: *.o
three: $(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.o
bar.o: bar.c
$(CC) -c bar.c -o bar.o
all.o: all.c
$(CC) -c all.c -o all.o
all.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: %.c
echo "target: $@ prereq: $<"
$(filter %.result,$(obj_files)): %.result: %.raw
echo "target: $@ prereq: $<"
从 obj_files 中筛选出所有以 .o 结尾的文件
: %.o: %.c是一个规则模式,表示每个.o依赖.c文件
筛选.result文件 表示依赖于.raw文件
模式规则(Pattern Rules)
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
将所有的 .c 文件编译成 .o 文件
双冒号:
all: blah
blah::
echo "hello"
blah::
echo "hello again"
#输出
echo "hello"
hello
echo "hello again"
hello again
对于同一个目标定义多个规则,会运行多次
all: blah
blah:
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 target3
target1:
echo "Running target1"
false # 这个命令会失败
target2:
echo "Running target2"
-false # 这个命令会失败,但错误会被忽略
target3:
echo "Running target3"
new_contents = "hello:\n\ttouch inside_file"
all:
mkdir -p subdir
printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
cd subdir && $(MAKE)
clean:
rm -rf subdir
流程:
new_contents是一个字符串不会作为命令被使用all作为默认目标被执行- 创建目录
subdir,向subdir/makefile文件写入new_contents的内容 - 进入
subdir然后递归调用makefile - 当前目录的
makefile内容是
hello:
touch inside_file
输出结果
make
mkdir -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 variable
echo $(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 variable
echo $$MY_GLOBAL_VAR # 输出为空,因为 MY_GLOBAL_VAR 不是环境变量
2.2 定义后,导出全局变量为环境变量
MY_GLOBAL_VAR="I am a global variable"
export MY_GLOBAL_VAR
all:
echo $(MY_GLOBAL_VAR) # 输出:I am a global variable
echo $$MY_GLOBAL_VAR # 输出:I am a global variable
一个具体的例子
new_contents = "hello:\n\techo \$$(cooly)"
all:
mkdir -p subdir
printf $(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 cooly
clean:
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:dependece
command
#否则就多了一个空格
target:dependece
command
- 使用
\$对$进行转义字符 然后解析变量
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= later
all:
@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} there
all:
echo $(one)
#output
echo hello there
hello there
只会递归一次把one从 hello 变成 hello there
行首的字符会被删除,行尾会,比如保留" "空格
with_spaces = hello
after = $(with_spaces)there
nullstring =
space = $(nullstring)
all:
echo "$(after)"
echo start"$(space)"end
#output
echo "hello there"
hello there
echo start" "end
start end
同时使用
foo := start
foo += more
all:
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 = cool
all:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
命令
make等于默认make all
输出
echo one is defined: cool
one is defined: coolmake other
输出
echo one is defined: nothing
one is defined: nothing
特定模式的变量(Pattern-specific variables)
%.c: one = cool
blah.c:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
Makefiles 的条件部分(Conditional part of Makefiles)
条件 if/else
foo = ok
all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif
检查一个变量是否为空
nullstring =
foo = $(nullstring) # end of line; there is a space here
all:
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 foo
echo "foo is defined"
endif
ifndef bar
echo "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

浙公网安备 33010602011771号