Makefile快速入门

编译概述

编译基础:

使用GCC编译程序时可以分为4个阶段:

(1)预处理(pre-processing) -E .c---->.i -I (Include) 将源文件生成中间文件
(2)编译(compiling) -S .i---->.s 将中间文件生成汇编
(3)汇编(Assembling) -c .s--->.o 将汇编转换成机器代码
(4)链接(Linking) .o--->可执行文件 -L(Link) 汇集成可执行文件

基本用法:

gcc 【options】 【filenames】

常用选项:

-c 只是编译,不生成可执行文件,将.c文件生成.o文件
-o outputfile 确定输出文件的名字为outputfile
-g 产生gdb所需要的符号信息,用于对源代码的调试
-O 优化编译链接,编译链接时间会比较慢
-O2 比-O更好的优化编译链接,编译链接时间会更加慢
-Wall 输出所有警告信息
-w 关闭所有警告信息
-Idirname 将dirname的内容加入到程序头文件目录列表中,在预处理阶段使用。I意指Include
-Ldirmane 将dirname的目录加入到程序的库文件搜索目录列表中,这是链接中使用的参数。L意指Link

makefile:

makefile文件和make工具一起使用,用于控制工程项目的编译和链接,也可以用来编写手册页和程序的安装。

make工具用于解释执行makefile文件中的内容。

makefile文件中通常包含源文件和目标文件的依赖关系以及从源文件生成目标文件的规则。

make工具可以根据makefile判断哪些文件需要被重新编译,目标文件的构建顺序等。

规则:

在讲述makefile之前,先来粗略地看一看makefile的规则。

target ... : prerequisites ...
    command
    ...
    ...

target:可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。对于标签这种特性,在后续的“伪目标”章节中会有叙述。

prerequisites:生成该target所依赖的文件和/或target

command:该target要执行的命令(任意的shell命令),一定要以tab开头

这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。即prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。

这就是makefile的规则,也就是makefile中最核心的内容。

静态模式:

静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:

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

例子如下:

objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@

如果我们的 %.o 有几百个,静态模式规则可以写完一堆规则,实在是太有效率了。

同名目标:

target1: dep1

target1: dep2
    cmd2

这种情况下,这两个相同的target1会被合并成:

target1: dep1 dep2
    cmd2

但如果第一条规则本身也带一个命令的话, makefile就无法合并, 给出警告,并用后面的规则替代前面的规则:

target1: dep1
    cmd1
target1: dep2
    cmd2

最后生成的是, 其实就是后一条替代了前一条,然后给出警告:

target1: dep2
    cmd2

可以参考Makefile 的重复目标-arley-ChinaUnix博客

常用函数:

函数调用,很像变量的使用,也是以 $ 来标识的,其语法如下:

$(<function> <arguments>)
#或者
${<function> <arguments>}

这里, 就是函数名,make支持的函数不多。 为函数的参数,参数间以逗号 , 分隔,而函数名和参数之间以“空格”分隔。函数调用以 $ 开头,以圆括号或花括号把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用 $(subst a,b,$(x)) 这样的形式,而不是 $(subst a,b, ${x}) 的形式。因为统一会更清楚,也会减少一些不必要的麻烦。

patsubst

格式:$(patsubst pattern,replacement,text)

名称:模式字符串替换函数

功能:查找text中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式pattern,如果匹配的话,则以replacement替换。这里,pattern可以包括通配符“%”,表示任意长度的字串。如果replacement中也包含“%”,那么,replacement中的这个“%”将是pattern中的那个“%”所代表的字串。(可以用“\”来转义,以“%”来表示真实含义的“%”字符)

返回:函数返回被替换过后的字符串。

示例:$(patsubst %.c,%.o, a.c b.c)把字串“a.c b.c”符合模式[%.c]的单词替换成[%.o],返回结果是“a.o b.o”

notdir

格式:$(notdir names)

名称:将参数中的路径去掉

功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分。

返回:返回文件名序列<names>的非目录部分。

示例:$(notdir src/foo.c hacks)返回值是“foo.c hacks”。

wildcard

格式:$(wildcard PATTERN...)

功能:扩展通配符

示例:$(wildcard .c ./foo/.c) 返回值是“foo.c hacks”。搜索当前目录及./foo/下所有以.c结尾的文件,生成一个以空格间隔的文件名列表。

foreach

格式:$(foreach <var>,<list>,<text>)

名称:用来做循环用

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

示例

names := a b c d
files := $(foreach n,$(names),$(n).o)

上面的例子中, $(name) 中的单词会被挨个取出,并存到变量 n 中, $(n).o 每次根据 $(n) 计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以, $(files) 的值是 a.o b.o c.o d.o

info

格式:$(info text...)

功能:打印处text的内容,相当于printf,常用于调试

示例:$(info "some text")打印 "some text"

filter

格式$(filter suffix…,$(SOURCES))

功能:目标串中找出符合匹配规则的

示例

sources := foo.c bar.c baz.s ugh.h 
foo: $(sources) 
cc $(filter %.c %.s,$(sources)) -o foo 

#使用“$(filter %.c %.s,$(sources))”的返回值给 cc 来编译生成目标“foo”,函数返回
#值为“foo.c bar.c baz.s” 

filter-out

格式$(filter-out SUFFIX…,$(SOURCES))

功能:从目标串中过滤掉符合匹配规则的

示例

objects=main1.o foo.o main2.o bar.o 
mains=main1.o main2.o 
$(filter-out $(mains),$(objects)) 

#实现了去除变量“objects”中“mains”定义的字串(文件名)功能。它的返回值
#为“foo.o bar.o”。

自动变量:

$@ :表示目标文件

$^ :表示所有的依赖文件

$< :表示第一个依赖文件

$? :表示比目标还要新的依赖文件列表

目标变量:

为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。

其语法是:

<target ...> : <variable-assignment>;

<target ...> : overide <variable-assignment>

variable-assignment;可以是前面讲过的各种赋值表达式,如 =:=+= 或是 ?= 。第二个语法是针对于make命令行带入的变量,或是系统环境变量。

这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
    $(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o : prog.c
    $(CC) $(CFLAGS) prog.c

foo.o : foo.c
    $(CC) $(CFLAGS) foo.c

bar.o : bar.c
    $(CC) $(CFLAGS) bar.c

在这个示例中,不管全局的 $(CFLAGS) 的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则), $(CFLAGS) 的值都是 -g

还有一个具体的示例:recipes/Makefile at master · chenshuo/recipes (github.com)

学以致用:

原始版本:

main.out:main.o tool1.o tool2.o
    gcc main.o tool1.o tool2.o -o main.out
main.o:main.c
    gcc main.c -c -o main.o
tool1.o:tool1.c
    gcc tool1.c -c -o tool1.o
tool2.o:tool2.c
    gcc tool2.c -c -o tool2.o
PHONY:clean
clean:
    rm -rf *.o *.out

加入变量:

OBJS = main.o tool1.o tool2.o
CC = gcc
CFLAGS += -c  -Wall -g
main.out:$(OBJS)
    $(CC) $(OBJS) -o main.out
main.o:main.c
    $(CC) main.c  $(CFLAGS) -o main.o
tool1.o:tool1.c
    $(CC) tool1.c $(CFLAGS) -o tool1.o
tool2.o:tool2.c
    $(CC) tool2.c $(CFLAGS) -o tool2.o
clean:
    $(RM) *.o *.out -r

自动变量:

OBJS = main.o tool1.o tool2.o
CC = gcc
CFLAGS += -c  -Wall -g

main.out:$(OBJS)
    $(CC) $(OBJS) -o $@
main.o:main.c
    $(CC) $^  $(CFLAGS) -o $@
tool1.o:tool1.c
    $(CC) $^  $(CFLAGS) -o $@
tool2.o:tool2.c
    $(CC) $^  $(CFLAGS) -o $@
clean:
    $(RM) *.o *.out -r

隐含规则:

OBJS = main.o tool1.o tool2.o
CC = gcc
CFLAGS += -c  -Wall -g
main.out:$(OBJS)
    $(CC) $(OBJS) -o $@
%.o:%.c
    $(CC) $^  $(CFLAGS) -o $@
clean:
    $(RM) *.o *.out -r
all:clean main.out
    @echo "clean first then compile then link"
    @echo $(OBJS)

OBJS := $(patsubst %.c,%.o,$(wildcard *.c))
CC = gcc
CFLAGS += -c  -Wall -g

main.out:$(OBJS);@echo "link"
    $(CC) $^ -o $@
    
%.o:%.c;@echo "complie"
    $(CC) $^ $(CFLAGS) -o $@

PHONY:clean
clean:
    $(RM) *.o *.out -r

练习题:

given:

all:ef cd
    @echo 123
cd:
    @echo 456
ef:
    @echo 789

输出:

789
456
123

given:

all:
    @echo 123
cd:
    @echo 456
ef:
    @echo 789

输出 :

123

given:

all:clean main.out
    @echo "clean first then compile then link"
main.out:tool1.o tool2.o main.o
    @echo "link"
    gcc main.o tool1.o tool2.o -o main.out
main.o:main.c
    @echo "link"
    gcc main.c -c -o main.o
tool1.o:tool1.c
    @echo "link"
    gcc tool1.c -c -o tool1.o
tool2.o:tool2.c
    @echo "link"
    gcc tool2.c -c -o tool2.o

PHONY:clean
clean:
    rm -rf *.o *.out

输出:

rm -rf *.o *.out
link
gcc tool1.c -c -o tool1.o
link
gcc tool2.c -c -o tool2.o
link
gcc main.c -c -o main.o
link
gcc main.o tool1.o tool2.o -o main.out
clean first then compile then link

最佳实践:

工程目录如下,请使用makefile构建工程:

├── include
│   ├── base
│   │   ├── BaseTypes.h
│   │   ├── InterfaceDef.h
│   │   ├── Keywords.h
│   │   └── stdc.h
│   ├── BaseMacro.h
│   ├── Cent.h
│   ├── common.h
│   └── Dollar.h
├── Makefile
├── README.md
├── src
│   ├── Cent.cpp
│   ├── common.cpp
│   └── Dollar.cpp
└── test
    ├── CentTest.cpp
    ├── DollarTest.cpp
    ├── FormatTest.cpp
    └── main.cpp

https://github.com/yanxicheung/usd

Q&A:

一个Makefile如何生成若干个可执行文件?

如果你的Makefile需要一口气生成若干个可执行文件,但你只 想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:

all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
    cc -o prog1 prog1.o utils.o

prog2 : prog2.o
    cc -o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
    cc -o prog3 prog3.o sort.o utils.o

Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。

由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一个标签不 会生成文件,所以不会有“all”文件产生。

于是,其它三个目标的规则总是会被决议。也就达到了我们一口 气生成多个目标的目的。 .PHONY : all 声明了“all”这个目标为“伪目标”。

(注:这里的显式 “.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一 个伪目标,执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)

Refrence:

  1. linux make makefile 内置变量 默认变量
  2. [makefile @,^, <,?](https://www.cnblogs.com/gamesun/p/3323155.html
  3. 跟我一起写Makefile
  4. Makefile中.PHONY的作用
  5. Makefile 中:= ?= += =的区别
  6. Makefile的静态模式%.o : %.c
  7. makefile内置变量及自动变量
  8. Makefile 语法及使用笔记_丨匿名用户丨的博客-CSDN博客_makefile判断文件大小
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容

  • makefile的作用:简化编译的过程,通过脚本来执行编译命令1、注释:‘#’开头:如: #这是一个注释 2、执行...
    闲人_999c阅读 1,047评论 0 0
  • 最近工作编译程序一直在用别人写的Makefile,但是没有系统的学习过,趁着放假学一波 0x00 Makefile...
    MachinePlay阅读 1,118评论 0 0
  • 1、什么是 Makefile 一个企业级项目,通常会有很多源文件,有时也会按功能、类型、模块分门别类的放在不同的目...
    zwb_jianshu阅读 50评论 0 0
  • 书写规则 规则包含两个部分,一个是依赖关系,一个是生成目标的方法。在Makefile中,规则的顺序是很重要的,因为...
    Stan_Z阅读 1,588评论 0 6
  • 隐含规则 在我们使用Makefile时,有一些我们会经常使用,而且使用频率非常高的东西,比如,我们编译C/C++的...
    Stan_Z阅读 388评论 0 0