GNU Make 笔记
参考文档:
GNU Make Manual https://www.gnu.org/software/make/manual/make.html
《跟我一起写makefile》
《GNU make》
GNU make 有哪些功能?
GNU make 如何使用?
包括了make的规则、变量、函数以及脚本。
GNU make 程序 “将源代码转换成可执行文件” 的工作进行自动化。
make 的优点是得到程序中各元素之间的关系,make 根据时间戳判断应该重新进行哪些步骤,
以产生你所需要的程序。 make 定义了一种语言,描述源文件、中间文件以及可执行文件之间的关系。
案例1:假设有如下程序
使用命令行编译
gcc -o hellomake hellomake.c hellofunc.c
缺点:
1. 如果有更多的文件,gcc命令复杂
2. 如果只改动某个文件,所有文件都需要重新编译
1. Makefile 版本1
hellomake: hellomake.c hellofunc.c
gcc -o hellomake hellomake.c hellofunc.c
hellomake 是工作目标
hellomake.c 与 hellofunc.c 是为了创建hellomake的必须存在的文件
gcc -o 是为了创建 hellomake 目标而执行的shell 命令
另一种写法
hellomake: hellomake.o hellofunc.o # hellomake 表示最终目标 gcc -o hellomake hellomake.o hellfunc.o hellomake.o : hellomake.c hellomake.h # hellomake.o 模块 gcc -c hellomake.c hellofunc.o: hellofunc.c hellomake.h #hellofunc 模块 gcc -c hellofunc.c
clean:
rm hellomake *.o
make 首先看到hellomake 目标, 发现必须得有 hellomake.o 与 hellofunc.o 必要条件
依次向下查看发现有 hellomake.o 目标, 有 hellomake.c 与 hellomak.h 文件,执行命令生成 hellomake.o 目标
依次向下查看发现有 hellofunc.o 目标, 有 hellofunc.c 与 hellomake.h 文件,执行命令生成 hellofunc.o 目标
生成 hellomake 目标
说明:Makefile 文件中包含一组用编译应用程序的规则,make 看到的第一项规则叫默认规则。
规则由三部分组成: target(目标), prerequiste(必要条件), command(命令).
target...: prereq1 prereq2 ...
commands
....
本例中:目标文件是 hellomake 和 hellomake.o hellofunc.o 文件, 依赖文件就是冒号后面的c文件和.h文件
工作目标 - 是一个必须建造的文件或进行的事情。
必要条件 - 是工作目标得以创建之前,必须事先存在的那些文件
执行的命令 - 当必要条件成立时,要创建目标执行的shell命令
Makefile 中一般有五个东西:
显示规则, 隐晦规则, 变量定义, 文件指示,注释
规则分为
具体规则 - 上面的版本一
模式规则 - 使用的是通配符,不是具体的名称
隐含规则 - 可以是模式规则 也可以是 内置于make的后缀规则。
静态模式规则 - 只能应用在特定的工作目标文件中。
GNU make 执行步骤:
1. 读入所有Makefile
2. 读入被include的其他Makefile
3. 初始化文件中的变量
4. 推导隐晦规则, 分析所有规则
5. 为所有的目标文件创建依赖关系
6. 根据依赖关系,决定哪些目标文件要重新定义
7. 执行命令
缺点:
* 所有代码文件都在同一目录中
* 如果增加一个文件,需要在target, command, clean中同时增加,增加了重复,下面通过定义变量来
解决这个问题。
通配符的概念
*.* 表示所有 文件名中带. 的文件名
? 代表任何一个单一字符
[...] 代表字符集
[^...] 代表字符集补集
~ 当前用户主目录
例如如下
prog: *.c // 列出所有文件
$(CC) -o $@ $^
(1) $@ ——目标文件的名称;
(2) $^ ——所有的依赖文件,以空格分开,不包含重复的依赖文件;
(3) $< ——第一个依赖文件的名称。
phony target 假想的目标的概念
任何不代表文件的工作
场景: 以工作目标充当标签来代表命令脚本, 比如 make clean , make world 等
clean:
rm -f *.o lexer.c
make 无法区分 文件形式的工作目标 与 假想的工作目标, 如果目录中有clean文件,
则会以文件形式的工作目标。
make 为了区分真正的文件与假想工作目标, 引入了 .PHONY 告诉 make , 该工作目标不是一个真正的文件
.PHONY: clean clean: rm -f *.o lexer.c
.PHONE 与注释一起用:
hello: build_msg hello.c gcc hello.c -o hello .PHONY: build_msg build_msg: @printf "#\n# Building hello \n#\n"
输出结果
& make # # Building hello # gcc hello.c -o hello
常用的假想目标
all: 执行编译应用程序的所有命令
install: 从已编译的二进制文件进行应用程序的安装
clean: 将产生源代码的二进制文件删除
disclean: 删除编译过程中所产生的任何文件
TAGS: 建立可供编辑器使用的标记表
info: 从Textinfo 源代码来创建GNU info 文件
check: 执行与应用程序相关的任何测试
变量 $(variable-name)
一个例子
#常数 CC := gcc MKDIR := mkdir -p # 内部变量 sources = *.c objects = $(subst .c, .o, $(sources)) # 函数 maybe-make-dir = $(if $(whildcard $1),, $(MKDIR) $1) assert-not-null = $(if $1,, $(error Illegal null value.))
make 中变量的类型
:= 用来创建简单变量
= 用来创建递归变量
?= 用来创建附带条件的变量赋值运算
+= 称为附加运算符
自动变量
$@ 工作目标的文件名
$% 档案文件成员结构中的文件名称
$< 第一个必要条件的文件名
$? 时间戳在工作目标之后的所有必要条件,并以空格隔开这些必要条件
$^ 所有必要条件的文件名,并以空格隔开
$+ 如同 $^, 但包含重复的文件名。
$* 工作目标的主文件名。
目录结构
VPATH = src include
vpath %.l %.c src
vpath %.h include
CPPFLAGS = -I include
VPATH 变量的内容是一份目录列表,可供make搜索其所需要的文件。
2. Makefile 版本2
CC=gcc CFLAGS=-I.
OBJ=hellomake.o hellofunc.o hellomake: $(OBJ) $(CC) -o hellomake $(OBJ) -I.
hellomake.o : hellomake.c hellomake.h gcc -c hellomake.c hellofunc.o: hellofunc.c hellomake.h gcc -c hellofunc.c
.PYTHON: clean
clean:
rm hellomake *.o
说明:使用变量的方式来简化了重复定义的问题
GNU make 很强大,具有自动识别依赖关系的能力,可以不用自己定义OBJ
3. Makefile 版本3
CC=gcc
CFLAGS=-I.
OBJ=hellomake.o hellofunc.o
hellomake: $(OBJ)
$(CC) -o hellomake $(OBJ) -I.
hellomake.o : hellomake.h
hellofunc.o: hellomake.h
.PYTHON: clean
clean:
rm hellomake $(OBJ)
说明:.PYTHON 表示clean是一个psuodo目标文件。
Makefile 版本5
CC=gcc CFLAGS=-I. DEPS = hellomake.h OBJ = hellomake.o hellofunc.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hellomake: $(OBJ) gcc -o $@ $^ $(CFLAGS)
Makefile 版本6
IDIR =../include CC=gcc CFLAGS=-I$(IDIR) ODIR=obj LDIR =../lib LIBS=-lm _DEPS = hellomake.h DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) _OBJ = hellomake.o hellofunc.o OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) $(ODIR)/%.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hellomake: $(OBJ) gcc -o $@ $^ $(CFLAGS) $(LIBS) .PHONY: clean clean: rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
在BSD与Linux平台通用的makefile
编译 make
Debug make DEBUG=1
BSD gmake BSD=1
# Makefile for utelnetd # # Configure this with the following environment variables: # # where to install INSTDIR := /usr/local/bin/ # GNU target string CROSS := # where to find login program ifneq ("", "$(BSD)") LOGIN := /usr/bin/login else LOGIN := /bin/login endif ifneq ("", "$(BSD)") CORE := utelnetd.core else CORE := core endif # nothing to configure below this line... ---8<---8<---8<--- PROGS = utelnetd INSTMODE = 0755 INSTOWNER = root INSTGROUP = root OBJS = utelnetd.o CC = $(CROSS)gcc INSTALL = install CFLAGS += -I. -pipe -DSHELLPATH=\"$(LOGIN)\" -Wall ifneq ("","$(DEBUG)") CFLAGS += -DDEBUG -g -Os STRIP = \# else CFLAGS += -fomit-frame-pointer STRIP = $(CROSS)strip endif ifeq ("1", "$(BSD)") CFLAGS += -DBSD endif all: $(PROGS) $(PROGS): $(OBJS) $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ $(STRIP) --remove-section=.comment --remove-section=.note $@ .PHONY: install install: $(PROGS) $(INSTALL) -d $(INSTDIR) $(INSTALL) -m $(INSTMODE) -o $(INSTOWNER) -g $(INSTGROUP) $(PROGS) $(INSTDIR) .PHONY: clean clean: rm -f $(PROGS) *.o $(CORE)
GNU make 官方文档 https://www.gnu.org/software/make/manual/make.html