目的
- 进行如项目的顶层目录后,运行make,即可直接编译项目中所有的源文件,并生成最终的可执行文件
- 实现头文件自动依赖
- 添加源文件不用修改Makefile,且可以自动编译新文件
- 顶层目录下添加文件夹,不用重新编写Makefile,直接拷贝其他文件夹下的Makefile,就可以自动编译整个文件夹下的源文件
目录结构
顶层文件夹名称test,二级文件夹按照模块分类,文件夹名称就是模块名称,顶层文件夹下包含一个顶层的Makefile,二级文件夹下包含二级Makefile。二级文件夹target存放的是编译的中间文件和最后的可执行文件,二级文件夹module1、module2和module3,是三个用于测试的模块,具体的目录结构如下图所示:
源文件的代码如下:
// main.h #ifndef __MAIN_H__ #define __MAIN_H__ #include <stdio.h> #include "add.h" #include "sub.h" #endif // main.c #include "main.h" int main() { printf("1 + 2 = %d ", add(1, 2)); printf("4 - 2 = %d ", sub(4, 2)); return 0; } // add.h #ifndef __ADD_H__ #define __ADD_H__ int add(int a, int b); #endif // add.c #include "add.h" int add(int a, int b) { return a + b; } //sub.h #ifndef __SUB_H__ #define __SUB_H__ int sub(int a, int b); int sub2(int a, int b); #endif // sub.c #include "sub.h" int sub(int a, int b) { return a - b; } // sub2.c #include "sub.h" int sub2(int a, int b) { return b - a; }
顶层Makefile
#设置编译器和相关命令 CC = gcc MKDIR = mkdir CP = cp RM = rm FIND = find #debug文件夹里的makefile文件需要最后执行,所以这里需要执行的子目录要排除debug文件夹,这里使用awk排除了debug文件夹,读取剩下的文件夹 SUBDIRS = $(shell ls -l | grep ^d | awk '{if($$9 != "target") print $$9}') #记住当前工程的根目录路径 ROOT_DIR=$(shell pwd) #最终bin文件的名字,可以更改为自己需要的 BIN = test #目标文件所在的目录 OBJS_DIR = target/tmp #bin文件所在的目录 BIN_DIR = target/bin TARGET = $(ROOT_DIR)/$(BIN_DIR)/$(BIN) #将以下变量导出到子shell中,本次相当于导出到子目录下的makefile中 export CC BIN OBJS_DIR BIN_DIR ROOT_DIR MKDIR CP RM FIND #注意这里的顺序,需要先执行SUBDIRS最后才能是DEBUG all : $(SUBDIRS) CREATE_DIR $(TARGET) #递归执行子目录下的makefile文件,这是递归执行的关键 .PHONY: $(SUBDIRS) $(SUBDIRS): make -C $@ #创建生成目标的文件夹 CREATE_DIR : @if [ ! -d $(ROOT_DIR)/$(BIN_DIR) ]; then $(MKDIR) -p $(ROOT_DIR)/$(BIN_DIR); fi #将所有的.o文件链接成可执行文件,设置成伪目标的原因是:希望编译都重新链接 .PHONY: $(TARGET) $(TARGET): $(CC) -o $@ $(shell find ./target/tmp -name *.o) #清除所有编译生成的文件 clean: @$(RM) -rf $(ROOT_DIR)/target/* @$(FIND) ./ -name "*.d" | xargs rm -rf
二级Makefile
#以下同根目录下的makefile的相同代码的解释 #获取所有的源文件名 CUR_SOURCE = ${wildcard src/*.c} #将所有的.o源文件名变成.o文件名 CUR_OBJS = ${patsubst %.c, %.o, $(CUR_SOURCE)} #获取当前目录的名称 CUR_DIR_NAME = $(shell pwd |sed 's/^(.*)[/]//g') #指定.o文件存放的路径 OUTPUT_DIR = $(ROOT_DIR)/$(OBJS_DIR)/$(CUR_DIR_NAME)/src #生成所有需要生成.o文件的全路径 OUTPUT_OBJS = $(addprefix $(ROOT_DIR)/$(OBJS_DIR)/$(CUR_DIR_NAME)/,$(CUR_OBJS)) #说明头文件路径,引入了什么头文件就在此处添加对应的头文件路径(需要手动修改) INCLUDEPATH = -I ./include
-I ../module2/include -I ../module3/include all : CREATE_DIR $(OUTPUT_OBJS) #创建存放目标的文件夹 CREATE_DIR : @if [ ! -d $(OUTPUT_DIR) ]; then $(MKDIR) -p $(OUTPUT_DIR); fi #生成.o文件,并制定.o文件路径 $(OUTPUT_DIR)/%.o : src/%.c $(CC) $(INCLUDEPATH) -c $< -o $@ #生成头文件依赖的目标 src/%.d : src/%.c @set -e; rm -f $@; $(CC) -MM $(INCLUDEPATH) $< > $@.$$$$; sed 's,($*).o[ :]*,$(OUTPUT_DIR)/1.o $@ : ,g' < $@.$$$$ > $@; rm -f $@.$$$$ #引入包含头文件依赖的.d文件 -include $(CUR_SOURCE:.c=.d)
缺陷
- 实现头文件自动依赖时,中间文件和源文件在同一级目录中,不是很好
- 没有预留链接库的接口