最近工作编译程序一直在用别人写的Makefile,但是没有系统的学习过,趁着放假学一波

makefile

0x00 Makefile 概述

一个企业级项目,通常会有很多源文件,有时也会按功能、类型、模块分门别类的放在不同的目录中,有时候也会在一个目录里存放了多个程序的源代码。

这时,如何对这些代码的编译就成了个问题。Makefile 就是为这个问题而生的,它定义了一套规则,决定了哪些文件要先编译,哪些文件后编译,哪些文件要重新编译。

整个工程通常只要一个 make 命令就可以完成编译、链接,甚至更复杂的功能。可以说,任何一个 Linux 源程序都带有一个Makefile 文件。

0x01 Makefile 的优点

管理代码的编译,决定该编译什么文件,编译顺序,以及是否需要重新编译;节省编译时间。如果文件有更改,只需重新编译此文件即可,无需重新编译整个工程;一劳永逸。Makefile 通常只需编写一次,后期就不用过多更改。

0x02 编译知识

Makefile最初是为了编译C/C++而诞生的, 所以它里面的很多隐藏规则都是针对 C/C++的。在讲 Makefile 之前有必要对 C/C++的编译有一点了解

过程如下:

compile

预处理器:将.c 文件转化成 .i 文件,使用的 gcc 命令是:gcc –E,对应于预处理命令 cpp;

编译器:将.c/.h 文件转换成.s 文件,使用的 gcc 命令是:gcc –S,对应于编译命令 cc –S;

汇编器:将.s 文件转化成 .o 文件,使用的 gcc 命令是:gcc –c,对应于汇编命令是 as;

链接器:将.o 文件转化成可执行程序,使用的 gcc 命令是: gcc,对应于链接命令是 ld;

加载器:将可执行程序加载到内存并进行执行,loader 和 ld-linux.so。

0x03 Makefile规则

Target...: Prerequsites...

Command

Command

...

Targets: Prerequisites;Command

Command

...

下面会称 Target 为目标, Prerequisites 为目标依赖, Command 为规则的命令行

Command 必须以[Tab]开始, Command 可以写成多行,通过来继行,但行尾的后不能有空格。

规则包含了文件之间的依赖关系和更新此规则 target 所需要的 Command

targets 可以使用通配符, 如果格式是"A(M)"表示档案文件(.a)中的成员“M”

在需要用本义的时候,使用两个$$来表示。

当规则的 target 是一个文件,它的任何一个依赖文件被修改后,在执行 make 时这个目标文件都会被重新编译或重新连接。如果有必要此 target 的一个依赖文件也会被先重新编译。

0x04伪目标

Makefile 中把那些没有任何依赖只有执行动作的目标称为“伪目标“(Phony targets)

.PHONY : clean

clean :

-rm edit $(objects

通过.PHONY 将 clean 声明为伪目标,避免当目录下有名为“clean”文件时,clean 无法执行

这样的目标不是为了创建或更新程序,而是执行相应动作。

0x05自动推导规则

在使用 make 编译.c 源文件时,编译.c 源文件规则的命令可以不用明确给出。这是因为 make 本身存在一个默认的规则,能够自动完成对.c 文件的编译并生成对应的.o 文件。它执行命令“cc -c”来编译.c 源文件。在 Makefile 中我们只需要给出需要重建的目标文件名(一个.o 文件),make 会自动为这个.o 文件寻找合适的依赖文件(对应的.c 文件。对应是指:文件名除后缀外,其余都相同的两个文件),而且使用正确的命令来重建这个目标文件。

例如, 现在有三个文件 test.cpp, my.cpp, my.h

image.png

test.cpp

#include

#include "my.h"

int main(int argc, char * argv[]) {

int a = 100, b = 101;

std::cout << "this code is for test makefile" << std::endl;

std::cout << xadd(a, b) << std::endl;

}

my.h

#ifndef _MY_H_

#define _MY_H_

int xadd(const int x, const int y);

#endif

my.cpp

#include "my.h"

int xadd(const int x, const int y)

{

return x + y;

}

对于上边的例子,此默认规则就使用命令“gcc -c test.cpp -o test.o”来创建文件“main.o”。对一个目标文件是“N.o”,倚赖文件是“N.c”的规则,完全可以省略其规则的命令行,而由 make 自身决定使用默认命令。此默认规则称为 make 的隐含规则。

test: test.cpp my.o

gcc -c -o test test.cpp

my.o: my.cpp my.h

gcc -c -o my.o my.cpp

clean :

rm test my.o

也可以用隐式规则

test: test.cpp my.o

my.o: my.cpp my.h

clean :

rm test my.o

效果是一样的

这里要说明一点的是, clean 不是一个文件,它只不过是一个动作名字,有点像c语言中的label一 样,其冒号后什么也没有,那么,make就不会自动去找它的依赖性,也就不会自动执行其后所定义的命令。 要执行其后的命令,就要在make命令后明显得指出这个label的名字。这样的方法非常有用,我们可以在一 个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。

0x06 规则书写建议

书写规则建议的方式是:单目标,多依赖。就是说尽量要做到一个规则中只存在一个目标文件,可有多个依赖文件。尽量避免使用多目标,单依赖的方式。

0x07 makefile 工作原理文和件搜索顺序

在默认的方式下,也就是我们只输入 make 命令。那么,

首先会搜索目录下的GNUmakefile,makefile,Makefile文件,或者make -f从指定文件读取

2.找到makefile后首先从第一个target开始,如果生成target依赖别的目标就递归从依赖开始

例如:上面的例子中,首先准备编译生成目标test,发现依赖my.o没有生成,就向下找my.o的生成,发现my.o的资源my.cpp,my.h已经就绪了,就先编译出my.o,回到test,发现 test.cpp和my.o全部就绪,使用规则Command生成目标test

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在 找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所 定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系 之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命 令将不会被自动执行,不过,我们可以显示要make执行。即命令—— make clean ,以此来清除所有 的目标文件,以便重编译。

0x08 makefile中使用变量

我们可以看到 .o 文件的字符串被重复了两次,如果我们的工程需要加入一个新的 .o 文件, 那么我们需要在两个地方加(应该是三个地方,还有一个地方在clean中)。

当然,我们的makefile并不复 杂,所以在两个地方加也不累,但如果makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方, 而导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也 就是一个字符串,理解成C语言中的宏可能会更好。

比如,我们声明一个变量 obj,表示所有obj文件,在makefile的一开始就定义

obj = my.o

maincpp = test.cpp

于是,我们就可以很方便地在我们的makefile中以 $(obj) 的方式来使用这个变量了,于是 我们的改良版makefile就变成下面这个样子:

obj = my.o

maincpp = test.cpp

test :$(maincpp) $(obj)

my.o: my.cpp my.h

clean:

rm $(obj)

于是如果有新的 .o 文件加入,我们只需简单地修改一下 obj 变量就可以了。

关于变量更多的话题,我会在后续给你一一道来。

0x09 另类风格的makefiles

既然我们的make可以自动推导命令,那么我看到那堆.o 和 .h 的依赖就有点不爽,那么多的重复的 .h ,能不能把其收拢起来,好吧,没有问题,这个对于make来说很容易,谁叫它提供了自动 推导命令和文件的功能呢?来看看最新风格的makefile吧。

obj = my.o

maincpp = test.cpp

test :$(maincpp) $(obj)

$(obj): my.h

clean:

rm $(obj) test

这种风格,让我们的makefile变得很简单,但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。 还看你的喜好了。我是不喜欢这种风格的,一是文件的依赖关系看不清楚,二是如果文件一多,要加入几个 新的.o 文件,那就理不清楚了。

0x10 清空目标文件的规则

每个Makefile中都应该写一个清空目标文件( .o 和执行文件)的规则,这不仅便于重编译,也很 利于保持文件的清洁。这是一个“修养”(呵呵,还记得我的《编程修养》吗)。一般的风格都是:

clean:

rm test $(obj)

更为稳健的做法是:

.PHONY: clean

clean:

rm test $(obj)

0x11 Makefile的文件名

默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、 “makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile” 这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”, 这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说, 大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris” ,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的-f和--file参数

make -f Makefile.Linux

make -f Makefile.mac

0x12 引用其他的Makefile

在Makefile使用include 关键字可以把别的Makefile包含进来,这很像C语言的 #include ,被包含的文件会原模原样的放在当前文件的包含位置。 include 的语法是:

include

filename 可以是当前操作系统Shell的文件模式(可以包含路径和通配符)。

在include 前面可以有一些空字符,但是绝不能是 Tab 键开始。 include 和 可以用一个或多个空格隔开。举个例子,你有这样几个Makefile: a.mk 、 b.mk 、 c.mk ,还有一个文件叫 foo.make ,以及一个变量 $(bar) ,其包含 了 e.mk 和 f.mk ,那么,下面的语句:

include foo.make *.mk $(bar)

等价于

include foo.make a.mk b.mk c.mk e.mk f.mk

如果make执行时,有 -I 或 --include-dir 参数,那么make就会在这个参数所指定的目 录下去寻找。

2.如果目录 /include (一般是: /usr/local/bin 或 /usr/include )存在的话,make也会去找。

如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的 文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是 不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以 在include前加一个减号“-”。如:

-include

0x13 环境变量MAKEFILES

如果你的当前环境中定义了环境变量 MAKEFILES ,那么,make会把这个变量中的值做一个类似于 include 的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和 include 不 同的是,从这个环境变量中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现 错误,make也会不理。

但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时, 所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许 有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。

0x14 变量定义及赋值:

变量直接采用赋值的方法即可完成定义,如:

INCLUDE = ./include/

变量取值:

用括号括起来再加个美元符,如:

`FOO = $(OBJ)`

系统自带变量:

通常都是大写,比如 CC、PWD、CFLAG,等等。

有些有默认值,有些没有。比如常见的几个:

CPPFLAGS : 预处理器需要的选项 如:-I

CFLAGS:编译的时候使用的参数 –Wall –g -c

LDFLAGS :链接库使用的选项 –L -l

变量的默认值可以修改,比如 CC 默认值是 cc,但可以修改为 gcc:CC=gcc

0x15 函数

Makefile 也为我们提供了大量的函数,同样经常使用到的函数为以下两个。需要注意的是,Makefile 中所有的函数必须都有返回值。在以下的例子中,假如目录下有 main.c、func1.c、func2.c 三个文件。

通配符:

用于查找指定目录下指定类型的文件,跟的参数就是目录+文件类型,比如:

src = $(wildcard ./src/*.c)

这句话表示:找到 ./src 目录下所有后缀为 .c 的文件,并赋给变量 src。

命令执行完成后,src 的值为:main.c func1.c fun2.c。

patsubst:

匹配替换,例如以下例子,用于从 src 目录中找到所有 .c 结尾的文件,并将其替换为 .o 文件,并赋值给 obj。

obj = $(patsubst %.c ,%.o ,$(src))

命令执行完成后,obj 的值为 main.o func1.o func2.o。

特别地,如果要把所有 .o 文件放在 obj 目录下,可用以下方法:

obj = $(patsubst ./src/%.c, ./obj/%.o, $(src))

mac linux makefile,Makefile简单入门相关推荐

  1. linux下Makefile中包含有shared library动态链接库文件时候的简单例子

    如果不知道什么是makefile,可以首先看我的另一篇博客: linux下Makefile的简单例子及解释 http://www.cnblogs.com/lihaozy/archive/2012/08 ...

  2. linux make教程,Linux下makefile的一个简单框架

    目录结构 tree . |-- Makefile `-- src |-- Makefile |-- bar | |-- Makefile | `-- bar.c `-- foo |-- Makefil ...

  3. Linux平台Makefile文件的编写基础篇和GCC参数详解

    问:gcc中的-I.是什么意思....看到了有的是gcc -I. -I/usr/xxxxx..那个-I.是什么意思呢 最佳答案 答:-Ixxx 的意思是除了默认的头文件搜索路径(比如/usr/incl ...

  4. Linux基本命令+Makefile

    1.linux下查看进程占用cpu的情况(top): 格式 top [-] [d delay] [q] [c] [S] [s] [i] [n] 主要参数 d:指定更新的间隔,以秒计算. q:没有任何延 ...

  5. Linux内核Makefile文件

    Linux内核Makefile文件(翻译自内 核手册) 转载自:http://blog.chinaunix.net/uid-21651676-id-60377.html Linux 内核Makefil ...

  6. Linux下Makefile的automake生成全攻略(转)

    Linux下Makefile的automake生成全攻略(转)[@more@] 文/余涛 作为Linux下的程序开发人员,大家一定都遇到过Makefile,用make命令来编译自己写的程序确实是很方便 ...

  7. mac退出linux命令行,mac/Linux日常入门命令行使用

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? Linux/mac 命令行操作简单介绍 linux/mac 日常入门命令行使用--文件以及文件夹操作 日常文件夹以及文件 ...

  8. Linux下Makefile编写语法

    原创 Linux下Makefile编写语法 2016-07-29 08:31:53 Datrilla 阅读数 1386更多 分类专栏: Linux Makefile 版权声明:本文为博主原创文章,遵循 ...

  9. Linux内核Makefile

    注:以下文字大部分来自韦东山<嵌入式Linux应用开发完全手册> Linux内核源码中含有很多个Makefile文件主要分为以下5类: Linux内核Makefile文件分类 顶层Make ...

最新文章

  1. 我去,你写的 switch 语句也太老土了吧
  2. 此字符不允许在标识符中使用_计算机中C语言的-基本语法
  3. mysql权限表整理
  4. R语言观察日志(part16)--Google‘s R Style Guide
  5. c# WebApi之接口返回类型详解
  6. Unreal Engine 4 —— 多线程任务构建
  7. Java读取Properties文件的六种方法
  8. matlab中给图像加几个矩形框_如何用 matlab 在图片上绘制矩形框 和 添加文字 ?...
  9. python xlwt_python的xlwt模块
  10. 微信小程序云函数发天气预报
  11. Steam 游戏服务器IP地址段
  12. openharmony常用网站
  13. ERROR: failed to establish dependency between database sgerp5 and diskgroup resource ora.DATA.dg
  14. 记一次 unicode-escape 和 utf-8 编码的互解
  15. linux笔记本设置开机自动禁用触摸板
  16. 【通用】Windows Server 2012突然无法复制文件到阿里云服务器
  17. 关于JDK8发送邮件失败的问题
  18. linux 使用 nginx 搭建 zookeeper 集群
  19. jq的ajax上传文件
  20. 可视化例子(14)——ECharts波浪图(或者称为河流图)

热门文章

  1. getAttribute()、setAttribute()、removeAttribute()的方法使用以及区别。
  2. 一场为13条公链,5万多种通证赋能的全球开发者大赛正式开始!一等奖30万!...
  3. 信息学奥赛一本通1038 苹果与虫子 计蒜客C1 苹果与虫子
  4. 2018年蓝桥杯省赛B组C++真题
  5. SharePoint 2010 主题
  6. MATLAB算法实战应用案例精讲-【自动驾驶】毫米波雷达(补充篇)
  7. 鸿蒙微内核基于,华为发布基于微内核、面向全场景的分布式操作系统:鸿蒙OS...
  8. Qt编写自定义控件插件路过的坑及注意事项
  9. 如何快速的搭建一个个人博客网站
  10. win7下硬盘安装Ubuntu 12.04