第三部分 Makefile 的工程组织

来源:互联网 发布:泰迪体质 知乎 编辑:程序博客网 时间:2024/06/06 00:37
写在前面的话:
如果库1依赖于库2,用户只用lib1的话,我还没看到,怎么避免用 -l库2。就是说 -l库1的同时,也要-l库2才能编译通过。 我现在能想到的只有用 pkg-config 加上 .pc 文件来免得用户知道要依赖什么。

上面介绍了 Automake(autotools) 的标准工程组织,下面对比以下,为了完成相近的工作,自己写Makefile怎么来组织工程,以及了解,为什么需要autotools。
一、为什么需要autotools
还是最先提到的两个原因:
1. 如何移植?
不同平台的编译器,参数不同,而Makefile必须指明这些,这对维护多个平台的人来说,是不小的学习和记忆负担。而autotools的configure为你做了这一切。且automake嵌了libtool,使得在各个平台上编译库异常的统一。
2. 如何制做安装包?
(1) 显然,autotools利于制做以源码发行的安装包,因为写Makefile.am的原则就是给变量赋值,大大简化了Makefile的编写。
(2) 对于开发者而言,以比Makefile更友好的方式,让你以想要的方式来编译,制做,安装你想处理的东西(见上面写Makefile.am的原则,还有其他的丰富的特性,见GNU官方的automake.html)。
(3) 对于拿到安装包的用户而言,它可以用configure来决定很多参数,调整编译和安装方式。──── 即:让用户改Makefile是痛苦的,相对与用./configure指定参数。这样使得你的安装包更友好。

知道了autotools的优势后,就知道对于怎么样的“简单”包可以自己手写Makefile,怎样的最好用autotools。

二、Makefile的工程组织参考:
1. Makefile 原则:──── 总原则是:Makefile是写规则,相比于Makefile.am是写赋值!
(1) 非源文件层的Makefile写 make -C subdir 和 export 变量:
make -C subdir :这个是说进入 subdir 来执行里面的make(当然里面要有Makefile)。如果子目录间有依赖关系,则这里应调整顺序来make -C
export 变量: 如果有上层Makefile的变量,希望传给make -C subdir中,subdir的Makefile,则需要用export给出。可能用到的是将顶层目录的绝对路径传给下层,因为下层可能希望这样来找到什么。
(2) 源文件层写“显示规则”和“抽象规则”,并“自成整体”:
1) 显式规则:就是一个库或程序,依赖哪些objects,然后采取的动作是什么。objects必须全部列出。一般用Make内置函数或内嵌shell来帮助我们列出。
2) 抽象规则:就是.c.o 的规则,见下面例子文件。
3) 自成整体:就是说比如你要引用另一个同级subdir下的头文件,你可以把这些头文件引用地统一写到上层的Makefile,用export到处,然后下层的众多Makefile就直接用了。也可以在下层的Makefile中自己维护这个。我选择后者,因为这样使得子目录,可以单独Make,不需要借助上层Makefile。但是我在例子里给出的,却是在上层export的。

说明 ──── 为什么不用隐式规则来生成.o:
隐式规则,比如对C文件的是 $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@,
而隐式规则是不够的!因为它的规则里,如main.o,只依赖于main.c,而不依赖于main.c中包含的头文件,以及头文件中包含的头文件。。。这样,如果你的头文件修改了,不会导致重新make。为此,我们引入了 gcc 的 -MD -MF $*.d 参数,这个参数,使得编译一个 main.c 时,会生成一个 main.d,里面含有 main.o 所有的依赖,包含所有头文件和头文件中引用的头文件!然后再在Makefile中用 “include 那些.d 文件” 把依赖关系包含进来!这样完善了最终的Makefile。 而为了引入 -MD -MF $*.d 就需要自己写.c -> .o的规则,即.c.o: 规则,写法见下面。

2. 技巧集中:
(1) 所有的东西,请先用变量定义后再引用:
比如你要生成的库名叫libhello.a,那么在上面先用 LIB_NAME := libhello.a 来给出,然后下面用$(LIB_NAME)来引用之。
(2) 用内嵌shell,避免记忆Makefile的内置函数:
如,列出所有的objects依赖,因为所有的.o来自.c,可以:
OBJS := $(shell ls|grep '/.c'|sed 's//.c//.o/g')
如果用Make内置的函数,就要用到$(wildcard ...) 和 $(patsubst ...)
(3) 用-避免忽略错误:
上面提到 “include 那些.d”,但是,第一次Make的时候,make显然会先完善这个Makefile,它看到这个include,会试图去把.d们引入进来,但是这时,还没有调用gcc呢,当然没有.d,所以,make会退出。除非你用 - 加在include前面,就会忽略这个错误了。然后,当第二次make的时候,.d已经有了,由.d引入的规则就开始生效了。
(4) 用 @ 避免打印信息:
在make的动作中,比如你写 @mkdir -p $(BUILD_DIR) 即可以避免这个信息在动作执行时被打印出来。
(5) 尽量用 $@ $< 和 $^:
下面的例子中出现了很多,尤其在VPATH的Makefile中,要用这些更好。

3. 例子
在这个例子中,顶层有一个总Makefile,然后有一个src目录和build目录。
src中放源码,如果你在顶层 make all 或 make clean,则是针对src目录。
build目录是演示VPATH的make,如果你在顶层 make all-vpath 或 make clean-vpath,则是针对build目录。

总体的Makefile(和src,build同级目录):
# Makefile for the project
# It's intened to make everything you like, for example the src dir or other non-code things

export TOP_DIR := $(shell pwd) # 这个在下层的Makefile中有用到
SRC_DIR := src
BUILD_DIR := build

.PHONY: all clean
all:
    @make -C $(SRC_DIR)
clean:
    @make -C $(SRC_DIR) clean

all-vpath:
    @make -C $(BUILD_DIR)
clean-vpath:
    @make -C $(BUILD_DIR) clean


(1) src 目录:
-------- src/Makefile: -------------
#MAKEFLAGS := -s
SRC_DIR := $(shell pwd)                  

INCLUDES := -I$(SRC_DIR)/libhello           # use INCLUDES to contain -I (NOTICE: these names are made by me)
LDFLAGS := -L$(SRC_DIR)/libhello/build      # use LDFLAGS to contain -L
MYFLAGS := -Wall -g -O3                     # use MYFLAGS to contain other flags
export CFLAGS := $(INCLUDES) $(LDFLAGS) $(MYFLAGS) # use CFLAGS to contain all flags passed to gcc
export HEADER_DIR := $(SRC_DIR)/libhello           # just for subdir Makefile convenience
export LIBS_DIR := $(SRC_DIR)/libhello/build       # just for subdir Makefile convenience

.PHONY: all clean
all:                                            
    @echo '===== building libhello ====='    # build libhello first, then bin
    make -C $(SRC_DIR)/libhello
    @echo
    @echo '===== building binary ====='
    make -C $(SRC_DIR)/bin
clean:
    @echo '===== cleaning libhello ====='
    make -C $(SRC_DIR)/libhello clean
    @echo
    @echo '===== cleaning binary ====='
    make -C $(SRC_DIR)/bin clean

-------- src/libhello/Makefile: ---------
LIB_NAME := libhello.a
OBJS := $(shell ls|grep '/.c'|sed 's//.c//.o/g')   # list all possible .o files' names for using in "explicit rule"
DEPS := $(shell ls|grep '/.c'|sed 's//.c//.d/g')   # list all possible .d files' names for using in "include"

# the place to keep build process files
BUILD_DIR := build

# Use vpath to let make find .a and .o in $(BUILD_DIR), when "explicit rule" are checked by Make,
# because *.o are in the $(BUILD_DIR) dir and not in the dir where make enters(where Makefile exits)
# VPATH is used to find target and dependence files. but has no effect on the command line files
VPATH := $(BUILD_DIR)

# "explicit rule"
$(LIB_NAME): $(OBJS)                        
    cd $(BUILD_DIR) && ar rcs $@ $(OBJS) # cd $(BUILD_DIR) because in .c.o we moved *.o to $(BUILD_DIR)
# "abstract rule"
.c.o:
    @mkdir -p $(BUILD_DIR)                # avoid failure if no $(BUILD_DIR) exists.
    $(CC) $(CFLAGS) -MD -MF $*.d -c $< -o $@ # produce .d .o files
    @mv $*.d $*.o $(BUILD_DIR)                # move them to $(BUILD_DIR) to make source dir clean.

# include .o <- .c & .h dependence
-include $(addprefix $(BUILD_DIR)/, $(DEPS))

# PHONY target
.PHONY: clean
clean:
    rm -rf $(BUILD_DIR)

---------- src/libhello/hello1.c hello2.c hello.h 和上面 automake例子一样 -------

---------- src/bin/Makefile ------------
# for this file is similiar to that in libhello, we omit many comments.
PROG := kid
OBJS := $(shell ls|grep '/.c'|sed 's//.c//.o/g')
DEPS := $(shell ls|grep '/.c'|sed 's//.c//.d/g')
LIBS := $(shell ls $(LIBS_DIR)/*.a)
L_LIBS := -lhello -lm

# the place to keep build process files
BUILD_DIR := build

# vpath to let make find .a and .o in $(BUILD_DIR)
VPATH := $(BUILD_DIR)

$(PROG): $(OBJS) $(LIBS)
    cd $(BUILD_DIR) && $(CC) $(OBJS) -o $@ $(CFLAGS) $(L_LIBS)
.c.o:
    @mkdir -p build
    $(CC) $(CFLAGS) -MD -MF $*.d -c $< -o $@
    @mv $*.d $*.o $(BUILD_DIR)

-include $(addprefix $(BUILD_DIR)/, $(DEPS))

.PHONY: clean
clean:
    rm -rf $(BUILD_DIR)

---------- src/bin/main.c 和上面Automake中的例子一样 ----------


(2) build 目录:
首先要说明,为了支持VPATH,你需要做什么:
a. 在build中建立和src中Makefile层次一样的目录树结构,并放上基本相同的Makefile。要改的如下:
b. 用VPATH指出“显示规则”和“抽象规则”中,dependences 所在的目录在src中的什么地方。这样target: object一行
c. 对于其他的需要找文件的地方,如 -I,还是需要你自己写明,不能依靠VPATH,没用的。

--------- build/Makefile -----------
.PHONY: all clean
all:
    make -C src
clean:
    make -C src clean


-------- build/src/Makefile ---------
#MAKEFLAGS := -s

INCLUDES := -I$(TOP_DIR)/src/libhello
LDFLAGS := -L$(TOP_DIR)/build/src/libhello
MYFLAGS := -Wall -g -O3
export CFLAGS := $(INCLUDES) $(LDFLAGS) $(MYFLAGS)

.PHONY: all clean
all:
    @echo '===== building libhello ====='
    make -C libhello
    @echo
    @echo '===== building binary ====='
    make -C bin
clean:
    @echo '===== cleaning libhello ====='
    make -C libhello clean
    @echo
    @echo '===== cleaning binary ====='
    make -C bin clean

---------- build/src/libhello/Makefile ---------
LIB_NAME := libhello.a
SRC_DIR := $(TOP_DIR)/src/libhello
OBJS := $(shell ls $(SRC_DIR)|grep '/.c'|sed 's//.c//.o/g')
DEPS := $(shell ls $(SRC_DIR)|grep '/.c'|sed 's//.c//.d/g')

# the place to keep build process files
BUILD_DIR := build

# vpath to let make find .o in $(SRC_DIR) when executing "explicit" and "abstract" rule.
# VPATH is used to find target and dependence files. but has no effect on the command line files
VPATH := $(SRC_DIR)

# explicit rule
$(LIB_NAME): $(OBJS)
    ar rcs $@ $^
# abstract rule
.c.o:
    $(CC) $(CFLAGS) -MD -MF $*.d -c $< -o $@

# include .o <- .c & .h dependence
-include $(DEPS)

# PHONY target
.PHONY: clean
clean:
    rm -rf *.o *.d $(LIB_NAME)


----------- build/src/bin/Makefile ------------
PROG := kid
SRC_DIR := $(TOP_DIR)/src/bin
OBJS := $(shell ls $(SRC_DIR)|grep '/.c'|sed 's//.c//.o/g')
DEPS := $(shell ls $(SRC_DIR)|grep '/.c'|sed 's//.c//.d/g')
LIBS := $(shell ls $(TOP_DIR)/build/src/libhello/*.a)
L_LIBS := -lhello -lm

# vpath to let make find .o in $(SRC_DIR) when
# executing "explicit" and "abstract" rule.
VPATH := $(SRC_DIR)

# explicit rule
$(PROG): $(OBJS) $(LIBS)
    $(CC) $^ -o $@ $(CFLAGS) $(L_LIBS)
# abstract rule
.c.o:
    $(CC) $(CFLAGS) -MD -MF $*.d -c $< -o $@

-include $(DEPS)

.PHONY: clean
clean:
    rm -rf *.o *.d $(PROG)
原创粉丝点击