目的

  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)

缺陷

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

一个通用的两级Makefile例子的更多相关文章

  1. 用guava快速打造两级缓存能力

    首先,咱们都有一共识,即可以使用缓存来提升系统的访问速度! 现如今,分布式缓存这么强大,所以,大部分时候,我们可能都不会去关注本地缓存了! 而在一起高并发的场景,如果我们一味使用nosql式的缓存,如 ...

  2. Linux C编程学习之开发工具3---多文件项目管理、Makefile、一个通用的Makefile

    GNU Make简介 大型项目的开发过程中,往往会划分出若干个功能模块,这样可以保证软件的易维护性. 作为项目的组成部分,各个模块不可避免的存在各种联系,如果其中某个模块发生改动,那么其他的模块需要相 ...

  3. 一个通用Makefile的编写

    作者:杨老师,华清远见嵌入式学院讲师. 我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文件.如果我们用gcc去一个一个编译每一个源文件的 ...

  4. 【linux】-Makefile简要知识+一个通用Makefile

    目录 Makefile Makefile规则与示例 为什么需要Makefile Makefile样式 先介绍Makefile的两个函数 完善Makefile 通用Makefile的使用 通用的Make ...

  5. 编写一个通用的Makefile文件

    1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe ...

  6. 一个通用Makefile详解

    我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文 件. 如果我们用gcc去一个一个编译每一个源文件的话,效率会低很多,但是如果我们可以写 ...

  7. 关于过两级mux的时序约束的添加(一个非常经典的时序约束问题)

    非常开心自己的微信公众号: <数字集成电路设计及EDA教程> 关注者超过了1700 里面主要讲解数字IC前端.后端.DFT.低功耗设计以及验证等相关知识,并且讲解了其中用到的各种EDA工具 ...

  8. 【PTA】5-1 输入一个正整数n,再输入n个学生的姓名和百分制成绩,将其转换为两级制成绩后输出。

    5-1 输入一个正整数n,再输入n个学生的姓名和百分制成绩,将其转换为两级制成绩后输出.要求定义和调用函数set_grade(stu, n),其功能是根据结构数组stu中存放的学生的百分制成绩scor ...

  9. 一个通用的makefile(一)

    最近在编写Android编译系统时,需要遍历每一个目录下每一个文件夹下的makefile,网上的方法有些繁琐 :就直接贴上自己遍历子目录深度为1:(for  temporary)(之后会继续更新) 下 ...

随机推荐

  1. 为什么说String是线程安全的

    String是final修饰的类,是不可变的,所以是线程安全的. 一.Java String类为什么是final的? 1.为了实现字符串池 2.为了线程安全 3.为了实现String可以创建HashC ...

  2. Nginx深入学习(一篇搞定)

     我们的口号是:人生不设限!  一.nginx简介 1.什么是nginx Nginx (engine x) 是一个高性能的HTTP和反向代理服务器,特点是占有内存少,并发能力强,事实上nginx的并发 ...

  3. Vue学习-组件的基本使用(局部组件)

    目录 示例代码 1.创建组件(构造器对象创建-Vue.extend) 2.注册组件 3.使用组件 4.语法糖创建并注册组件 示例代码 http://jsrun.net/H8vKp/edit 1.创建组 ...

  4. DOM-BOM-EVENT(5)

    5.宽.高.位置相关 5.1.clientX/clientY clientX和clientY表示鼠标在浏览器可视区的坐标位置 <script> document.onclick = fun ...

  5. P1131 [ZJOI2007]时态同步【树形dp】

    时态同步 从叶子到根节点统计修改次数.树形\(dp\)思想. 题目描述 小\(Q\)在电子工艺实习课上学习焊接电路板.一块电路板由若干个元件组成,我们不妨称之为节点,并将其用数字\(1,2,3-\). ...

  6. C# Thread、lock

    class Program { private static readonly object obj = new object(); static void Main(string[] args) { ...

  7. 服务消费者(Ribbon)

    上一篇文章,简单概述了服务注册与发现,在微服务架构中,业务都会被拆分成一个独立的服务,服务之间的通讯是基于http restful的,Ribbon可以很好地控制HTTP和TCP客户端的行为,Sprin ...

  8. 【error fixed】E: Package 'oracle-java8-installer' has no installation candidate

    问题:安装oracle-java8-installer按照如下指南失败: How To Install Java with Apt-Get on Ubuntu 16.04[https://www.di ...

  9. Oracle查询表空间使用率很慢

    Oracle查询表空间使用率很慢 问题描述 执行查询表空间的语句,需要接近2min的时间才能执行完成. 以前也在其他客户的生产库遇到过一样的情况,当时是由于回收站的内容过多引起的. 不过这次的情况却不 ...

  10. " 橘松 " 的自我介绍

    昵称:(OrangeCsong)橘松(在其他平台也是这个名字) 年龄:95后(摩羯座) 性别:boy 性格:性格还阔以,不轻易发脾气,沉稳.喜欢独立思考. 爱好:运动(工作了,运动时间太少),基金理财 ...