目的

  1. 进行如项目的顶层目录后,运行make,即可直接编译项目中所有的源文件,并生成最终的可执行文件
  2. 实现头文件自动依赖
  3. 添加源文件不用修改Makefile,且可以自动编译新文件
  4. 顶层目录下添加文件夹,不用重新编写Makefile,直接拷贝其他文件夹下的Makefile,就可以自动编译整个文件夹下的源文件

目录结构

  顶层文件夹名称test,二级文件夹按照模块分类,文件夹名称就是模块名称,顶层文件夹下包含一个顶层的Makefile,二级文件夹下包含二级Makefile。二级文件夹target存放的是编译的中间文件和最后的可执行文件,二级文件夹module1、module2和module3,是三个用于测试的模块,具体的目录结构如下图所示:

  源文件的代码如下:

  1. // main.h
  2. #ifndef __MAIN_H__
  3. #define __MAIN_H__
  4.  
  5. #include <stdio.h>
  6. #include "add.h"
  7. #include "sub.h"
  8.  
  9. #endif
  10.  
  11. // main.c
  12. #include "main.h"
  13. int main()
  14. {
  15. printf("1 + 2 = %d\n", add(1, 2));
  16. printf("4 - 2 = %d\n", sub(4, 2));
  17. return 0;
  18. }
  19.  
  20. // add.h
  21. #ifndef __ADD_H__
  22. #define __ADD_H__
  23. int add(int a, int b);
  24. #endif
  25.  
  26. // add.c
  27. #include "add.h"
  28. int add(int a, int b)
  29. {
  30. return a + b;
  31. }
  32.  
  33. //sub.h
  34. #ifndef __SUB_H__
  35. #define __SUB_H__
  36. int sub(int a, int b);
  37. int sub2(int a, int b);
  38. #endif
  39.  
  40. // sub.c
  41. #include "sub.h"
  42. int sub(int a, int b)
  43. {
  44. return a - b;
  45. }
  46.  
  47. // sub2.c
  48. #include "sub.h"
  49. int sub2(int a, int b)
  50. {
  51. return b - a;
  52. }

  

顶层Makefile

  1. #设置编译器和相关命令
  2. CC = gcc
  3. MKDIR = mkdir
  4. CP = cp
  5. RM = rm
  6. FIND = find
  7.  
  8. #debug文件夹里的makefile文件需要最后执行,所以这里需要执行的子目录要排除debug文件夹,这里使用awk排除了debug文件夹,读取剩下的文件夹
  9. SUBDIRS = $(shell ls -l | grep ^d | awk '{if($$9 != "target") print $$9}')
  10.  
  11. #记住当前工程的根目录路径
  12. ROOT_DIR=$(shell pwd)
  13.  
  14. #最终bin文件的名字,可以更改为自己需要的
  15. BIN = test
  16.  
  17. #目标文件所在的目录
  18. OBJS_DIR = target/tmp
  19.  
  20. #bin文件所在的目录
  21. BIN_DIR = target/bin
  22. TARGET = $(ROOT_DIR)/$(BIN_DIR)/$(BIN)
  23.  
  24. #将以下变量导出到子shell中,本次相当于导出到子目录下的makefile中
  25. export CC BIN OBJS_DIR BIN_DIR ROOT_DIR MKDIR CP RM FIND
  26.  
  27. #注意这里的顺序,需要先执行SUBDIRS最后才能是DEBUG
  28. all : $(SUBDIRS) CREATE_DIR $(TARGET)
  29.  
  30. #递归执行子目录下的makefile文件,这是递归执行的关键
  31. .PHONY: $(SUBDIRS)
  32. $(SUBDIRS):
  33. make -C $@
  34.  
  35. #创建生成目标的文件夹
  36. CREATE_DIR :
  37. @if [ ! -d $(ROOT_DIR)/$(BIN_DIR) ]; then $(MKDIR) -p $(ROOT_DIR)/$(BIN_DIR); fi
  38.  
  39. #将所有的.o文件链接成可执行文件,设置成伪目标的原因是:希望编译都重新链接
  40. .PHONY: $(TARGET)
  41. $(TARGET):
  42. $(CC) -o $@ $(shell find ./target/tmp -name *.o)
  43.  
  44. #清除所有编译生成的文件
  45. clean:
  46. @$(RM) -rf $(ROOT_DIR)/target/*
  47. @$(FIND) ./ -name "*.d" | xargs rm -rf

二级Makefile

  1. #以下同根目录下的makefile的相同代码的解释
  2.  
  3. #获取所有的源文件名
  4. CUR_SOURCE = ${wildcard src/*.c}
  5.  
  6. #将所有的.o源文件名变成.o文件名
  7. CUR_OBJS = ${patsubst %.c, %.o, $(CUR_SOURCE)}
  8.  
  9. #获取当前目录的名称
  10. CUR_DIR_NAME = $(shell pwd |sed 's/^\(.*\)[/]//g')
  11.  
  12. #指定.o文件存放的路径
  13. OUTPUT_DIR = $(ROOT_DIR)/$(OBJS_DIR)/$(CUR_DIR_NAME)/src
  14.  
  15. #生成所有需要生成.o文件的全路径
  16. OUTPUT_OBJS = $(addprefix $(ROOT_DIR)/$(OBJS_DIR)/$(CUR_DIR_NAME)/,$(CUR_OBJS))
  17.  
  18. #说明头文件路径,引入了什么头文件就在此处添加对应的头文件路径(需要手动修改)
  19. INCLUDEPATH = -I ./include\
    -I ../module2/include\
  20. -I ../module3/include
  21.  
  22. all : CREATE_DIR $(OUTPUT_OBJS)
  23.  
  24. #创建存放目标的文件夹
  25. CREATE_DIR :
  26. @if [ ! -d $(OUTPUT_DIR) ]; then $(MKDIR) -p $(OUTPUT_DIR); fi
  27.  
  28. #生成.o文件,并制定.o文件路径
  29. $(OUTPUT_DIR)/%.o : src/%.c
  30. $(CC) $(INCLUDEPATH) -c $< -o $@
  31.  
  32. #生成头文件依赖的目标
  33. src/%.d : src/%.c
  34. @set -e; rm -f $@; \
  35. $(CC) -MM $(INCLUDEPATH) $< > $@.$$$$; \
  36. sed 's,\($*\)\.o[ :]*,$(OUTPUT_DIR)/\1.o $@ : ,g' < $@.$$$$ > $@; \
  37. rm -f $@.$$$$
  38.  
  39. #引入包含头文件依赖的.d文件
  40. -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. 面试题40:最小的 k 个数

    import java.util.Arrays; /** * Created by clearbug on 2018/2/26. * * 面试题40:最小的 k 个数 * * 注意:因为前两天在陌陌面 ...

  2. 深入浅出腾讯BERT推理模型--TurboTransformers

    Overview TurboTransformers是腾讯最近开源的BERT推理模型,它的特点就是一个字,快.本人用BERT(huggingface/transformers)在V100上做了测试,测 ...

  3. .net core 使用Tu Share获取股票交易数据

     一.什么是Tu Share Tushare是一个免费.开源的python财经数据接口包.主要实现对股票等金融数据从数据采集.清洗加工 到 数据存储的过程,用户可以免费(部分数据的下载有积分限制)的通 ...

  4. SpringBoot--使用redis实现分布式限流

    1.引入依赖 <!-- 默认就内嵌了Tomcat 容器,如需要更换容器也极其简单--> <dependency> <groupId>org.springframew ...

  5. vs2013, EF6.0.0.0 使用Migrations来更新数据库时报错

    1.vs中,程序包管理器控制台 2.执行,Enable-Migrations 报错: Migrations have already been enabled in project 'dd'. To ...

  6. VScode和IntelliJ IDEA设置自动换行

    VScode自动换行 点击左上角的File-->Auto Save即可实现多文件的自动换行; IDEA自动换行 点击左侧空白处,选择Soft-Wrap就是当前文件自动换行,选择Configure ...

  7. 小师妹学JVM之:JIT中的PrintAssembly续集

    目录 简介 JDK8和JDK14中的PrintAssembly JDK8中使用Assembly JDK14中的Assembly 在JMH中使用Assembly 总结 简介 上篇文章和小师妹一起介绍了P ...

  8. 烦人的B数

    B树:二叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于走右结点: B-树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点: 所有关键字在整颗树中出现, ...

  9. JVM源码分析之Object.wait/notify实现

    ​ “365篇原创计划”第十一篇.   今天呢!灯塔君跟大家讲:   JVM源码分析之Object.wait/notify实现       最简单的东西,往往包含了最复杂的实现,因为需要为上层的存在提 ...

  10. 每日一题 - 剑指 Offer 54. 二叉搜索树的第k大节点

    题目信息 时间: 2019-07-04 题目链接:Leetcode tag:二叉搜索树 中序遍历 递归 难易程度:中等 题目描述: 给定一棵二叉搜索树,请找出其中第k大的节点. 示例1: 输入: ro ...