一个通用的两级Makefile例子

目的

  1. 进行如项目的顶层目录后,运行make,即可直接编译项目中所有的源文件,并生成最终的可执行文件
  2. 实现头文件自动依赖
  3. 添加源文件不用修改Makefile,且可以自动编译新文件
  4. 顶层目录下添加文件夹,不用重新编写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)

 

 缺陷

  • 实现头文件自动依赖时,中间文件和源文件在同一级目录中,不是很好
  • 没有预留链接库的接口

 

posted @ 2019-08-21 10:29  Yong_无止境  阅读(675)  评论(0编辑  收藏  举报