大型工程Makefile实战分析

Make和Makefile作为工程管理命令行的典型代表,如今是很少人用了,绝大多数人都是用ide来进行工程管理啦,现代的ide方便到你根本不用去接触编译器的命令行,只要在窗口里面一个一个点击勾选框即可,而接触Make和Makefile的大部分是接触Linux系统后才认识它的,因为好多开源软件都是用它来管理工程呢。

很多老鸟会告诉你安装一个源码包形式发布的Linux软件一般的做法是:configure-make-make install。其中configure程序是生成Makefile的一个步骤,只有这个顺利执行了才有可能接下来的步骤。也有一些人推荐你使用autoconf和automake之类的工具自动生成Makefile,感觉看起来都挺复杂的,本文要讲的是手写的Makefile,结合之前遇到过的项目来分析。

其实我对Makefile也不是很熟悉,一知半解,曾经专门看过一本书籍,但是它的隐含规则实在难懂,加上实际需要的并不多,大部分时候是读和修改而不是从头开始编写,借着这次总结我想更深入的了解它。

工程案例分析—Appro IPNC RDK

主要的也是有两个文件MakefileRules.make文件,其中Rule.make的作用是定义了大量的变量用于控制各种编译条件。

1
all: clean ipncapp

其中的ipncapp的子目标又有:

1
ipncapp: app hdvpss iss mcfw fsupdate
1
app: appdepend applibs appinstall
1
2
3
4
applibs:
ifneq ($(MAKE_TARGET), depend)
$(MAKE) -C$(IPNC_DIR) ARCH=arm CROSS_COMPILE=$(BUILD_TOOL_PREFIX) $(MAKE_TARGET)
endif

其中IPNC_DIR指向了:

1
IPNC_DIR          := $(IPNC_INSTALL_DIR)/ipnc_app

而在ipnc_app文件的顶层makefile还没到编译的阶段,这里还只是调用了make到各个子目录下。

1
2
3
4
5
6
7

all: $(SUBDIRS)

$(SUBDIRS):
@echo
@echo Making all in subdirectory $@...
@$(MAKE) -C $@

而子目录有:

1
SUBDIRS = interface sys_server multimedia network root_filesys webdata utils

而这几个目录下的Makefile也只是调用make到更深一层的子目录下进行编译。

可以看到在大型工程中,有多个程序或者库需要编译的,通常就是用这种方式:顶层上定义好各种各样的变量,编译器路径,各种编译条件(比如说不同开发板,不同内存,自定义),然后根据条件到各个子目录下执行make,而各个子目录有多个程序又类似这样做。

工程案例分析——APUE 3rd Editon source code

1
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

上面是apue的顶层makefile,作用很简单只是跳转到各个子目录执行各个子目录的makefile而已。注意里面中连续两个美元符号,只是因为这里是执行shell命令中的for,需要引用i,而美元符号在makefile中有特殊作用,这里连续两个表示使用$这个符号。

apue所有工程都需要用到libapue.a这个库,所以lib文件夹是放到最前的,我们来看下lib文件夹下的makefile是如何写的。

1
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

这里先调用了systype.sh脚本得到操作系统类型的变量赋值给PLATFORM,然后包含对应操作系统的特定Make.defines,这个文件定义若干必要的变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 编译器
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 -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

其中COMPILE.cLINK.c是两个比较少见的内置变量,你可以执行make -p -f查看这些变量的默认值。

在回到上上个Makefile,$?自动化的变量,表示所有比目标新依赖目标的集合。以空格分隔。

ar命令是用于生成静态库,其对象是.o文件。RANLIB这里只有打印的作用。其它依赖的.o文件用makefile的隐含规则生成。

隐含规则是Makefile中比较难懂的一个点,这里的makefile并没有写什么
只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来。并且自动是用来了Make.deinfe.linux中CFLAGS指定的标志来编译。

接下来我们看下另外一个文件夹signal的makefile是如何写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
ROOT=..
PLATFORM=$(shell $(ROOT)/systype.sh)
include $(ROOT)/Make.defines.$(PLATFORM)

CLD =

ifeq "$(PLATFORM)" "linux"
CLD = child
endif
ifeq "$(PLATFORM)" "solaris"
CLD = child
endif

PROGS = critical mask read1 read2 reenter sigtstp sigusr suspend1 suspend2
MOREPROGS = systest2 tsleep2

all: $(PROGS) $(MOREPROGS) abort.o sleep1.o sleep2.o system.o $(CLD)

%: %.c $(LIBAPUE)
$(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) $(LDLIBS)

tsleep2: tsleep2.o sleep2.o $(LIBAPUE)
$(CC) $(CFLAGS) -o tsleep2 tsleep2.o sleep2.o $(LDFLAGS) $(LDLIBS)

systest2: systest2.o system.o $(LIBAPUE)
$(CC) $(CFLAGS) -o systest2 systest2.o system.o $(LDFLAGS) $(LDLIBS)

clean:
rm -f $(PROGS) $(MOREPROGS) $(TEMPFILES) *.o file.hole $(CLD)

include $(ROOT)/Make.libapue.inc

上面分为通用程序的处理,还有另外两个程序单独进行处理,先看通用的

1
2
%:	%.c $(LIBAPUE)
$(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) $(LDLIBS)

其中$@表示所有目标的集合,用空格隔开。而%的做用类似shell中的通配符*。


关注我的微信订阅号,获取最新文章。

微信公众号