很久以前写的一篇技术备忘了,隐约记得当时是看了APUE 2nd Edition这本书后,下载的源码在CentOS7以及MacOX10.X上都编译不过。在解决编译问题的同时,被APUE源码中的MakeFile中的天书给震惊后,学习了一些相关入门知识。并以看懂并向后来的好奇宝宝们解释APUE的MakeFile含义作为小目标,记录了这篇文章。
1. MakeFile的基础格式
target...:prerequisites
command
...
target 就是我们需要生成的目标文件。可以是可执行文件,也可以是object file中间文件,还可以是其他的标签
prerequisites 就是target所依赖的文件
command 就是生成target需要执行的命令,特别注意的是所有的shell command都可以在这里执行
下面就是一个最朴实的MakeFile文件,没有使用任何技巧
all:foo
foo:hello.o world.o
gcc -o foo hello.o world.o -I.
hello.o:hello.c hello.h world.h
gcc -c hello.c
world.o:world.c world.h
gcc -c world.c
clean:
rm hello.o world.o
2. 隐晦(高级)规则
我们可以给需要编译的这些文件名称用一个变量来代替,有点像常量的使用方法一样
其实我们看到的那些开源代码的Makefile用了很多隐晦规则
比如test.o一般来说对应的源文件就是test.c,并且调用编译器生成[.o]文件也是固定的。那么我们就不用像上面那样这样写了,而是像下面一样省略
objects=hello.o world.o
all:foo
foo:$(objects)
gcc -o foo $(objects)
hello.o:hello.h world.h
world.o:world.h
.PHONY:clean
clean:
rm $(objects)
注释和shell脚本一样用#号,记得命令必须以tab开头
命令前面加一个-减号是告诉make忽略这条命令可能引起的错误
代表了$HOME目录,fengzhao代表了某位
自动化变量
$? 所有比target更新的prerequisites,以空格间隔,如下所示
print:*.c
lpr -p $?
特别注意的是,make为变量赋值一个含有通配符的变量需要用上wildcard函数,否则通配符就不会被解释
objects=.o #objects真的就只是.o而已
objects:=$(wildcard *.o)#objects会用通配符去展开这个变量得到当前目录下面的中间文件
:= 和 = 的区别是
A=$(B)
B=hello.c world.c#上一行还没有定义B,但是用=已经可以引用到后面定义的B,然后给A赋值
B:=hello.c world.c
A:=$(B)#用:=进行必须引用已经定义了的变量
==?=== 的意思是 hello?=world 如果hello没有定义的话,它就等于world。如果定义了的,那么什么都不做
3. 多个target的语法
target可以是多个
静态模式的形式如下
target:target-pattern:prereq-pattern
command
...
举个例子来说明
objects=hello.o world.o
$(objects):%.o:%.c
$(CC) -c $(CFLAGS) $< -o $@
第二行的意思就是目标是objects里面那些[.o]文件,把xxx.o中的xxx取出来,找到的xxx.c就是其依赖的文件
==$<== 表示的 prereq-pattern 指代的文件,在这里就是 hello.c world.c
==$@== 表示的 target 指代的文件,在这里就是 hello.o world.o
再看一个加入了filter函数的例子
objects=hello.o foo.elc world.o
$(filter %.o, $(objects)):%.o:%.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc, $(objects)):%.elc:%el
emacs -f batch-byte-compile $<
这里的filter就是一个make build-in函数。函数名和参数用空格分隔,参数之间用,分隔
source=hello.c world.c
include ($source:.c=.d)
这个语法的意思是用.d来替换source中的.c(.c必须是结尾处的),然后形成新的新的集合
source=hello.c world.c
include ($source:%.c=%.d)
该语法的效果同上,只不过用的是静态规则
在命了前面加上 @ 可以让命令不显示出来
如果要执行多条(行)命令,且后面的命令依赖前面的命令,需要在命令之间加上 ; 号,并且写在一行
==$^== 表示 prereq 指代的文件集合并且去重
==$*== 表示模式 % 符号前面的部分
x=var1
var2=Hello
y=$(subst,1,2,$(x))
这里的函数subst是把$(x)中的1全部替换成2,所以y最后等于Hello
4. 其它更老的规则
模式变量
prog:CFLAGS=-g
目标变量
%.o:CFLAGS=-O
这两种变量都是,当且仅当特定的目标需要被执行打时候,用设置的变量取代环境变量的值
后缀规则,其实就是老式的隐含规则
.c.o:
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
库文件的目标是
helloworld.a(hello.o world.o):hello.o world.o
ar cr helloworld.a hello.o world.o
看APUE的make发现它的makefile写的那叫一个简单,能用隐含规则的全部用上了
比如 all: helloworld 就这么一句,合理推测的话,make会去自动寻找helloworld.c并去调用对应compile规则
比如 objs: hello.o world.o 也是一句,道理同上
还需要注意的是
编译 .c 文件到可执行文件的隐含规则的名字叫
COMPILE.c=$(CC) $(CFLAGS) $(CPPFLAGS) -c
链接 .c 文件到obj文件的隐含规则的名字叫
LINK.c=$(CC) $(CFLAGS) $(CPPFLAGS) $(LDDIR) $(LDFLAGS)
没想到看了上面这么多的内容,APUE的makefile还是遇到了小挑战,不过思考后还是解决了
GCC
gcc -c hello.c //将只生成 hello.o
链接
- ar 命令的作用是把obj文件,打包成一个 .a 库文件,ar rv libtest.a hello.o fibo.o 便于其他的程序链接 libtest.a。其他程序通过 -ltest 就可以链接到这个库
- nm 命令的作用是查看 ar 命令打包好的 .a 库文件里面由哪些符号构成