makefile 入门使用

通常一个大型程序由多个模块文件构成的,按照其功能划分,模块文件会分布在不同的目录中,模块文件之间有包含有头文件,调用函数的情况,他们之间存在依赖关系。

大多数情况下,我们只是修改了其中某些文件,而不是所有文件,按理说,我们只需要把那些修改过的文件,并且依赖于这些文件的相关文件重新编译即可,用不着重新编译全部文件。

所以问题来了,有没有办法自动对那些有改过的文件重新编译呢?这实际上要分为 2 个问题:

  • 目标文件依赖哪些文件?
  • 依赖的文件是否更新?

为了解决这2个问题,业界就有了 make 和 makefile。总的来说,它俩并不是用来编译程序,它们只负责找出哪些文件变化,并且根据依赖关系找出受影响的文件,然后执行事先在 makefile 中定义好的命令规则。只是该规则大多数情况是调用 gcc 或 nasm 进行编译。因此这也就完成了我们上面提到的 2 个问题。

makefile 基本语法

目标文件:依赖文件
[tab] 命令

eg:
a.o: a.c
  gcc -c a.c -o a.o
a.out: a.o
  gcc a.o -o a.out
  • 目标文件是指此规则中想要生成的文件,可以是 .o 结尾的目标文件,也可以是可执行文件,也可以是伪目标。
  • 依赖文件是指要生成此规则中的目标文件。通常不止一个,是一个依赖文件的列表。
  • 命令是指此规则中要执行的动作,各种 shell命令,一个命令单独占用一行,行首必须以 Tab 开头。

根据以上规则,我们已经知道了目标文件依赖哪些文件,但还需要解决第2个问题。怎么判断依赖的文件是否更新?

在 Linux 中,每个文件有3种时间:

  • atime
    access time 访问文件数据部分时间
  • ctime
    change time 文件属性部分或数据部分的改变时间
  • mtime
    modify time 文件数据部分的修改时间
1:2
    @echo "makefile test ok"

这个makefile文件的意义就是 " 如果文件2的 mtime 比文件1的 mtime 要新,则打印 "makefile test ok" "。

综上,依赖关系定义在文件 makefile 中,make 程序通过解析 makefile 文件,根据mtime标签自动找出 变更的文件 以及 依赖此变更文件的目标文件,然后对所有受影响的相关文件执行事先定义好的命令规则。

makefile 的文件名并不固定,可以用 -f 参数指定。默认是先寻找名为 GNUmakefile 的文件,若该文件不存在再去找名为 makefile 的文件,若 makefile 也不存在,最后去找名为 Makefile 的文件。

t1:1
  @echo "target1"
t2:1
  @echo "target2"

若采用make 目标名称,比如make t2,则会单独执行目标名称处的规则。
若采用make ,则会单独执行在 makefile 中第一个出现的目标。

变量

= := ?= 区别
目标变量

伪目标

有时我们有"并不关心是否产生真实的目标文件,我们只希望通过 make 不要考虑 mtime,而是总能去执行一些命令",比如

clean:
    rm *.o     # 清理文件

这时,make规定,当规则中不存在依赖文件时,这个目标文件名就称为 -- 伪目标。

  • .PHONY

  • 约定俗成的伪目标名称

伪目标名称 功能描述
all 通常用于完成所有模块的编译工作
clean 通常用于清空编译完成的所有目标文件
dist 将打包文件再压缩成gz 文件
Install 通常将编译好的程序复制到安装目录下
printf 通常用于打印已经发生改变的文件
tar 通常用于将文件打包成tar文件
test 测试makefile 流程

make: 递归式推导目标

test1.o : test1.c
  gcc -c test1.c -o test1.o

test2.o : test2.c
  gcc -c test2.c -o test2.o

test.bin : test1.o test2.o
  gcc test1.o test2.o -o test.bin

all : test.bin
  @echo "compile done"

当执行make all时,make 首先发现 all 依赖于 test.bin,寻找 test.bin 不存在,于是便去寻找 test.bin的依赖文件 test1.o test2.o,同样不存在,于是寻找能生成 test1.o 和 test2.o 规则。找到后执行相应规则,有了test1.o 和 test2.o 再返回生成 test.bin,最后在执行生成 all 的规则。

自定义变量与系统变量

makefile中定义变量基本格式:

变量名=值    # 多个值之间用空格分开,值仅仅支持字符串类型。即使数字也会当作字符串处理
eg:

objfiles = test1.o test2.o
test.bin: $(objfiles)

除了自定义的变量,make也定义了很多系统变量

变量名 描述
AS 汇编语言编译器,默认是 as
CC C语言编译器,默认是 gcc
CXX C++语言编译器,默认 g++
RM 删除命令,默认是 rm -f
ASFLAGS 汇编语言编译器参数,无默认值
CFLAGS C语言编译器参数,无默认值
CXXFLAGS C++编译器参数,无默认值
LDFLAGS 链接器参数,无默认值

隐含规则

对于一些使用频率非常高的规则,make 把它们当成是默认的,不需要显式地写出来,当用户未在 makefile 中显示定义规则时,将默认使用隐含规则进行推导。

隐含规则只限于那些编译过程中基本固定的依赖关系,比如C语言代码文件扩展名为.c,编译生成的目标文件扩展名是.o。并且若想通过隐含规则自动推导生成目标,存在于文件系统上的文件,除扩展名之外的文件名部分必须相同。

自动化变量

为了方便,make 还支持一种自动化变量:

自动化变量 描述
$@ 表示规则中目标文件名集合
$< 表示规则中依赖文件中的第1个文件
$^ 表示规则中所有依赖文件的集合
$? 表示规则中所有比目标文件mtime更新的依赖文件的集合
test1.o : test1.c
  gcc -c test1.c -o test1.o

test2.o : test2.c
  gcc -c test2.c -o test2.o

objfiles = test1.o test2.o
test.bin : $(objfiles)
  gcc $^ -o $@

all : test.bin
  @echo "compile done"

再方便的就是利用正则表达式匹配,%.o 表示多有以 .o 结尾的文件,make 会拿这个字符串模式去文件系统上查找文件,默认当前路径。

%.o : %.c  # test1.c test2.c 都在当前目录。
  gcc -c $^ -o $@

objfiles = test1.o test2.o
test.bin : $(objfiles)
  gcc $^ -o $@

all : test.bin
  @echo "compile done"

makefile 中执行shell命令

SVN_VERSION:=$(shell svnversion)
COMP_DATE:=$(shell date)

vim 编辑makefile

vim设置 tab 键为 4 个空格,编辑 makefile 时 可以先敲 ctrl-v 组合键,再敲 tab键,这样就不会被转换成空格了。

makefile中常用函数

  • wildcard
    扩展通配符

  • notdir
    去除目录信息,只保留文件名。

  • patsubst
    替换通配符

    # tree
    .
    ├── a
    │   ├── a2.cpp
    │   └── a.cpp
    ├── b
    │   ├── b2.cpp
    │   └── b.cpp
    └── Makefile
    
    # cat Makefile
    src=$(wildcard ./a/*.cpp ./b/*.cpp)
    dir=$(notdir $(src))
    obj=$(patsubst %.cpp,%.o,$(dir))
    
    all:
      @echo $(src)
      @echo $(dir)
      @echo $(obj)
    

    输出:

    ./a/a2.cpp ./a/a.cpp ./b/b2.cpp ./b/b.cpp
    a2.cpp a.cpp b2.cpp b.cpp
    a2.o a.o b2.o b.o
    
  • foreach

    $(foreach <var>,<list>,<text>)
    

    这个函数的意思是,把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。
    每一次<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

    # cat Makefile
    SRC_PATH=./a/ ./b/
    SRC_FILE=$(foreach SUB_DIR,$(SRC_PATH),$(wildcard $(SUB_DIR)*.cpp))
    ALL_FILE=$(notdir $(SRC_FILE))
    OBJ_FILE=$(patsubst %.cpp,%.o,$(ALL_FILE))
    
    info:
      @echo $(SRC_FILE)
      @echo $(ALL_FILE)
      @echo $(OBJ_FILE
    

    输出

    ./a/a2.cpp ./a/a.cpp ./b/b2.cpp ./b/b.cpp
    a2.cpp a.cpp b2.cpp b.cpp
    a2.o a.o b2.o b.o
    

一个简单的Makefile模板

目录规划
# tree
.
├── log
│   ├── AsyncLog.cpp
│   └── AsyncLog.h
├── main.cpp
├── Makefile
├── net
│   ├── Acceptor.cpp
│   ├── Acceptor.h
│   ├── Buffer.cpp
│   ├── Buffer.h
...
Makefile
CXX=g++

COMP_DATA:=$(shell date)

OUT_PATH=./bin/
OBJ_PATH=./obj/
DEBUG_PATH=debug/
RELEASE_PATH=release/
OUT_NAME=main

SRC_PATH=./ ./net/ ./log/
VPATH = ./ : ./net/ : ./log/
SRC_FILE=$(foreach SUB_DIR,$(SRC_PATH),$(wildcard $(SUB_DIR)*.cpp))
ALL_FILE=$(notdir $(SRC_FILE))
OBJ_FILE=$(patsubst %.cpp,%.o,$(ALL_FILE))

OBJ_DEBUG_FILE=$(addprefix $(OBJ_PATH)$(DEBUG_PATH),$(notdir $(OBJ_FILE)))
OUT_DEBUG_FILE=$(OUT_PATH)$(DEBUG_PATH)$(OUT_NAME)

DEBUG_CXXFLAGS=-g -Wall -Wno-conversion-null -Wno-format-security -Werror -DCOMP_DATE='"$(COMP_DATA)"'

debug: pre_debug $(OUT_DEBUG_FILE)

pre_debug:
    -$(shell mkdir $(OBJ_PATH) -p)
    -$(shell mkdir $(OBJ_PATH)$(DEBUG_PATH) -p)
    -$(shell mkdir $(OUT_PATH) -p)
    -$(shell mkdir $(OUT_PATH)$(DEBUG_PATH) -p)

$(OUT_DEBUG_FILE) : $(OBJ_DEBUG_FILE)
    $(CXX) $(DEBUG_CXXFLAGS) $(addprefix $(OBJ_PATH)$(DEBUG_PATH), $(notdir $^)) -o $@ -lpthread

$(OBJ_PATH)$(DEBUG_PATH)%.o : %.cpp
    $(CXX) -c $(DEBUG_CXXFLAGS)  $< -o $@

clean:
    @echo "make clean"
    -$(shell rm $(OBJ_PATH) -rf )
    -$(shell rm $(OUT_PATH)$(DEBUG_PATH)$(OUT_NAME) -f)
    -$(shell rm $(OUT_PATH)$(RELEASE_PATH)$(OUT_NAME) -f)

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

推荐阅读更多精彩内容

  • 来自陈浩的一片老文,但绝对营养。 示例工程:3 个头文件*.h,和 8 个 C 文件*.c。 初 编译过程,源文件...
    周筱鲁阅读 4,668评论 0 17
  • 一.makefile格式 1.makefile 文件一般命名 为makefile 即可,放在目录下面,直接调出 t...
    浩林Leon阅读 7,709评论 0 2
  • 0. 作用 Makefile文件告诉Make怎样编译和连接成一个程序。 1. Makefile基本语法与执行 示例...
    jdzhangxin阅读 4,553评论 2 6
  • makefile关系到整个工程的编译规则,一个工程中的源文件不计其数,按其类型、功能、模块分别放在若干的目录当中,...
    Joe_HUST阅读 1,870评论 0 3
  • 2017-11-06红旗小散红旗小散日记 (免责声明:本人复盘以及个股观点,仅供交流参考,不作为投资依据。) 一、...
    红旗小散阅读 159评论 0 0