• 实现头文件自动依赖
  • 添加源文件不用修改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\n", add(1, 2));
        printf("4 - 2 = %d\n", 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)
  • 实现头文件自动依赖时,中间文件和源文件在同一级目录中,不是很好
  • 没有预留链接库的接口
  •