翻看到最前面的一篇文章:比手写makefile好用一万倍CMake!,CMake无疑是一个很好的自动编译链接工具,但是从某种程度上来说,用了别人封装好的东西,就会失去很多主动性,被束缚住了,因此,对于原装的make的了解,是必要的,我一直坚信,懂底层方为上。
如果在Windows环境下,如果你追求快速开发,可以跳过这篇文章,VS的内置编译环境和CMAKE和QMAKE诸如此类的应用封装的确实完整,我个人是不喜欢VS的,每次有问题看其源代码总是感觉怪怪的,可能它是P.J.Plauger 版本?我也不懂,在QT下开发的话qmake和cmake已经很完善了,几乎不用拓展什么了。
本篇文章尽量在一个星期内更完,以GNU make英文文档为参考,我尽量做到详略得当。
有关于GNU的可参考GPL协议和GNU系统
引用比尔盖茨在2004年的一句话
“个人计算机是人类迄今为止创造的最强大工具,我认为这种说法并无偏颇”。摘自《计算机体系结构-量化研究方法》
慢慢更新哈~
写Makefile
包含其他Makefile文件
include 告诉make 暂停读取当前Makefile 文件,而去读取其他Makefile 文件。
include filename...
include 的一种使用场景就是当有几个Makefile各自处理它们的程序时,需要使用一个相同的变量定义或者模块规则。
CF=lh
include my.mk
foo:
ifdef CF
echo "yes"
else
echo "no"
endif
另一种使用场景是当想从源文件自从生成条件时。在Makefile 中,依赖关系可能依赖一大堆文件,比如main.o 依赖于main.c 和defs.h ,而在代码中我们可能这么写main.o:main.c ,即.c 改变时.o 会被重新编译,但.h 不会影响.o ,所以需要建立.o 和.h 的依赖,一般编译器在编译代码时会自动寻找,即加上-M 或者-MM 参数
cc -MM main.c [0]
main.o: main.c
所以一般我们这么做
sources = foo.c bar.c
include $(sources:.c=.d)
如下示例
cc -MMD main.c
cat main.d [0]
main.o: main.c def.h
当读取文件有错误的时候,可以使用-include 不理那些错误
如何阅读一个Makefile文件
写规则
两种写法
targets : prerequisites
recipe
…
targets : prerequisites ; recipe
recipe
…
特殊目标
名称 | 描述 |
---|
.PHONY | 当他作为一个target的时候,make 会无条件的运行它,无论具有该名称的文件是否存在或其最后修改时间是什么。 | .SUFFIXES | 用于检查后缀规则的后缀列表,过时了,不看了 | .DEFAULT | 当木目标存在时,则使用他作为一个默认目标文件 | .PRECIOUS | 所依赖的目标被给予以下特殊处理:当make运行中断,目标文件不会被删除,同时,如果目标文件是一个中间文件,也不会被删除 | .INTERMEDIATE | 没影响 | .SECONDARY | 所依赖的目标被视为中间文件,除了它们永远不会自动删除 | .SECONDEXPANSION | | .DELETE_ON_ERROR | | .IGNORE | | .LOW_RESOLUTION_TIME | | .SILENT | | .EXPORT_ALL_VARIABLES | | .NOTPARALLEL | | .ONESHELL | | .POSIX | |
示例
all:gao
@echo "final"
.DEFAULT:
@echo "In default"
如何使用变量
变量定义方式
immediate = deferred
immediate ?= deferred
immediate := immediate
immediate ::= immediate
immediate += deferred or immediate
immediate != immediate
=和:=的区别:make一般会将整个makefile展开后,再决定变量的值,变量的值将会是整个makefile中最后被指定的值,这个是’=‘,而’':="表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。
其中的?= 等于
ifeq($(origin FOO), undefined)
FOO=bar
endif
变量引用的高级功能
替换引用用您指定的更改替换变量的值
foo := a.o b.o l.a c.o
bar := $(foo:.o=.c)
计算变量名
重载一个变量
override var=value
未定义变量:如果要清除变量,将其值设置为空通常就足够了,但在origin 中对于空和undefine 还是有区别的。
undefine foo
undefine bar
模板匹配变量值
此处要谈论的是% 符号
特殊变量
MAKEFILE_LIST :包含每一个被make 解析的Makefile 文件名
name1:=$(lastword $(MAKEFILE_LIST))
all:
echo $(name1)
.DEFAULT_GOAL :如果没有定义target 将使用默认的目标
ifeq ($(.DEFAULT_GOAL),)
$(warning no default goal is set)
endif
.PHONY: main
main:;@echo $@
$(warning default goal is $(.DEFAULT_GOAL))
名称 | 作用描述 |
---|
MAKE_RESTARTS | 它将包含此实例重新启动的次数 | MAKE_TERMOUT | | MAKE_TERMERR | 这些值可用于来确定 make 本身是否正在写入终端 | .RECIPEPREFIX | | .VARIABLES | | .FEATURES | | .INCLUDE_DIRS | | .EXTRA_PREREQS | |
Makefile的条件部分
控制语句
ifeq...else...endif
如下例子
lib=
foo:
ifeq ($(origin $(lib)), undefined)
echo "yes"
else
echo "no"
endif
条件定义
ifdef var
else
endif
常见函数
函数这部分我不想写大量的篇幅去写,函数这部分可以说是一个API,是功能性的,是要大家通过不断的实践去摸索的,要用什么,怎么用,什么时候用,都要不断的实践。
函数语法
$(function arguments)
${function arguments}
字符串替换函数
$(subst from,to,text)
$(patsubst pattern,replacement,text)
$(strip string)
$(findstring find,in)
$(filter pattern…,text)
$(filter-out pattern…,text)
$(sort list)
$(word n,text)
$(wordlist s,e,text)
$(words text)
$(firstword names…)
$(lastword names…)
文件名相关函数
$(dir names…)
$(notdir names…)
$(suffix names…)
$(basename names…)
$(addsuffix suffix,names…)
$(addprefix prefix,names…)
$(join list1,list2)
$(wildcard pattern)
$(abspath names…)
$(realpath names…)
条件控制相关函数
$(if condition,then-part[,else-part])
$(or condition1[,condition2[,condition3…]])
$(and condition1[,condition2[,condition3…]])
循环相关函数
$(foreach var,list,text)
示例如下
dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
文件相关函数
file 函数允许makefile 写入或读取文件。有两种写方式:覆盖写(override)和添加写(append),默认情况下当文件不存在时会自动创建。
$(file op filename[,text])
调用相关函数
$(call variable,param,param,…)
这里的函数的一个模拟的概念,如下一个函数和使用
reverse = $(2) $(1)
foo = $(call reverse,a,b)
其他函数
origin :不修改变量的值,只是查看变量的信息
$(origin variable)
undefined :未定义变量default :默认变量environment :环境变量environment override :环境重载变量file :这个变量被定义在Makefile 文件内部override :这个变量在一个Makefile 被重载automatic :自动定义变量
flavor :类似于origin 函数,
$(flavor variable)
undefined :未定义变量recursive :递归扩展变量simple :简单扩展变量
管理make行为函数
$(error text…)
$(warning text…)
$(info text…)
脚本函数
$(shell str...)
隐式规则
自动生成变量
变量 | 描述 |
---|
$@ | 表示这个目标文件 | $% | 仅是函数库中,表示规则中的目标成员名 | $< | 条件序列的第一个条件 | $? | 所有比目标新的依赖目标的集合,以空格分割 | $^ | 条件序列 | $+ | 可获取在条件序列中的重复条件 | $ | 按空格分割条件序列 | $* | 表示目标中’%'及其之前的部分 |
make常见错误
make中产生致命错误的一般前面会有***
实践例子
例1:使用通配符编译所有的.c文件为.o文件和可执行文件
f=$(wildcard *.c)
fo=$(f:.c=.o)
fp=$(fo:.o=)
all: $(fo) $(fp)
%.o: %.c
${CC} -c $^ -o $@
%: %.o
${CC} $^ -o _$@
例2:从.d、.c、.h、.o生成顺序来生成目标文件
f=$(wildcard *.c)
fo=$(f:.c=.o)
.PHONY: all
all: $(fo)
%.o: %.c
gcc -c -g -Wall $< -o $@ -MD -MF $*.d -MP
.PHONY: clean
clean:
rm -f *.d *.o *.out
注意此时的.d文件使用位置是在gcc中的,也可以放在依赖文件中,但是考虑到编译位置,所以最好还是使用$*.d
参数说明:
-M :生成文件的依赖关系,同时也把一些标准库的头文件包含了进来,在GNU下是-MM -MG :要求把缺失的头文件按存在对待,并且假定他们和源文件在同一目录下,必须和 ‘-M’ 选项一起用。-MF :当使用了 “-M” 或者 “-MM” 选项时,则把依赖关系写入名为 “File” 的文件中。若同时也使用了 “-MD” 或 “-MMD”,“-MF” 将覆写输出的依赖文件的名称-MD :等同于 -M -MF File,但是默认关闭了 -E 选项-MP :生成的依赖文件里面,依赖规则中的所有 .h 依赖项都会在该文件中生成一个伪目标,其不依赖任何其他依赖项。-MMD :类似于 “-MD”,但是输出的依赖文件中,不包含标准头文件
-E选项是GCC的生成预处理文件的选项。
当然,如果需要每个.o文件都生成一个可执行文件
ff=$(f:.c=)
.PHONY: all
all: $(fo) $(ff)
%:%.o
gcc $^ -o _$@
|