zhuanlan.zhihu.com /p/350297509

快速的理解MakeFile+读懂一个MakeFile

25-31 minutes

是否工程开发中遇到了如下情况:

1、平时几乎不写makefile,在开源的项目或者先辈们的工程中看到了一个makefile文件,然后懵圈了;

2、上次写makefile已经是好几年前了,很多语法都忘了...;

3、尝试写个用例,执行make马上报错,还看不懂。

本文的目的主要是侧重”读“而不是“写”makefile,总结点入门指南,给有需要的同学:

~~~下面分级了,挑自己合适的级别~~~

## 零级:从未用过,初次接触;

## 初级:略懂原理,开始下手;

## 中级:以前写过,似曾相识;

-----------------------------------------------------------------------------------

1 零级

1.1 Makefile的出现

MakeFile是一个GNU推出的编译开发工具,能为一些编译过程提供服务。举个例子,你写了很多.c、.cpp、.h文件,怎么把他们编译成最后能够执行的.exe文件,

你可以手动的一步一步的编译,也可以用MakeFile来辅助你编译,用了MakeFile除了能提升效率,还能避免人为操作导致错误。

为什么是一步一步? 用惯了IDE(集成开发环境 如MVS、Qt)的同学,先回顾一下linux shell上编译套路:

一个hello.c 代码如下:

#include <stdio.h>

int main() {
  printf("Hello!");
  return 0;
}

生成hello(hello.exe)所需要执行的bash命令:gcc -v -o hello hello.c 。

该过程可拆解为4个步骤:预处理、编译、汇编、链接。大部分情况下,不需要拆分成这四步来完成文件的生成,一般都是直接生成二进制.o文件,然后链接成可执行文件(或者动态库.so .dll 或者静态库.a .lib)。

这些过程中用得比较多的就是:源码(.c .h)、机器码(.o)、静态库(.a)以及执行文件。是不是所有编译工作都能用编译器(g++/gcc/cc)完成? 是的,可以的,对于简单的工程来说你完全可以写个.sh文件完成。那为什么用MakeFile?

省事、准确。对于大的工程(文件多,一个.c对应了一个.o文件,最后还涉及.o文件和.a文件链接)编译汇编工作如果手动操作就会变得繁琐且重复,那么makefile出现能极大的简化这个重复繁琐的过程,而且对于不同的操作系统,也需要让编译自动适应。

1.2 MakeFile怎么运作

一句话:MakeFile里面大致是先处理一些环境变量或者参数,然后从某一个位置开始执行命令集。

对于一个初学者,大概浏览一个makefile:

1、区分哪些是进行的前处理/变量处理(根据规则定义或处理参数) 。

2、找到target: 包含了冒号(colon :)找到他们,target都是顶格抒写的, "<target> : <***> " , target下面的带[tab]缩进的行,就是它包含的命令,找到所有的target。

3、执行target: 一般是实现第一个target,(也可以用make 指定,如make clean,就只执行"clean" 这个target)。

来个示例:

其中参数就定义了两个,target有三个,MakeFile 可以有多个“target”,target之间可以独立,也可以相互关联/依赖,但只能从某一个开始执行,默认情况下从第一个target执行。例子中的target0由两个子target组成。

可以类比一下C语言, "变量的处理"相当于宏参数的定义,"找target并执行" 类似于找一个main函数开始执行命令;

上面举例了一个makefile的基本的内容, 省略了很多细节,比如什么是“.PHONY:”,这些都是在这个基础上慢慢加的。

-----------------------------------------------------------------------------------

2 初级

2.1 第一个用例(执行一个target)

配合上面的hello.c,然后创建一个makefile 的文件如下:

$(warning Demo1)
VAR = "I'm variable"

hello:hello.c
	$(CXX) $^ -o $@    
foo:
	@echo $(VAR)
clean:
	rm -f hello

在该目下(包含hello.c makefile)运行:make 之后就看到了hello 可执行的文件。

说明1:上面的代码target有三个(找冒号),target就执行第一个hello: ,而 foo、clean不会执行,要想执行需要指定 比如"make foo" 或者"make clean"

说明2: $(warning <...>) 这句不管执行哪个target 都会执行。

说明3:新手难懂的hello 这个target里面的“$(CXX) $^ -o $@ ” 暂时先不管 ,翻译出来是“g++ hello.c -o hello”,其中$(CXX) 默认是g++,此处也可以用 $(CC) 默认是cc命令。

2.2 第二个用例(执行多个target)

上列中Makefile的内容稍作修改:

$(warning Demo 2)
VAR = "I'm variable"

all: hello foo
hello:hello.c
	$(CC) $^ -o $@    
foo:
	@echo $(VAR)
clean:
	rm -f hello

说明:执行make后,默认执行的target是all,而all包含了hello foo ,按顺序依次执行。

2.3 第三个用例(省事的抒写方式,一次编译多个.o文件)

还是上面的用例基础,把hello.c文件复制一份,重命名为hello_copy.c。文件里面包含(hello.c hello_copy.c makefile), makefile的内容如下:

$(warning Demo 2)
obj = hello.o hello_copy.o

all:$(obj)
$(obj):%.o: %.c
	$(CC) %content%lt; -o $@
.PHONY: clean
clean:
	rm -f $(obj)

说明1: 第一个target是all,而all由两个.o文件构成,对应的两个.c文件可以同时编译;

说明2:target可以是变量,例子中的obj是一个变量。

说明3:这里加上了一个.PHONY,究竟是啥呢?它不是一个真正的target。作用就是防止make里面文件与target重名,....$%^&*&……%¥”自己搜索一下。

初级就来三个用例吧,多了讲不清,因为你缺少makefile的语法知识,“$^”是啥?if条件怎么写?loop怎么写?看中级吧

----------------------------------------------------------------------------------

3 中级

中级的水平应该是基本能看懂的水平,需要对Makefile的语法有个基本的了解。根据自己的状态读一下makefile的编写语法吧,ABC:

A 时间充足,英语比较好,看原版:

B 英语吃力的同学,推荐看看这个翻译:

C 时间不足的同学,就是实际的遇到问题,再查吧。

判断你有没有中级毕业,过一下测试,看能答对多少?

part1-格式与启动:

1.1 "独立行的可执行命令必须以什么开头?"

1.2 "如何让某些命令执行但不显示命令的内容?"

1.3 "make命令指定一个Makefile文件的方法,如make.txt?默认的搜索名称顺序是什么?"

1.4 "target执行的顺序"

1.5 "make只显示命令不执行命令?"

1.6 "执行相关的两条命令,比如cd ../../ 然后ls -h,命令是否能抒写在两行?"

1.7 "命令之前加'-'的作用?"

1.8 "在makefile中嵌套另一个makefile,执行子目录的make方法?"

1.9 "如何把父makefile里的变量传递给子目录的make?"

part-1 答案:

1.1 "Tab键,非空格。", 1.2 "以@开头即可", 1.3 '''make –f make.txt 一般用默认查找顺序“GNUmakefile”、“makefile”和“Makefile”''', 1.4 "一般执行第一个目标,除非指定 比如 make clean", 1.5 "make -n 或者 make --just-print", 1.6"不能独立的成为两行,要用';'分割。", 1.7 "不管命令是否错误都认为是成功的。", 1.8 "cd subdir && $(MAKE) 或者 $(MAKE) -C subdir", 1. 9 "export value ,若要传递全部变量,只写export,不跟参数。",

part2-字符与变量

2.1 "对一个变量取值的抒写格式?"

2.2 "输出字符$ 和# 方式?"

2.3 "%通配符表示什么?"

2.4 "s.%.c匹配的是什么?"

2.5 ".c.o: 含义?"

2.6 "赋值方式中 =、 :=、+=、?=的区别?"

2.7 "$(foo:.o=.c)的含义?"

2.8 "$($(var)_objects:.o=.c)含义?"

2.9 "%.o : CFLAGS = -g"

2.10 "$@ %content%lt; $^ $+ $? 的含义?"

part2-答案:

2.1 "${} 或者$()", 2.2 "\$ \#", 2.3 "表示一个或多个任意字符", 2.4 "s. 开头, .c 结尾的文件名, (s..c也算)", 2.5 "相当于 %o : %c", 2.6 "= 赋值, := 覆盖之前的值,?= 若没有定义过就赋值,+= 追加值", 2.7 "把foo变量里面以.o结尾的字符串替换为.c字符串", 2.8 "var_objects 里面的.o 替换为.c", 2.9"设置taget(%.o)的CFLAGS变量为-g,全局的设置将被忽略", 2.10 "$@编译的目标文件/文件集合 %content%lt; 取第一个依赖(多个按照顺序)$^ 取全部依赖(去除重复) $+取全部(不去除重复) $?比目标新的依赖集合 ",

part3-常见函数:

3.1 "ifeq语句的抒写格式?"

3.2 "define command enddef 的作用?"

3.3 "判断两个量不相等的关键字,或者判断是否定义了某个变量?"

3.4 "错误提示函数"

3.5 "$(origin <variable>)的作用?"

3.6 "if函数如何写? "

3.7 "foreach函数的抒写"

3.8 "$(join aaa bbb , 111 222 333) 返回值?"

3.9 "$(addprefix src/,foo bar) 返回值是? "

3.10 "$(addsuffix .c,foo bar) 返回值是 ?"

3.11 "$(suffix src/foo.c src-1.0/bar.c hacks) 返回值是 ?"

3.12 "$(subst ee,EE,feet on the street)返回结果?"

3.13 "$(patsubst %.c,%.o,x.c.c bar.c)返回结果?"

3.14 "$(sort <list>)作用?"

3.15"$(filter %.c %.s,$(sources))作用"?

3.16 "$(findstring <find>,<in>)作用? "

3.17 "$(strip <string>)作用?"

3.18 "$(wildcard *.c) 作用?"

3.19 "例举常见的字符串替换函数?"

3.20 "$(CXX) –c $(CPPFLAGS) $(CFLAGS) 的隐含规则?"

part3-答案

3.1 "ifeq (<arg1>, <arg2>) else endif", 3.2"定义命令包,可以重复使用", 3.3 "ifneq (<arg1>, <arg2>) endif, ifdef <variable-name> endif", 3.4"$(error <text ...>)", 3.5 "判断变量的来源,比如是undefined 、default、file、command line等", 3.6 "$(if <condition>,<then-part>,<else-part>) 如果条件非空就执行then-part", 3.7"$(foreach <var>,<list>,<text>) var在list中取值然后执行text", 3.8 "aaa111 bbb222 333", 3.9 "src/foo src/bar", 3.10"foo.c bar.c", 3.11 ".c .c", 3.12 "fEEt on the strEEt", 3.13 "x.c.o bar.o", 3.14 "对list排序。", 3.15 "返回sources里面.c和.s结尾的词", 3.16 "在<in>里面找 <find> 找到返回<find>", 3.17 "去掉前后空格", 3.18"返回所有.c的文件", 3.19"$(subst <from>,<to>,<text>) $(patsubst <pattern>,<replacement>,<text>) ", 3.20"<n>.o 的目标的依赖目标会自动推导为 <n>.cc 或是 <n>.C",

part4-常见的默认参数:

4.1 "AS参数的含义?"

4.2 "CXX参数的含义?"

4.3 "CC参数的含义?"

4.4 "RM参数的含义?"

4.5 "默认的C语言编译器参数?"

4.6 "AR参数?"

4.7 "$(AR) r $@ $*.o 含义?"

part4-答案

4.1 "汇编语言编译程序。默认命令是 as。as -o hello.o hello.s", 4.2 " C++语言编译程序。默认命令是 g++", 4.3 "C语言编译程序。默认命令是 cc", 4.4 "默认命令是 rm –f", 4.5 "CFLAGS,默认值是空", 4.6 "函数库文件的打包命令(Archive),函数库文件是对Object文件(程序编译的中间文件)的打包文件,将目标文件打包为静态链接库*.a", 4.7 ".o文件打包成目标",

------------------------------------------------------------------------------

makefile的读,没有写“高级”。高级的能“读”懂makefile,需要真正的“写”,孰能生巧,写10个以上的中小型项目吧,GitHub上面资源丰富。

最后补充点:

makefile的新手坑

常见错误1 :Makefile:2: *** recipe commences before first target. Stop.

原因:在写目标前,写了shell命令

复现:

改正:

常见错误2:Makefile:2: *** missing separator. Stop.

原因:命令前面不是TAB输入。是空格。

复现:

改正:

常见错误3:/bin/sh: -c: line 0: *****

原因:bash命令写得有问题。

比如:/bin/sh: -c: line 0: syntax error near unexpected token `fi'

      if [ ${var} -eq 2 ]; then\  # then后面需要一个空格。
        echo "hello";\ 
      fi

if的空格问题:

/bin/sh: line 0: [: 5-eq: unary operator expected

/bin/sh: [5: command not found

原因:if判断没有写对。中括号里面的空格丢失

if [ ${var3} -eq 5 ]; then echo "hello"; fi

::...
免责声明:
当前网页内容, 由 大妈 ZoomQuiet 使用工具: ScrapBook :: Firefox Extension 人工从互联网中收集并分享;
内容版权归原作者所有;
本人对内容的有效性/合法性不承担任何强制性责任.
若有不妥, 欢迎评注提醒:

或是邮件反馈可也:
askdama[AT]googlegroups.com


订阅 substack 体验古早写作:


点击注册~> 获得 100$ 体验券: DigitalOcean Referral Badge

关注公众号, 持续获得相关各种嗯哼:
zoomquiet


自怼圈/年度番新

DU22.4
关于 ~ DebugUself with DAMA ;-)
粤ICP备18025058号-1
公安备案号: 44049002000656 ...::