工程通用makefile简单实现
最近学习了一下makefile,总结了一些经验,自己试着写了一套简单通用的软件工程makefile,总结下来以后可能会用到。
我的工程目录是这样的结构:
顶层目录是app/,app/include/存放公共的头文件,app/lib/存放公共的.a,app/obj/存放下层模块编译出的.a。
app层下面有三个模块分别是init,mod1,mod2。各模块编译出的.o放在obj下面,然后链接成.a文件放到上一级目录的obj下面。
app层通过链接lib和obj下面所有的.a来生成可执行文件。
模块目录下面的makefile:
DIR_INC = ./inc ../mod1/inc ../mod2/inc
DIR_SRC = ./src
DIR_OBJ = ./obj
DIR_BIN = ../obj
TARGET = $(DIR_BIN)/libinit.a
INCLUDE += $(DIR_INC)
DEFINES +=
CC ?= gcc
CFLAGS ?= -Wall
AR ?= ar
ARFLAG ?= -cr
SRC = $(wildcard ${DIR_SRC}/*.c)
OBJ = $(patsubst %.c,${DIR_OBJ}/%.o,$(notdir ${SRC}))
all : $(TARGET)
$(TARGET):$(OBJ)
$(AR) $(ARFLAG) $@ $(OBJ)
$(DIR_OBJ)/%.o:$(DIR_SRC)/%.c
$(CC) $(CFLAGS) $(addprefix -I, $(INCLUDE)) $(addprefix -D, $(DEFINES)) -c $< -o $@
.PHONY : clean
clean :
rm -rf $(DIR_OBJ)/*.o
app目录下面的makefile:
DIR_APP = $(shell pwd)
DIR_MOD = ./mod1 ./mod2 ./init
DIR_OBJ = ./obj
DIR_BIN = ./bin
DIR_LIB = ./lib
TARGET = $(DIR_BIN)/app1
export INCLUDE = $(DIR_APP)/include
export CC ?= gcc
export CFLAGS ?= -Wall -g
export DEFINES ?= _DEBUG_
all :
@for subdir in $(DIR_MOD); \
do $(MAKE) -C $$subdir all || exit 1; done
$(CC) -o $(TARGET) -Wl,--start-group $(DIR_OBJ)/*.a $(DIR_LIB)/*.a -Wl,--end-group
.PHONY : clean
clean :
@for subdir in $(DIR_MOD); \
do $(MAKE) -C $$subdir clean || exit 1; done
rm -rf $(DIR_BIN)/* $(DIR_OBJ)/*
make all
make clean
需要注意的事项:
1.模块目录下面的DIR_INC需要手工指定需要包含的头文件路径。
2.模块目录下面的TARGET指定为需要输出的.a,设计上是每个模块单独生成一个.a。
3.原本想要实现的效果是,在每个模块目录下面单独执行makefile,可以生成.a,无需依赖上层,这样就需要.c中包含头文件时指定相对路径,或者DIR_INC指定清楚需要的头文件路径。
4.模块下面的makefile,用了一些?=定义的变量,表示如果上层没有传递该变量下来,则使用本定义来执行。这样可以减少对上层makefile的依赖。
遇到的几个问题:
1.模块层makefile编译时,提示找不到头文件:
原因是:定义头文件的变量有很多项时,需要每一项都指定-I参数,需要使用加头函数$(addprefix -I, $(INCLUDE)),这样编译时每一项都会加-I参数。宏定义同理,都要加-D。
2.在app层编译可执行程序时,链接所有.a,提示函数未定义:
原因是:链接器在链接.a时,由于链接的顺序问题,导致有些.a中的函数定义找不到。此时需要使用-Wl,--start-group *.a -Wl,--end-group,意思是链接时,在中间包含的所有.a中查找符号定义,直到找到为止。
可以优化的地方:
1.不知道这样可以不可以,在app层makefile中,指定INCLUDE=$(shell find ./ -name inc),这样上层就可以把所有头文件路径传给模块层使用,模块层无需再手工修改需要包含的路径了。
2.可以在app层上再指定一层project层,大型工程可能会编译出很多可执行程序,可以集中控制app层编译。
3.编译选项之类的,可以在app层指定一个compilier.mk文件,专门定义清楚。
可以使用的调试手段:
1.nm filename
查看文件的函数符号表。U表示不是本文件定义的函数。一般是调用的其他文件的函数。T表示本文件定义的函数。
U App1Lib
U App1Mod1
U App1Mod2
0000000000000000 T main
U puts
2.readelf -a filename
解析文件的符号表。
3.ar -t
查看.a链接了哪些文件。
常用makefile命令
参考:
http://blog.csdn.net/haoel/article/details/2886/
http://www.cnblogs.com/Anker/p/3242207.html