一个通用的两级Makefile例子
目的
- 进行如项目的顶层目录后,运行make,即可直接编译项目中所有的源文件,并生成最终的可执行文件
- 实现头文件自动依赖
- 添加源文件不用修改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)
缺陷
- 实现头文件自动依赖时,中间文件和源文件在同一级目录中,不是很好
- 没有预留链接库的接口
一个通用的两级Makefile例子的更多相关文章
- 用guava快速打造两级缓存能力
首先,咱们都有一共识,即可以使用缓存来提升系统的访问速度! 现如今,分布式缓存这么强大,所以,大部分时候,我们可能都不会去关注本地缓存了! 而在一起高并发的场景,如果我们一味使用nosql式的缓存,如 ...
- Linux C编程学习之开发工具3---多文件项目管理、Makefile、一个通用的Makefile
GNU Make简介 大型项目的开发过程中,往往会划分出若干个功能模块,这样可以保证软件的易维护性. 作为项目的组成部分,各个模块不可避免的存在各种联系,如果其中某个模块发生改动,那么其他的模块需要相 ...
- 一个通用Makefile的编写
作者:杨老师,华清远见嵌入式学院讲师. 我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文件.如果我们用gcc去一个一个编译每一个源文件的 ...
- 【linux】-Makefile简要知识+一个通用Makefile
目录 Makefile Makefile规则与示例 为什么需要Makefile Makefile样式 先介绍Makefile的两个函数 完善Makefile 通用Makefile的使用 通用的Make ...
- 编写一个通用的Makefile文件
1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe ...
- 一个通用Makefile详解
我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文 件. 如果我们用gcc去一个一个编译每一个源文件的话,效率会低很多,但是如果我们可以写 ...
- 关于过两级mux的时序约束的添加(一个非常经典的时序约束问题)
非常开心自己的微信公众号: <数字集成电路设计及EDA教程> 关注者超过了1700 里面主要讲解数字IC前端.后端.DFT.低功耗设计以及验证等相关知识,并且讲解了其中用到的各种EDA工具 ...
- 【PTA】5-1 输入一个正整数n,再输入n个学生的姓名和百分制成绩,将其转换为两级制成绩后输出。
5-1 输入一个正整数n,再输入n个学生的姓名和百分制成绩,将其转换为两级制成绩后输出.要求定义和调用函数set_grade(stu, n),其功能是根据结构数组stu中存放的学生的百分制成绩scor ...
- 一个通用的makefile(一)
最近在编写Android编译系统时,需要遍历每一个目录下每一个文件夹下的makefile,网上的方法有些繁琐 :就直接贴上自己遍历子目录深度为1:(for temporary)(之后会继续更新) 下 ...
随机推荐
- 面试题40:最小的 k 个数
import java.util.Arrays; /** * Created by clearbug on 2018/2/26. * * 面试题40:最小的 k 个数 * * 注意:因为前两天在陌陌面 ...
- 深入浅出腾讯BERT推理模型--TurboTransformers
Overview TurboTransformers是腾讯最近开源的BERT推理模型,它的特点就是一个字,快.本人用BERT(huggingface/transformers)在V100上做了测试,测 ...
- .net core 使用Tu Share获取股票交易数据
一.什么是Tu Share Tushare是一个免费.开源的python财经数据接口包.主要实现对股票等金融数据从数据采集.清洗加工 到 数据存储的过程,用户可以免费(部分数据的下载有积分限制)的通 ...
- SpringBoot--使用redis实现分布式限流
1.引入依赖 <!-- 默认就内嵌了Tomcat 容器,如需要更换容器也极其简单--> <dependency> <groupId>org.springframew ...
- vs2013, EF6.0.0.0 使用Migrations来更新数据库时报错
1.vs中,程序包管理器控制台 2.执行,Enable-Migrations 报错: Migrations have already been enabled in project 'dd'. To ...
- VScode和IntelliJ IDEA设置自动换行
VScode自动换行 点击左上角的File-->Auto Save即可实现多文件的自动换行; IDEA自动换行 点击左侧空白处,选择Soft-Wrap就是当前文件自动换行,选择Configure ...
- 小师妹学JVM之:JIT中的PrintAssembly续集
目录 简介 JDK8和JDK14中的PrintAssembly JDK8中使用Assembly JDK14中的Assembly 在JMH中使用Assembly 总结 简介 上篇文章和小师妹一起介绍了P ...
- 烦人的B数
B树:二叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于走右结点: B-树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点: 所有关键字在整颗树中出现, ...
- JVM源码分析之Object.wait/notify实现
“365篇原创计划”第十一篇. 今天呢!灯塔君跟大家讲: JVM源码分析之Object.wait/notify实现 最简单的东西,往往包含了最复杂的实现,因为需要为上层的存在提 ...
- 每日一题 - 剑指 Offer 54. 二叉搜索树的第k大节点
题目信息 时间: 2019-07-04 题目链接:Leetcode tag:二叉搜索树 中序遍历 递归 难易程度:中等 题目描述: 给定一棵二叉搜索树,请找出其中第k大的节点. 示例1: 输入: ro ...