本文内容:
- 最简单的Makefile知识点归纳,通过对apue-Makefile的内容,解剖出Makefile机制,并在此过程中,实践自己的algs4-hhc算法库工程(当然,是以学习Makefile为开始)
- 这编写过程中,为了方便本人复习,会适当提供参考资料,部分内容超出Makefile知识范围,自行跳过。
阅读人群:
- 读者没有任何编译原理知识,可以先去补充(推荐阅读《嵌入式linux开发教程 上册》10.1.3.2:gcc编译过程;这极其重要。
- 读者刚从windows转过来,一直使用IDE,也可以去补充。
apue.3e--Makefile解剖
- 顶层Makefile
DIRS = lib intro sockets advio daemons datafiles db environ \
fileio filedir ipc1 ipc2 proc pty relation signals standards \
stdio termios threadctl threads printer exercises
all:
for i in $(DIRS); do \
(cd $$i && echo "making $$i" && $(MAKE) ) || exit 1; \
done
clean:
for i in $(DIRS); do \
(cd $$i && echo "cleaning $$i" && $(MAKE) clean) || exit 1; \
done
解析如下:
- 使用shell脚本的循环语句for,逐次进入各子目录里面执行make,并打印出making xxx。(其实该文件不算是一个Makefile,它更像是一个脚本,该脚本会在make的命令下执行,以便于提供子层make的环境)
- 而循环的次序也是有讲究的,所有的子层都会调用到libapue.a,所以第一个make的对象就是lib,接着可以随意。
- cd xxx || exit 1;意味着进入脚本目录,否则退出。在make过程中,会因为缺失某个目录而直接make error停止编译。链接,需要注意的是,这里使用到了逻辑或运算符,执行有先后顺序,前者为真则不需要之后后者。
- cd &&i,因为&是在Makefile中有特殊应用(自动变量),想执行shell命令,需要引用i,所以连续使用&符号。
参考链接:
shell--for循环写法,
链接
- 通用lib--Makefile
#
# Makefile for misc library.
#
ROOT=..
PLATFORM=$(shell $(ROOT)/systype.sh)
include $(ROOT)/Make.defines.$(PLATFORM)
LIBMISC = libapue.a
OBJS = bufargs.o cliconn.o clrfl.o \
daemonize.o error.o errorlog.o lockreg.o locktest.o \
openmax.o pathalloc.o popen.o prexit.o prmask.o \
ptyfork.o ptyopen.o readn.o recvfd.o senderr.o sendfd.o \
servaccept.o servlisten.o setfd.o setfl.o signal.o signalintr.o \
sleepus.o spipe.o tellwait.o ttymodes.o writen.o
all: $(LIBMISC) sleep.o
$(LIBMISC): $(OBJS)
$(AR) rv $(LIBMISC) $?
$(RANLIB) $(LIBMISC)
clean:
rm -f *.o a.out core temp.* $(LIBMISC)
include $(ROOT)/Make.libapue.inc
解析如下:
- 定位顶层(编译根目录root),执行shell脚本,获取平台信息,然后include包含顶层Makefile(定义编译参数)
- 定义打包变量LIBMISC、打包变量的依赖文件OBJS(所需依赖的.o文件),多行连续语句只用\链接。
- 通过依赖关系自动生成的中间文件(.o),默认执行命令:CC CFLAGS -c -o xxx.o xxx.c,所以下面的make过程(第一加粗部分)就不难理解为何是统一的格式编译libapue.a的依赖对象。
- all: $(LIBMISC) sleep.o过程是先执行对象LIBMISC,然再执行sleep.o,Makefile都有按照编写顺序去执行。
- 打包ar过程,自动变量(LIBMISC所依赖的OBJS)的时间戳要比LIBMISC要新的时候进行打包(或更新打包)
- clean规则,-f强制执行rm,*.o(这里作为中间文件),core是内存映像文件(程序崩溃产生),core链接,temp*是顶层传入
- 最后(所有子层都有)包含的Make.libapue.inc,确保规则的目标能够正确找到libapue.a(子层详细解析),因为使用include命令,所以该行(哪怕是在文件最后一行)都会首先执行的(第二次include执行)。
- 下面提供编译过程,详细对照思考
h265@H265:algs4-hhc$ make -C apue.3e/lib/ (单独编译lib,方便观察)
make: Entering directory '/home/h265/sharefile/repos/trunk/algs4-hhc/apue.3e/lib' (make自动产生,无需编写)
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o bufargs.o bufargs.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o cliconn.o cliconn.c
····省略相同格式但不同对象的编译过程,都生成对应的.o文件,提供ar打包使用····
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ttymodes.o ttymodes.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o writen.o writen.c
ar rv libapue.a bufargs.o cliconn.o clrfl.o daemonize.o error.o errorlog.o lockreg.o locktest.o openmax.o pathalloc.o popen.o prexit.o prmask.o ptyfork.o ptyopen.o readn.o recvfd.o senderr.o sendfd.o servaccept.o servlisten.o setfd.o setfl.o signal.o signalintr.o sleepus.o spipe.o tellwait.o ttymodes.o writen.o
ar: 正在创建 libapue.a
a - bufargs.o
a - cliconn.o
····省略相同格式但不同对象,都是打包过程····
a - ttymodes.o
a - writen.o
echo libapue.a
libapue.a
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o sleep.o sleep.c
make: Leaving directory '/home/h265/sharefile/repos/trunk/algs4-hhc/apue.3e/lib'
- Make.libapue.inc
$(LIBAPUE):
(cd $(ROOT)/lib && $(MAKE))
解析如下:
- 类似顶层目录,进入lib目录,执行make(子层详细解析该文件作用)
- 这其实也是一个规则,每个子层里面被包含都会被展开,那么也将会成为子层Makefile里面的一个规则,所以能够实现解决libapue.a缺失的情况。
- 顶层Makefile参数定义文件
# Common make definitions, customized for each platform
# Definitions required in all program directories to compile and link
# C programs using gcc.
CC=gcc
COMPILE.c=$(CC) $(CFLAGS) $(CPPFLAGS) -c
LINK.c=$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS)
LDFLAGS=
LDDIR=-L$(ROOT)/lib
LDLIBS=$(LDDIR) -lapue $(EXTRALIBS)
CFLAGS=-ansi -g -I$(ROOT)/include -Wall -DLINUX -D_GNU_SOURCE $(EXTRA)
RANLIB=echo
AR=ar
AWK=awk
LIBAPUE=$(ROOT)/lib/libapue.a
# Common temp files to delete from each directory.
TEMPFILES=core core.* *.o temp.* *.out
参数解析:
#编译器
CC:编译器
COMPILE.c和LINK.c是内置变量
- COMPILE.c指明了自动依赖生成的执行命令,也就是CC CFLAGS -c -o xxx.o xxx.c的前面,后面是依赖关系导入的。(在以后,对CFLAGS的修改,不会改变自动依赖生成的执行命令)
- 后来测试发现,即时去掉这两个内置变量,自动推导关系依然不变,说明默认与上述设置是一致的,apue只是为了确保正确而编写。
LDFLAGS链接选项:缺省
#链接
LDDIR链接目录:lib目录
LDLIBS链接参数:指定链接库-lapue及其所在目录
#编译标志
CFLAGS编译参数:指定-ansi(C89)标准、加入调试信息-g、包含头文件-I、打开所有警告-Wall、
-DLINUX:相当于在头文件上添加#define LINUX;根据需要,在头文件条件内容,链接
#其它实用工具
RANLIB仅打印作用
AR打包
AWK(暂时未明白)
LIBAPUE和TEMPFILES全局的编译自定义变量
搜索路径解析:如果读者需要查看的话,可以在makefile里面的CFLAGS添加-v
gcc搜索头文件过程,配合编译选项-I
#include "..." search starts here:
#include <...> search starts here:
../include
/usr/lib/gcc/i686-linux-gnu/6/include
/usr/local/include
/usr/lib/gcc/i686-linux-gnu/6/include-fixed
/usr/include/i386-linux-gnu
/usr/include
End of search list.
gcc搜索库过程,配合编译选项-L
LIBRARY_PATH=
/usr/lib/gcc/i686-linux-gnu/6/:
/usr/lib/gcc/i686-linux-gnu/6/../../../i386-linux-gnu/:
/usr/lib/gcc/i686-linux-gnu/6/../../../../lib/:
/lib/i386-linux-gnu/:/lib/../lib/:
/usr/lib/i386-linux-gnu/:
/usr/lib/../lib/:
/usr/lib/gcc/i686-linux-gnu/6/../../../:
/lib/:
/usr/lib/
链接,头文件与库文件、搜索顺序、环境变量
解析如下:
- 完整定义CFLAGS,是因为子层都利用了依赖关系自动生成.o文件,命令格式如CC CFLAGS -c -o xxx.o xxx.c,所以需要赋予足够的自动编译信息来提供编译。CC和CFLAGS都是make命令默认的变量,由用户修改设定。
- 一般子层Makefile(以sockets为例)apue.3e/sockets/makefile
ROOT=..
PLATFORM=$(shell $(ROOT)/systype.sh)
include $(ROOT)/Make.defines.$(PLATFORM)
ifeq "$(PLATFORM)" "solaris"
EXTRALIBS = -lsocket -lnsl
endif
PROGS = ruptime ruptimed ruptimed-fd ruptimed-dg
MOREPROGS = findsvc ruptime-dg
all: $(PROGS) $(MOREPROGS) clconn.o clconn2.o initsrv1.o initsrv2.o
%: %.c $(LIBAPUE)
ruptime: ruptime.o clconn2.o $(LIBAPUE)
$(CC) $(CFLAGS) -o ruptime ruptime.o clconn2.o $(LDFLAGS) $(LDLIBS)
ruptimed: ruptimed.o initsrv2.o $(LIBAPUE)
$(CC) $(CFLAGS) -o ruptimed ruptimed.o initsrv2.o $(LDFLAGS) $(LDLIBS)
ruptimed-fd: ruptimed-fd.o initsrv2.o $(LIBAPUE)
$(CC) $(CFLAGS) -o ruptimed-fd ruptimed-fd.o initsrv2.o $(LDFLAGS) $(LDLIBS)
ruptimed-dg: ruptimed-dg.o initsrv2.o $(LIBAPUE)
$(CC) $(CFLAGS) -o ruptimed-dg ruptimed-dg.o initsrv2.o $(LDFLAGS) $(LDLIBS)
clean:
rm -f $(PROGS) $(MOREPROGS) $(TEMPFILES) *.o
include $(ROOT)/Make.libapue.inc
解析如下:
- make的条件语句,判断是否相等if-eq,对象是两个字符串
- %: %.c $(LIBAPUE)的意思是:可能是搜索到所有目前的.c和libapue文件
- ruptime: ruptime.o clconn2.o $(LIBAPUE)每一个子规则都添加上LIBAPUE,以此实现勘测到LIBAPUE是否存在的功能。
- 证明include $(ROOT)/Make.libapue.inc作用(不编译libapue.a情况下),并且将include $(ROOT)/Make.libapue.inc注释掉
h265@H265:algs4-hhc$ make -C apue.3e/sockets/
make: Entering directory '/home/h265/sharefile/repos/trunk/algs4-hhc/apue.3e/sock ets
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptime.o ruptime.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o clconn2.o clconn2.c
make: *** No rule to make target '../lib/libapue.a', needed by 'ruptime'。 停止。
make: Leaving directory '/home/h265/sharefile/repos/trunk/algs4-hhc/apue.3e/socke ts
验证过程:
- ruptime目标编译自动寻找依赖文件libapue.a(由顶层自定义变量传递LIBAPUE=$(ROOT)/lib/libapue.a),发现并没有(因为我们还没有编译过lib子层),所以失败了。
- 先对此子层执行clean,然后编译lib,在编译此子层,成功编译。
结论:include $(ROOT)/Make.libapue.inc能够在某目录缺少libapue.a依赖的情况下,重新编译lib子层,生成libapue.a。
- 单独编译某个规则的目标,其包含的依赖,基本上是自身的.o和子层的公共模块(clconn_x和initsrv_x)以及liapue.a。
- 编译all目标,则会导入所有对象,并在文件当中找到对应的子目标(all-ruptime这个路径),然后执行。
- 通常,依赖库.a等还未编译,都可以从新包含库.a的Makefile,实现编译,并将.a放到规则的依赖上,实现自动寻找。
自动依赖生成(注意当前目录是子层sockets)
单一编译对象
h265@H265:sockets$ make ruptime (先编译好lib,不编译也可以,同样可以找到这些信息)
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptime.o ruptime.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o clconn2.o clconn2.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -o ruptime ruptime.o clconn2.o -L../lib -lapue
all编译对象
h265@H265:sockets$ make all
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptime.o ruptime.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o clconn2.o clconn2.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -o ruptime ruptime.o clconn2.o -L../lib -lapue
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptimed.o ruptimed.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o initsrv2.o initsrv2.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -o ruptimed ruptimed.o initsrv2.o -L../lib -lapue
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptimed-fd.o ruptimed-fd.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -o ruptimed-fd ruptimed-fd.o initsrv2.o -L../lib -lapue
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptimed-dg.o ruptimed-dg.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -o ruptimed-dg ruptimed-dg.o initsrv2.o -L../lib -lapue
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE findsvc.c -L../lib -lapue -o findsvc
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE ruptime-dg.c -L../lib -lapue -o ruptime-dg
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o clconn.o clconn.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o initsrv1.o initsrv1.c
好了,到目前为止,已经基本解析了apue-Makefile的所有层了,如果你觉得还需要继续加深,可以往下看。
下面,是抽出部分值得思考的子层Makefile,都可以涉及不同的make知识点。
子层apue.3e/datafiles/Makefile
ROOT=..
PLATFORM=$(shell $(ROOT)/systype.sh)
include $(ROOT)/Make.defines.$(PLATFORM)
PROGS = strftime
all: $(PROGS) getpwnam.o
%: %.c $(LIBAPUE)
$(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) $(LDLIBS)
clean:
rm -f $(PROGS) $(TEMPFILES) *.o
include $(ROOT)/Make.libapue.inc
解析如下:
%: %.c $(LIBAPUE)
$(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) $(LDLIBS)
通用程序的处理:%作为通配符*,意在将所有%.c都按照下面的命令执行,并生成.o文件,同时也提供了libapue链接。
从这里开始,是从原理上去讲述Make的知识,仅供本人复习参考,无关人事可以离场。
确实,不管Makefile如何复制,都不会脱离它的基本(也是唯一)的规则(条件):
target: prerequisites
command
·····················
编译目标:依赖关系文件
执行命令
make的自动变量
自动变量
|
含义
|
举例
|
$@
|
规则的目标
|
|
$<
|
|
|
$^
|
规则的所有依赖文件
|
|
$?
|
规则的所有依赖有更新的文件
|
|
$(@D)
|
|
|
$(@F)
|
|
|
下面是零散的Makefile知识点,无关人事请散场
- Makefile赋值运算符(:= ?= += =)链接 。即时、条件,追加,延迟、四种类型。
- 在Hi3516A/mpp/sample/venc/Makefile里面,有
$(TARGET):%:%.o $(COMM_OBJ)
规则的目标为TARGET,所依赖为规则的依赖.o文件%:%.o(先%:搜索到TARGET的依赖文件,然后选择%.o文件),加上通用依赖COMM_OBJ
比方说,TARGET为abc,那么%:%.o的结果就为abc.o
- 编译过程:预处理--编译--汇编--链接
.c——.i——.s——.o——.out(文件后缀变换过程)
在.c——.o,直接编译即可,不需要加入lib库,假设ab.c里面使用到线程,.c——.o过程,不需要加入-lpthread,因为这仅仅是汇编过程,不需要将库链接进去。
- 每个子目录makefile改变某变量,不会影响到其他子目录makefile中相同的变量。
├── lib
│ └── Makefile
├── Makefile
├── Makefile.param
├── Make.libcomm.inc
└── test
└── Makefile
Makefile.param--
LDFLAGS为空
测试过程:
lib/Makefile修改LDFLAGS+=-lrt,先编译lib,后编译test,在test中没有修改
LDFLAGS,随后在test中任一目标的规则下打印
LDFLAGS的值,发现为空。
结论:成立。
- GNU 的 make 工作时的执行步骤入下:(想来其它的 make 也是类似)
1、读入所有的 Makefile。
2、读入被 include 的其它 Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。
1-5 步为第一个阶段,6-7 为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,
make把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如 果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。
- 在makefile过程中,声明的变量(或者使用export声明),在当前makefile结束后,将被清理。不会对后来的makefile造成影响。
示例工程(Linux-32位编译通过)
下载链接
总结Makefile工程编写
- 依赖关系,您的工程结构要合理,有开发经验就很好理解了。
- 每个子目录有当前的SRCS、OBJS、PROGS、MOREPROGS
- 几乎是必备的all、clean
- 编译选项,关键是CFLAGS、LDFLAGS、LIBS、REL_DIR等关键指定
- 编译平台,关键是CC、AR、LD等关键参数
- 编译参数,这个太多了,也是必须的,有些关键点是-g -O等等
解析的参考文档
Makefile简要解析:
关于apue的解剖示例:
在自定义工程中直接是使用apue.h与libapue.a,
链接
随后,UNIX圣经还两本:《UNIX网络编程》卷1:套接字编程 卷2:进程间通信
下面是编译指导:
本文内容:
- 最简单的Makefile知识点归纳,通过对apue-Makefile的内容,解剖出Makefile机制,并在此过程中,实践自己的algs4-hhc算法库工程(当然,是以学习Makefile为开始)
- 这编写过程中,为了方便本人复习,会适当提供参考资料,部分内容超出Makefile知识范围,自行跳过。
阅读人群:
- 读者没有任何编译原理知识,可以先去补充(推荐阅读《嵌入式linux开发教程 上册》10.1.3.2:gcc编译过程;这极其重要。
- 读者刚从windows转过来,一直使用IDE,也可以去补充。
apue.3e--Makefile解剖
- 顶层Makefile
DIRS = lib intro sockets advio daemons datafiles db environ \
fileio filedir ipc1 ipc2 proc pty relation signals standards \
stdio termios threadctl threads printer exercises
all:
for i in $(DIRS); do \
(cd $$i && echo "making $$i" && $(MAKE) ) || exit 1; \
done
clean:
for i in $(DIRS); do \
(cd $$i && echo "cleaning $$i" && $(MAKE) clean) || exit 1; \
done
解析如下:
- 使用shell脚本的循环语句for,逐次进入各子目录里面执行make,并打印出making xxx。(其实该文件不算是一个Makefile,它更像是一个脚本,该脚本会在make的命令下执行,以便于提供子层make的环境)
- 而循环的次序也是有讲究的,所有的子层都会调用到libapue.a,所以第一个make的对象就是lib,接着可以随意。
- cd xxx || exit 1;意味着进入脚本目录,否则退出。在make过程中,会因为缺失某个目录而直接make error停止编译。链接,需要注意的是,这里使用到了逻辑或运算符,执行有先后顺序,前者为真则不需要之后后者。
- cd &&i,因为&是在Makefile中有特殊应用(自动变量),想执行shell命令,需要引用i,所以连续使用&符号。
参考链接:
shell--for循环写法,
链接
- 通用lib--Makefile
#
# Makefile for misc library.
#
ROOT=..
PLATFORM=$(shell $(ROOT)/systype.sh)
include $(ROOT)/Make.defines.$(PLATFORM)
LIBMISC = libapue.a
OBJS = bufargs.o cliconn.o clrfl.o \
daemonize.o error.o errorlog.o lockreg.o locktest.o \
openmax.o pathalloc.o popen.o prexit.o prmask.o \
ptyfork.o ptyopen.o readn.o recvfd.o senderr.o sendfd.o \
servaccept.o servlisten.o setfd.o setfl.o signal.o signalintr.o \
sleepus.o spipe.o tellwait.o ttymodes.o writen.o
all: $(LIBMISC) sleep.o
$(LIBMISC): $(OBJS)
$(AR) rv $(LIBMISC) $?
$(RANLIB) $(LIBMISC)
clean:
rm -f *.o a.out core temp.* $(LIBMISC)
include $(ROOT)/Make.libapue.inc
解析如下:
- 定位顶层(编译根目录root),执行shell脚本,获取平台信息,然后include包含顶层Makefile(定义编译参数)
- 定义打包变量LIBMISC、打包变量的依赖文件OBJS(所需依赖的.o文件),多行连续语句只用\链接。
- 通过依赖关系自动生成的中间文件(.o),默认执行命令:CC CFLAGS -c -o xxx.o xxx.c,所以下面的make过程(第一加粗部分)就不难理解为何是统一的格式编译libapue.a的依赖对象。
- all: $(LIBMISC) sleep.o过程是先执行对象LIBMISC,然再执行sleep.o,Makefile都有按照编写顺序去执行。
- 打包ar过程,自动变量(LIBMISC所依赖的OBJS)的时间戳要比LIBMISC要新的时候进行打包(或更新打包)
- clean规则,-f强制执行rm,*.o(这里作为中间文件),core是内存映像文件(程序崩溃产生),core链接,temp*是顶层传入
- 最后(所有子层都有)包含的Make.libapue.inc,确保规则的目标能够正确找到libapue.a(子层详细解析),因为使用include命令,所以该行(哪怕是在文件最后一行)都会首先执行的(第二次include执行)。
- 下面提供编译过程,详细对照思考
h265@H265:algs4-hhc$ make -C apue.3e/lib/ (单独编译lib,方便观察)
make: Entering directory '/home/h265/sharefile/repos/trunk/algs4-hhc/apue.3e/lib' (make自动产生,无需编写)
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o bufargs.o bufargs.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o cliconn.o cliconn.c
····省略相同格式但不同对象的编译过程,都生成对应的.o文件,提供ar打包使用····
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ttymodes.o ttymodes.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o writen.o writen.c
ar rv libapue.a bufargs.o cliconn.o clrfl.o daemonize.o error.o errorlog.o lockreg.o locktest.o openmax.o pathalloc.o popen.o prexit.o prmask.o ptyfork.o ptyopen.o readn.o recvfd.o senderr.o sendfd.o servaccept.o servlisten.o setfd.o setfl.o signal.o signalintr.o sleepus.o spipe.o tellwait.o ttymodes.o writen.o
ar: 正在创建 libapue.a
a - bufargs.o
a - cliconn.o
····省略相同格式但不同对象,都是打包过程····
a - ttymodes.o
a - writen.o
echo libapue.a
libapue.a
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o sleep.o sleep.c
make: Leaving directory '/home/h265/sharefile/repos/trunk/algs4-hhc/apue.3e/lib'
- Make.libapue.inc
$(LIBAPUE):
(cd $(ROOT)/lib && $(MAKE))
解析如下:
- 类似顶层目录,进入lib目录,执行make(子层详细解析该文件作用)
- 这其实也是一个规则,每个子层里面被包含都会被展开,那么也将会成为子层Makefile里面的一个规则,所以能够实现解决libapue.a缺失的情况。
- 顶层Makefile参数定义文件
# Common make definitions, customized for each platform
# Definitions required in all program directories to compile and link
# C programs using gcc.
CC=gcc
COMPILE.c=$(CC) $(CFLAGS) $(CPPFLAGS) -c
LINK.c=$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS)
LDFLAGS=
LDDIR=-L$(ROOT)/lib
LDLIBS=$(LDDIR) -lapue $(EXTRALIBS)
CFLAGS=-ansi -g -I$(ROOT)/include -Wall -DLINUX -D_GNU_SOURCE $(EXTRA)
RANLIB=echo
AR=ar
AWK=awk
LIBAPUE=$(ROOT)/lib/libapue.a
# Common temp files to delete from each directory.
TEMPFILES=core core.* *.o temp.* *.out
参数解析:
#编译器
CC:编译器
COMPILE.c和LINK.c是内置变量
- COMPILE.c指明了自动依赖生成的执行命令,也就是CC CFLAGS -c -o xxx.o xxx.c的前面,后面是依赖关系导入的。(在以后,对CFLAGS的修改,不会改变自动依赖生成的执行命令)
- 后来测试发现,即时去掉这两个内置变量,自动推导关系依然不变,说明默认与上述设置是一致的,apue只是为了确保正确而编写。
LDFLAGS链接选项:缺省
#链接
LDDIR链接目录:lib目录
LDLIBS链接参数:指定链接库-lapue及其所在目录
#编译标志
CFLAGS编译参数:指定-ansi(C89)标准、加入调试信息-g、包含头文件-I、打开所有警告-Wall、
-DLINUX:相当于在头文件上添加#define LINUX;根据需要,在头文件条件内容,链接
#其它实用工具
RANLIB仅打印作用
AR打包
AWK(暂时未明白)
LIBAPUE和TEMPFILES全局的编译自定义变量
搜索路径解析:如果读者需要查看的话,可以在makefile里面的CFLAGS添加-v
gcc搜索头文件过程,配合编译选项-I
#include "..." search starts here:
#include <...> search starts here:
../include
/usr/lib/gcc/i686-linux-gnu/6/include
/usr/local/include
/usr/lib/gcc/i686-linux-gnu/6/include-fixed
/usr/include/i386-linux-gnu
/usr/include
End of search list.
gcc搜索库过程,配合编译选项-L
LIBRARY_PATH=
/usr/lib/gcc/i686-linux-gnu/6/:
/usr/lib/gcc/i686-linux-gnu/6/../../../i386-linux-gnu/:
/usr/lib/gcc/i686-linux-gnu/6/../../../../lib/:
/lib/i386-linux-gnu/:/lib/../lib/:
/usr/lib/i386-linux-gnu/:
/usr/lib/../lib/:
/usr/lib/gcc/i686-linux-gnu/6/../../../:
/lib/:
/usr/lib/
链接,头文件与库文件、搜索顺序、环境变量
解析如下:
- 完整定义CFLAGS,是因为子层都利用了依赖关系自动生成.o文件,命令格式如CC CFLAGS -c -o xxx.o xxx.c,所以需要赋予足够的自动编译信息来提供编译。CC和CFLAGS都是make命令默认的变量,由用户修改设定。
- 一般子层Makefile(以sockets为例)apue.3e/sockets/makefile
ROOT=..
PLATFORM=$(shell $(ROOT)/systype.sh)
include $(ROOT)/Make.defines.$(PLATFORM)
ifeq "$(PLATFORM)" "solaris"
EXTRALIBS = -lsocket -lnsl
endif
PROGS = ruptime ruptimed ruptimed-fd ruptimed-dg
MOREPROGS = findsvc ruptime-dg
all: $(PROGS) $(MOREPROGS) clconn.o clconn2.o initsrv1.o initsrv2.o
%: %.c $(LIBAPUE)
ruptime: ruptime.o clconn2.o $(LIBAPUE)
$(CC) $(CFLAGS) -o ruptime ruptime.o clconn2.o $(LDFLAGS) $(LDLIBS)
ruptimed: ruptimed.o initsrv2.o $(LIBAPUE)
$(CC) $(CFLAGS) -o ruptimed ruptimed.o initsrv2.o $(LDFLAGS) $(LDLIBS)
ruptimed-fd: ruptimed-fd.o initsrv2.o $(LIBAPUE)
$(CC) $(CFLAGS) -o ruptimed-fd ruptimed-fd.o initsrv2.o $(LDFLAGS) $(LDLIBS)
ruptimed-dg: ruptimed-dg.o initsrv2.o $(LIBAPUE)
$(CC) $(CFLAGS) -o ruptimed-dg ruptimed-dg.o initsrv2.o $(LDFLAGS) $(LDLIBS)
clean:
rm -f $(PROGS) $(MOREPROGS) $(TEMPFILES) *.o
include $(ROOT)/Make.libapue.inc
解析如下:
- make的条件语句,判断是否相等if-eq,对象是两个字符串
- %: %.c $(LIBAPUE)的意思是:可能是搜索到所有目前的.c和libapue文件
- ruptime: ruptime.o clconn2.o $(LIBAPUE)每一个子规则都添加上LIBAPUE,以此实现勘测到LIBAPUE是否存在的功能。
- 证明include $(ROOT)/Make.libapue.inc作用(不编译libapue.a情况下),并且将include $(ROOT)/Make.libapue.inc注释掉
h265@H265:algs4-hhc$ make -C apue.3e/sockets/
make: Entering directory '/home/h265/sharefile/repos/trunk/algs4-hhc/apue.3e/sock ets
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptime.o ruptime.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o clconn2.o clconn2.c
make: *** No rule to make target '../lib/libapue.a', needed by 'ruptime'。 停止。
make: Leaving directory '/home/h265/sharefile/repos/trunk/algs4-hhc/apue.3e/socke ts
验证过程:
- ruptime目标编译自动寻找依赖文件libapue.a(由顶层自定义变量传递LIBAPUE=$(ROOT)/lib/libapue.a),发现并没有(因为我们还没有编译过lib子层),所以失败了。
- 先对此子层执行clean,然后编译lib,在编译此子层,成功编译。
结论:include $(ROOT)/Make.libapue.inc能够在某目录缺少libapue.a依赖的情况下,重新编译lib子层,生成libapue.a。
- 单独编译某个规则的目标,其包含的依赖,基本上是自身的.o和子层的公共模块(clconn_x和initsrv_x)以及liapue.a。
- 编译all目标,则会导入所有对象,并在文件当中找到对应的子目标(all-ruptime这个路径),然后执行。
- 通常,依赖库.a等还未编译,都可以从新包含库.a的Makefile,实现编译,并将.a放到规则的依赖上,实现自动寻找。
自动依赖生成(注意当前目录是子层sockets)
单一编译对象
h265@H265:sockets$ make ruptime (先编译好lib,不编译也可以,同样可以找到这些信息)
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptime.o ruptime.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o clconn2.o clconn2.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -o ruptime ruptime.o clconn2.o -L../lib -lapue
all编译对象
h265@H265:sockets$ make all
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptime.o ruptime.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o clconn2.o clconn2.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -o ruptime ruptime.o clconn2.o -L../lib -lapue
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptimed.o ruptimed.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o initsrv2.o initsrv2.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -o ruptimed ruptimed.o initsrv2.o -L../lib -lapue
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptimed-fd.o ruptimed-fd.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -o ruptimed-fd ruptimed-fd.o initsrv2.o -L../lib -lapue
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o ruptimed-dg.o ruptimed-dg.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -o ruptimed-dg ruptimed-dg.o initsrv2.o -L../lib -lapue
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE findsvc.c -L../lib -lapue -o findsvc
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE ruptime-dg.c -L../lib -lapue -o ruptime-dg
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o clconn.o clconn.c
gcc -ansi -g -I../include -Wall -DLINUX -D_GNU_SOURCE -c -o initsrv1.o initsrv1.c
好了,到目前为止,已经基本解析了apue-Makefile的所有层了,如果你觉得还需要继续加深,可以往下看。
下面,是抽出部分值得思考的子层Makefile,都可以涉及不同的make知识点。
子层apue.3e/datafiles/Makefile
ROOT=..
PLATFORM=$(shell $(ROOT)/systype.sh)
include $(ROOT)/Make.defines.$(PLATFORM)
PROGS = strftime
all: $(PROGS) getpwnam.o
%: %.c $(LIBAPUE)
$(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) $(LDLIBS)
clean:
rm -f $(PROGS) $(TEMPFILES) *.o
include $(ROOT)/Make.libapue.inc
解析如下:
%: %.c $(LIBAPUE)
$(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) $(LDLIBS)
通用程序的处理:%作为通配符*,意在将所有%.c都按照下面的命令执行,并生成.o文件,同时也提供了libapue链接。
从这里开始,是从原理上去讲述Make的知识,仅供本人复习参考,无关人事可以离场。
确实,不管Makefile如何复制,都不会脱离它的基本(也是唯一)的规则(条件):
target: prerequisites
command
·····················
编译目标:依赖关系文件
执行命令
make的自动变量
自动变量
|
含义
|
举例
|
$@
|
规则的目标
|
|
$<
|
|
|
$^
|
规则的所有依赖文件
|
|
$?
|
规则的所有依赖有更新的文件
|
|
$(@D)
|
|
|
$(@F)
|
|
|
下面是零散的Makefile知识点,无关人事请散场
- Makefile赋值运算符(:= ?= += =)链接 。即时、条件,追加,延迟、四种类型。
- 在Hi3516A/mpp/sample/venc/Makefile里面,有
$(TARGET):%:%.o $(COMM_OBJ)
规则的目标为TARGET,所依赖为规则的依赖.o文件%:%.o(先%:搜索到TARGET的依赖文件,然后选择%.o文件),加上通用依赖COMM_OBJ
比方说,TARGET为abc,那么%:%.o的结果就为abc.o
- 编译过程:预处理--编译--汇编--链接
.c——.i——.s——.o——.out(文件后缀变换过程)
在.c——.o,直接编译即可,不需要加入lib库,假设ab.c里面使用到线程,.c——.o过程,不需要加入-lpthread,因为这仅仅是汇编过程,不需要将库链接进去。
- 每个子目录makefile改变某变量,不会影响到其他子目录makefile中相同的变量。
├── lib
│ └── Makefile
├── Makefile
├── Makefile.param
├── Make.libcomm.inc
└── test
└── Makefile
Makefile.param--
LDFLAGS为空
测试过程:
lib/Makefile修改LDFLAGS+=-lrt,先编译lib,后编译test,在test中没有修改
LDFLAGS,随后在test中任一目标的规则下打印
LDFLAGS的值,发现为空。
结论:成立。
- GNU 的 make 工作时的执行步骤入下:(想来其它的 make 也是类似)
1、读入所有的 Makefile。
2、读入被 include 的其它 Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。
1-5 步为第一个阶段,6-7 为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,
make把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如 果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。
- 在makefile过程中,声明的变量(或者使用export声明),在当前makefile结束后,将被清理。不会对后来的makefile造成影响。
示例工程(Linux-32位编译通过)
下载链接
总结Makefile工程编写
- 依赖关系,您的工程结构要合理,有开发经验就很好理解了。
- 每个子目录有当前的SRCS、OBJS、PROGS、MOREPROGS
- 几乎是必备的all、clean
- 编译选项,关键是CFLAGS、LDFLAGS、LIBS、REL_DIR等关键指定
- 编译平台,关键是CC、AR、LD等关键参数
- 编译参数,这个太多了,也是必须的,有些关键点是-g -O等等
解析的参考文档
Makefile简要解析:
关于apue的解剖示例:
在自定义工程中直接是使用apue.h与libapue.a,
链接
随后,UNIX圣经还两本:《UNIX网络编程》卷1:套接字编程 卷2:进程间通信
下面是编译指导: