《跟我一起写Makefile》笔记

【参考资料】
【1】《跟我一起写Makefile》
【2】https://www.cnblogs.com/wanghuaijun/p/8092747.html
【3】https://www.ibm.com/developerworks/cn/linux/l-makefile/
【4】http://www.laruence.com/2009/11/18/1154.html

备注:项目需要重新梳理下Makefile知识点,主要是《跟我一起写Makefile》,该文写得非常清楚,上一次看份文档的时候都快十年,当时的笔记也没了,很多事情真是恍若隔世。
笔记最后增加了一些autoconf和automake是参考其他资料

第一部分 概述

自动化编译依赖于IDE内部的make工具,例如Delphi的make、Visual C++的nmake和Linux GNU的make。它们解析了makefile里的命令。

第二部分 关于程序的编译和链接

编译: 把源文件变成中间文件,例如windows下的obj,Unix下的.o
链接: 把大量的中间文件变成执行文件

库文件: 把一部分中间目标文件打包,例如windows下的lib和Unix下的.a

第三部分 Makefile介绍

一、Makefile 规则
target ... : prerequisites ...  
command

target: 目标文件,可以是obj文件,也可以是执行文件
prerequisites: 生成target所需要的目标
command: make需要执行的命令

二、一个实例
edit: main.o kbd.o
cc -o edit main.o kbd.o

main.o: main.c main.h
cc -c main.c
kbd.o: kbd.c defs.h command.h  
cc -c kbd.c  
clean:
rm edit main.o kbd.o
三、make是如何工作的
  1. 在当前目录下寻找Makefile或makefile文件
  2. 如果找到,把文件中第一个target作为本makefile的最终target,例如上例中的edit
  3. 如果目标不存在,或其存在更新的依赖,则重新生成目标文件
  4. 如果目标文件的依赖不存在,则执行命令生成依赖文件,依次递归
  5. make clean命令执行clean下的脚本
四、makefile中使用变量
object = main.o kbd.o

#这里使用$(object)这个变量来替代main.o kbd.o
edit: $(object) 

第四部分 Makefile总述

一、makefile里有什么
内容 功能
显示规则 如何生成一个或多个目标文件
隐晦规则 makefile的自动推导机制
变量定义 用于引用的字符串变量
文件指示 makefile之间的引用包含,类似include
注释 与shell脚本一样采用#
二、makefile的文件名
  1. 默认采用makefile或Makefile
  2. 可以采用自定义命名,例如Make.Linux,需要make -f Make.Linux
三、引用其他makefile
include foo.make *.mk $(bar)

#这里$(bar)代表e.mk、f.mk,因此等价于

include foo.make a.mk b.mk c.mk e.mk f.mk  

makefile引用的路径规则:

  1. make命令时有-I或者–include-dir,在此参数下查找
  2. 如果存在/include,make也会去查找
  3. 可以用-include 来忽略找不到引用的错误
四、Make工作方式
  1. 读入所有的makefile
  2. 读入被include的其他makefile
  3. 初始化文件中的变量
  4. 推导隐晦规则,并分析所有规则
  5. 为所有的目标文件创建关系链
  6. 根据关系链决定哪些目标文件要重新生成
  7. 执行生成命令

第五部分 书写规则

一、规则举例
foo.o: foo.c defs.h #foo 模块  
cc -c -g foo.c 
二、在规则中使用通配符
通配符 作用
* 代替零个、单个或多个字符
代替单个字符,其中$?代表比目标还要新的文件列表
[…] 匹配括号内的任一字符
四、文件搜索
VPATH变量
VPATH=src:../headers
#定义了src和../headers两个目录,make在此目录下搜索
vpath关键字
#1. 为符合pattern规则的文件制定搜索路径<directories>
#vpath <pattern><directories>
vpath %.h ../headers

#2. 清除符合pattern规则的文件设置好的搜索目录
#vpath <pattern>

#3. 清除所有设置好的搜索目录
#vpath
五、伪目标

例如clean不是真正意义上的目标,而是一个标签。通常可以加

.PHONY: clean
clean:
rm *.o temp

也可以利用伪目标实现多目标生成,例如:

all: prog1 prog2 prog3
.PHONY : all

#注意:伪目标本身也可以作为依赖
六、多目标

多个目标同时依赖于一个文件,例如

bigoutput littleoutput : text.g
generate text.g -$(subst output, ,$@) > $@

#此处bigoutput和littleoutput都依赖于text.g  
#上述规则等价于  
bigoutput : text.g  
generate text.g -big > bigoutput  
littleoutput : text.g  
generate text.g -little > littleoutput

#上面$@表示目标的集合,-$(subst output, ,$@)表示执行makefile的一个函数,函数名是subst,
#是用来截取字符串,把bigoutput中的output去掉  

七、静态模式

语法规则如下

<targets..> : <target-pattern>: <prereq-patterns...>
<commands>
objects = foo.o bar.o  
all: $(objects)  

#下面的%就是上面的foo bar
$(objects) : %.o: %.c 
# $< 表示所有依赖集,即foo.c bar.c
$(CC) -c $(CFLAGS) $< -o $@  

#上面规则等价如下  
foo.o: foo.c
$(CC) -c $(CFLAGS)  foo.c -o foo.o  
bar.o: bar.c
$(CC) -c $(CFLAGS)  bar.c -o bar.o  

八、自动生成依赖性
cc -M main.c
#-M会自动把依赖的头文件加进来,实际输出  
main.o: main.c defs.h  

#注意:若使用GNU,则必须使用-MM参数,否则会增加一系列系统头文件  

第六部分 书写命令

一、显示命令

@echo 正在编译…
显示 “正在编译…”
echo 正在编译…
显示 echo 正在编译… 正在编译…

make参数加 -n 或者-just-print 只显示命令
make参数加 -s 或者-slient 全面禁止命令显示

二、命令执行

如果下一条命令要用到上一条命令的结果,则用;分割,例如

cd /home/test;pwd
#此时pwd打印的是cd 命令的结果,否则则是当前makefile的目录  
三、命令出错

make加-i或者-ignore-errors,表示忽略全部错误
make加-k或者-keep-going,表示中止当前出错规则,执行下一规则

四、嵌套执行make
  1. 当存在一个比较大的工程时,我们把每个子目录都保留自己的makefile
#用subsystem关键字  
subsystem:
cd subdir && $(MAKE)  

#等价于
subsystem:
$(MAKE) -C subdir  

  1. 可以用export来传递参数给下级makefile
#传递variable这边变量
export variable = value
#不传递某个变量
unexport variable  
五、定义命令包
#类似宏定义
define run-yacc
yacc $(firstword $^)
mv y.tab.c$@
#endif

#使用时
$(run-ycc)

第七部分 使用变量

一、变量的基础

变量采用 ( ) ()或者 ()包起来,用$ 表示真实的
++注意:Makefile里用shell变量必须是${}++

二、变量中的变量
ifeq (0, ${MAKELEVEL})
curdir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} hosttype=${host-type} whiami=${whoami}  
endif

= 表示: 基本的赋值操作
:= 表示: 覆盖之前的值
?= 表示: 如果之前没有赋值,则用等号后的值
+= 表示: 追加等号后的值

x = foo
y = $(x) bar
x = xyz
#这个例子里y是xyz bar
x := foo
y := $(x) bar
x := xyz
#这个例子里y是foo bar
三、变量的高级用法
变量值得替换
foo := a.o b.o c.o
bar := $(foo: .o = .c)
#把.o后缀变成.c后缀
把变量的值再当成变量
x=y
y=z
$(a) := $($(x))
#此时$(a)的值是z  

第八部分 使用条件判断

主要是ifeq、ifneq、ifdef三个。
其中ifdef时若变量值为非空,则表达式真,例如:

bar = 
foo = $(bar)  
ifdef foo
froboss = yes
else
froboss = no
endif

第九部分 使用函数

二、字符串处理函数
subst

语法: $(subst, , , )
功能: 把text中的from换成to,返回结果

patsubst

语法: $(patsubst, , , )
功能: 模式字符串替换

$(patsubst, %.c, %.o, x.c, bar.c)
#将x.c, bar.c换成x.o,bar.o
strip

语法: $(strip )
功能: 去掉开头和结尾的空格

findstring

语法: $(findstring, , )
功能: 在in里找find字符串,找到则返回find,否则返回空字符串

filter

语法: $(filter <pattern…>, )
功能: 以pattern模式过滤text字符串,返回符合模式的字符串

sources := foo.c bar.c baz.s gh.h
foo: $(sources)
cc $(filter %c %.s, $(sources)) -o foo
#这里gh.h被过滤掉 
filter-out

语法: $(filter-out <pattern…>, )
功能: 反过滤,与fileter相反

sort

语法: $(sort )
功能: 返回排序后的字符串

$(sort foo bar lose) #返回bar foo lose
word

语法: $(word , )
功能: 取text中的第n个单词

word-list

语法: $(word-list , , )
功能: 取text中的从第s个到第e个单词

words

语法: $(words )
功能: 返回text包含的单词数

firstwords

语法: $(firstwords )
功能: 返回text中第一个单词

三、文件名操作函数

注意:下面的语法中names代表多个

dir

语法: $(dir <names…>)
功能: 取出name中的文件路径部分,即最后一个反斜杠之前的部分

nodir

语法: $(nodir <names…>)
功能: 取出name中文件部分,与dir相反

sufix

语法: $(sufix <names…>)
功能: 取出names中的后缀部分

basename

语法: $(basename <names…>)
功能: 取出names中的前缀部分

addsuffix

语法: $(addsuffix , <names…> )
功能: 增加后缀

addprefix

语法: $(addprefix , <names…> )
功能: 增加前缀

join

语法: $(join ,)
功能: 逐次拼接list1和list2

$(join aaa bbb, 111 222 333)
#返回aaa111 bbb222 333
四、foreach函数
names := a b c d
files := $(foreach n, $(names), $(n).o)  
#执行结束后a.o b.o c.o d.o  
六、call函数

用call来向表达式传递参数,例如

reverse = $(1) $(2)  
foo = $(call reverse, a, b)  
#此时foo的值是a b,理解是call调用了reverse,调用时参数是a,b,然后返回结果
八、shell函数

执行系统的shell命令

files := $(shell echo *.c)
九、控制makefile函数

makefile的错误输出,其中error的输出会让makefile停止
$(error <text…>)
$(warning <text…>)

第十部分 Make的运行

三、指定目标
目标 功能
all 编译所有目标
clean 删除make所有创建的文件
install 安装已经编译好的程序
print 列出改变过的源文件
tar 打包tar文件
dist 把tar压缩成Z文件或者gz文件

第十一部分 autoconf/automake

autoconf、automake工具帮助程序员快速生成一个符合开源规范的Makefile文件,用户只需要采用./confiure, make make, install命令就可以进行编译、安装。

准备

准备一个空的文件里,创建一个c文件如下:

#include <stdio.h>
int main(int argc, char** argv){
     printf("%s", "Hello, Linux World!\n");
     return 0;
}
第一步:执行autoscan

在这里插入图片描述

autoscan命令产生autoscan.log和configure.scan两个文件,其中configure.scan是autoscan根据目录下源码生成的一个configure模板。在此我们将configure.scan更名为configure.in,并做修改如下:
在这里插入图片描述

第二步:执行aclocal和autoconf

aclocal命令: 生成aclocal.m4,包含后续automake要用到的一些其他宏定义
autoconf命令: 产生configure这个执行文件
在这里插入图片描述

第三步:新建Makefile.am

后续automake会根据此文件创建Makefile.in,填写内容如下

UTOMAKE_OPTIONS=foreign
bin_PROGRAMS=helloworld
helloworld_SOURCES=helloworld.c

在这里插入图片描述

第四步:执行autoheader

autoheader命令创建config.h.in
在这里插入图片描述

第五步:执行automake

执行命令automake --add-missing

错误1:
Makefile.am: error: required file ‘./NEWS’ not found
Makefile.am: error: required file ‘./README’ not found
Makefile.am: error: required file ‘./AUTHORS’ not found
Makefile.am: error: required file ‘./ChangeLog’ not found
则提前创建上述文件即可

错误2:
/usr/share/automake-1.15/am/depend2.am: error: AMDEP does not appear in AM_CONDITIONAL
安装libtool即可 apt install libtool

执行完后会生成Makefile.in
在这里插入图片描述

第六步:执行configure

这个步骤中configure命令将上一步中的Makefile.in变成符合标准的Makefile。
在这里插入图片描述

第七步:执行make

这步就是常规意义上的make,最终会生成执行文件helloworld。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Fredric_2014/article/details/84705847