[编译]Makefile兼APUE解析(UNP12)

来源:互联网 发布:autodesk123d for mac 编辑:程序博客网 时间:2024/06/14 11:53

本文内容:

- 最简单的Makefile知识点归纳,通过对apue-Makefile的内容,解剖出Makefile机制,并在此过程中,实践自己的algs4-hhc算法库工程(当然,是以学习Makefile为开始)
- 这编写过程中,为了方便本人复习,会适当提供参考资料,部分内容超出Makefile知识范围,自行跳过。

阅读人群:

- 读者没有任何编译原理知识,可以先去补充(推荐阅读《嵌入式linux开发教程 上册》10.1.3.2:gcc编译过程;这极其重要。
- 读者刚从windows转过来,一直使用IDE,也可以去补充。

apue.3e--Makefile解剖

  1. 顶层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
 解析如下:
  1. 使用shell脚本的循环语句for,逐次进入各子目录里面执行make,并打印出making xxx。(其实该文件不算是一个Makefile,它更像是一个脚本,该脚本会在make的命令下执行,以便于提供子层make的环境)
  2. 而循环的次序也是有讲究的,所有的子层都会调用到libapue.a,所以第一个make的对象就是lib,接着可以随意。
  3. cd xxx || exit 1;意味着进入脚本目录,否则退出。在make过程中,会因为缺失某个目录而直接make error停止编译。链接,需要注意的是,这里使用到了逻辑或运算符,执行有先后顺序,前者为真则不需要之后后者。
  4. cd &&i,因为&是在Makefile中有特殊应用(自动变量),想执行shell命令,需要引用i,所以连续使用&符号。
参考链接:
shell--for循环写法,链接

  1. 通用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
解析如下:
  1. 定位顶层(编译根目录root),执行shell脚本,获取平台信息,然后include包含顶层Makefile(定义编译参数)
  2. 定义打包变量LIBMISC、打包变量的依赖文件OBJS(所需依赖的.o文件),多行连续语句只用\链接。
  3. 通过依赖关系自动生成的中间文件(.o),默认执行命令:CC CFLAGS -c -o xxx.o xxx.c,所以下面的make过程(第一加粗部分)就不难理解为何是统一的格式编译libapue.a的依赖对象。
  4. all:    $(LIBMISC) sleep.o过程是先执行对象LIBMISC,然再执行sleep.o,Makefile都有按照编写顺序去执行。
  5. 打包ar过程,自动变量(LIBMISC所依赖的OBJS)的时间戳要比LIBMISC要新的时候进行打包(或更新打包)
  6. clean规则,-f强制执行rm,*.o(这里作为中间文件),core是内存映像文件(程序崩溃产生),core链接,temp*是顶层传入
  7. 最后(所有子层都有)包含的Make.libapue.inc,确保规则的目标能够正确找到libapue.a(子层详细解析),因为使用include命令,所以该行(哪怕是在文件最后一行)都会首先执行的(第二次include执行)。
  8. 下面提供编译过程,详细对照思考
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'

  1. Make.libapue.inc
$(LIBAPUE):
    (cd $(ROOT)/lib && $(MAKE))
解析如下:
  1. 类似顶层目录,进入lib目录,执行make(子层详细解析该文件作用)
  2. 这其实也是一个规则,每个子层里面被包含都会被展开,那么也将会成为子层Makefile里面的一个规则,所以能够实现解决libapue.a缺失的情况。

  1. 顶层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是内置变量
  1. COMPILE.c指明了自动依赖生成的执行命令,也就是CC CFLAGS -c -o xxx.o xxx.c的前面,后面是依赖关系导入的。(在以后,对CFLAGS的修改,不会改变自动依赖生成的执行命令)
  2. 后来测试发现,即时去掉这两个内置变量,自动推导关系依然不变,说明默认与上述设置是一致的,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/

链接,头文件与库文件、搜索顺序、环境变量
解析如下:
  1. 完整定义CFLAGS,是因为子层都利用了依赖关系自动生成.o文件,命令格式如CC CFLAGS -c -o xxx.o xxx.c,所以需要赋予足够的自动编译信息来提供编译。CC和CFLAGS都是make命令默认的变量,由用户修改设定。

  1. 一般子层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
解析如下:
  1. make的条件语句,判断是否相等if-eq,对象是两个字符串
  2. %:    %.c $(LIBAPUE)的意思是:可能是搜索到所有目前的.c和libapue文件
  3. ruptime:    ruptime.o clconn2.o $(LIBAPUE)每一个子规则都添加上LIBAPUE,以此实现勘测到LIBAPUE是否存在的功能。
  4. 证明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。
  1. 单独编译某个规则的目标,其包含的依赖,基本上是自身的.o和子层的公共模块(clconn_x和initsrv_x)以及liapue.a。
  2. 编译all目标,则会导入所有对象,并在文件当中找到对应的子目标(all-ruptime这个路径),然后执行。
  3. 通常,依赖库.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知识点,无关人事请散场

  1. Makefile赋值运算符(:= ?= += =)链接 。延迟、即时、条件,追加,四种类型。
  2. 在Hi3516A/mpp/sample/venc/Makefile里面,有
$(TARGET):%:%.o $(COMM_OBJ)
规则的目标为TARGET,所依赖为规则的依赖.o文件%:%.o(先%:搜索到TARGET的依赖文件,然后选择%.o文件),加上通用依赖COMM_OBJ
比方说,TARGET为abc,那么%:%.o的结果就为abc.o
  1. 编译过程:预处理--编译--汇编--链接
     .c——.i——.s——.o——.out(文件后缀变换过程)
     在.c——.o,直接编译即可,不需要加入lib库,假设ab.c里面使用到线程,.c——.o过程,不需要加入-lpthread,因为这仅仅是汇编过程,不需要将库链接进去。
  1. 每个子目录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的值,发现为空。
结论:成立。

示例工程(Linux-32位编译通过)

下载链接


总结Makefile工程编写

  1. 依赖关系,您的工程结构要合理,有开发经验就很好理解了。
    1. 每个子目录有当前的SRCS、OBJS、PROGS、MOREPROGS
    2. 几乎是必备的all、clean
  2. 编译选项,关键是CFLAGS、LDFLAGS、LIBS、REL_DIR等关键指定
  3. 编译平台,关键是CC、AR、LD等关键参数
  4. 编译参数,这个太多了,也是必须的,有些关键点是-g -O等等


解析的参考文档

Makefile简要解析:
http://blog.csdn.net/huyansoft/article/details/8924624
http://blog.chinaunix.net/uid-21948941-id-3061864.html
关于apue的解剖示例:
http://onetracy.com/2015/10/25/Makefile/
http://www.cnblogs.com/emrick/p/4960720.html

随后,UNIX圣经还两本:《UNIX网络编程》卷1:套接字编程 卷2:进程间通信
下面是编译指导:
《网络编程》关于 UNIX网络编程 卷1 的 unp.h 和源码编译问题
Unix网络编程 卷2:进程间通信(linux环境下源代码使用)
http://blog.csdn.net/wm_1991/article/details/49184335
0 0
原创粉丝点击