一个简单的通用Makefile实现
一个简单的通用Makefile实现
Makefile是Linux下程序开发的自动化编译工具,一个好的Makefile应该准确的识别编译目标与源文件的依赖关系,并且有着高效的编译效率,即每次重新make时只需要处理那些修改过的文件即可。Makefile拥有很多复杂的功能,这里不可能也没必要一一介绍,为了简化问题的复杂性,本文仅和大家讨论针对单目录下的C/C++项目开发,如何写一个通用的Makefile。
首先,我们假设当前工程目录为prj/,该目录下有6个文件,分别是:main.c、abc.c、xyz.c、abc.h、xyz.h和Makefile。其中main.c包含头文件abc.h和xyz.h,abc.c包含头文件abc.h,xyz.c包含头文件xyz.h,而abc.h又包含了xyz.h。它们的依赖关系如图1。
图1 文件依赖关系
第一次使用Makefile应该写成这个样子(假设生成目标main):
main:main.o abc.o xyz.o
gcc main.o abc.o xyz.o -o main
main.o:main.c abc.h xyz.h
gcc -c main.c –o main.o -g
abc.o:abc.c abc.h xyz.h
gcc -c abc.c –o abc.o -g
xyz.o:xyz.c xyz.h
gcc -c xyz.c -o xyz.o -g
clean:
rm main main.o abc.o xyz.o -f
虽然这样Makefile完全符合Makefile的书写规则,但是当代码文件再增加几倍后,再管理这些命令将会是一个噩梦!!!因此Makefile提供了默认规则和自动推导帮我们完成一些常用功能。然后,我们将Makefile修改如下:
EXE=main
CC=gcc
OBJ=main.o abc.o xyz.o
CFLAGS=-g
$(EXE):$(OBJ)
$(CC) $^ -o $@
clean:
rm $(EXE) $(OBJ) -f
变量EXE,CC,OBJ分别代指目标程序名,编译器名,目标文件名。CFLAGS是Makefile的预定义变量,它会附加在每条编译命令(gcc -c)之后。
$(EXE)是对变量的引用,$^代指所有的依赖项——即$(OBJ),$@代指目标项——即$(EXE)。该命令等价于:$(CC) $(OBJ) -o $(EXE)。
这个Makefile只有目标文件链接的命令,源文件的编译命令都被忽略了!这正是Makefile的自动推导功能——它可以将目标文件自动依赖于同名的源文件,即:
main.o:main.c
gcc -c main.c -o main.o
abc.o:abc.c
gcc -c abc.c -o abc.o
xyz.o:xyz.c
gcc -c xyz.c -o xyz.o
按照上述方式,只要工程下增加了源文件后,只需要在OBJ初始化处增加一个*.o即可。但是这种方式是有问题的,Makefile的自动推导功能只会推导出目标文件对源文件的依赖关系,而不会增加头文件的依赖关系!!!这导致的直接问题就是修改项目的头文件,不会导致make的自动更新!除非修改头文件后运行一次make clean,再运行make…… :-)
为了能让make自动包含头文件的依赖关系,我们需要做一点额外的工作。幸运的是gcc为我们提供了一个编译选项(gcc -M,对于g++是-MM),能输出目标文件的依赖关系!比如:
$gcc -M main.c
main.o:main.c abc.h xyz.h
如果将每个源文件的依赖关系包含到Makefile里,就可以使得目标文件自动依赖于头文件了!再次修改原先的Makefile:
EXE=main
CC=gcc
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
CFLAGS=-g
all:depend $(EXE)
depend:
@$(CC) -MM $(SRC) > .depend
-include .depend
$(EXE):$(OBJ)
$(CC) $(OBJ) -o $(EXE)
clean:
@rm $(EXE) $(OBJ) .depend -f
我们虚设了一个目标all,它依赖于depend和实际的目标EXE。而depend正式将所有的源文件对应的目标文件的依赖关系输入到.depend文件,并包含在Makefile内!这里有几个细节需要说明:
1..depend文件是隐藏文件,避免和工程的文件混淆。
2.include命令之前增加符号‘-’,避免第一次make时由于.depend文件不存在报告错误信息。
3.SRC初始化为wildcard *.c表示当前目录下的所有.c源文件,这就省去了我们手动输入新增的源文件。
4.OBJ初始化为SRC:.c=.o,表示将SRC中所有.c结尾的文件名替换为.o结尾的,这样就自动生成了源文件的目标文件序列。
5.clean的rm命令钱@符号表示执行该命令时不输出任何信息。
这样,每次执行make时都会重新计算目标文件的依赖关系,并输出到.depend文件,然后包含到Makefile后进行编译工作,这样目标文件的依赖关系就不会出错了!而我们得到了一个能自动包含源文件和识别头文件依赖关系的Makefile,将该文件应用于任何单目录的C/C++工程(C++需要修改部分细节,不作赘述)都能正常工作。
但是,这种方式也有一定的不足,当头文件的依赖关系不发生变化时,每次make也会重新生成.depend文件。如果这样使得工程的编译变得不尽人意,那么我们可以尝试将依赖文件拆分,使得每个源文件独立拥有一个依赖文件,这样每次make时变化的只是一小部分文件的依赖关系。
EXE=main
CC=gcc
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
DEP=$(patsubst %.c,.%.d,$(SRC))
CFLAGS=-g
$(EXE):$(OBJ)
$(CC) $^ -o $@
$(DEP):.%.d:%.c
@set -e;\
rm -f $@;\
$(CC) -M $< > $@.$$$$;\
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;\
rm -f $@.$$$$
-include $(DEP)
clean:
@rm $(EXE) $(OBJ) $(DEP) -f
该Makefile增加了一个变量DEP,初始化为patsubst %.c,.%.d,$(SRC),表示将SRC中的以*.c结尾的源文件名替换为.*.d的形式,比如main.c对应着文件.main.d,这就是main.c的依赖关系文件,且是隐藏的。
为了生成每个源文件的依赖文件,建立了目标依赖关系$(DEP):.%.d:%.c,该关系表示,对于目标DEP,通过$@可以访问一个依赖文件,通过$>则访问对应的同名源文件。命令部分使用\连接,表示当前命令作为一个整体在一个进程内执行。该组命令的含义是:将gcc -M生成的信息输出到一个临时文件,然后在:之前加上当前的文件名输出到依赖文件。比如对于main.c生成的临时文件信息为:
main.o:main.c abc.h xyz.h
处理后依赖文件信息是:
main.o .main.d:main.c abc.h xyz.h
这样的依赖关系表示main.o和它的依赖关系文件的依赖项是一致的,只要相关的源文件或头文件发生了改变,才会重新生成目标文件和依赖关系文件,也就达到了依赖关系文件单独更新的目的了。
虽然如此,但是这样的Makefile也不是完美的。现假设工程目录内新增一个源文件lmn.c,按照Makefile的指令make后会产生.lmn.d依赖关系文件。而如果我们再删除lmn.c源文件后,重新make后.lmn.d依然存在!尤其是当重复增删很多源文件后,工程目录下可能会存在很多无用的依赖文件,当然这些问题可以通过make clean解决。
通过前边的讨论,我们得到一个能在单目录工程下工作的通用Makefile,至于是实现为单独一个依赖文件的形式,还是每个源文件产生一个独立的依赖文件,要根据程序作者自己的喜恶来选择。虽然每种方法都有一些细微的瑕疵,但是不影响这个通用的Makefile的实用性,试想一下在工程目录下拷贝一份当前的Makefile,稍加修改便可以正确的编译开发,一定会令人心情大好。希望本文对你学习Linux写的程序开发有所帮助!
一个简单的通用Makefile实现的更多相关文章
- 向大家推荐一个C/C++通用Makefile
在使用 Makefile 之前,只需对它进行一些简单的设置即可:而且一经设置,即使以后对源程序文件有所增减一般也不再需要改动 Makefile.因此,即便是一个没有学习过 Makefile 书写规则的 ...
- 一个简单驱动的makefile
KVERS = $(shell uname -r) #Kernel modulesobj-m += hello.o build: kernel_modules kernel_modules: make ...
- C/C++通用Makefile
最近的项目又回到了Linux上运行,这就需要在Linux下编译项目,写Makefile针对习惯了Windows的程序员来说是一件痛苦的事,如果有一个通用的Makefile该多好啊,本着这样的目的,我再 ...
- 一步一步写一个简单通用的makefile(三)
上一篇一步一步写一个简单通用的makefile(二) 里面的makefile 实现对通用的代码进行编译,这一章我将会对上一次的makefile 进行进一步的优化. 优化后的makefile: #Hel ...
- 一步一步写一个简单通用的makefile(一)
经常会用写一些小的程序有的是作为测试,但是每次都需要写一些简单的GCC 命令,有的时候移植一些项目中的部分代码到小程序里面进行测试,这个时候GCC 命令并不好些,如果写啦一个比较常用的makefile ...
- [置顶]
自己写一个简单通用的Makefile
转自:http://blog.csdn.net/u011913612/article/details/52102241 一.makefile的作用 Makefile是用于自动编译和链接的,一个工程有很 ...
- C 封装一个通用链表 和 一个简单字符串开发库
引言 这里需要分享的是一个 简单字符串库和 链表的基库,代码也许用到特定技巧.有时候回想一下, 如果我读书的时候有人告诉我这些关于C开发的积淀, 那么会走的多直啊.刚参加工作的时候做桌面开发, 服务是 ...
- 一个通用Makefile详解
我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文 件. 如果我们用gcc去一个一个编译每一个源文件的话,效率会低很多,但是如果我们可以写 ...
- 一个通用Makefile的编写
作者:杨老师,华清远见嵌入式学院讲师. 我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文件.如果我们用gcc去一个一个编译每一个源文件的 ...
随机推荐
- cv_prj2
Computer Vision Project 2 – Harris Corner Detector 姓名: 王兴路 学号: 3140102282 指导老师: 宋明黎 2016-12-16 19:30 ...
- C#之方法的重载与递归
(1). 方法的重载:方法的名称相同,但参数不同. 1.如果参数的个数相同,那么参数的类型不能相同. 2.如果参数的类型相同,那么参数的个数不能相同. (2).方法的递归:方法自己调用自己.用于找出一 ...
- 关于webstorm 对 vue的设置
1. 首先安装vue插件,安装方法: setting --> plugin ,点击plugin,在内容部分的左侧输入框输入vue,会出现两个关于vue的插件,点击安装即可.安装完成后,就可 ...
- 移动web前端小结(一)--摘自小鹿_同学
一.框架 框架:Bootstrap+HTML5 Boilerplate. 两个框架整合到一起可以看一下这位大神的文章:<使用 Bootstrap 和 HTML5 Boilerplate 开始一个 ...
- oracle权限语句大全
Oracle 系统默认的几个用户: sys --------网络管理用户,具有最高数据库管理权限 system------本地管理用户,权限次于sys scott-------普通用户,默认是锁住的( ...
- 卸载AppDomain动态调用DLL异步线程执行失败
应用场景 动态调用DLL中的类,执行类的方法实现业务插件功能 使用Assembly 来实现 但是会出现逻辑线程数异常的问题 使用AppDomain 实现动态调用,并卸载. 发现问题某个插件中开启异步线 ...
- 经验总结之Android framework开发
本人在某做手机ROM的厂商工作近两年,虽然工作年限不长,但是对此类“工种”已有比较初步的认识,写下来供大家参考借鉴. framework的工作相对于其他比较成熟的软件开发类似,分为维护和需求开发. 需 ...
- iOS 设置视图半透明而子控件不透明
用一张半透明的图片做背景 使用colorWithWhite:alpha:方法 white后面的参数表示灰度,从0-1之间表示从黑到白的变化,alpha就是你想调整的透明度 bgView.backgr ...
- [UCSD白板题] Pairwise Distinct Summands
Problem Introduction This is an example of a problem where a subproblem of the corresponding greedy ...
- hdu 5543 Pick The Sticks(动态规划)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5543 题意:给你一根长为m的长木板和一些小木棒,每一根小木棒有它的长度和价值,这些小木棒要放在长木板上 ...