linux编译----makefile的思考与总结
在一个大型项目中,要知晓代码结构,顶层makefile的分析是比不可少的
首先先看顶层makefile的分析,这是一个实际的公司的makefile,可能回涉及一些专业东西看不太懂,忽略即可
先来分析顶层makefile
# define BNT6000 Terminal's release version.
VERSION = 3
PATCHLEVEL = 0
SUBLEVEL = 0
EXTRAVERSION = 1
ACA_RELEASE=$(VERSION).$(PATCHLEVEL).$(SUBLEVEL).$(EXTRAVERSION)
#定义了一些参数,主要用于生成产品版本号
include Makefile.inc
#在 Makefile 使用 include 关键字可以把别的 Makefile 包含进来,这很像 C 语言的 #include,被包含的文件会原模原样的放在当前文件的包含位置。
#makefile.inc其实就是定义了一些编译参数,目录,以及编译方法!
# directories visited install, programs, check, etc.
# acadb
#
export TOPDIR := $(shell pwd) #如果你要传递变量到下级 Makefile 中,那么你可以使用这样的声明: export
export MAINDIR := $(TOPDIR)/main
export SDKDIR := $(TOPDIR)/sdk
export INCDIR := $(TOPDIR)/util
#subdir都是一些子目录
SUBDIRS=util protocol netcomm STM32COMM center Rs232 record record/2012 audio lcd printer update jilt BD $(module_dir) format apply main pack
all: bnt6000
bnt6000: copy_lib
@echo "#define BUILD_DATE \"`date +%Y-%m-%d`\"" > $(INCDIR)/version.h
@echo "#define SW_VER \"$(SOFTWARE_VERSION)\"" >> $(INCDIR)/version.h
@echo "#define BUILD_SVN_NO \"`svn info | grep "Last Changed Rev: " | sed -e "s/Last Changed Rev: //g"`\"" >> $(INCDIR)/version.h
@echo "#define SW_VERSION SW_VER\",build:\"BUILD_SVN_NO\":\"BUILD_DATE\"\\0\"" >> $(INCDIR)/version.h
@echo "#define PRODUCT_TYPE \"$(PRODUCT_TYPE)\"" >> $(INCDIR)/version.h
@for d in $(SUBDIRS); do (cd $$d && $(MAKE)); done # 一种语法结构,进入子目录并且分别编译!
copy_lib:
ifeq ($(COMM_MODULE), G3)
cp audio/libsqlite3-3g.so -dpRf audio/libsqlite3.so
cp util/libcurl_3g.so.4 -dpRf util/libcurl.so
cp util/libzlog_3g.so.1.1 -dpRf util/libzlog.so
else
cp audio/libsqlite3-2g.so -dpRf audio/libsqlite3.so
cp util/libcurl_2g.so.4 -dpRf util/libcurl.so
cp util/libzlog_2g.so.1.1 -dpRf util/libzlog.so
endif
install: auth
# @if [ "$LOGNAME" != "root" ]; then echo "Only root can install, quit!"; exit 1; fi
# mkdir -p $(HOME)/workspace
mkdir -p $(PREFIX)
mkdir -p $(PREFIX)/bin $(PREFIX)/etc $(PREFIX)/lib $(PREFIX)/include
@for d in $(SUBDIRS); do (cd $$d && $(MAKE) install); done
# chown root.root $(PREFIX)/bin/*
uninstall:
# @if [ "$LOGNAME" != "root" ]; then echo "Only root can uninstall, quit!"; exit 1; fi
@for d in $(SUBDIRS); do (cd $$d && $(MAKE) uninstall); done
@rm -f $(PREFIX)/include/*.h
@rm -f $(PREFIX)/lib/*.a
img:
./main/mkimg.ramfs
build:
@echo "just for packing atomically---------------------------------------------start---------"
cd ./bin&& ./build.sh
clean:
@for d in $(SUBDIRS); do (cd $$d && $(MAKE) clean); done
make clean -C av
help:
@echo "Auth Server makefile."
@echo "version : $(ACA_RELEASE)"
@echo
@echo "Syntax:"
@echo " aca -- make all auth source code. It is default make."
@echo " clean -- make clean all auth source code."
@echo " install -- make all auth source code, and then install programs to DESDIR."
@echo " uninstall -- uninstall programs from DESDIR."
@echo " help -- print this help."
# Define ACA Release Version.
version.h: ./Makefile
@echo "/*" > .ver
@echo " * version.h" >> .ver
@echo " *" >> .ver
@echo " * Copyright (C) 2007 UNIS." >> .ver
@echo " *" >> .ver
@echo " * ACA Release Version." >> .ver
@echo " * This file comes from Makefile. Do not modify it manually." >> .ver
@echo " */" >> .ver
@echo >> .ver
@echo \#define ACA_RELEASE \"$(ACA_RELEASE)\" >> .ver
@echo \#define ACA_VERSION_CODE `expr $(VERSION) \\* 65536 + $(PATCHLEVEL) \\* 256 + $(SUBLEVEL)` >> .ver
@echo '#define ACA_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))' >>.ver
@mv -f .ver $@
@sed '/[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/ c $(ACA_RELEASE)' etc/version > /tmp/version.1
@cp -dpRf /tmp/version.1 etc/version
@rm -f /tmp/version
include Makefile.inc
在 Makefile 使用 include 关键字可以把别的 Makefile 包含进来,这很像 C 语言的 #include,被包含的文件会原模原样的放在当前文件的包含位置。
include 的语法是: include filename
可以是当前操作系统 Shell 的文件模式(可以保含路径和通配符) 在 include 前面可以有一些空字符,但是绝不能是[Tab]键开始。
include 和可以用一个或 多个空格隔开。举个例子,你有这样几个 Makefile:a.mk、b.mk、c.mk,还有一个文件叫 foo.make,以及一个变量$(bar),其包含了 e.mk 和 f.mk,那么,下面的语句: include foo.make *.mk $(bar) 等价于: include foo.make a.mk b.mk c.mk e.mk f.mk make 命令开始时,会把找寻 include 所指出的其它 Makefile,并把其内容安置在当前 的位。
就好像 C/C++的#include 指令一样。如果文件都没有指定绝对路径或是相对路径的话, make 会在当前目录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个 目录下找:
1、如果 make 执行时,有“-I”或“--include-dir”参数,那么 make 就会在这个参数 所指定的目录下去寻找。
2、如果目录/include(一般是:/usr/local/bin 或/usr/include)存在的话, make 也会去找。如果有文件没有找到的话,make 会生成一条警告信息,但不会马上出现致 命错误。它会继续载入其它的文件,一旦完成 makefile 的读取,make 会再重试这些没有找 到,或是不能读取的文件,如果还是不行,make 才会出现一条致命信息。如果你想让 make 不理那些无法读取的文件,而继续执行,你可以在 include 前加一个减号“-”。
如: -include 其表示,无论 include 过程中出现什么错误,都不要报错继续执行。和其它版本 make 兼 容的相关命令是 sinclude,其作用和这一个是一样的。
export TOPDIR := $(shell pwd) #在makefile中执行shell命令,就是当前路径 export MAINDIR := $(TOPDIR)/main export SDKDIR := $(TOPDIR)/sdk export INCDIR := $(TOPDIR)/util
如果你要传递变量到下级 Makefile 中,那么你可以使用这样的声明: export 如果你不想让某些变量传递到下级 Makefile 中,那么你可以这样声明: unexport
如: 示例一: export variable = value 其等价于:
variable = value
export variable
其等价于: export variable := value 其等价于:
variable := value
export variable
示例二: export variable += value 其等价于:
variable += value
export variable
如果你要传递所有的变量,那么,只要一个 export 就行了。后面什么也不用跟,表示 传递所有的变量。 需要注意的是,有两个变量,一个是 SHELL,一个是 MAKEFLAGS,这两个变量不管你是 否 export,其总是要传递到下层 Makefile 中,特别是 MAKEFILES 变量,其中包含了 make 的参数信息,如果我们执行“总控 Makefile”时有 make 参数或是在上层 Makefile 中定义 了这个变量,那么 MAKEFILES 变量将会是这些参数,并会传递到下层 Makefile 中,这是一 个系统级的环境变量。
all: bnt6000 bnt6000: copy_lib #这而并没有用伪目标,因为只有一个需要生成,这种写法每次都会编译,因为all目标总是不存在的
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同 样可以作为“默认目标”,只要将其放在第一个。
一个示例就是,如果你的 Makefile 需要 一口气生成若干个可执行文件,但你只想简单地敲一个 make 完事,并且,所有的目标文件 都写在一个 Makefile 中,那么你可以使用“伪目标”这个特性:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o cc -o prog1 prog1.o utils.o
prog2 : prog2.o cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o cc -o prog3 prog3.o sort.o utils.o
我们知道,Makefile 中的第一个目标会被作为其默认目标。我们声明了一个“all”的 伪目标,其依赖于其它三个目标。由于伪目标的特性是,总是被执行的,所以其依赖的那三 个目标就总是不如“all”这个目标新。所以,其它三个目标的规则总是会被决议。也就达 到了我们一口气生成多个目标的目的。“.PHONY : all”声明了“all”这个目标为“伪目 标”。 随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也 可成为依赖。看下面的例子:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
“make clean”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪 目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和 “make cleandiff”命令来达到清除不同种类文件的目的。
copy_lib: ifeq ($(COMM_MODULE), G3) cp audio/libsqlite3-3g.so -dpRf audio/libsqlite3.so cp util/libcurl_3g.so.4 -dpRf util/libcurl.so cp util/libzlog_3g.so.1.1 -dpRf util/libzlog.so #做了一些拷贝库的工作 else cp audio/libsqlite3-2g.so -dpRf audio/libsqlite3.so cp util/libcurl_2g.so.4 -dpRf util/libcurl.so cp util/libzlog_2g.so.1.1 -dpRf util/libzlog.so endif
我们可以从上面的示例中看到三个关键字:ifeq、else 和 endif。ifeq 的意思表示条 件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括 号括起。else 表示条件表达式为假的情况。endif 表示一个条件语句的结束,任何一个条件 表达式都应该以 endif 结束。
第一个是我们前面所见过的“ifeq”
ifeq (arg1,arg2 )
ifeq 'arg1' 'arg2'
ifeq "arg1" "arg2"
ifeq "arg1" 'arg2'
ifeq 'arg1' "arg2"
比较参数“arg1”和“arg2”的值是否相同。当然,参数中我们还可以使用 make 的函数。
#subdir都是一些子目录
SUBDIRS=util protocol netcomm STM32COMM center Rs232 record record/2012 audio lcd printer update jilt BD $(module_dir) format apply main pack
@echo "#define BUILD_DATE \"`date +%Y-%m-%d`\"" > $(INCDIR)/version.h @echo "#define SW_VER \"$(SOFTWARE_VERSION)\"" >> $(INCDIR)/version.h @echo "#define BUILD_SVN_NO \"`svn info | grep "Last Changed Rev: " | sed -e "s/Last Changed Rev: //g"`\"" >> $(INCDIR)/version.h @echo "#define SW_VERSION SW_VER\",build:\"BUILD_SVN_NO\":\"BUILD_DATE\"\\0\"" >> $(INCDIR)/version.h @echo "#define PRODUCT_TYPE \"$(PRODUCT_TYPE)\"" >> $(INCDIR)/version.h
生成一些参数到version.h中,这个与打包有关,暂时不用管
主要是`date +%Y-%m-%d`用来取得命令的执行结果
显示命令
通常,make 会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在 命令行前,那么,这个命令将不被 make 显示出来,
最具代表性的例子是,我们用这个功能 来像屏幕显示一些信息。
如: @echo 正在编译 XXX 模块...... 当 make 执行时,会输出“正在编译 XXX 模块......”字串
但不会输出命令,如果没 有“@”,那么,make 将输出:
echo 正在编译 XXX 模块......
正在编译 XXX 模块......
如果 make 执行时,带入 make 参数“-n”或“--just-print”,那么其只是显示命令, 但不会执行命令,这个功能很有利于我们调试我们的 Makefile,看看我们书写的命令是执 行起来是什么样子的或是什么顺序的。 而 make 参数“-s”或“--slient”则是全面禁止命令的显示
@for d in $(SUBDIRS); do (cd $$d && $(MAKE)); done # 一种语法结构,进入子目录并且分别编译!
其实可以优化成
@for d in $(SUBDIRS); do ( $(MAKE) -C $$d); done
使用“-C”参数来指定 make 下层 Makefile
$$表示$,用来shell下引用变量,而$A或者$(A)则是Makefile的变量。
rule_1
for i in 1 2 3 4 5; do echo $(i); done
上面的代码不会连续打印 1 2 3 4 5
但下面的代码会:
rule_1:
for i in 1 2 3 4 5; do echo $$(i); done
如下实例:
files=main.exe a.exe b.exe
all:
for name in `echo $(files) | sed s/.exe//g`; \
do \
rm -f "$$name".o; \
done
makefile.inc
SHELL = /bin/bash
SOFTWARE_VERSION = v4.3.18
#
# You can overwrite these flags with make ARCH=xxx CROSS_COMPILE=xxx
#
#ARCH = x86
ARCH = HI3515
# GSM = 中国移动GPRS, CDMA=支持中国电信CDMA网
#COMM_MODULE = GSM
COMM_MODULE = G3
#COMM_MODULE = CDMA
#PRODUCT_TYPE = BNT5000
PRODUCT_TYPE = BNT4000HD
#PRODUCT_TYPE = BNT4000HG
#PRODUCT_TYPE = BNT4000HC
# V_GU=通用,V_WK=网阔, V_NQ=西藏纳曲
#Commpany = -DV_GU
Commpany = -DV_WK
#Commpany = -DV_NQ
#Commpany =
# JT808=标准808协议, JT808_EX=支持补充JT808协议
#PROTOCOL = -DJT808
PROTOCOL = -DJT808_EX
ifeq ($(PRODUCT_TYPE), BNT4000HD)
CROSS_COMPILE = arm-hismall-linux-
CFLAGS = -O -Wall -Wno-deprecated -DDEBUG -Dhi3515 -D$(PRODUCT_TYPE) $(PROTOCOL) $(D_MODULE) $(Commpany)
module_dir += av
CXX=
else
CROSS_COMPILE = arm-none-linux-gnueabi-
CXX=
CFLAGS = -O -Wall -Wno-deprecated -DDEBUG $(PROTOCOL) $(D_MODULE) $(Commpany)
module_dir =
endif
# G网
ifeq ($(COMM_MODULE), GSM)
D_MODULE=-DGSM
MODULE_INC_DIR=-I../newgprs
MODULE_LIB_DIR=-L../newgprs
MODULE_LIB=-lnewgprs
module_dir += newgprs
# C网
else ifeq ($(COMM_MODULE), CDMA)
D_MODULE=-DCDMA
MODULE_INC_DIR=-I../cdma
MODULE_LIB_DIR=-L../cdma
MODULE_LIB=-lcdma
module_dir += cdma
else ifeq ($(COMM_MODULE), G3)
D_MODULE= -DG3
MODULE_INC_DIR=-I../av/3G -I../av/upgrade
MODULE_LIB_DIR=-L../av/3G -L../av/upgrade
MODULE_LIB=../av/3G/lib3G.a ../av/upgrade/libupgrade.a
module_dir += av/3G av/upgrade
endif
#
# Include the make variables (CC, etc...)
#
CXX = $(CROSS_COMPILE)g++
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)g++
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
RANLIB = $(CROSS_COMPILE)ranlib
STRIP = $(CROSS_COMPILE)strip
MAKE = make
ALLFLAGS = $(CFLAGS) $(CXXFLAGS) $(CPPFLAGS)
#
# Explicit rules
#
%.o: %.c
$(CXX) $(ALLFLAGS) $(INCLUDEPATH) -o $@ $< -c
%.o: %.cpp
$(CXX) $(ALLFLAGS) $(INCLUDEPATH) -o $@ $< -c
%.o: %.cc
$(CXX) $(ALLFLAGS) $(INCLUDEPATH) -o $@ $< -c
#
# Directorys
#
# You can override this by pass DESTDIR=xxx to the make
AUTH_ROOT = /auth
PREFIX = /bnt6000
#
#
# dependency
#
ACE_ROOT=/usr/local/ace
#PGSQL_ROOT=/usr/local/pgsql
PGSQL_ROOT=/usr
MC_ROOT=/mc
BOOST_ROOT=/usr/local/boost
BOOST_INCLUDE=$(BOOST_ROOT)/include/boost-1_33_1
BOOST_LIB=$(BOOST_ROOT)/lib
# # Explicit rules # %.o: %.c $(CXX) $(ALLFLAGS) $(INCLUDEPATH) -o $@ $< -c %.o: %.cpp $(CXX) $(ALLFLAGS) $(INCLUDEPATH) -o $@ $< -c %.o: %.cc $(CXX) $(ALLFLAGS) $(INCLUDEPATH) -o $@ $< -c
上面指定的隐式规则,说明,即如果makefile中没有说明如何生成.o 的默认生成规则,说明.o如何生成.o
那么在makefie中你就可以按如下规则
test:*.o
$(CXX) $(CPPFLAG) $< -o$@
而不用指明如何生成.o!
makefile也有自己的隐式规则
“$@”(自动化变量),这个变量表示着目前规则中所有的目标的集合,
$@ 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于 目标中模式定义的集合。
$< 依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将 是符合模式的一系列的文件集。注意,其是一个一个取出来的。
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
其中,"$@"表示所有的目标,"$<"表示了所有依赖目标的挨个值。这些奇怪的 变量我们叫"自动化变量"