实现头文件自动依赖
添加源文件不用修改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)
实现头文件自动依赖时,中间文件和源文件在同一级目录中,不是很好
没有预留链接库的接口