是否工程开发中遇到了如下情况:
1、平时几乎不写makefile,在开源的项目或者先辈们的工程中看到了一个makefile文件,然后懵圈了;
2、上次写makefile已经是好几年前了,很多语法都忘了...;
3、尝试写个用例,执行make马上报错,还看不懂。
本文的目的主要是侧重”读“而不是“写”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出现能极大的简化这个重复繁琐的过程,而且对于不同的操作系统,也需要让编译自动适应。
一句话: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:”,这些都是在这个基础上慢慢加的。
-----------------------------------------------------------------------------------
配合上面的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命令。
上列中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 ,按顺序依次执行。
还是上面的用例基础,把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怎么写?看中级吧
----------------------------------------------------------------------------------
中级的水平应该是基本能看懂的水平,需要对Makefile的语法有个基本的了解。根据自己的状态读一下makefile的编写语法吧,ABC:
A 时间充足,英语比较好,看原版:
B 英语吃力的同学,推荐看看这个翻译:
C 时间不足的同学,就是实际的遇到问题,再查吧。
判断你有没有中级毕业,过一下测试,看能答对多少?
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?"
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,不跟参数。",
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; $^ $+ $? 的含义?"
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; 取第一个依赖(多个按照顺序)$^ 取全部依赖(去除重复) $+取全部(不去除重复) $?比目标新的依赖集合 ",
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) 的隐含规则?"
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",
4.1 "AS参数的含义?"
4.2 "CXX参数的含义?"
4.3 "CC参数的含义?"
4.4 "RM参数的含义?"
4.5 "默认的C语言编译器参数?"
4.6 "AR参数?"
4.7 "$(AR) r $@ $*.o 含义?"
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上面资源丰富。
最后补充点:
原因:在写目标前,写了shell命令。
复现:
改正:
原因:命令前面不是TAB输入。是空格。
复现:
改正:
原因: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
或是邮件反馈可也:
askdama[AT]googlegroups.com
订阅 substack 体验古早写作:
关注公众号, 持续获得相关各种嗯哼: