1. 作用
通俗的来讲:
其实和 shell 脚本差不多,只不过有一些自己的规则可以让你使用起来更方便,比如你写 shell 脚本的话,只能写在一个文件里然后执行这个文件,makefile 可以让你执行这个里面的某一条命令。
2. 格式
Makefile文件由一系列规则(rules)构成。每条规则的形式如下。
<target> : <prerequisites>
[tab] <commands>
上面第一行冒号前面的部分,叫做"目标"(target),冒号后面的部分叫做"前置条件"(prerequisites);第二行必须由一个tab键起首,后面跟着"命令"(commands)。
"目标"是必需的,不可省略;"前置条件"和"命令"都是可选的,但是两者之中必须至少存在一个。
2.1 目标
当目标是文件名的时候,比如这时的目标是a.txt:
a.txt: b.txt c.txt
cat b.txt c.txt > a.txt
当目标为一个操作的名字时,比如这个clean,称为‘伪目标’,但是当你执行 make clean
的时候,如果正好有一个文件叫做clean,那么这个命令不会执行。因为Make发现clean文件已经存在,就认为没有必要重新构建了,就不会执行指定的rm命令。
clean:
rm *.o
所以,一般我们会明确声明clean是"伪目标":
.PHONY: clean
clean:
rm *.o temp
多个伪目标直接用空格隔开
像.PHONY这样的内置目标名还有不少,可以查看手册。
2.2 前置条件
2.3 命令
var-lost:
export foo=bar
echo "foo=[$$foo]"
上面代码执行后(make var-lost),取不到foo的值。因为两行命令在两个不同的进程执行。一个解决办法是将两行命令写在一行,中间用分号分隔。
- 两行命令写在一行,中间用分号分隔。
- 换行符前加反斜杠转义。
- 加上.ONESHELL:
var-kept:
export foo=bar; echo "foo=[$$foo]"
var-kept:
export foo=bar; \
echo "foo=[$$foo]"
.ONESHELL:
var-kept:
export foo=bar;
echo "foo=[$$foo]"
3. 语法
3.1 注释
用 #
3.2 回声
make会打印每条命令,然后再执行,这就叫做回声(echoing)。
在命令的前面加上@,就可以关闭回声。
注:通常只在注释和纯显示的echo命令前面加上@。
test:
@# 这是测试
@echo TODO
3.3 通配符
*、? 、[…]
3.4 变量与赋值
txt = Hello World
test:
@echo $(txt)
用Shell变量,需要在美元符号前,再加一个美元符号,这是因为Make命令会对美元符号转义。
test:
@echo $$HOME
VARIABLE = value
# 在执行时扩展,允许递归扩展。
VARIABLE := value
# 在定义时扩展。
VARIABLE ?= value
# 只有在该变量为空时才设置值。
VARIABLE += value
# 将值追加到变量的尾端。
3.5 自动变量
1、$@
$@指代当前目标,就是Make命令当前构建的那个目标。比如,make foo的 $@ 就指代foo。
2、$<
< 就指代p1。
等等…
3.6 判断与循环
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
LIST = one two three
all:
for i in $(LIST); do \
echo $$i; \
done
# 等同于
all:
for i in one two three; do \
echo $i; \
done
3.7 函数
$(function arguments)
# 或者
${function arguments}
Makefile提供了许多内置函数,可供调用。下面是几个常用的内置函数。
1、shell 函数:
用来执行 shell 命令
srcfiles := $(shell echo src/{00..99}.txt)
2、call 函数:
$(call variable,param,param,…)
$(1),$(2)等等表示param,param,…
等等…还有好多函数
最后的实践
我项目中的一些实践:
SHELL := /bin/bash
.PHONY: prerequ-program client server prod_server prod_client
install:|prerequ-program
define require_install
if test "$(shell which $(1))" = ""; \
then \
brew install $(2); \
else \
echo $(1) is exists. skip install; \
fi
endef
prerequ-program:
@$(call require_install,mongod,mongo)
mkdir -p ./db/
if [ "${shell pgrep mongod}" = "" ]; then mongod --bind_ip 127.0.0.1 --fork --dbpath ./db/ --logpath ./db/mongod.log; fi
@echo "start mongod success!"
# 开发模式
server:
cd server && npm install && npm run dev
client:
cd client && npm install && npm run dev
# 生产模式
prod_server:
cd server && npm install && npm start
prod_client:
cd client && npm install && npm run build
比如我的项目要安装 mongodb,那么调用shell函数判断一下有没有 mongod,没有的话就 brew install mongo
,否则在当前目录下新建文件 mkdir -p ./db/