Makefile简单入门

最近工作编译程序一直在用别人写的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)中的成员“

在需要用本义的时候,使用两个$$来表示。
当规则的 target 是一个文件,它的任何一个依赖文件被修改后,在执行 make <target>时这个目标文件都会被重新编译或重新连接。如果有必要此 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 <iostream>
#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 命令。那么,

  1. 首先会搜索目录下的GNUmakefile,makefile,Makefile文件,或者make -f从指定文件读取
    2.找到makefile后首先从第一个target开始,如果生成target依赖别的目标就递归从依赖开始
    例如:上面的例子中,首先准备编译生成目标test,发现依赖my.o没有生成,就向下找my.o的生成,发现my.o的资源my.cpp,my.h已经就绪了,就先编译出my.o,回到test,发现 test.cppmy.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>

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

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

include foo.make *.mk $(bar)

等价于

include foo.make a.mk b.mk c.mk e.mk f.mk
  1. 如果make执行时,有 -I--include-dir 参数,那么make就会在这个参数所指定的目 录下去寻找。

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

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

-include <filename>

0x13 环境变量MAKEFILES

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

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

0x14 变量定义及赋值:

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

INCLUDE = ./include/

变量取值:

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

      `FOO = $(OBJ)`

系统自带变量:

通常都是大写,比如 CCPWDCFLAG,等等。

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

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))

更多可以参考https://seisman.github.io/how-to-write-makefile/overview.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 来自陈浩的一片老文,但绝对营养。 示例工程:3 个头文件*.h,和 8 个 C 文件*.c。 初 编译过程,源文件...
    周筱鲁阅读 4,675评论 0 17
  • makefile 介绍 make命令执行时,需要一个 makefile 文件,以告诉make命令如何去编译和链接程...
    Stan_Z阅读 1,616评论 2 15
  • makefile关系到整个工程的编译规则,一个工程中的源文件不计其数,按其类型、功能、模块分别放在若干的目录当中,...
    Joe_HUST阅读 1,870评论 0 3
  • 1.Makefile规范 target 这 一 个 或 多 个 的 目 标 文 件 依 赖 于prerequisi...
    G风阅读 1,876评论 0 3
  • 早上我洗完脸刷完牙我去读了一会书写了一会字之后我休息了一会就到了吃午饭的的候了。 吃完午饭我又读了一会书读呀读,读...
    赵研博阅读 108评论 0 1