主要参考百问网科技:http://wiki.100ask.org/
1 Makefile
规则与示例
参考:
doc/gunmake.htm
1.1 为什么需要 Makefile
1.1.1 高效地编译程序
参考 Visual Studio
,修改源文件或者头文件,只会重新编译 修改过的文件,就可以生成 APP
。
1.1.2 Makefile 其实挺简单
一个简单的 Makefile
文件包含一系列的“规则”,其样式如下:
目标(target)…: 依赖(prerequiries)…
<tab>命令(command)
如果“依赖文件”比“目标文件”更加新,那么执行“命令”来重新生成“目标文件”。
命令被执行的 2 个条件:依赖文件比目标文件新,或是 目标文件还没生成。
1.2 Makefile
的 2 个函数
1.2.1 $(foreach var,list,text)
-
对
list
中的每一个元素,取出来赋给var
,然后把var
改为text
所描述的形式 -
示例
objs := a.o b.o dep_files := $(foreach f, $(objs), .$(f).d) # 最终 dep_files := .a.o.d .b.o.d
1.2.2 $(wildcard pattern)
-
pattern
所列出的文件是否存在,把存在的文件都列出来 -
示例
src_files := $(wildcard *.c) # 最终 src_files 中列出了当前目录下的所有.c 文件
1.3 一步一步完善 Makefile
源码:
src/test_Makefile
1.3.1 示例
-
第 1 个
Makefile
,简单粗暴,效率低test : main.c sub.c sub.h gcc -o test main.c sub.c
-
第 2 个
Makefile
,效率高,相似规则太多太啰嗦,不支持检测头文件test : main.o sub.o gcc -o test main.o sub.o main.o : main.c gcc -c -o main.o main.c sub.o : sub.c gcc -c -o sub.o sub.c clean: rm *.o test -f
-
第 3 个
Makefile
,效率高,精炼,不支持检测头文件test : main.o sub.o gcc -o test main.o sub.o %.o : %.c gcc -c -o $@ $< clean: rm *.o test -f
-
第 4 个
Makefile
,效率高,精炼,支持检测头文件,但是需要手工添加头文件规则test : main.o sub.o gcc -o test main.o sub.o %.o : %.c gcc -c -o $@ $< sub.o : sub.h clean: rm *.o test -f
-
第 5 个
Makefile
,效率高,精炼,支持自动检测头文件objs := main.o sub.o test : $(objs) gcc -o test $^ # 需要判断是否存在依赖文件 # .main.o.d .sub.o.d dep_files := $(foreach f, $(objs), .$(f).d) dep_files := $(wildcard $(dep_files)) # 把依赖文件包含进来 ifneq ($(dep_files),) include $(dep_files) endif %.o : %.c gcc -Wp,-MD,.$@.d -c -o $@ $< clean: rm *.o test -f distclean: rm $(dep_files) *.o test -f
1.3.2 规则
$@
:表示目标文件$^
:表示所有的依赖文件$<
:表示第一个依赖文件
2 通用 Makefile 的使用
源码:src/general_Makefile
2.1 简介
百问网 参考 Linux
内核的 Makefile
编写了一个通用的 Makefile
,它可以用来编译应用程序。
2.2 特点
-
支持多个目录、多层目录、多个文件;
-
支持给所有文件设置编译选项;
-
支持给某个目录设置编译选项;
-
支持给某个文件单独设置编译选项;
-
简单、好用。
3 通用 Makefile 的解析
3.1 零星知识点
3.1.1 make
命令的使用
执行 make
命令时,它会去当前目录下查找名为 Makefile
的文件,并根据它的指示去执行操作,生成第一个目标。
-
可以使用
-f
项指定文件,不再使用名为Makefile
的文件$ make -f Makefile.build
-
可以使用
-C
选项指定目录,切换到其他目录里去$ make -C a/ -f Makefile.build
-
可以指定目标,不再默认生成 第一个目标
$ make -C a/ -f Makefile.build other_target
3.1.2 即时变量,延时变量
# 变量的定义语法形式如下
A = xxx # 延时变量,在使用时才展开、才确定
B ?= xxx # 延时变量,只有第一次定义时赋值才成功;如果曾定义过,此赋值无效
C := xxx # 立即变量
D += yyy # 如果 D 在前面是延时变量,那么现在它还是延时变量
# 如果 D 在前面是立即变量,那么现在它还是立即变量
在 GNU make
中对变量的赋值有两种方式:延时变量、立即变量。
3.1.3 变量的导出
- 在编译程序时,会不断地使用
make -C dir
切换到其他目录,执行其他目录里的Makefile
。 - 如果想让某个变量的值在所有目录中都可见,要把它
export
出来。 - 比如
CC = $(CROSS_COMPILE)gcc
- 这个
CC
变量表示编译器,在整个过程中都是一样的。 - 定义它之后,要使用
export CC
把它导出来。
- 这个
3.1.4 Makefile
中可以使用 shell
命令
TOPDIR := $(shell pwd) # 这是个立即变量, TOPDIR 等于 shell 命令 pwd 的结果
3.1.5 Makefile
中怎么放置第 1 个目标
-
执行
make
命令时如果不指定目标,那么它默认是去生成第 1 个目标。所以 第 1 个目标 ,位置很重要。 -
有时候不太方便把第 1 个目标完整地放在文件前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖与命令。
First_target: # 这句话放在前面 ...... # 其他代码,比如 include 其他文件得到后面的 xxx 变量 First_target : $(xxx) $(yyy) # 在文件的后面再来完善 command
3.1.6 假想目标
-
Makefile
中有这样的目标clean: rm -f $(shell find -name "*.o") rm -f $(TARGET)
如果当前目录下有名为
clean
的文件,执行make clean
时它就不会执行那些删除命令。这时需要把clean
这个目标,设置为 假想目标 ,这样可以确保执行make clean
时,删除命令肯定可以得到执行。 -
使用下面的语句把
clean
设置为假想目标.PHONY : clean
3.2 常用的函数
3.2.1 $(foreach var,list,text)
-
对
list
中的每一个元素,取出来赋给var
,然后把var
改为text
所描述的形式。 -
示例
objs := a.o b.o dep_files := $(foreach f, $(objs), .$(f).d) # 最终 dep_files := .a.o.d .b.o.d
3.2.2 $(wildcard pattern)
-
pattern
所列出的文件是否存在,把存在的文件都列出来。 -
示例
src_files := $(wildcard *.c) # 最终 src_files 中列出了当前目录下的所有.c 文件
3.2.3 $(filter pattern…,text)
-
把
text
中符合pattern
格式的内容,filter
出来,留下来。 -
示例
obj-y := a.o b.o c/ d/ DIR := $(filter %/, $(obj-y)) # 结果为:c/ d/
3.2.4 $(filter-out pattern…,text)
-
把
text
中符合pattern
格式的内容,filter-out
出来、扔掉。 -
示例
obj-y := a.o b.o c/ d/ DIR := $(filter-out %/, $(obj-y)) # 结果为: a.o b.o
3.2.5 $(patsubst pattern,replacement,text)
-
寻找
text
中符合格式pattern
的字,用replacement
替换它们。pattern
和replacement
中可以使用通配符。 -
示例
subdir-y := c/ d/ subdir-y := $(patsubst %/, %, $(subdir-y)) # 结果为: c d
3.3 通用 Makefile
的设计思想
3.3.1 在 Makefile
中确定要编译的文件、目录,例如
obj-y += main.o
obj-y += a/
Makefile
文件总是被 Makefile.build
包含的。
3.3.2 在 Makefile.build
中设置编译规则,有 3 条编译规则
-
进入子目录编译
$(subdir-y): make -C $@ -f $(TOPDIR)/Makefile.build
-
编译当前目录中的文件
%.o : %.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
-
当前目录下的
.o
和子目录下的built-in.o
要打包起来built-in.o : $(cur_objs) $(subdir_objs) $(LD) -r -o $@ $^
3.3.3 顶层 Makefile
中把顶层目录的 built-in.o
链接成 APP
$(TARGET) : built-in.o
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
4 总结
4.1 添加宏定义
添加宏定义是利用 gcc
的语法规则,一般是为了再编译过程中进行功能的定制区分。
4.1.1 Makefile
应该怎样修改
TEST = $(test)
ifeq ($(TEST),1)
CFLAGS += -DTEST_DEF
#$(info TEST = $(TEST))
endif
4.1.2 C
程序里怎么使用
#include <stdio.h>
void fun1()
{
printf("this is fun1\n");
}
void fun2()
{
printf("this is fun2\n");
}
int main(int argc, char *argv[])
{
fun1();
#ifdef TEST_DEF
fun2();
#endif
return 0;
}
4.1.3 测试
-
不加参数
# 一定要 clean $ make clean # 编译 $ make # 执行 $ ./test this is fun1
-
加参数
# 一定要 clean $ make clean # 编译,有参数 $ make test=1 # 执行 $ ./test this is fun1 this is fun2