Makefile 示例
介绍
make
命令在当前目录依次搜索 Makefile
、makefile
、GNUmakefile
文件,若找不到就报错- make 命令支持通过时间戳判断源文件和编译产物的先后关系,减少文件重新编译,提高编译速度
help
$ make --help
Usage: make [options] [target] ...
Options:
-b, -m Ignored for compatibility.
-B, --always-make Unconditionally make all targets.
-C DIRECTORY, --directory=DIRECTORY
Change to DIRECTORY before doing anything.
-d Print lots of debugging information.
--debug[=FLAGS] Print various types of debugging information.
-e, --environment-overrides
Environment variables override makefiles.
--eval=STRING Evaluate STRING as a makefile statement.
-f FILE, --file=FILE, --makefile=FILE
Read FILE as a makefile.
-h, --help Print this message and exit.
-i, --ignore-errors Ignore errors from recipes.
-I DIRECTORY, --include-dir=DIRECTORY
Search DIRECTORY for included makefiles.
-j [N], --jobs[=N] Allow N jobs at once; infinite jobs with no arg.
-k, --keep-going Keep going when some targets can't be made.
-l [N], --load-average[=N], --max-load[=N]
Don't start multiple jobs unless load is below N.
-L, --check-symlink-times Use the latest mtime between symlinks and target.
-n, --just-print, --dry-run, --recon
Don't actually run any recipe; just print them.
-o FILE, --old-file=FILE, --assume-old=FILE
Consider FILE to be very old and don't remake it.
-O[TYPE], --output-sync[=TYPE]
Synchronize output of parallel jobs by TYPE.
-p, --print-data-base Print make's internal database.
-q, --question Run no recipe; exit status says if up to date.
-r, --no-builtin-rules Disable the built-in implicit rules.
-R, --no-builtin-variables Disable the built-in variable settings.
-s, --silent, --quiet Don't echo recipes.
-S, --no-keep-going, --stop
Turns off -k.
-t, --touch Touch targets instead of remaking them.
--trace Print tracing information.
-v, --version Print the version number of make and exit.
-w, --print-directory Print the current directory.
--no-print-directory Turn off -w, even if it was turned on implicitly.
-W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE
Consider FILE to be infinitely new.
--warn-undefined-variables Warn when an undefined variable is referenced.
This program built for x86_64-pc-linux-gnu
Report bugs to <bug-make@gnu.org>
说明:
-C
指定路径make -j 4
指定并行编译的任务数,可以大大缩短编译时间,-j
指定 CPU 个数
-j [N], --jobs[=N] Allow N jobs at once; infinite jobs with no arg.
规则
Makefile 由多条规则组成
目标: [依赖1[ 依赖2 [ 依赖3 [...]]]]
\t命令
[target] ... : [prerequisites] ...
<tab>[command]
...
...
目标
一个 target 表示一条规则,也称为 伪目标(PHONY)
- 一个
Makefile
中可以有多个目标,一般第一个为默认目标 .PHONY
的作用有两个- 一
.PHONY
后跟伪目标
,直接在 Makefile 中执行 伪目标
的命令。忽略 Makefile 同级目录下的同名的文件 - 二是提高执行 makefile 时的效率
依赖
是可选的,通常是多个文件名、伪目标,必须是已有的规则- 可以通过 shell 获取,如
$(shell find . -type f -name "*.sh")
- 每行命令前必须有一个
<tab>
键,如果想用其他键,通过内置变量 .RECIPEPREFIX
声明
.RECIPEPREFIX = >
all:
> echo Hello, world
命令
构建一个 target 的具体命令集合- 也可以为空,用来表示一种依赖关系
- 命令以横杠
-
开头,表示忽略命令执行的状态 make
默认会打印每条命令,再执行,该行为被称为回声
,命令前加 @
可以禁用该打印
- 多目标时,可以使用
%
进行匹配,如下压缩命令:
xxx-%.gz: xxx-%
gzip --force --keep xxx-$*.exe
.DEFAULT_GOAL
设置默认目标,不设置默认为第一个
.DEFAULT_GOAL := default
default:...
shell
shell 函数
参数是操作系统的 shell 命令,功能和使用 (`) 相同- 示例
ipaddress := $(shell ip a)
${}
与$()
区别
- $() 与 ``(反引号)都是用来作命令替换的
- ${} 变量替换,把变量的真实值带入
- 每行命令在一个单独的 shell 中执行,Shell 之间没有继承关系
# 执行时 make var-lost,取不到 foo 的值,两行命令在两个不同的进程执行
test-sh:
export foo=bar
echo "foo=[$$foo]"
# 方法一:将两行命令写在一行,中间用分号分隔
test-sh:
export foo=bar; echo "foo=[$$foo]"
# 方法二:在换行符前加反斜杠转义
test-sh:
export foo=bar; \
echo "foo=[$$foo]"
list-file:
for file in `ls /usr/local/bin/`; do\
echo $${file};\
done;\
# 方法三:为目标加上 .ONESHELL:
.ONESHELL:
test-sh:
export foo=bar;
echo "foo=[$$foo]"
include
将别的 Makefile
包含进来,这很像 C 语言的 #include
# 假设有 Makefile a.mk、b.mk、c.mk,$(bar) 包含 e.mk f.mk,则
include foo.make *.mk $(bar)
# 等价于
include foo.make a.mk b.mk c.mk e.mk f.mk
# include 当文件不存在时会报错,使用 `-` 忽略错误,也可以使用 `sinclude` 替代
-include <filename>
类似的参数:
条件判断
# ifeq
mode = debug
hello: hello.c
ifeq ($(mode),debug)
@echo "debug mode"
gcc -g -o hello hello.c
else
@echo "release mode"
gcc -o hello hello.c
endif
# ifneq
mode = debug
hello: hello.c
ifneq ($(mode),)
@echo "debug mode"
gcc -g -o hello hello.c
else
@echo "release mode"
gcc -o hello hello.c
endif
# ifdef
mode =
hello: hello.c
ifdef mode
@echo "debug mode"
gcc -g -o hello hello.c
else
@echo "release mode"
gcc -o hello hello.c
endif
# ifndef
mode =
hello: hello.c
ifndef mode
@echo "debug mode"
gcc -g -o hello hello.c
else
@echo "release mode"
gcc -o hello hello.c
endif
for
- Makefile 使用 Bash 语法实现判断和循环
LIST = one two three
all:
for i in $(LIST); do \
echo $$i; \
done
# 等同于
all:
for i in one two three; do \
echo $i; \
done
赋值运算
Makefile 一共提供了四个赋值运算符 (=
、:=
、?=
、+=
),区别:
# 执行时扩展,允许递归扩展
VARIABLE = value
# 定义时扩展
VARIABLE := value
# 只有在该变量为空时才设置值
VARIABLE ?= value
# 将值追加到变量的尾端
VARIABLE += value
自动变量(Automatic Variables)
Make 命令还提供一些自动变量,它们的值与当前规则有关
$@
指当前目标,即 Make 命令当前构建的那个目标
比如,make foo
的 $@
就指 foo
a.txt b.txt:
touch $@
# 等同于
a.txt:
touch a.txt
b.txt:
touch b.txt
$<
指第一个前置条件- 比如,规则为
t: p1 p2
,那么 $<
就指 p1
a.txt: b.txt c.txt
cp $< $@
# 等同于
a.txt: b.txt c.txt
cp b.txt a.txt
$?
指比目标更新的所有前置条件,之间以空格分隔
- 比如,规则为
t: p1 p2
,其中 p2
的时间戳比 t
新,$?
就指 p2
$^
指所有前置条件,之间以空格分隔
- 比如,规则为
t: p1 p2
,那么 $^
就指 p1 p2
$*
指匹配符 %
匹配的部分
比如 %
匹配 f1.txt
中的 f1
,$*
就表示 f1
$(@D)
和 $(@F)
分别指向 $@
的目录名和文件名
- 比如,
$@
是 src/input.c
,那么 $(@D)
的值为 src
,$(@F)
的值为 input.c
$(<D)
和 $(<F)
分别指向 $<
的目录名和文件名
函数
Makefile 还可以使用函数,格式如下。
$(function arguments)
# 或
${function arguments}
wildcard
用来在 Makefile 中,替换 Bash 的通配符
srcfiles := $(wildcard src/*.txt)
$(subst from,to,text)
# 将字符串 feet on the street 替换成 fEEt on the strEEt
$(subst ee,EE,feet on the street)
$(patsubst pattern,replacement,text)
# 将文件名 x.c.c bar.c 替换成 x.c.o bar.o
$(patsubst %.c,%.o,x.c.c bar.c)
- 替换后缀名名函数的写法是:
变量名 + 冒号 + 后缀名
替换规则,本质是 patsubst
函数的一种简写形式
# 将变量OUTPUT中的后缀名 .js 全部替换成 .min.js
min: $(OUTPUT:.js=.min.js)
Makefile 示例
Golang
# https://www.xiexianbin.cn/program/tools/2016-01-09-makefile/index.html
.PHONY: all test clean build build-linux build-mac build-windows
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
BINARY_NAME=main
BINARY_LINUX=$(BINARY_NAME)-linux
BINARY_MAC=$(BINARY_NAME)-darwin
BINARY_WIN=$(BINARY_NAME)-windows
help: ## Show this help.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
all: clean test build build-linux build-mac build-windows ## Build all
test: ## run test
$(GOTEST) -v ./...
clean: ## run clean bin files
$(GOCLEAN)
rm -f bin/$(BINARY_NAME)
build: ## build for current os
$(GOBUILD) -o bin/$(BINARY_NAME) -v
build-linux: ## build linux amd64
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o bin/$(BINARY_LINUX) -v
build-mac: ## build mac amd64
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GOBUILD) -o bin/$(BINARY_MAC) -v
build-windows: ## build windows amd64
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) -o bin/$(BINARY_WIN) -v