一个通用的两级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)(之后会继续更新) 下 ...
随机推荐
- ssh生成单个公钥
ssh生成单个公钥命令: ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 查看公钥: cat ~/.ssh/id_rsa ...
- .Net Core 中GC的工作原理
前言 .NET 中GC管理你服务的内存分配和释放,GC是运行公共语言运行时(CLR Common Language Runtime)中,GC可以帮助开发人员有效的分配内存和和释放内存,大多数情况下是不 ...
- linux 上设置mysql开机自启
此方式是通过安装包安装的,如果是yum安装的rpm包,可参考yum安装MySQL8.0 三个月之前安装的mysql,记得是设置了开机自启,但是今天再次进入的时候发现,无法登录,报错如下 原因是mysq ...
- Python 简明教程 ---10,Python 列表
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 程序 = 算法 + 数据结构 -- Nicklaus Wirth 目录 从这句话程序 = 算法 + ...
- IDEA版本彩虹屁插件idea-rainbow-fart,一个在你编程时疯狂称赞你的 IDEA扩展插件
缘起 是否听说过程序员鼓励师,不久前出了一款vscode的插件rainbow-fart,可以在写代码的时候,匹配到特定关键词就疯狂的拍你马屁. vscode的下载尝试过,但是作为日常将IDEA作为主力 ...
- 小师妹学JVM之:JIT中的PrintAssembly
目录 简介 使用PrintAssembly 输出过滤 总结 简介 想不想了解JVM最最底层的运行机制?想不想从本质上理解java代码的执行过程?想不想对你的代码进行进一步的优化和性能提升? 如果你的回 ...
- SerializableClob转String
ORACLE数据库读取CLOB字段,JAVA中获取到类型为SerializableClob,如何转换成String类型那? 以下是代码示例: SerializableClob sc = ...
- pycharm中连接MySql出现 Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc.的错误解决
具体异常如下: 这个异常特别烦人,是mysql连接驱动的问题,可以用二步解决: 第一步 从网上下载驱动,从mysql-connector-java_8.0.16-1ubuntu16.04_all.de ...
- VScode 配置c/c++环境(结合各大网站的blog和官方文档)
第一步:下载mingw64 首先,在各大搜索引擎上搜索mingw64,进入其官网下载即可 需要注意的是,下载默认的是先从网页上下载下载器,再进行选择下载.但是在部分电脑上下载会出现什么mingw64. ...
- mysql 常见使用场景
场景1:表中多条相同账户数据,根据创建日期排序取第一条. select * from cms_client_rpq rpq where id = ( select id from cms_client ...