Makefile 详解
编译时混合使用动态库和静态库
编译某个测试代码时,出现了下面的错误:
g++ -std=c++11 -o testlurkcli main.cpp -L. -llurkcli-lasl -static
/usr/bin/ld: cannot find -lstdc++
/usr/bin/ld: cannot find -lm
/usr/bin/ld: cannot find -lc
collect2: error: ld returned 1 exit status
这个错误,是在最后的链接阶段,没有找到 libstdc++,libm,libc等这几个库。正常而言,这几个库的动态库都是存在的,这里因为使用了”-static”选项,导致链接时没有找到这几个库的静态版本。
网上查了一下,大部分是推荐把这几个库的静态库版本找到并软连接到/usr/lib64/中。
不过这里采用一种动态库和静态库混合编译的方法去解决。具体编译过程如下:
# g++ -std=c++11 main.cpp liblurkcli.a libasl.a -lpthread-o testlurkcli
或者:
# g++ -std=c++11 main.cpp -L. -llurkcli -lasl -lpthread -o testlurkcli
或者:
# g++ -v -std=c++11 main.cpp -L. -Wl,-Bstatic -llurkcli-lasl -Wl,-Bdynamic -lpthread -otestlurkcli
注意,最后一种方式,使用-Wl,-Bstatic以及-Wl,-Bdynamic,给连接器ld传递链接选项,-Wl,-Bstatic使得后面的-l库使用静态连接的方式,而-Wl,-Bdynamic使得后面的-l库使用动态链接的方式。所以,上面的命令中,-llurkcli –lasl使用的是静态连接,而-lpthread,以及连接器自己默认连接的-lstdc++, -lm, -lgcc_s, -lgcc, -lc, -lgcc_s, -lgcc,都是采用的动态链接。
下面的话,摘自:
https://ftp.gnu.org/pub/old-gnu/Manuals/ld-2.9.1/html_node/ld_3.html#SEC3
--start-group和--end-group 解决 ld 链接 error 时依赖顺序的问题
原因就是链接多个静态库,且这些静态库之间有依赖时,静态库在command line的顺序不对
举例:假设liba.a 依赖于libb.b,目标test需要liba和libb,那么生成test的命令应该为gcc -o test -la -lb
而不是gcc -o test -lb -la
也可以使用ld的 --start-group xxx --end-group来将所有相互依赖的静态库放到xxx位置,只有就能保证search多次了
arm-himix200-linux-gcc -o rtmp -w -DISP_V2 -DHI_RELEASE -DVER_X=1 -DVER_Y=0 -DVER_Z=0 -DVER_P=0 -DVER_B=10 -DUSER_BIT_32 -DKERNEL_BIT_32 -Wno-date-time -DSENSOR0_TYPE=SONY_IMX335_MIPI_5M_30FPS_12BIT -DSENSOR1_TYPE=SONY_IMX335_MIPI_5M_30FPS_12BIT
-mcpu=cortex-a7 -mfloat-abi=softfp -mfpu=neon-vfpv4 -fno-aggressive-loop-optimizations -ldl -ffunction-sections -fdata-sections -O2 -fstack-protector-strong -fPIC $(SRCS) -Wl,--start-group $(INCLUDE) $`(LIBS) -Wl,--end-group
大致说明如下:
使用gcc参数-Wl,–gc-sections,不链接未用函数,减小可执行文件大小
- 在编译C、Ada源文件(C++也可以),在gcc/g++编译选项中增加
-ffunction-sections、-fdata-sections
,在编译生成的.o目标文件中,会将每个函数或数据段,放在各种单独独立的section中; - 在链接生成最终可执行文件时,如果带有
-Wl,--gc-sections
参数,并且之前编译目标文件时带有-ffunction-sections -fdata-sections
参数,则链接器ld不会链接未使用的函数,从而减小可执行文件大小; - 如果使用了-r的链接参数,来产生重定位的输出,需要显示的调用-e参数来指定程序入口。否则-Wl,--gc-sections不会生效。
CC = gcc
CXX = g++
LINK = gcc
LD = ld
# prefix ?= arm-none-eabi-
debug ?= 0
CC = $(prefix)gcc
AS = $(prefix)as
LD = $(prefix)ld
GDB = $(prefix)gdb
OC = $(prefix)objcopy
# CFLAGS_D = $(COMPILER_FLAGS) -c -g -O0 -Wall -Werror
# CFLAGS_R = $(COMPILER_FLAGS) -c -g -O2 -Werror
# CXXFLAGS_D = $(COMPILER_FLAGS) -c -g -O0 -Werror
# CXXFLAGS_R = $(COMPILER_FLAGS) -c -g -O2 -Werror
INC+= -I. -Isrc
LIBS +=-lm -lpthread
RELEASE = 0
TARGET = epoll
# CCFLAGS = $(CFLAGS_R)
ifeq ($(RELEASE),0)
# debug
CFLAGS = $(CFLAGS_D)
CXXFLAGS = $(CXXFLAGS_D)
else
# release
CFLAGS = $(CFLAGS_R)
CXXFLAGS = $(CXXFLAGS_R)
endif
ifeq ($(DEBUG), 1)
CFLAGS += -g
CFLAGS += -DDEBUG
else
CFLAGS += -O2
CFLAGS += -DNDEBUG
endif
TARGET_PATH := $(PWD)
# TARGET := $(SRCS:%.c=%)
TARGET :=tlog
SRCS := $(wildcard *.c)
CPPSRC := $(wildcard *.cpp)
OBJS := $(SRCS:%.c=%.o)
OBJS +=$(ASM:%.s=%.o)
OBJS +=$(CPPSRC:%.cpp=%.o)
LIBFILE = libtlog.a
.PHONY: all
all: $(TARGET) $(LIBFILE)
$(LIBFILE): $(OBJS)
$(RM) $@ && $(AR) rcs $(OUTPUT)$@ $(OBJS)
$(TARGET): $(OBJS)
gcc $(LDFLAGS) $(OBJS) -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.cpp
$(CXX) $(CCFLAGS) -c $< -o $@
%.o: %.s
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
$(RM) $(LIB_OBJS) $(LIBFILE)
rm -rf $(TARGET)
rm -rf $(OBJS)
海思中的Makefile
ifeq ($(PARAM_FILE), )
PARAM_FILE:=../Makefile.param
include $(PARAM_FILE)
endif
# target source
OBJS := $(SMP_SRCS:%.c=%.o)
CFLAGS += $(COMM_INC)
MPI_LIBS += $(REL_LIB)/libhdmi.a
.PHONY : clean all
all: $(TARGET)
$(TARGET):$(COMM_OBJ) $(OBJS)
@$(CC) $(CFLAGS) -lpthread -lm -o $(TARGET_PATH)/$@ $^ -Wl,--start-group $(MPI_LIBS) $(SENSOR_LIBS) $(AUDIO_LIBA) $(REL_LIB)/libsecurec.a -Wl,--end-group
clean:
@rm -f $(TARGET_PATH)/$(TARGET)
@rm -f $(OBJS)
@rm -f $(COMM_OBJ)
cleanstream:
@rm -f *.h264
@rm -f *.h265
@rm -f *.jpg
@rm -f *.mjp
@rm -f *.mp4
鸿蒙代码中的Makefile
CROSS_COMPILE=arm-hisiv300-linux-
CC=$(CROSS_COMPILE)gcc
CXX=$(CROSS_COMPILE)g++
AR=$(CROSS_COMPILE)ar
RANLIB= $(CROSS_COOMPILE)ranlib
STRIP=$(CROSS_COMPILE)strip
TARGET = hello
CC = gcc
CFLAGS = -std=c99 -Wall -I.
LINKER = gcc
SRC_DIR = src
OBJ_DIR = bin/obj
BIN_DIR = bin
SOURCES := $(wildcard $(SRC_DIR)/*.c)
OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
RM_R = rm -r
MKDIR_P = mkdir -p
LIBS =
LDFLAGS = -Wall -I. -lm
LDFLAGS += -ldl -ffunction-sections -fdata-sections -Wl,--gc-sections -O2 -fstack-protector-strong -fPIC
$(BIN_DIR)/$(TARGET): $(OBJECTS)
@$(LINKER) $(OBJECTS) $(LDFLAGS) -o $@ -Wl,--start-group $(INCS) $(LIBS) -Wl,--end-group
$(OBJECTS): $(OBJ_DIR)/%.o : $(SRC_DIR)/%.c
@$(MKDIR_P) ${BIN_DIR} $(OBJ_DIR)
@$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
@$(RM_R) $(OBJ_DIR) $(BIN_DIR)
arm-himix200-linux-gcc -o rtsp-h264 -Dhi3516dv300 -DSENSOR0_TYPE=SONY_IMX327_MIPI_2M_30FPS_12BIT -DSENSOR1_TYPE=SONY_IMX327_MIPI_2M_30FPS_12BIT -DHI_RELEASE -DHI_XXXX -DISP_V2 -DHI_ACODEC_TYPE_INNER -mcpu=cortex-a7 -mno-unaligned-access -fno-aggressive-loop-optimizations -ffunction-sections -fdata-sections $(INCLUDE) $(LIBS)
变量的基本赋值
知道了如何定义,下面我们来说一下 Makefile 的变量的四种基本赋值方式:
简单赋值 ( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效。
递归赋值 ( = ) 赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。
条件赋值 ( ?= ) 如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
追加赋值 ( += ) 原变量用空格隔开的方式追加一个新值。
简单赋值
x:=foo
y:=$(x)b
x:=new
test:
@echo "y=>$(y)"
@echo "x=>$(x)"
Linux 变量
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
建立一个入口Makefile执行指定子目录中的Makefile
#编译指定子目录
# SUBDIRS=libskt dir2 dir3
#编译所有子目录
SUBDIRS = $(shell ls -d src/* |grep -v "output")
.PHONY: all clean
all:
for subdir in $(SUBDIRS); do \
echo "Make in $$subdir";\
make -C $$subdir;\
done
.PHONY: clean
clean:
@echo Making clean
@list='$(SUBDIRS)'; for subdir in $$list; do \
echo "Clean in $$subdir";\
$(MAKE) -C $$subdir clean;\
done
常用内置变量
查看makefile 变量值 make -p
-p, --print-data-base Print make's internal database.即打印makefile 的执行规矩等。从而查看到相应的makefile中的各个变量的值。
$(CURDIR)
Makefile 中变量赋值
1:尽在Makefile文件的目标项冒号后的另起一行的代码才是shell代码。
第一种方式:xx = xx1
// 这里时makefile代码
第二种方式:yy:xx = xx2
// 这是是makefile代码,makefile允许变量赋值时,'='号两边留空格 ??????可以吗
第三种shell方式:
yy:
a=xx3 // 只有这里是shell代码 ,shell不允许‘=’号两边有空格哦。
yy:
a= xx3 // 只有这里是shell代码 ,shell不允许‘=’号两边有空格哦。
注意:此时a的值是 " xx3", 注意多了一个空格
有一个例外:
xx=$(shell 这里的代码也是shell代码)
变量传递
2:Makefile中的shell,每一行是一个进程,不同行之间变量值不能传递。所以,Makefile中的shell不管多长也要写在一行。
eg:
SUBDIR=src example
all:
// 这里往下是一行shell
@for subdir in $(SUBDIR); \
do \
echo "building " $$subdir; \
done
变量引用 Makefile变量$ shell的变量$$
3:Makefile中的变量以$开头,使用$(VAR)或${VAR}来引用变量的定义。 所以,为了避免和shell的变量冲突,shell的变量以·$$·开头
注意:Makefile中在对一些简单变量的引用,我们也可以不使用“()”和“{}”来标记变量名,而直接使用“$x”的格式来实现,此种用法仅限于变量名为单字符的情况。另外自动化变量也使用这种格式。对于一般多字符变量的引用必须使用括号了标记,否则make将把变量名的首字母作为作为变量而不是整个字符串(“$PATH”在Makefile中实际上是“$(P)ATH”)。这一点和shell中变量的引用方式不同。shell中变量的引用可以是“${xx}”或者“$xx”格式。但在Makefile中多字符变量名的引用只能是“$(xx)”或者“${xx}”格式。
eg1:从当前目录路径中提取出 /application 或 /base_class 之前的部分
PROJECT_ROOT_DIR = $(shell pwd | awk -F'/application|/base_class' '{print $$1}')
eg2:上例中$$subdir就是shell中的变量, 而$(SUBDIR)是Makefile的中的变量
Makefile中规避错误以及$
符号的转义 -> $$
在处理时, 需要注意下Makefile对$符号的处理. Makefile的变量大都是$(VAR)的形式, 因此在增加$(DESTDIR)就需要将$字符转义一下(引用Bash变量的时候也一样). 因此我们可以用sed来实现一下:
sed -i -e '/^install/,/^\s*$$/{s/$$(PREFIX)/$$(DESTDIR)\/$$(PREFIX)/g}' Makefile
修改完之后, 打包完成~
基本原
.PYTHON: 伪目标总是不如其它文件“新”,因此它总是被执行。
@ 使命令在被执行前不被回显。
– 使任何命令行的任何非零退出状态都被忽略。 忽略 exit 1
- 使命令行可以通过指定 -n、-q 或 -t 选项来执行。
模式变量 %
targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern是指明了targets的模式,也就是的目标集模式。
prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。
这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。 所以,我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”那么你可以使用反斜杠“\”进行转义,来标明真实的“%”字符。 看一个例子: 上面的例子中,指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标, 试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。再看一个例子: files = foo.elc bar.o lose.o $(filter %.o,$(files)): %.o: %.c $(filter %.elc,$(files)): %.elc: %.el $(filter%.o,$(files))表示调用Makefile的filter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不用多说了吧。这个例字展示了Makefile中更大的弹性。 $@ -- 代表目标文件(target) 要生成目标 ... : 生成目标所需的依赖 ... export $@ 很有可能不兼容于其它版本的 make,所以,你应该尽量避免使用"$",除非是在隐含规则 在Makefile中写shell代码有点诡异,和不同的shell语法不太一样,如果不了解,看Makefile会莫名其妙。下面总结了一些。 Makefile本质上来讲也是shell脚本,即每条command都是shell进程,运行完shell进程都会退出 这样输入make test,结果相当于两个进程,都退出了。 等价于在shell下输命令一样。 这里make my_test,结果相当于一个进程。当前目录是/root 而 此时 如果make执行的命令前面加了@字符,则不显示命令本身而只显示它的结果; Android中会定义某个变量等于@,例如 hide:= @ 1、在Makefile中只能在target中调用Shell脚本,其他地方是不能输出的。比如如下代码就是没有任何输出: 以上代码任何时候都不会输出,没有在target内,如果上述代码改为如下: 以上代码,在make all的时候将会执行echo命令。 最后打印结果是: 2、在Makefile中执行shell命令,一行创建一个进程来执行。这也是为什么很多Makefile中有很多行的末尾都是“; \”,以此来保证代码是一行而不是多行,这样Makefile可以在一个进程中执行,例如: 上述可以看出for循环中每行都是以”; \”结尾的。 3、Makefile中所有以$打头的单词都会被解释成Makefile中的变量。如果你需要调用shell中的变量(或者正则表达式中锚定句位$),都需要加两个$符号($$)。实例如下: 例子中的第一个${PATH}引用的是Makefile中的变量,而不是shell中的PATH环境变量,后者引用的事Shell中的PATH环境变量。
如果我们的objects = foo.o bar.o
all: $(objects)
$(objects):
%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.cbar.c”。
而命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
$(CC) -c $(CFLAGS) $< -o $@
emacs -f batch-byte-compile $<
自动变量 $
$^ -- 代表所有的依赖文件(components)
$< -- 代表第一个依赖文件(components中最左边的那个)。command #必须要以[Tab]键开始
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于
目标中模式定义的集合。
$% 仅匹配目标文件
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a
(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix
下是[.a],Windows 下是[.lib]),那么,其值为空。
$<
依赖目标中的第一个目标名字。如果依赖目标是以模式( 即"%")定义的,那么"$<"将
是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$? 依赖的集合(只提取比目标新的)
所有比目标新的依赖目标的集合。以空格分隔。
$^ 依赖的集合(去重)
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量
会去除重复的依赖目标,只保留一份。
$+ 依赖的集合(不去重)
这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$* 去除后缀名(不推荐使用)
这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的
模式是"a.%.b",那么,"$"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比
较有较。如果目标中没有模式的定义,那么"$"也就不能被推导出,但是,如果目标文件的
后缀是 make 所识别的,那么"$"就是除了后缀的那一部分。例如:如果目标是"foo.c",因
为".c"是 make 所能识别的后缀名,所以,"$"的值就是"foo"。这个特性是 GNU make 的,
或是静态模式中。如果目标中的后缀是 make 所不能识别的,那么"$"就是空值。Makefile和Shell
test:
gcc -c main.c -o main.o
gcc -c a.c -o a.o
my_test:
cd /home;mkdir test
own_test:
cd /home
mkdir test
make own_test
,相当于两个进程,
第一个进程是cd /home,运行完回到了/root。这时再运行第二个shell命令就会在/root下创建一个test目录.(makefile中的shell进程命令,跟直接在shell输的命令相同,但是它都会结束本身,即exit)变量输出 @隐藏自身的输出 - 出错继续运行
通常make执行的命令如果出错(该命令的退出状态非0)就立刻终止,不再执行后续命令,但如果命令前面加了-号,即使这条命令出错,make也会继续执行后续命令。
通常rm命令和mkdir命令前面要加-号,因为rm要删除的文件可能不存在,mkdir要创建的目录可能已存在,这两个命令都有可能出错,但这种错误是应该忽略的。VAR="Hello"
echo "$(VAR)"
all:
.....
VAR="Hello"
all:
echo "$(VAR)"
.....
echo ""Hello""
"Hello"SUBDIR=src example
all:
@for subdir in $(SUBDIR); \
do\
echo "building "; \
done
PATH="/data/"
all:
echo ${PATH}/*Makefile中变量,即"/data"*/
echo $$PATH /*shell中的变量*/
以上三点的是Makefile调用shell应该注意的地方,写Makefile一定要注意。
go arm makefile
#
# Makefile
#
# Path to raspberry pi mounted root (by sshfs)
RPIROOT=/home/k/mnt/pi/
# enable sdjournal
#GOTAGS=
GOTAGS=-tags 'sdjournal'
#
VERSION=1.2
REVISION=`git describe --always`
DATE=`date +%Y%m%d%H%M%S`
USER=`whoami`
BRANCH=`git branch | grep '^\*' | cut -d ' ' -f 2`
LDFLAGS="\
-X github.com/prometheus/common/version.Version=$(VERSION) \
-X github.com/prometheus/common/version.Revision='$(REVISION) \
-X github.com/prometheus/common/version.BuildDate=$(DATE) \
-X github.com/prometheus/common/version.BuildUser=$(USER) \
-X github.com/prometheus/common/version.Branch=$(BRANCH)"
LDFLAGS_PI="-w -s \
-X github.com/prometheus/common/version.Version=$(VERSION) \
-X github.com/prometheus/common/version.Revision=$(REVISION) \
-X github.com/prometheus/common/version.BuildDate=$(DATE) \
-X github.com/prometheus/common/version.BuildUser=$(USER) \
-X github.com/prometheus/common/version.Branch=$(BRANCH)"
logmonitor: build
.PHONY: build
build:
CGO_ENABLED=1 go build $(GOTAGS) -v -o logmonitor -ldflags $(LDFLAGS)
.PHONY: build_pi
build_pi:
GOGCCFLAGS="-fPIC -O4 -Ofast -pipe -march=native -mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -s" \
GOARCH=arm GOARM=6 CGO_ENABLED=1 GOOS=linux \
CC=arm-linux-gnueabi-gcc \
CXX=arm-linux-gnueabi-g++ \
CGO_LDFLAGS="-L$(RPIROOT)/lib/arm-linux-gnueabihf -lsystemd \
-Wl,-rpath-link=$(RPIROOT)/lib/arm-linux-gnueabihf \
-Wl,-rpath-link=$(RPIROOT)/lib/ \
-Wl,-rpath-link=$(RPIROOT)/usr/lib/ \
-Wl,-rpath-link=$(RPIROOT)/usr/lib/arm-linux-gnueabihf " \
go build $(GOTAGS) -v -o logmonitor-arm -ldflags $(LDFLAGS_PI)
logmonitor-arm: build_pi
.PHONY: install_pi
install_pi: logmonitor-arm
ssh pi "systemctl --user stop logmonitor"
ssh pi "[ -f ~/prometheus/logmonitor-arm ] && mv -f ~/prometheus/logmonitor-arm ~/prometheus/logmonitor-arm.old"
scp logmonitor-arm pi:prometheus/
ssh pi "systemctl --user start logmonitor"
ssh pi "systemctl --user status logmonitor"
.PHONY: build_arm5
build_arm5:
GOGCCFLAGS="-fPIC -O4 -Ofast -pipe -march=native -marm -s" \
GOARCH=arm GOARM=5 CGO_ENABLED=1 GOOS=linux \
CC=arm-linux-gnueabi-gcc \
CXX=arm-linux-gnueabi-g++ \
CGO_LDFLAGS="-L$(RPIROOT)/lib/arm-linux-gnueabihf -lsystemd \
-Wl,-rpath-link=$(RPIROOT)/lib/arm-linux-gnueabihf \
-Wl,-rpath-link=$(RPIROOT)/lib/ \
-Wl,-rpath-link=$(RPIROOT)/usr/lib/ \
-Wl,-rpath-link=$(RPIROOT)/usr/lib/arm-linux-gnueabihf " \
go build $(GOTAGS) -v -o logmonitor-arm5 -ldflags $(LDFLAGS_PI)
.PHONY: run
run:
#go run -v *.go -log.level debug
CGO_ENABLED="1" go-reload `ls *.go | grep -v _test.go` -log.level debug
.PHONY: clean
clean:
rm -f logmonitor logmonitor-arm
# vim:ft=make
Makefile 入口函数的实现
很多时候,我们的代码结构需要将源代码划分成多个子模块,每个模块应该可以单独编译和执行,而我们又希望有一个总的Makefile将他们串联起来,本文就是针对这一问题而来的。
方法1:
SUBDIRS = SUB_DIR_NAME_1 SUB_DIR_NAME_2
.PHONY: all
all:
@list='$(SUBDIRS)'; for subdir in $$list; do \
echo "Clean in $$subdir";\
cd $$subdir && $(MAKE);\
done
.PHONY: clean
clean:
@list='$(SUBDIRS)'; for subdir in $$list; do \
echo "Clean in $$subdir";\
cd $$subdir && $(MAKE) clean;\
done
方法2:
SUBDIRS = SUB_DIR_NAME_1 SUB_DIR_NAME_2
.PHONY: all
all:
@list='$(SUBDIRS)'; for subdir in $$list; do \
echo "Clean in $$subdir";\
$(MAKE)-C $$subdir;\
done
.PHONY: clean
clean:
@echo Making clean
@list='$(SUBDIRS)'; for subdir in $$list; do \
echo "Clean in $$subdir";\
$(MAKE)-C $$subdir clean;\
done
方法3:
SUBDIRS = SUB_DIR_NAME_1 SUB_DIR_NAME_2
.PHONY: all $(SUBDIRS)
all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
SUB_DIR_NAME_2 : SUB_DIR_NAME_1
.PHONY: clean
clean:
@echo Making clean
@list='$(SUBDIRS)'; for subdir in $$list; do \
echo "Clean in $$subdir";\
$(MAKE) -C $$subdir clean;\
done
注释:
1.
-C DIR
--directory=DIR
在读取Makefile之前,进入目录“DIR”,就是切换工作目录到“DIR”之后执行make。存在多个“-C”选项时,make的最终工作目录是第一个目录的相对路径。如:“make –C / -C etc”等价于“make –C /etc”。一般此选项被用在递归地make调用中。
2.
在递归调用时,使用$(MAKE)而不是make:
1)
变量“MAKE”的值是“make”。如果其值为“/bin/make”那么上边规则的命令就为“cd subdir && /bin/make”。这样做的好处是:当我们使用一个其它版本的make程序时,可以保证最上层使用的make程序和其子目录下执行的make程序保持一致。
2)
变量“MAKE”的这个特点是:在规则的命令行中如果使用变量“MAKE”,标志“-t”、“-n”和“-q”在这个命令的执行中不起作用。尽管这些选项是告诉make不执行规则的命令行,但包含变量“MAKE”的命令行除外,它们会被正常执行。同时,执行make的命令行选项参数被通过一个变量“MAKEFLAGS”传递给子目录下的make程序。