Makefile 嵌套和宏定义的应用详解
在linux中使用Makefile实现自动化编译的时候,除了常用的功能外,还有一些不太常用却非常实用的技巧或是功能,比如Makefile嵌套,Makefile中定义宏,Makefile中export变量等。在这里做一个学习记录以供参考学习。(一)Makefile 嵌套 Makefile嵌套可以分两种,一种是在顶层设计一个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 <suffix>,<names...> )
把后缀<suffix>加到<names>中的每个单词后面。
$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。
basename
$(basename <names...> )
从文件名序列<names>中取出各个文件名的前缀部分。
$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar hacks”。
notdir
$(notdir <names...> )
从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分。
$(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 <stdio.h>
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$
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)