|
翻看到最前面的一篇文章:比手写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 _$@
|