一个通用的两级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)(之后会继续更新) 下 ...
随机推荐
- 黎活明8天快速掌握android视频教程--25_网络通信之资讯客户端
1 该项目的主要功能是:后台通过xml或者json格式返回后台的视频资讯,然后Android客户端界面显示出来 首先后台新建立一个java web后台 采用mvc的框架 所以的servlet都放在se ...
- 入门大数据---Kafka生产者详解
一.生产者发送消息的过程 首先介绍一下 Kafka 生产者发送消息的过程: Kafka 会将发送消息包装为 ProducerRecord 对象, ProducerRecord 对象包含了目标主题和要发 ...
- 推荐一种通过刷leetcode来增强技术功底的方法
背景 如果前人认为这个一种学习提高或者检验能力的成功实践.而自己目前又没有更好的方法,那就不妨试一试. 而不管作为面试官还是被面试者,编码题最近越来越流行.而两种角色都需要思考的问题是希望考察什么能力 ...
- python+opencv实现检测物体聚集区域
内容涉及:二值图像转换 / 检测连通区域面积 / 在原图上画框等 import cv2 import numpy as np for n in open('list.txt'): # list.txt ...
- js基础练习题(2)
5.函数 1.按要求封装两个函数 1.封装一个函数,要求输入字符串转化成数组弹出 2.封装一个函数,要求能求出三个数中的最小值,注意:不准使用js内置函数 2.封装一个函数,求参数的和,注意:参数不固 ...
- Python 图像处理 OpenCV (12): Roberts 算子、 Prewitt 算子、 Sobel 算子和 Laplacian 算子边缘检测技术
前文传送门: 「Python 图像处理 OpenCV (1):入门」 「Python 图像处理 OpenCV (2):像素处理与 Numpy 操作以及 Matplotlib 显示图像」 「Python ...
- ASP.NET MVC 中解决Session,Cookie等依赖的方式
原文:https://blog.csdn.net/mzl87/article/details/90580869 本文将分别介绍在MVC中使用Filter和Model Binding两种方式来说明如何解 ...
- 在web开发中,为什么前端比后端更得到转行程序员的青睐?必看!
1.Web开发分类与区别 人们通常将Web分为前端和后端,前端相关的职位有前端设计师(UI/UE),前端开发工程师,后端相关的有后端开发工程师. 2.技术栈区别 看各大招聘网站上,公司对前端开发工程师 ...
- 机器学习入门:极度舒适的GBDT原理拆解
机器学习入门:极度舒适的GBDT拆解 本文旨用小例子+可视化的方式拆解GBDT原理中的每个步骤,使大家可以彻底理解GBDT Boosting→Gradient Boosting Boosting是集成 ...
- Traffic Real Time Query System 圆方树+LCA
题目描述 City C is really a nightmare of all drivers for its traffic jams. To solve the traffic problem, ...