Skip to main content

make

caution
  • $(wildcard src/**/*.ts) 在有些平台上是 $(wildcard src/*/*.ts)
    • 不能遍历所有文件
    • 推荐 shell 展开或 find 命令
  • macOS 自带 make 是 v3.1
# 修改默认 shell
SHELL=/bin/bash

# 保留中间文件
.PRECIOUS: public/modules/wener-apis-%.system.js
# 二次求值 $$
# 这个需要放在前面
.SECONDEXPANSION:

# 总是执行
.PHONY: always
always:

# 所有 target 都不依赖 fs
.PHONY: $(MAKECMDGOALS)

# export 所有变量
.EXPORT_ALL_VARIABLES:

# makefile 所在目录
cwd := $(notdir $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))))

# 替换空格为逗号
text := hello a b c
comma:= ,
empty:=
space:= $(empty) $(empty)
rel := $(subst $(space),$(comma),${text})

# @ 不输出这行命令
ok:
@echo OK
# 二次求值
do-%: ok $$(wildcard src/modules/%/*.c)
# ? ok - @ do-xxx - % - < ok - ^ ok - + ok - | - * xxx
echo '?' $? - '@' $@ - '%' $% - '<' $< - '^' $^ - '+' $+ - '|' $| - '*' $*

make-%: always
# 如果文件存在才执行
ifneq ("$$(wildcard src/modules/$*/Makefile)","")
$(MAKE) -f src/modules/$*/Makefile build
else
@echo Skip - no makefile
endif

# 单次
ifneq ("$(wildcard $(PATH_TO_FILE))","")
FILE_EXISTS = 1
else
FILE_EXISTS = 0
endif

# 循环
LIST = one two three
foreach:
for i in $(LIST); do \
echo $$i; \
done

# 目录切换
foo : bar/lose
cd $(<D) && gobble $(<F) > ../$@

# 使用 ONESHELL 则简单一些 make v3.2+, macOS 自带的 make 是 3.1
.ONESHELL:
foo : bar/lose
cd $(@D)
gobble $(@F) > ../$@

要求环境变量

check-env:
ifndef ENV
$(error ENV is undefined)
endif

接收任意额外参数

CMD_ARGS = $(filter-out $@,$(MAKECMDGOALS))
%:
@:
run:
@echo RUN $(CMD_ARGS)
make run app

帮助文档

.PHONY: help
.DEFAULT_GOAL := help
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

Variables

  • ${foo}, $(foo)
    • $foo 实际是 $(f)oo - 所以不要这样用
  • 使用场景 targets, prerequisites, recipes, most directives, new variable values
  • =
    • set variable - 递归展开,每次求值展开
    • 不可以 CFLAGS = $(CFLAGS) -O, 会导致无限循环
      • 使用 +=
  • :=,::=
    • Simply expanded variables
    • 立即展开,执行一次
    • 递归展开 a := $($(x))
  • ?=
    • 默认值
  • override variable := value
    • 覆盖变量,可以使用 =,:=,+=
  • undefine variable
    • 取消变量定义
  • ${var:a=b},$(var:a=b)
    • 替换
    • 等同于 $(patsubst %a,%b,var)
  • 求值顺序
    • 覆盖变量
    • Setting Variables, verbatim definition - Defining Multi-Line Variables
    • 环境变量
    • 自动变量
    • 常量变量、隐性规则
  • 注意
    • 依赖项会继承变量
vardesc
$@target
$*% in target
# 多行变量
# 等同于 two-lines = echo foo; echo $(bar)
define two-lines
echo foo
echo $(bar)
endef

$(info $(origin foo))
$(info $(flavor bar))


EXTRA_CFLAGS =
# 私有变量
prog: private EXTRA_CFLAGS = -L/usr/local/lib
prog: a.o b.o

# ?=
ifeq ($(origin FOO), undefined)
FOO = bar
endif
%.out: %.input1 %.input2
merge $<1 $<2 $@
%.out: %.input1 %.input2
merge $^ $@

doit: project.out
# force build
touch $@

特殊变量

vardesc
MAKEFILE_LIST
.DEFAULT_GOAL
MAKE_RESTARTS
MAKE_TERMOUT,MAKE_TERMERR
.RECIPEPREFIX
.VARIABLES
.FEATURES
.INCLUDE_DIRS
.EXTRA_PREREQS
MAKECMDGOALSmake 时的目标
MAKEmake
MAKEFILES
  • CURDIR
    • 尽量用 CURDIR
    • 由 make 维护
    • 支持 -C 切换
  • PWD
    • 环境变量提供
info:
echo $(info CURDIR is from $(origin CURDIR)) $(info PWD is from $(origin PWD))

Targets

Standard Targets

targetdesc
all
clean
mostlyclean保留不想重新编译的文件,例如 libgcc.a
distclean,realclean,clobber比 clean 清除更多文件,例如配置文件,link
install
print显示变化了的源文件
tar源文件 tar
shar源文件 shar
dist
TAGS更新 tags 文件
check,test测试

特殊目标

targetdesc
.PHONY总是运行
.SUFFIXES后缀模式自动匹配,现在一般使用 %
.DEFAULT没找到规则时的默认目标
.PRECIOUS保留中间文件
.INTERMEDIATE表明是中间文件
.SECONDARY默认依赖为二次展开
.SECONDEXPANSION$$ 二次展开
.DELETE_ON_ERROR错误时删除文件
.IGNORE忽略错误,现在一般使用 -
.LOW_RESOLUTION_TIME处理文件修改时间时用更低精度,现在一般不使用
.SILENT不输出,类似 -s
.EXPORT_ALL_VARIABLES导出所有变量
.NOTPARALLEL不并行,忽略 -j
.ONESHELL运行在一个 shell 里而不是每行一个 shell
.POSIXPOSIX 兼容模式

函数

Recipes

subsystem:
# 透传 make
cd subdir && $(MAKE) $(MFLAGS)

ifeq (0,${MAKELEVEL})
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif

带帮助的 Makefile

build: ## Build
echo BUILD
.PHONY: help
help: ## 帮助
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
make help