Makefile用法分析

   

在linux开发中,应用程序的编译基本都采用GNU的make工具,而make搭配Makefile来实现工程代码的编译,在越是大型复杂的项目中,make的强悍之处越是明显。在使用了一段时间make后,对其用法进行分析。本文是在学习了陈皓的“跟我一起学Makefile”文章后,对自己学习的小结。

一.简单的例子

现在,我们有一个C++的项目需要进行编译,项目包含3个头文件,3个.cpp文件,分别是main.cpp,main.h,read.cpp,read.h,write.cpp,write.h,下面就是我们的Makefile了:

我们需要生成的目标是可执行文件test,test由中间目标文件.o链接生成,.o文件根据依赖关系由相应的头文件和.cpp源文件编译而成,伪目标clean用于清除目标文件和.o文件。

在我的Makefile中,我们发现编译工具cc即使不指明为g++也可以正常编译,但是如果是在目标文件链接时不指明的话,会有提示找不到相关的库,那关于这个cc到底是根据什么来选择编译工具的呢?通过查阅资料和测试发现,编译程序cc一般默认的都是gcc,当我们对源文件编译时,对于.c文件,gcc和g++分别识别为c和c++程序,对于.cpp文件,两者都会识别为c++程序,所以在编译时我们完全可以用默认cc来编译.c和.cpp文件(当然对于其他平台或嵌入式中还是要指定对应的编译工具,防止出错)。但是链接时,需要对.o中间文件链接库生成可执行程序,g++会链接c++的库,gcc链接c的库,所以如果链接时需要指明编译器。在平常使用中,考虑到的易维护和防出错,都是统一将编译和链接的编译器都指明为g++。

好,通过这个Makefile实现了我们的功能需求,但是我们发现,这种Makefile虽然逻辑简单,但是一旦当项目体积非常大时,写Makefile会是一件非常痛苦的事,需要写几百个中间目标文件的依赖关系,OK,别担心,GNU的make很强大,它可以通过隐晦规则进行自动推导。

二.隐晦规则和自动推导

GNU的make工具存在隐晦规则,只要make看到一个.o文件,他会默认把对应的.c或.cpp(后面讨论时会都用.c文件来讨论)文件加到依赖关系中,比如如果有一个main.o,那么他会默认把main.c加到其依赖关系中,并且make会对其进行自动推导,包括文件的依赖关系和后面的命令。那么,我们又可以得到一个新的Makefile了。

这样我们得到了一个看起来更简洁的Makefile,我们不用书写每个中间目标文件的编译指令了,因为make会自动推导,但是这样貌似只是部分降低了我们的劳动力,对于几百个源文件的程序,我们还是需要写出来每个文件隐晦规则之外的依赖关系,虽然不用写编译指令,但还是非常多,而且这样的Makefile通用性并不高,如果我们写了个其他的测试程序,这个Makefile我们还是需要更改许多,OK,make这个工具的静态模式和自动变量可以帮我们解决这个问题。

三.静态模式和自动变量

Make中的静态模式的语法如下:

<targets ...>: <target-pattern>: <prereq-patterns ...>

<commands>

....

targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。

target-parrtern是指明了targets的模式,也就是的目标集模式。

prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。

举个例子,对于前面的objects目标集合,<target-parrtern>定义成“%.o”,意思是我们的<target>集合中都是以“.o”结尾的,而如果我们的<prereq-parrterns>定义成“%.c”,意思是对<target-parrtern>所形成的目标集进行二次定义,其计算方法是,取<target-parrtern>模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。如main.o通过模式取到的依赖目标就是main.c文件。静态模式帮助我们完成将几百个源文件用一种通用的模式来代表,我们可以不用写出所有中间文件的依赖关系,只要用一个静态模式就OK了。

接下来有个问题,在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。这就需要make 的自动化变量。

Make的自动化变量。 所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。

下面是所有的自动化变量及其说明:

$@

表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于

目标中模式定义的集合。

$%

仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a (bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。

$<

依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。

$?

所有比目标新的依赖目标的集合。以空格分隔。

$^

所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

$+

这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。

$*

这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo"。这个特性是GNU make的, 很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。

更新后的Makefile如下:

OK,写到这里,我们的Makefile已经非常非常简洁了,但是这里面存在两个问题,第一个问题是,我们所有的目标文件的依赖关系都只有相应的源文件,并没有其他所需头文件的依赖关系,比如main.o的依赖关系包含了main.cpp,main.h,read.h,write.h,在首次编译时,依赖关系会找到相应的相应的头文件,但是之后如果修改了某个头文件,则再次编译头文件并不会更新,所以这种方式中缺少头文件的依赖关系,一种解决方法是每次重新编译时,先清空之前的编译目标文件就OK了,还有一种方法是建立.c文件的依赖关系(下次再写)。

第二个问题是,我们发现,我们还是要写出所有的目标文件集,同样的,对于几百个中间文件的项目来说,这也是很头疼的事,我们可以通过通配符和一些函数来解决这个问题。

我们的需求是让make自己找到当前目录下所有的源文件,并且对这些源文件根据依赖关系进行编译处理,实现完全的自动化。首先我们需要用通配符*来找到所有的源文件,并对应生成所有源文件对应的中间目标文件,这主要通过patsubst函数来实现,该函数是模式字符串替换函数(对于所有的函数可以查询后文的函数表),帮我们将依赖目标集的所有源文件对应生成.o文件。

Makefile写到这里,应该是简洁性和通用性都比较高的了,但是,现在的makefile在实际中可用性不高,因为实际项目中源文件往往放置在多个文件夹中,而且还会设置许多编译参数,所以下面将会写一个体现源文件结构复杂度和编译器参数的makefile。

四.编译器参数

现在,我们假设,我们的read.cpp,read.h在当前目录的read目录下,write.cpp和write.h在当前目录下的write目录下,那么我们新的makefile又出炉了。

原有的wildcard是匹配当前目录下所有的源文件,但是有多个文件夹,就无法匹配到了,于是在这里我们就可以使用VPATH这个变量(这个make默认识别的变量用于在当前目录下查找文件查找不到时,默认到VPATH路径下查找),同时还需要使用一个函数forreach。由于这里只匹配了源文件,相应的头文件的位置没有包含,所以还需要包含一下头文件。

OK,到这里,貌似我们已经写出了一个比较好的Makefile,但是,实际的编译参数我们都还没涉及,下面就具体分析一下,GNU下的编译工具的编译参数。

五.链接库

有时我们需要链接第三方库,那么如何链接库,如何使用库中的函数?

首先,在源代码中,我们需要包含所使用的库的接口函数的头文件;

其次,在Makefile中,我们需要在头文件和库的搜索路径中分别加入所需头文件和库的路径。假设库和库的头文件在路径/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209路径下,那么包含方式

头文件:-I/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209    //加入头文件路路径

库:-L/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209  //加入库文件路径

如果库的名字是libminiupnpc.a,那么在链接时的参数就是 -lminiupnpc

类似于下图:

结束。

Makefile学习总结的更多相关文章

  1. [转]Windows平台下Makefile学习笔记

    Windows平台下Makefile学习笔记(一) 作者:朱金灿 来源:http://blog.csdn.net/clever101 决心学习Makefile,一方面是为了解决编译开源代码时需要跨编译 ...

  2. makefile 学习归纳

    makefile 学习归纳 一直希望 好好整理下 makefile的写法,这在linux编程界是必备技能.下面就好好的说道说道. 可以参考的大神总结 整理 makefile是供make命令执行的 脚本 ...

  3. <转>Windows平台下Makefile学习笔记(二)

    本文转自:http://blog.csdn.net/clever101/article/details/8286066 上次我们学习了怎么用Makefile编译一个控制台工程.这次我们学习一下如何使用 ...

  4. makefile学习(1)

    GNU Make / Makefile 学习资料 GNU Make学习总结(一) GNU Make学习总结(二) 这篇学习总结,从一个简单的小例子开始,逐步加深,来讲解Makefile的用法. 最后用 ...

  5. (二)我的Makefile学习冲动&&编译过程概述

    前言 一 年轻的冲动 二 学习曲线 1 Makefile基本语法 2 bash基础 3 world 三 编译过程概述 1 主机预装工具 2 编译host工具 3 编译交叉工具链 4 编译内核模块 5 ...

  6. Makefile学习笔记

    ls -l 查看文件详细信息 1.gcc -E test.c -o test.i//预编译gedit test.i //查看:高级C 2.gcc -Wall -S test.i -o test.s// ...

  7. makefile学习笔记(多目录嵌套调用、变量使用)

    http://blog.csdn.net/leexiang_han/article/details/9274229   学习了几天的makefile的嵌套调用编写也有一些心得,先声明,我也是初学者写文 ...

  8. Makefile学习(一)变量

    鉴于之前有一些了解,还有自己的学习习惯,我一上来就看Makefile的变量这一章.主要脉络是根据GNU make中文手册. 第六章:Makefile中的变量 6使用变量 定义:变量是一个名字,代表一个 ...

  9. makefile 学习一

    近期在学习nginx,由于实在linux下,一些代码须要用makefile文件来编译,比較节省时间. 由于在nginx中加入一个新的模块假设用./configure方法来加入,特别是当你的代码有错时, ...

随机推荐

  1. 论MySQL何时使用索引,何时不使用索引

    索引: 使用索引可快速访问数据库表中的特定信息.索引是对数据库表中一列或多列的值进行排序的一种结构,例如 employee 表的姓(name)列.如果要按姓查找特定职员,与必须搜索表中的所有行相比,索 ...

  2. Linux Namespaces机制——实现

    转自:http://www.cnblogs.com/lisperl/archive/2012/05/03/2480573.html 由于Linux内核提供了PID,IPC,NS等多个Namespace ...

  3. 你知道现在有一种新的OCR技术叫“移动端车牌识别”吗?

    核心内容:车牌识别.OCR识别技术.移动端车牌识别.手机端车牌识别.安卓车牌识别.Android车牌识别.iOS车牌识别 一.移动端车牌识别OCR技术研发原理 移动端车牌识别是基于OCR识别的一种应用 ...

  4. 编写原生的Node.js模块

    导语:当Javascript的性能遭遇瓶颈,或者需要增强Javascript能力的时候,就需要依赖native模块来实现了. 应用场景 日常工作中,我们经常需要将原生的Node.js模块做为依赖并在项 ...

  5. FFmpeg安装(windows环境)

    ♣FFmpeg是什么? ♣FFmpeg组成 ♣下载工具 ♣安装FFmpeg ♣应用到j2ee项目 前言:学习视频编码,一定要知道雷霄骅(leixiaohua1020)的专栏 ,伟大的程序员,26岁去世 ...

  6. JavaSE教程-04Java中循环语句for,while,do···while-思维导图

    思维导图看不清楚时: 1)可以将图片另存为图片,保存在本地来查看 2)右击在新标签中打开放大查看

  7. Java web中常见编码乱码问题(一)

    最近在看Java web中中文编码问题,特此记录下. 本文将会介绍常见编码方式和Java web中遇到中文乱码问题的常见解决方法: 一.常见编码方式: 1.ASCII 码 众所周知,这是最简单的编码. ...

  8. ASP微信开发获取用户经纬度

    wx.config({ //debug: true, debug: true, appId: '<%= appId %>', timestamp: '<%= timestamp %& ...

  9. java原生实现屏幕设备遍历和屏幕采集(捕获)等功能

    前言:本章中屏幕捕获使用原生java实现,屏幕图像显示采用javacv1.3的CanvasFrame 一.实现的功能 1.屏幕设备遍历 2.本地屏幕图像采集(也叫屏幕图像捕获) 3.播放本地图像(采用 ...

  10. Bootstrap 常用组件汇总

    Bootstrap 官方文档:http://www.bootcss.com/ Bootstrap Multiselect 多选下拉组件 官方文档:http://www.kuitao8.com/demo ...