linux 下 make嵌套编译,Makefile 嵌套和宏定义的应用详解

在linux中使用Makefile实现自动化编译的时候,除了常用的功能外,还有一些不太常用却非常实用的技巧或是功能,比如Makefile嵌套,Makefile中定义宏,Makefile中export变量等。在这里做一个学习记录以供参考学习。

(一)Makefile 嵌套

Makefile嵌套可以分两种,一种是在顶层设计一个Makefile,编译所有子目录下的所有文件,另外一种是在每个子目录中个设计一个Makefile,再顶层执行Make之后,嵌套执行各个子目录的Makefile。

(1)顶层执行首先需要了解Makefile的三个通配符:

1、

wildcard : 扩展通配符

2、

patsubst :替换通配符

具体使用查看下面实例:

文件结构如下:

biao@ubuntu:~/test/Makefile_test$ tree

.

├── Makefile

├── testA

│   ├── testA.cpp

│   └── testA.h

├── testB

│   ├── testB.cpp

│   └── testB.h

├── testC

│   ├── testC.cpp

│   └── testC.h

└── test.cpp

3 directories, 8 files

biao@ubuntu:~/test/Makefile_test$

testA.cpp内容如下,testB,testC与之相同

#include "testA.h"

void Function_TestA(void)

{

printf("%s %s %d I am Test A \n",__FILE__,__FUNCTION__,__LINE__);

}在最顶层的Makefile 编译所有目录下的文件,Makefile内容为:

CC := $(COMPILE_CROSS)gcc

GG := $(COMPILE_CROSS)g++

INC := -I ./testA

INC += -I ./testB

INC += -I ./testC

TESTA_CPP := $(wildcard ./testA/*.cpp)

TESTB_CPP := $(wildcard ./testB/*.cpp)

TESTC_CPP := $(wildcard ./testC/*.cpp)

TESTA_OBJ := $(patsubst %.cpp,%.o,$(TESTA_CPP))

TESTB_OBJ := $(patsubst %.cpp,%.o,$(TESTB_CPP))

TESTC_OBJ := $(patsubst %.cpp,%.o,$(TESTC_CPP))

SRC_C   := $(wildcard *.c)

SRC_CPP := $(wildcard *.cpp)

OBJ_C   := $(patsubst %.c,%.o,$(SRC_C))

OBJ_CPP := $(patsubst %.cpp,%.o,$(SRC_CPP))

OBJS :=$(OBJ_CPP)

OBJS +=$(OBJ_C)

test:$(OBJS) $(TESTA_OBJ) $(TESTB_OBJ) $(TESTC_OBJ)

echo $(OBJS)

$(GG) $(CFLAG) $^ -o $@  $(INC)

$(OBJ_C):%.o:%.c

echo $(OBJ_C)

$(CC) $(CFLAG) -c $< -o $@ $(INC)

$(OBJ_CPP):%.o:%.cpp

echo $(OBJ_CPP)

$(GG) $(CFLAG) -c $< -o $@ $(INC)

$(TESTA_OBJ):%.o:%.cpp

echo $(TESTA_OBJ)

$(GG) $(CFLAG) -c $< -o $@ $(INC)

$(TESTB_OBJ):%.o:%.cpp

echo $(TESTB_OBJ)

$(GG) $(CFLAG) -c $< -o $@ $(INC)

$(TESTC_OBJ):%.o:%.cpp

echo $(TESTC_OBJ)

$(GG) $(CFLAG) -c $< -o $@ $(INC)

clean:

$(RM) ./testA/*.o ./testB/*.o ./testC/*.o ./*.o test

对上面命令做一个简单的介绍:

TESTA_CPP := $(wildcard ./testA/*.cpp)  获取./testA/目录下的所有的.cpp文件列表TESTA_OBJ := $(patsubst %.cpp,%.o,$(TESTA_CPP)) 将TESTA_CPP列表中所有文件名的后缀.cpp替换为.o,这样我们就可以得到在./testA/目录可生成的.o文件列表echo $(OBJS) 输出变量OBJS的值

$(GG) $(CFLAG) $^ -o $@  $(INC)  生成最后的目标文件test,扩展开来实际执行的是:

g++  test.o testA/testA.o testB/testB.o testC/testC.o -o test  -I ./testA -I ./testB -I ./testC

$(CC) $(CFLAG) -c $< -o $@ $(INC) 生成依赖文件test.o,扩展开来实际执行的是:

g++  -c test.cpp -o test.o -I ./testA -I ./testB -I ./testC

Makefile执行结果:

biao@ubuntu:~/test/Makefile_test$ make

echo test.o

test.o

g++  -c test.cpp -o test.o -I ./testA -I ./testB -I ./testC

echo ./testA/testA.o

./testA/testA.o

g++  -c testA/testA.cpp -o testA/testA.o -I ./testA -I ./testB -I ./testC

echo ./testB/testB.o

./testB/testB.o

g++  -c testB/testB.cpp -o testB/testB.o -I ./testA -I ./testB -I ./testC

echo ./testC/testC.o

./testC/testC.o

g++  -c testC/testC.cpp -o testC/testC.o -I ./testA -I ./testB -I ./testC

echo test.o

test.o

g++  test.o testA/testA.o testB/testB.o testC/testC.o -o test  -I ./testA -I ./testB -I ./testC

biao@ubuntu:~/test/Makefile_test$

程序运行结果:

biao@ubuntu:~/test/Makefile_test$ ./test

begin to test

testA/testA.cpp Function_TestA 5 I am Test A

testB/testB.cpp Function_TestB 5 I am Test B

testC/testC.cpp Function_TestC 5 I am Test C

end to test

biao@ubuntu:~/test/Makefile_test$

上面测试文件下载链接:http://download.csdn.net/download/li_wen01/10255763

(2)多层嵌套执行

嵌套执行就是在每个编译目录下都有一个Makefile文件,在最顶层再设计一个Makefile 按顺序调用各个子目录中的Makefile,实现整个工程的编译。

工程目录如下:

biao@ubuntu:~/test/Makefile_test$ tree

.

├── libs

├── main

│   ├── inc

│   │   └── test.h

│   ├── Makefile

│   └── src

│       └── test.cpp

├── Makefile

├── testA

│   ├── inc

│   │   └── testA.h

│   ├── Makefile

│   └── src

│       └── testA.cpp

└── testB

├── inc

│   └── testB.h

├── Makefile

└── src

└── testB.cpp

10 directories, 10 files

biao@ubuntu:~/test/Makefile_test$

biao@ubuntu:~/test/Makefile_test$

顶层Makefile:

#Top Makefile for C program

all:

$(MAKE) -C ./testA

$(MAKE) -C ./testB

$(MAKE) -C ./main

clean:

$(MAKE) -C ./testA clean

$(MAKE) -C ./testB clean

$(MAKE) -C ./main clean

$(RM)    libs/*命令:

$(MAKE) -C ./testA

会进入testA目录执行testA目录下的Makefile文件

main 执行文件目录Makefile

# A Makefile to generate archive file

CC := $(COMPILE_CROSS)gcc

GG := $(COMPILE_CROSS)g++

CFLAGS += -g -Wall -Werror -O2

LDFLAGS += ./../libs/testA.a ./../libs/testB.a

INC := -I ../main/inc

INC += -I ../testA/inc

INC += -I ../testB/inc

SRC_FILES = $(wildcard src/*.cpp)

SRC_OBJ   = $(patsubst %.cpp,%.o,$(SRC_FILES))

SRC_BIN      = target_bin

all:$(SRC_BIN)

$(SRC_BIN):$(SRC_OBJ)

$(CC)  -o $@  $^ $(INC) $(LDFLAGS)

$(SRC_OBJ):%.o:%.cpp

$(GG) $(CFLAG) -c $< -o $@ $(INC)

clean:

$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN)

这里调用testA 和testB 生成的库文件链接成可执行文件

test库文件目录Makefile:

# A Makefile to generate archive file

CFLAGS += -g -Wall -Werror -O2

CC := $(COMPILE_CROSS)gcc

GG := $(COMPILE_CROSS)g++

INC := -I ../main/inc

INC += -I ../testA/inc

INC += -I ../testB/inc

SRC_FILES = $(wildcard src/*.cpp)

SRC_OBJ   = $(patsubst %.cpp,%.o,$(SRC_FILES))

SRC_LIB      = testA.a

all:$(SRC_LIB)

$(SRC_LIB):$(SRC_OBJ)

$(AR) rcs $@ $^

cp $@ ../libs

$(SRC_OBJ):%.o:%.cpp

$(GG) $(CFLAG) -c $< -o $@ $(INC)

clean:

$(RM) $(SRC_OBJ) $(SRC_LIB)

distclean:

$(RM) $(SRC_OBJ) $(SRC_LIB)

上面Makefile是将testA目录中的文件编译成库

编译结果为:

biao@ubuntu:~/test/Makefile_test$ make

make -C ./testA

make[1]: Entering directory '/home/biao/test/Makefile_test/testA'

g++  -c src/testA.cpp -o src/testA.o -I ../main/inc  -I ../testA/inc  -I ../testB/inc

ar rcs testA.a src/testA.o

cp testA.a ../libs

make[1]: Leaving directory '/home/biao/test/Makefile_test/testA'

make -C ./testB

make[1]: Entering directory '/home/biao/test/Makefile_test/testB'

g++  -c src/testB.cpp -o src/testB.o -I ../main/inc  -I ../testA/inc  -I ../testB/inc

ar rcs testB.a src/testB.o

cp testB.a ../libs

make[1]: Leaving directory '/home/biao/test/Makefile_test/testB'

make -C ./main

make[1]: Entering directory '/home/biao/test/Makefile_test/main'

g++  -c src/test.cpp -o src/test.o -I ../main/inc  -I ../testA/inc  -I ../testB/inc

gcc  -o target_bin  src/test.o -I ../main/inc  -I ../testA/inc  -I ../testB/inc  ./../libs/testA.a ./../libs/testB.a

make[1]: Leaving directory '/home/biao/test/Makefile_test/main'

biao@ubuntu:~/test/Makefile_test$ tree

.

├── libs

│   ├── testA.a

│   └── testB.a

├── main

│   ├── inc

│   │   └── test.h

│   ├── Makefile

│   ├── src

│   │   ├── test.cpp

│   │   └── test.o

│   └── target_bin

├── Makefile

├── testA

│   ├── inc

│   │   └── testA.h

│   ├── Makefile

│   ├── src

│   │   ├── testA.cpp

│   │   └── testA.o

│   └── testA.a

└── testB

├── inc

│   └── testB.h

├── Makefile

├── src

│   ├── testB.cpp

│   └── testB.o

└── testB.a

10 directories, 18 files

biao@ubuntu:~/test/Makefile_test$ 嵌套执行工程文件下载链接:

http://download.csdn.net/download/li_wen01/10256031

(3)多层嵌套执行 - 进阶

在嵌套执行Makefile的时候,我们希望在顶层定义的变量在底层的Makefile中一样的使用,这时我们可以使用export导出该变量。同时为了Makefile的简洁,我们这里引进了通配符:addprefix,addsuffix,basename,notdir

预备知识点:

addprefix     添加前缀

addsuffix    添加后缀

basename 取前缀函数

notdir  去除目录

----------------------------------------------

addprefix

$(addprefix fixstring,string1 string2 ...) 比如:LIBSO = testA testB$(addprefix -l,$(LIBSO)) 执行后变成-ltestA  -ltestB

addsuffix

$(addsuffix , )

把后缀加到中的每个单词后面。

$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。

basename

$(basename )从文件名序列中取出各个文件名的前缀部分。

$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar hacks”。

notdir

$(notdir )从文件名序列中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分。

$(notdir src/foo.c hacks)返回值是“foo.c hacks”。

--------------------------------------------------------------------------------------------------

查看编译目录:

biao@ubuntu:~/test/Makefile_test$ tree

.

├── bin

├── lib

├── main

│   ├── main.cpp

│   ├── main.h

│   ├── Makefile

│   └── obj

├── Makefile

├── testA

│   ├── Makefile

│   ├── obj

│   ├── testA_1

│   │   ├── testA_1.cpp

│   │   └── testA_1.h

│   └── testA_2

│       ├── testA_2.cpp

│       └── testA_2.h

└── testB

├── Makefile

├── obj

├── testB_1

│   ├── testB_1.cpp

│   └── testB_1.h

└── testB_2

├── testB_2.cpp

└── testB_2.h

最顶层目录Makefile:

#ARCH=arm

ifeq ($(ARCH),arm)

COMPILE_CROSS:= arm-hisiv300-linux-

else

COMPILE_CROSS:=

endif

CC    := $(COMPILE_CROSS)gcc

GG    := $(COMPILE_CROSS)g++

LD    := $(COMPILE_CROSS)ld

STRIP := $(COMPILE_CROSS)strip

export CC LD STRIP

EXPORTBASEPATH=/usr/local

EXPORTPATH:=$(EXPORTBASEPATH)/lib

export EXPORTPATH

#define path of the shared lib to export

#输出的共享库所在路径

TOP_PATH = $(shell pwd)

BIN_PATH = $(TOP_PATH)/bin/

LIB_PATH = $(TOP_PATH)/lib/

export BIN_PATH

export LIB_PATH

INCL += -I$(TOP_PATH)/main/

INCL += -I$(TOP_PATH)/testA/testA_1/

INCL += -I$(TOP_PATH)/testA/testA_2/

INCL += -I$(TOP_PATH)/testB/testB_1/

INCL += -I$(TOP_PATH)/testB/testB_2/

export INCL

CFLAGS := -Wall -fPIC -O3

CFLAGS += $(INCL)

export CFLAGS

SUB_DIR := testA testB main

all:CHECKDIR $(SUB_DIR)

CHECKDIR:

mkdir -p $(BIN_PATH) $(LIB_PATH)

$(SUB_DIR):ECHO

make -C $@

ECHO:

@echo $(SUB_DIR)

@echo begin compile

# shell for command to clean

clean:

@for n in $(SUB_DIR); do $(MAKE) -C $$n clean; done

rm -rf $(BIN_PATH)*

rm -rf $(LIB_PATH)*

①export 导出底层Makefile中需要使用的变量。

②-fpic:产生代码位置无关代码,不添加这个生成库文件的时候会报错。

③执行clean命令时,使用shell 中的for 循环调用执行各个目录的make clean 清除数据。

shell 中for命令的使用可以查看博客:shell 编程 for 循环详解及应用实例

testA 和testB 中的Makefile 生成动态函数库,已备main函数调用,该Makefile内容为:

#********define shared lib short name**********

#定义共享库的短名称,需要根据实际情况进行更改

EXECUTABLE := testA

#********define shared libs used by the lib,separated by space*********

#使用的共享库列表,以空格分开, 需要根据实际情况更改

SOLIBNAME=lib$(EXECUTABLE).so

PROJECT_SRC+= $(wildcard ./testA_1/*.cpp)

PROJECT_SRC+= $(wildcard ./testA_2/*.cpp)

LDFLAGS = -shared

OBJS = $(addprefix ./obj/, $(addsuffix .o,$(basename $(notdir $(PROJECT_SRC)))))

all:

@mkdir -p obj

@for file in $(PROJECT_SRC); do \

OBJ=`basename $$file|sed -e 's/\.cpp/\.o/' -e 's/\.c/\.o/'`; \

$(CC) $(CFLAGS) -c -o obj/$$OBJ $$file; \

done; \

$(CC) $(LDFLAGS) -o $(EXPORTPATH)/$(SOLIBNAME) $(OBJS)

cp $(EXPORTPATH)/$(SOLIBNAME) $(LIB_PATH)

clean:

rm -rf ./obj/*.o ./obj/*.a ./*.so

在main目录中调用动态库,生成可执行文件test,该部分Makefile内容为:

LIBSO := testA testB

PROJECT_SRC+=./main.cpp

OUTPUT_EXECUT := test

OBJS = $(addprefix ./obj/, $(addsuffix .o,$(basename $(notdir $(PROJECT_SRC)))))

all:

@mkdir -p obj

@for file in $(PROJECT_SRC); do \

OBJ=`basename $$file|sed -e 's/\.cpp/\.o/' -e 's/\.c/\.o/'`; \

echo -e "$(CC) -c -o $$OBJ\n$$file"; \

$(CC) $(CFLAGS) -c -o obj/$$OBJ $$file; \

done; \

echo $(OBJS)

$(CC) $(CFLAGS) -o $(OUTPUT_EXECUT) $(OBJS) -L$(LIB_PATH) $(addprefix -l,$(LIBSO))

@echo "Compile complete"

@cp $(OUTPUT_EXECUT) $(BIN_PATH)

clean:

@rm -rf ../bin/*

@rm -rf ./obj/*.o

@rm -rf $(OUTPUT_EXECUT)完整的工程文件可以在这里下载:

http://download.csdn.net/download/li_wen01/10257384

(二)Makefile 添加宏定义

宏定义使用前缀-D,在编译过程中可以把宏定义追加到CFLAG中。宏定义有两种相似的写法

【第一种】-D DEFINES     【第二种】-D DEFINES=CONDITION

源文件:

使用两种不同的方式实例

#include

int main(void)

{

printf("Into Main Function \n");

#ifdef TEST_A

printf("I am Test A\n");

#endif

#if TEST_B

printf("I am Test B \n");

#endif

return 0;

}

Makefile文件:

# 指令编译器和选项

CC := $(COMPILE_CROSS)gcc

GG := $(COMPILE_CROSS)g++

# 宏定义

DEFS = -DTEST_A -DTEST_B=1

CFLAGS += $(DEFS)

SRC_C := $(wildcard *.c)

SRC_CPP :=$(wildcard *.cpp)

OBJ_C := $(patsubst %.c,%.o,$(SRC_C))

OBJ_CPP := $(patsubst %.cpp,%.o,$(SRC_CPP))

OBJS :=$(OBJ_CPP)

OBJS +=$(OBJ_C)

test:$(OBJS)

$(GG) $(CFLAGS) $^ -o $@  $(INC)

$(OBJS)::%.o:%.cpp

$(GG) $(CFLAGS) -c $< -o $@  $(INC)

clean:

$(RM) *.o test

编译执行结果如下:

biao@ubuntu:~/test/Makefile_test$

biao@ubuntu:~/test/Makefile_test$ make

g++ -DTEST_A -DTEST_B=1     -c main.cpp -o main.o

g++ -DTEST_A -DTEST_B=1     main.o -o test

biao@ubuntu:~/test/Makefile_test$ ls

main.cpp  main.o  Makefile  test

biao@ubuntu:~/test/Makefile_test$ ./test

Into Main Function

I am Test A

I am Test B

biao@ubuntu:~/test/Makefile_test$