在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$ 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐