Makefile Note (Update in 2022-03-29)
- make file 的组成部分 
 1. make file 由 变量,目标,规则,注释,函数等部分组成
 2. 其中目标则代表编译的目标,如果编译目标的规则并不会生成目标文件时,则需要 .PHONY 指明
 3. 变量的复制有如下几种:a = b,a := b,a ?= b,a += b
 4. 规则为执行目标生成,或特定任务处理的一组脚本集合,该集合的脚本将会由/bin/sh指定的 shell 解释器来执行,而且此处的注释也交由解释器解释
 5. 常见的函数有:$(error xxx),$(warning xxx),$(subst from,to,text),$(patsubst pattern,replacement,text),$(strip string),$(findstring find,in),$(filter pattern...,text),$(sort list),$(call ...),$(shell ...),$(foreach var, list, text),$(join ...),$(basename ...),$(nodir ...),$(dir ...)...
- make 返回值
    
 make 总共有三个返回值:0,1,2
 0 执行成功
 1 执行失败
 2 加了-q选项,并且 make 不需要对一些目标更新
- make 的参数
    
 检查规则- -n / --just-print / --dry-run / --recon不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试 makefile 很有用处。
- -t / --touch把目标文件的时间更新,但不更改目标文件。
- -q / --question找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。
- -W file / --what-if=file / --assume-new=file / --new-file=file这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。
 
 常规参数- -C dir / --dirctory=dir指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make -C ~hchen/test -C prog”等价于“make -C ~hchen/test/prog”。
- -f可以用于指定 make 用于解释的根 makefile, 比如- make –f build/platform_x86.mk
- -I dir / --include-dir=dir指定一个被包含 makefile 的搜索目标。可以使用多个 “-I” 参数来指定多个目录。
- -debug=options输出 make 的调试信息,有以下几个选项:- a / b / v / i / j / m, make 的- -d参数相当与- --debug=a。
- -j指明同时运行命令的个数,如果不指明该参数,则 GNU make 默认同一时间只有一条命令执行。 这个值通常和你 CPU 的核数(n)相关,如果你全速编译可以设置- j(1.2-1.5)*n, 如果你还有其他作业需要处理,请适当调整到- j的值为 n-(1~3)。
- -i在执行时忽略所有的错误。
- -k / --keep-going出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了。
- -r禁止 make 使用任何隐含规则。
- -R / --no-builtin-variabes禁止make使用任何作用于变量上的隐含规则。
- -s / --silent / --quiet在命令运行时不输出命令的输出。
- -S / --no-keep-going / --stop取消 -k 选项的作用。因为有些时候, make 的选项是从环境变量 “MAKEFLAGS” 中继承下来的。 所以你可以在命令行中使用这个参数来让环境变量中的 -k 选项失效。
- -o file / --old-file=file / --assume-old=file不重新生成的指定的 file,即使这个目标的依赖文件新于它。
- -p / --print-data-base输出 makefile 中的所有数据,包括所有的规则和变量。 这个参数会让一个简单的 makefile 都会输出一堆信息。如果你只是想输出信息而不想执行 makefile,你可以使用- make -qp命令。 如果你想查看执行 makefile 前的预设变量和规则,你可以使用- make –p –f /dev/null。 这个参数输出的信息会包含着你的 makefile 文件的文件名和行号,所以,用这个参数来调试你的 makefile 会是很有用的,特别是当你的环境变量很复杂的时候。
- -B / --always更新所有目标(重编译)
- -e / --environment-overrides指明环境变量的值覆盖 makefile 中定义的变量的值。
- -W file / --what-if=file / --new-file=file / --assume-file=file假定目标 file 需要更新,如果和 “-n” 选项使用,那么这个参数会输出该目标更新时的运行动作。 如果没有 -n 那么就像运行 UNIX 的 touch 命令一样,使得 file 的修改时间为当前时间。
- --no-print-directory禁止“-w”选项。
 
- make 如何知道你的代码应该被更新的?
    
 make 会根据目标依赖的文件的 mtime 是否新于目标文件来判断该目标是否需要重新编译。 如果你的目标脚本执行并没有生成目标文件,或者生成的目标文件并不等于目标名称,则每次执行到该目标时都会重新编译
- 编写目标规则的三种形式
    
 如下所示,但注意, command 如果不是和 target 在同一行,则需要以一个 TAB 作为缩进targets : prerequisites command ... targets : prerequisites command ... targets : prerequisites ; command command ... 
- 多目标推到 
 多目标的编写方式如下,make 会根据变量中目标名称以及后面的规则进行展开,其中$<会替换成依赖,$@会替换成目标名称。$(objects): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ 
- make 的工作流程
1. 读入所有的Makefile。 2. 读入被include的其它Makefile。 3. 初始化文件中的变量。 4. 推导隐晦规则,并分析所有规则。 5. 为所有的目标文件创建依赖关系链。 6. 根据依赖关系,决定哪些目标要重新生成。 7. 执行生成命令。 
- 命令包 
 命令包以define开始, 以 endif 结束,如:define run-yacc yacc $(firstword $^) mv y.tab.c $@ endef
 在使用的时候后直接在规则里面调用$(run-yacc)即可
 命令包中使用的$@$^在实际的调用位置会替换成实际的目标和依赖名
- 条件判断
    
 make 仅支持一种条件判断方式即fieq condition, 其中最常见的方式是:ifeq ($(VAR),'some_value') ... else ... endif
 而 condition 可以是变量和变量的对比,也可以时变量和参数的对比
 在 condition 中也可以使用 make 支持的函数
 同时还可以使用单变量,有值为真,无值为假
- Tips- make 会将 makefile 中找到的第一个 target 作为默认 target
- 
        make 默认会寻找 Makefile 为名的文件(或者是 makefile ,GNUmakefile 视具体的工具而定),但如果你使用的 -f参数,则会直接采用指定的文件
- 在 makefile 中使用 include 包含外部文件之后,外部文件会直接展开到 include 的位置
- 
        在编写目标的规则脚本前加上 @可以在编译的时候不打印规则本身
- 
        正常情况下,规则脚本执行如果报错,则会导致 make 终止运行,但是在规则之前加上 -可以让 make 忽略报错
- 
        在 make 的时候还可以追加 -n或者--just-print参数,这回打印出目标规则所有的执行语句,但不会执行他们,非常适合调试的时候使用。
- 当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。
 
- 特殊变量
    
 VPATH该变量保存一个路径列表,路径与路径用冒号分割,该变量主要指示 make 在进行自动推导的时候搜索文件的目录, make 会按照(当前目录,以及该变量指示目录的顺序)来便利。当然也可以在 makefile 中使用vpath关键字,这里后面带的目录以空格分割即可
 MAKECMDGOALS该变量存放你在执行 make 指令时传入的目标列表,如你输入的是make clean, 那么此时该变量的值就为 clean; 不过如果你没有指定,则该变量为空。
 $$$$生成一组随机数字符串
 $$代表 $ 字符
- 变量
    
 使用=对变量赋值时,在只有使用到该变量的时候才会展开,因此如果你的变量 A 的值包含了另外一个变量 B,而变量 B 的值会在之后发生变化,那么在引用 A 的时候,会使用到 B 改变之后的值
 如果要避免上述情况可以使用:=来进行赋值,使用这种方式赋值,值会在赋值的时候便展开
 如果变量定义过长还可以使用+=来进行追加
 其次还有一个赋值
 高级用法 1:$(var:a=b)或者$(foo:%.o=%.c), 把 var 变量中以 a 结尾的值替换成 b 结尾的值
 如果要定义多行命令可以使用如下方式:define two-lines echo foo echo $(bar) endef
 这和命令包非常相似,区别在于,命令包中的规则以 TAB 开头,而这个没有,make 在解释的时候也是依据此来区分的。
- 变量的 override
    
 通常,如无特殊申明,makefile 中的变量是可以在执行 make 的时候携带参数来改变的。
 比如,你在 makefile 中定义了一个 var 变量(比如var=old_value),然后你在对该 makefile 进行编译的时候给 var 传递了一个值:比如make var=new_value那么此时 var 的值 old_value 就会被 new_value 所取代
 而如果你想在 makefile 中忽略参数指定的值,则可以使用 override 指示符,如:override var=old_value
- 目标变量
    
 顾名思义,专为目标定义的变量,该变量会覆盖外部的全局变量,使用方法如下:target: var1=value1 target: var2=value2 target: use $(var1) $(var2) do something
 此外,make 还支持模式变量,即可以给符合该模式的所有 target 定义变量,如:%.o: CFLAGS ?= -O2 $(Objects):%.o:%.c $(CC -c $@ $(CFLAGS) $^
makefile 中的函数和变量一样,可以使用大括号来调用,也可以使用小括号来调用,但是为了风格的统一,建议使用同一种风格。
函数的使用基本形式如 $(function_name, arg1,arg2...), 下面我们会集中介绍集中常用的函数
- 打印相关 
 $(warning text...)编写该函数的地方会打印出 text 字符串做为警告信息,
 $(error text...)编写该函数的地方会打印出目标字符串作为错误信息,并会退出 make 的编译过程
- 外部调用
    
 $(shell shell_centence), 该函数主要用于调用 shell 脚本并返回脚本的标准输出
 $(call expression,args1,args2...), 该函数主要运行 expression 表达式,而后面的 args1, args2 则为该表达式的参数,在表达式中,参数可以使用${1},$(2)的形式引用,如:func = $(shell cd ${1} && find -name *.${2}) $(call func,./src,cpp) $(call func,./src,go)
- 判断 
 $(if condition,then-part>)OR$(if condition,then-part,else-part>), 和#ifeq类似,可以包含两个参数,也可以包含三个参数, 使用例子如下:$(if $(filter foo,bar),@echo match is broken,@echo match works),viewpdf :=$(if $(filter Darwin,$(uname)), open, evince)
- 字符串处理
    
 $(subst from,to,text)将字符串 text 中的 from 字符串替换成 to 字符串;
 $(patsubst pattern,replacement,text)将 text 字符串中符合 pattern 格式的字符串替换成 replacement 的串。pattern 中可以使用%作为通配符,匹配任意长的字符串; 而 replacement 中的%则表示用 patern 在 text 中匹配到的串;此外,这和我们前面“变量章节”说过的相关知识有点相似。 如$(var:pattern=replacement;)相当于$(patsubst pattern,replacement,$(var)), 而$(var: suffix=replacement)则相当于$(patsubst %suffix,%replacement,$(var))。
 $(strip string)去掉 string 中开头和结尾的空格
 $(findstring find,in)在 in 代表的字符串中找到 find 这个字符串,如果找到,则会返回 find 字符串,如果没有找到,则会返回空串
 $(filter pattern...,text)以 pattern 过滤 text 中的字符串,返回符合 pattern 模式的字符串
 $(filter-out pattern...,text)该函数返回 text 中去除了符合 pattern 模式的字符串
 $(sort list)返回对 list 重排之后中以单词升序排列的字符串
 $(word n,text)返回字符串 text 中第 n 个单词。如果 n 比 text 中的单词数要大,那么返回空字符串。
 $(wordlist startn,endn,text)从 text 中取出从 startn 到 endn (包含)的单词列表,startn 和 endn 为整形数值, 如果 startn 比 text 中的单词数要大,那么返回空字符串。如果 endn 大于 text 的单词数,那么返回从 startn 开始,到 text 结束的单词串。
 $(words text)统计 text 的单词个数
 $(firstword text)取字符串 text 中的第一个单词。
- 文件处理
    
 $(dir names...)取出 names... (文件名序列) 中目录部分
 $(notdir names...)取出 names... (文件名序列) 的非目录部分(即最后一个 / 后的名字)
 $(suffix names...)取出 names... (文件名序列) 中文件的后缀(即文件名最后一个.后的字符串),如果文件没有.那么不会返回此文件的后缀
 $(basename names...)与 suffix 相反,该函数用于取除后缀意外的文件名部分(未包含前面路径部分)
 $(addsuffix suffix,name...)给 name... 序列中每一个文件追加后缀
 $(addprefix prefix,name...)给 name... 序列中每一个文件追加前缀
 $(join list1,list2)将两个 list 拼接到一起
- 遍历
    
 $(foreeach var,list,text)把参数 list 中的单词逐一取出放到参数 var 所指定的变量中,然后再执行 text 所包含的表达式。
在查看一些开源的代码时,我们经常会看到一个 makefile 中通常都会包含一些共同的 target, 他们完成的功能也大体相同, 而新手了解这些 target 的体意图之后,会提高自己的分析代码效率,而如果你在开发过程中也遵循这些通识,会让你的代码可阅读性更强,结构更完整。 从而间接的提升你的研发效率。
- all 这个伪目标是所有目标的目标,其功能一般是编译所有的目标
- clean mostlyclean distclean realclean clobber 这些目标则代表功能是删除所有被 make 创建的文件,而具体的差别在于清理的程度。后四者清理的程度相对于 clean 会更多一点,不过这也是工程实际所考虑的问题
- install 这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
- print 这个伪目标的功能是例出改变过的源文件。
- tar 这个伪目标功能是把源程序打包备份。也就是一个tar文件
- shar
- dist 个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件
- TAGS 这个伪目标功能是更新所有的目标,以备完整地重编译使用。
- check && test 这两个伪目标一般用来测试makefile的流程。
- gcc 中常用的一些语法
首先是编译,两次面试都碰到这个问题,其实在实际应用中很少用到,但到特殊情况下不知道也很麻烦,从 c 的源码编译到可执行文件经历着这么四个步骤 预处理(生成预处理文件)--> 编译(产生汇编文件) --> 汇编(产生机器可识别文件) --> 链接(将各个分散的文件链接到一起,并生成程序入口)。通常在编译的时候,可能要自己加入一些指定的库,头文件目录等,其中-I/path指定头文件目录,-L/path指定库文件的目录,-lname (ex:-lmath)指定库文件的名称(可省略 lib 的前缀),-D指定全局的符号常量(也就是 C 文件中的宏定义常量,在编译预处理的时候将会作为文件中的普通宏定义常量加入计算),-Wall该选项会发现程序中一系列的常见错误报告,具体包含哪些选项请 man 一下(不知道 man 什么意思,google 一下 ‘linux man’),-Werror把所有的警告当作错误处理。
- debug 程序时在文件中用到的宏
__DATA__获取当前日期,__TIME__获取当前时间,__LINE__获取打印信息在当前文件中的行号,__FILE__���取当前代码所在文件的文件名,__STDC__如果当前编译器符合 ISO 标准,其宏值为1,__STDC_VERSION__C标准版本(如果版本为 C89 那么值为 199409L,如果版本为 C99 那么值为 199901L),__STDC_HOSTED__本地系统(hosted,表示拥有完整的标准 C 库)值为1。
- 平台区分(Machine-Dependent)
这里之列举常见的,这些参数也是可在编译不同平台做代码兼容而使用, x86/Windows如果在文件中发现这个宏为真,则标识当前编译平台为 x86/Windows 平台,注意有 x86/Windows 和 x86 之分,Darwin苹果 OSX 平台,MIPS一看便知,GNU/Linux同上...., 用法嘛自己去搜索,也可以在 github 上下个多平台兼容的项目下来研究研究。
- C++11 新新特性
在这里只是简单的介绍一下各个特性的关键作用,详细的解析与用法请参考后面的 [link3]传送门。auto相当一个泛型变量类型,可用于生成任何变量,变量的类型会由编译器自动分配;decltype获取一个变量的变量类型,可用于申请新的变量;nullptr空指针,以前的空指针用 0 表示,但是 0 被隐式的保存为整形,目的就是为了避免此种情况;for支持像 java 类似的 foreach 特性;Lambda 表达式相当于 java 中的闭包;make_tuple()变长参数申请;新类型指针(unique_ptr, shared_ptr, weak_ptr);新枚举类型像 java 一样封装到一个类里面......(参考文档中,这篇很值得看 --> C++开发者都应该使用的10个C++11特性)
- gcc 参数
-x // 指定文件所用的语言类型;可选项("c","objective-c","c-header","c++", // "cpp-output","assembler","assembler-with-cpp") -x none // 关闭对任何语言的说明 -M // 输出文档所直接和间接依赖头文件, 用法:gcc -M main.c) -MM // 输出文档所直接依赖的头文件 -O[n] // 不使用`-O'选项时,编译器的目标是减少编译的开销,使编译结果能够调试. // 意味着语句是独立的:如果在两条语句之间用断点中止程序,你可以对任何变量重新 // 赋值,或者在函数体内把程序计数器指到其他语句,以及从源程序中 精确地获取你期待的结果. // 使用了`-O'选项,编译器会试图减少目标码的大小和执行时间. // n=1: 对于大函数,优化编译占用稍微多的时间和相当大的内存. // n=2: 包含 n=1 的情况,几乎执行所有的优化工作, // n=3: 在 n=1 的情况下还打开了 -finline-functions 选项 // n=0: 不优化 .. 详细请移步至 LINK1 or LINK5.
- 
查看当前系统中存在那些可以连接到的库 ldconfig -p,更多选项在下面说明:ldconfig 的参数 -v ldconfig 将扫描以及打印能搜到的目录(包括默认的 /lib,/usr/lib 和配置到 /etc/ld.so.conf 中的)。 -n 仅仅扫描打印指定目录。 -N 不重建 /etc/ld.so.cache 目录,但为指定 -X 选项运行 ldconfig 的时候照样会更新文件的连接。 -X 不更新文件的连接 -f 此参数指定动态库的配置文件,默认是 /etc/ld.so.conf -C 此参数指定动态库的缓存文件,默认是 /etc/ld.so.cache -r 此参数指定动态索引时的根目录,默认为 / ,比如在调用 /etc/ld.so.conf 时实际调用的是 //etc/ld.so.conf -l 进入专家模式手动建立动态库的连接 -p 打印所有共享库的名字 -c 用于指定缓存文件所使用的格式,共有三种:old(老格式),new(新格式)和compat(兼容格式,此为默认格式). -V 打印 ldconfig 的版本信息
|  |  | 
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号