一个通用的两级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)(之后会继续更新) 下 ...
随机推荐
- Mac上使用brew另装ruby和gem的新玩法
[本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 众所周知,Mac ...
- maven中pom.xml中配置整理: groupId、artifactId、parent、dependency、dependencyManagement区别
<groupId>com.mycompany.commonmaven</groupId> <artifactId>commonmaven</artifactI ...
- junit基本介绍视频笔记1
程序员每天工作的基本流程: 1.从svn检出代码: 2.运行单元测试,测试无误,进入下一步: 3.开始一天的代码编写工作: 4.代码提交到服务器之前进行单元测试: 5.单元测试通过提交到svn服务器. ...
- Springboot在包含有参构造方法的类中使用@Value注解取值
我们在Springboot中经常使用@Value注解来获取配置文件中的值,像下面这样 @Component class A { @Value("${user.value}") pr ...
- Web安全之验证码绕过
一,验证码绕过(on client) 首先让burpsuite处于抓包状态,打开pikachu的验证码绕过(on client)随意输入账号和密码,验证码先不输入,点击login,会提示验证码错误 然 ...
- VC GDI+基础用法VC
#include "GdiPlus.h" // 使用GDI+ 命名空间 using namespace Gdiplus; // 与GDI+ 相关的其它头文件,如:GraphicsP ...
- 记一次发布/更新npm包的过程及包版本管理
您可以发布包含package.json文件的任何目录.这里如何首次发布程序包以及如何在以后更新程序包. 如何发布包 制备 了解npm政策 在开始之前,如果您对网站礼仪,命名,许可或其他指南有疑问,最好 ...
- 关于for循环和Iterator遍历ArrayList的性能问题
今日看到@DriveMan的一篇博客,题为<ArrayList集合实现RandomAccess接口有何作用?为何LinkedList集合却没实现这接口?>,文中提到对于实现了RandomA ...
- js小案例(排他案例)
描述:点击哪个,哪个就变蓝色,其余的都是粉色 <!DOCTYPE html> <html lang="en"> <head> <meta ...
- ORA-04063: package body "DBSNMP.BSLN_INTERNAL" has errors
ORA-04063: package body "DBSNMP.BSLN_INTERNAL" has errors 问题描述: 警告日志出现报错: Sun Jun 21 00:00 ...