Makefile在开源项目中还是相当的常见的,熟悉他的基本语法,还是很有必要的,其次是Makefile相对于shell脚本的优点就是他的关联性,和前置条件等都很好的解决的构建链条的问题。有些学ccpp的同学可能比较熟悉,我们这个核心不关注于这个,主要是使用在日常中make一些cli参数n参数:使用n参数,让make命令输出将要执行的操作步骤,而不是真正执行这些操作;makefilegit:(master)touchMakefile2makefilegit:(master)makenrmfMakefile1Makefile2Makefile3makefilegit:(master)lsMakefileMakefile1Makefile2f参数:使用f参数,后面可以接一个文件名,用于指定一个文件作为makefile文件。如果没有使用f选项,则make命令会在当前目录下查找名为makefile的文件,如果该文件不存在,则查找名为Makefile的文件。C参数一般当我们调用其他目录的makefile,可以直接makeC执行完退回当前make命令,类似于shellinclude可以引用用其他的makefile,类似于其他编程语言的import,和环境变量MAKEFILES等效 Makefile文件includea。makeb。makeall:echoaechobechohello a。make文件echoa:echohelloa b。make文件echob:echohellob 执行 1)可以发现include却是是把它完完全全的copy到了头部makefilegit:(master)makehelloa 2)继续,完全符合makefilegit:(master)makeallhelloahellobhellomakefile一些环境变量MAKE 其实就是你的make环境变量的,whichmake即可。PHONY:allall:echomake路径:(MAKE) 输出makefilegit:(master)makemake路径:LibraryDeveloperCommandLineToolsusrbinmakeRM 这个主要是当作rmf参数。PHONY:allclean:(RM)Makefile1Makefile2Makefile3 输出:makefilegit:(master)makermfMakefile1Makefile2Makefile3MAKEFILELIST MAKEFILELIST的变量,它是个列表变量,在每次make读入一个make文件时,都把它添加到最后一项,gnumake有效。Makefile文件all:echo当前makefile:(MAKEFILELIST)(MAKE)fMakefile2Makefile文件2all:echo当前makefile:(MAKEFILELIST) 输出makefilegit:(master)make当前makefile:Makefile当前makefile:Makefile2 所以依靠这个可以获取当前路径,但是目前没有模拟出MAKEFILELIST多个列表。PHONY:first:echo(MAKEFILELIST)second:echo(lastword(MAKEFILELIST))third:echo(realpath(lastword(MAKEFILELIST)))latest:firstsecondthirdecho(shelldirname(realpath(lastword(MAKEFILELIST)))) 执行gosourcegit:(master)makelatestMakefileMakefileUsersfanhaodonggocodegosourceMakefileUsersfanhaodonggocodegosourcemakefile文件书写规则 makefile文件由一组依赖关系和规则构成。每个依赖关系都由一个目标(即将要创建的文件)和一个该目标所依赖的源文件组成;规则描述了如何通过这些依赖文件创建目标。简单的来说,makefile文件的写法如下:target:prerequisitescommand1command2。。。 其中,target是即将要创建的目标(通常是一个可执行文件),target后面紧跟一个冒号,prerequisite是生成该目标所需要的源文件(依赖),一个目标所依赖的文件可以有多个,依赖文件与目标之间以及各依赖文件之间用空格或制表符Tab隔开,这些元素组成了一个依赖关系。随后的命令command就是规则,也就是make需要执行的命令,它可以是任意的shell命令。另外,makefile文件中,注释以号开头,一直延续到该行的结束。 比如下面这个,target就是hello,prerequisite是hello。c的文件hello:hello。c(CC)ohello。sShello。c(CC)ohello。ochello。s(CC)ohellohello。o 构建c项目all:testtest:test。oanotherTest。ogccWalltest。oanotherTest。ootesttest。o:test。cgcccWalltest。canotherTest。o:anotherTest。cgcccWallanotherTest。cclean:rmrf。otest GNU的make工作时的执行步骤如下:读入所有的Makefile。读入被include的其它Makefile。初始化文件中的变量。推导隐晦规则,并分析所有规则。为所有的目标文件创建依赖关系链。根据依赖关系,决定哪些目标要重新生成。执行生成命令。 15步为第一个阶段,67为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。 当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。申明变量类似宏一样,他会对变量进行引用,在执行时扩展,允许递归扩展:如果变量申明符合先来后到,和含义一样,但是如果申明a引用了b但是b还没有申明,此时认为b为空a(b)1b2c:(d)1d2all:echo(a)echo(c) 输出makefilegit:(master)make211 奇怪的现象:可以发现我们申明a变量后,但是输出的时候却是100,可以发现cli传递的优先级最高,不可以被覆盖makefilegit:(master)makea1001001?如果a变量前面已经申明过了,那么后面a?xxx则因为前面已经申明了a,所以不进行赋值,也就是a?xxxx无效,如果前面没有申明则有效AhelloA?helloworldall:echo(A) 输出:hello这个类似于a1,意思就是在原来的基础上,很方便,下面提供demobuildargs:raceifeq((vendor),true)buildargsmodvendorendifall:echo(buildargs) 输出:makefilegit:(master)makevendortrueracemodvendor命令行参数echo:echo(arg) 执行:makefilegit:(master)makeargruoyuruoyu执行函数1、calldefine宏定义 类似于C语言的宏定义编译生成到bin目录下definebuildsh。build。sh(1)。bin(strip(2))endef脚手架脚本gobuild:pre(callbuild,cmdgobuildmain。go,gobuild)2、自带函数 格式(命令参数)all:echo(lastword123) 输出makefilegit:(master)make33、调用shell函数all:echo(shelldirnamedatatest) 执行makefilegit:(master)makedataMakefile文件的语法target:prerequisites〔tab〕commandstarget:目标,支持模式匹配prerequisites:前置条件,可以有多个,支持模式匹配commands:前面必须有tab,是shell命令makefile函数命令1、注释 注释一般使用开头表示,但是如果注释在目标的命令包含一般all定义了全部all:hello 执行makefilegit:(master)makehello2、关闭回声 这个其实很简单,就是在执行shell命令的时候,往往会打印日志,所以这里提供了很好的解决方式,使用符号all:echohelloworld 执行后会发现,每次执行的时候都会打印回声makefilegit:(master)makeechohelloworldhelloworld 所以可以将makefile文件改成以下all:echohelloworld 输出makefilegit:(master)makehelloworld3、通配符 和bash一样,主要有等通配符,主要是在shell脚本中使用new:forxin{1,2,3,4};dotouchx。doneclean:(RM)。test 执行makefilegit:(master)makenewforxin{1,2,3,4};dotouchx。donemakefilegit:(master)lsgreptest1。test2。test3。test4。testmakefilegit:(master)makecleanrmf。testmakefilegit:(master)lsgreptest4、模式匹配 主要是对文件名的支持!主要是在目标和依赖中使用,使用匹配符,可以将大量同类型的文件,只用一条规则就完成构建。。o:。c 等同于f1。o:f1。cf2。o:f2。c 不懂的可以看一下这篇文章,对比一下模式匹配和通配符的区别:https:blog。csdn。netBobYuan888articledetails88640923 理解模式匹配必须了解下面这四个 :目标的名字 :构造所需文件列表所有所有文件的名字 :构造所需文件列表的第一个文件的名字 ?:构造所需文件列表中更新过的文件 大致原理:我要找f1。o的构造规则,看看Makefile中那个规则符合。然后找到了。o:。c来套一下来套一下。o和我要找的f1。o匹配套上了,得到f1。所以在后面的。c就表示f1。c了。OK进行构造 1、例子一(编译c文件)。o:。c。hecho目标的名字:,依赖的第一个文件:,依赖的全部文件:,所更新的文件:?(CC)all:utils。oecho编译clean:(RM)。i。s。omain 执行,可以看到完全符合我们的例子目标的名字:utils。o,依赖的第一个文件:utils。c,依赖的全部文件:utils。cutils。h,所更新的文件:utils。cutils。hccoutils。ocutils。c编译for循环1、makefile:foreach循环 语法:(foreach,(gvar),;),这里需要变量引用需要使用()list:(shellls)all:(foreachitem,(list),echo(item);echo(realpath(item));) 输出:makefilegit:(master)makeMakefileUsersfanhaodongnotenotedemomakefileMakefileMakefile1UsersfanhaodongnotenotedemomakefileMakefile1Makefile2UsersfanhaodongnotenotedemomakefileMakefile23、shell:for循环list:(shellls)all:forxin(list);done 记住一点就好,符号转移需要使用 执行makefilegit:(master)makemforMakefileMakefile1Makefile2a。makeb。makeif函数1、makefile:if函数 命令格式:(if,;,;)all:(if(shellcommandv(arg)),echocommand(arg)isexist,echocommand(arg)isnotexist) 执行makefilegit:(master)makearggocommandgoisexistmakefilegit:(master)makearggo1commandgo1isnotexist2、shell:if函数all:if〔commandv(arg)〕;thenechocommand〔(arg)〕elseechocommand〔(arg)〕fi 执行makefilegit:(master)makearggocommand〔go〕isexistmakefilegit:(master)makearggo1command〔go1〕isnotexist执行多个命令echo:echohelloworldecho2:echohelloworld2 执行:makefilegit:(master)makeechoecho2helloworldhelloworld2宏定义defineechoechohello,(1)!endefARG:ifdefargARG:(arg)elseARG:NULLendifall:print(callecho,world)echo(ARG)print:echoarg:(arg) 执行makefilegit:(master)makeargworldarg:worldhello,world!world系统环境变量 申明推荐:export变量名称,获取使用{变量名称}GOPROXY:https:goproxy。cn,directexportGOPROXYall:echo{GOPROXY}编译C项目 c项目往往很复杂,设计到预编译,编译,汇编,链接的过程 1、文件(头文件、main文件) 1、utils。hifndefADDHdefineADDHintadd(inta,intb);endif 2、utils。cintadd(intx,inty){} 3、main。c 注意:头文件的寻找方式先搜索当前目录然后搜索I指定的目录,例如I。head再搜索gcc的环境变量CPLUSINCLUDEPATH(C程序使用的是CINCLUDEPATH)最后搜索gcc的内定目录includestdio。hincludeutils。hintmain(intargc,charconstargv〔〕){printf(12d,add(1,2));return0;} 假如。h文件放在head目录cppgit:(master)lsheadutils。h可以发现编译异常,异常时。h文件未找到cppgit:(master)gcccmain。comain。omain。c:2:10:fatalerror:utils。hfilenotfoundincludeutils。h1errorgenerated。修改I参数可以发现通过cppgit:(master)gccI。headcmain。comain。ocppgit:(master)lsgrepmain。omain。o2、预编译E E:预编译,这一步主要是将头文件,宏定义展开到文件,是文本形式cppgit:(master)gccEmain。comain。icppgit:(master)tailf10main。itail:10:Nosuchfileordirectorymain。i可以看到这里是把utils。h的头文件信息copy过来了intadd(inta,intb);3main。c2intmain(intargc,charconstargv〔〕){printf(12d,add(1,2));return0;}3、编译S 编译为汇编代码,是文本形式cppgit:(master)gccSmain。iomain。s 4、汇编c 就是编译成二进制的汇编文件,是可重定位目标程序,属于二进制文件cppgit:(master)gcccmain。somain。ocppgit:(master)hexdumpCmain。o00000000cffaedfe070000010300000001000000。。。。。。。。。。。。。。。。0000001004000000080200000020000000000000。。。。。。。。。。。。。。。0000002019000000880100000000000000000000。。。。。。。。。。。。。。。。0000003000000000000000000000000000000000。。。。。。。。。。。。。。。。00000040b0000000000000002802000000000000。。。。。。。。(。。。。。。。00000050b0000000000000000700000007000000。。。。。。。。。。。。。。。。0000006004000000000000005f5f746578740000。。。。。。。。text。。0000007000000000000000005f5f544558540000。。。。。。。。TEXT。。0000008000000000000000000000000000000000。。。。。。。。。。。。。。。。0000009042000000000000002802000004000000B。。。。。。。(。。。。。。。000000a0d8020000030000000004008000000000。。。。。。。。。。。。。。。。cppgit:(master)objdumpdmain。omain。o:fileformatMachO64bitx8664DisassemblyofsectionTEXT,text:0000000000000000main:0:55pushqrbp1:4889e5movqrsp,rbp4:4883ec20subq32,rsp8:c745fc00000000movl0,4(rbp)f:897df8movledi,8(rbp)12:488975f0movqrsi,16(rbp)16:bf01000000movl1,edi1b:be02000000movl2,esi20:e800000000callq0main0x2525:488d3d16000000leaq22(rip),rdi2c:89c6movleax,esi2e:b000movb0,al30:e800000000callq0main0x3535:31c9xorlecx,ecx37:8945ecmovleax,20(rbp)3a:89c8movlecx,eax3c:4883c420addq32,rsp40:5dpopqrbp41:c3retq5、链接 对于ccpp语言来说,最难的就是链接了!这里也设计到隐晦规则了,首先。o是符合main。o,utils。o的,所以会执行两次cc,最终链接成功伪目标,这里定义的目标不会去文件系统里寻找。PHONY:allcleanCC属于makefile的全局变量,已经定义好了,但是我们使用gcc需要指定CC:gcc目前的目标项目名称也就是。目前的依赖项目。o:。c(CC)oall:installrunclean当依赖符合模式匹配时候,会执行上面的。o:。cinstall:utils。omain。ogccomainutils。omain。orun:。mainclean:(RM)。i。s。omain 执行cppgit:(master)makegcccutils。coutils。ogcccmain。comain。ogccomainutils。omain。o。main123rmf。i。s。omain帮助 如果你想写help,可以使用下面那个表达式。PHONY:helpecho:打印echoechohelloall:打印echo1help:帮助awkBEGIN{FS:。?}〔azAZ〕:。?{sub(n,sprintf(22c,),2);printf33〔36m20s33〔0ms,1,2}(MAKEFILELIST) 其实很简单,了解awk语法的话,知道awk条件动作文件名所谓条件就是正则表达式,分隔符是:。?,然后匹配的条件是以字母开头的〔root19096dee708bdata〕catdemo。txt11221112233 匹配一下〔root19096dee708bdata〕awk{printf1s2s,1,2}demo。txt11122211112122233 我们要拿到我们的结果!所以需要匹配有空格的,匹配空格就是s〔root19096dee708bdata〕awks{printf1s2s,1,2}demo。txt111222122233