Makefile 基础 - 让我欢喜让我忧

你这样,一个女人,让我欢喜让我忧... --- 周华健

 

Makefile 就是一个让人欢喜让人忧的东西,

欢喜呢?使用起来超方便。

忧呢?就是语法比较麻烦,一大堆一大堆的,有时候会繁杂。

当然,了解它之后,或许,忧,会少一点吧。

比如,

“我没事”时,你应该说“我错了”,而不是去玩游戏。

“肚子疼”时,你应该帮她揉揉肚子,而不是“多喝开水”。

 

这一篇呢,将带你进入一个让你欢喜让你忧的世界-----Makefile。

    1、为什么要用 Makefile?

    2、Makefile的编写规则。

    3、Makefile常用的函数。

 

1、为什么要用 Makefile?

    很久很久以前,人们不用 Makefile,生产一个程序,就只能手动敲命令。

    一开始,程序很小很小,只有一个文件,只需要在shell里敲上:

        gcc main.c -o main

    后来,程序越来越大,有非常多的文件,需要敲入这个:

        gcc main.c 1.c 2.c 3.c ... ... ... -o main

    每次修改某个文件,都需要重新编译所有文件,要好久好久呀,编译效率非常低下。

    有没有办法让提高效率呢?

        gcc -c main.c 1.c 2.c 3.c ... ... ... 分别生成 所有的.o文件;

        gcc main.o 1.o 2.o 3.o ... ... ... -o main 再把生成的.o文件链接成目标文件main;

    如果修改 1.c,只需要 先 

        gcc -c 1.c  生成 1.o 文件;

        gcc main.o 1.o 2.o 3.o ... ... ... -o main 再把刚生成的 1.o,和其他之前生成的.o文件,一起链接成目标文件main

    改哪个编哪个,编译效率倒是提高了,不过,敲命令,也够受了,如果改很多文件的话,还要记得,改了哪个,如果编译比较大型的工程,光是敲命令,都得敲到手抽筋,于是,make应运而生。

    make机制:make的机制类似于“改哪个编哪个”,make 会自动检查源文件.c和对应的文件.o的最后修改时间,如果某个源文件.c的最后修改时间,比对应的文件.o要新,说明,这个文件修改过,需要重新编译,make就会自动编译对应的文件,再把新编译生成的新的对应文件.o,和之前已生成的.o文件,一起链接成目标文件xxxx。

    Ex: 如果 1.c 被更改,make 会自动检查 .c 的修改时间 和 .o 的修改时间,发现,1.c 比 1.o 要新,此时,便自动编译 1.c,再把 1.o 2.o 3.o ... ... ... 一起链接成 目标文件 main

    那么,make 怎么知道我要编译哪些文件呢?是只编译1.c 2.c?还是编译全部?或者其它的呢?

    这个,就看 Makefile 了,make是个大厨,而 Makefile 就是菜谱,菜谱里写着,菜名,原材料,加工方法等。

 

2、Makefile的编写规则。

    上面说到,make是大厨,Makefile是菜谱,我们就来看一看这菜谱吧。

    菜名:原材料

    <Tab>加工方法

main: main.o
    gcc main.o -o main            #gcc前面一定要 <Tab>,一定要,一定要,重要事情说三遍!

main.o: main.c
    gcc -c main.c

testString:
    echo "shuang!shuang!shuang!"

clean:
    rm -rf *.o main

    main是我们最终想得到的菜,菜谱如下:

    main: main.o

        gcc main.o -o main

    main,就是菜名,

    main.o 就是原材料,

    gcc main.o -o main 就是加工方式。

    试一下,开始做菜 make

    效果同直接在命令行输入:

        gcc -c main.c

        gcc main.o -o main

    我们的加工方式是 gcc main.o -o main,为什么还会有一条 gcc -c main.c呢?

    因为我们的原材料,main.o,也是一道菜,是由 gcc -c main.c 加工而成,请看下一条。

 

    上一道菜中的 main.o 这个原材料,它是另一道叫main.o的菜,菜谱如下:

    main.o: main.c

        gcc -c main.c

    main.o,就是菜名,

    main.c,就是原材料,

    gcc -c main.c,就是加工方式

    效果同直接在命令行输入:gcc -c main.c

 

    testString 和 clean 这道菜呢,是试菜和吃光菜,不需要原材料:

    testString:

        echo "shuang!shuang!shuang!"

    意思是试菜时,要喊 爽!爽!爽!

    效果同直接在命令行输入:echo "shuang!shuang!shuang!"

    clean:

        rm -rf *.o main

    清理 所有的.o文件 和 main文件

    效果同直接在命令行输入:rm -rf *.o main

 

直接 make 呢?

效果同 make main

 

总结:

    a、当我们执行 make xxx(菜名)时,make会自动执行 xxx(菜名)下的命令(加工方式);

    b、当我们执行 make 时,make 会自动执行第一条 xxx:(菜谱上第一个菜名)所对应的命令(加工方式); 

    c、加工方式前面的<Tab>,必须是退格键,不能是空格或者其它;

 

3、Makefile变量的声明和赋值。

    PS:Makefile里所有变量都是字符串

    变量的声明,方法有:=、?=、:=、+=、define endef

    变量的使用,方法有:$(var)

        var="i am a rich man!"

        var?="i am a rich man!"

        var:="i am a rich man!"

        var+="Good!"

        define var

        "i am a rich man!"

        endef

 

    =、?=、define:延时变量,在使用的时候才确定值

    :=:立即变量,在定义的时候就确定值

    +=:看前面的,前面的是延时就是延时,前面的是立即就是立即

 

        var1=abc

        var2=$(var1)def

        var1=ghi

        echo $(var2) 

    输出 ghidef

    =、?=、define是延时变量,所以,在 echo $(var2) 时,才确定 var2的值是 $(var1)def,var1的值是 ghi

 

        var1=abc

        var2:=$(var1)def

        var1=ghi

        echo $(var2) 

    输出 abcdef

    := 是立即变量,所以,在var2:=$(var1)def 时,就已经确定了var2的值是 abcdef

 

    ?=只有在变量第一次赋值的时候有效,也就是说,如果前面变量没被赋值,就赋值,如果有赋值,则不执行赋值,跳过。

 

        var1?=abc 或者 var1=abc 或者 var1:=abc

        var1?=def

        echo $(var1) 

    输出 abc

 

        var1?=def

        echo $(var1) 

    输出 def

 

    +=字符串连接,在原有的字符串上加上后续内容,不过中间会有空格喔,呵呵哒

 

        var1=abc

        var1+=def

        echo $(var1) 

    输出 abc def

 

4、Makefile常用的函数。

字符串替换和分析函数:

   $(subst from,to,text)

   在text中使用to替换from.

   echo $(subst ee,EE,feeet on the street!) 输出 fEEet on the strEEt!

 

    $(patsubst pattern,repace,text)

    在text中寻找符合pattern格式的字,用repace代替它。

    $(patsubst %.c,%.o,aa.c.c bb.c) 输出 aa.c.o bb.o

 

    $(strip string) 去掉前导和结尾,并把中间多个空格压缩成一个

    $(strip     I      am  a     rich    man!!           ) 输出 I am a rich man!!

 

    $(findstring find,string) 在string中寻找find,有则返回find,无则返回空

    $(findstring rich man,I am a rich man!!) 输出 rich man

 

    $(filter pat...,text) 返回在text中“匹配用空格隔开的pat...”的字,去除不匹配的字。

    $(filter %h %n,I am a rich-man) 输出 rich-man

 

    $(filter-out pat...,text) 返回在text中“匹配用空格隔开的pat...”的字,去除不匹配的字。

    $(filter-out %h %n,I am a rich-man) 输出 I am a

 

    $(sort list...) 去除list...中用空格隔开的重复的单词,并按字母排序,先符号,后数字,再字母,大小写区分

    $(sort 1. I am a rich man !man Man a rich) 输出 !man 1. I Man a am man rich

 

文件名函数:

    $(dir dirs...) 抽取每一部分的目录部分,目录从文件名的首字符起到最后一个/结束的字符

    $(dir /mnt/src.c hack/ddr/aab.a kko.c) 输出/mnt/ hack/ddr/ ./

    /mnt/src.c 的目录是 /mnt/

    hack/ddr/aab.a 的目录是 hack/ddr/

    kko.c 的目录是 ./

 

    $(notdir names...) 抽取文件名

    $(notdir /mnt/src.c hack/ddr/aab.a kko.c) 输出src.c aab.a kko.c

 

    $(suffix names...) 抽取文件名后缀

    $(suffix /mnt/src.c hack/ddr/aab.a kko.d rich-man) 输出.c .a .d

 

    $(basename names...) 抽取除后缀外的其他字符

    $(basename /mnt/src.c hack/ddr/aab.a kko.d rich-man) 输出/mnt/src hack/ddr/aab kko rich-man

 

    $(addsuffix suffix,name...) 加文件名后缀

    $(addsuffix .c,1413 1314 rich-man) 输出1413.c 1314.c rich-man.c

 

    $(addprefix prefix,names...) 加前缀,比如 src/, hack.c bb.c 输出 src/hack.c 

    $(addprefix rich-,man womem boy girl) 输出 rich-man rich-womem rich-boy rich-girl

 

    $(wildcard *.c) 返回当前文件夹下.c的文件,输出 1.c 2.c main.c

 

其他函数:

    $(foreach var,list,text) 将list和var扩展,再将每个list赋给var,text引用var再进行扩展。

    dirs:=a b c d e

    files:=$(foreach tmp,$(dirs),$(tmp).txt)

    echo $(files)

    输出:a.txt b.txt c.txt d.txt e.txt

    dir第1次扩展=a, tmp=a

    dir第2次扩展=b, tmp=b

    …………

    最后tmp.txt就是 a.txt b.txt c.txt d.txt e.txt

 

    $(if condition,then,else)

    先把condition展开,如果非空,就执行then,如果空,就执行else。

    file:=a b c d

    $(if $(file),havefile,nothavefile) 返回havefile

    #file:=

    $(if $(file),havefile,nothavefile) 返回nothavefile

 

5、其它:

    $@:菜名

    $^:所有原材料,用空格隔开

    $<:第一个原材料

 

main: main.c 1.c 2.c 3.c

    gcc -o main main.c 1.c 2.c 3.c

可以写成:

    gcc -o $@ $^

 

最后,我们在上面例子的 Makefile 文件上,增加 1.c 2.c 3.c,再运用变量,函数,把它改成一个比较通用的Makefile。

src:=$(shell ls *.c)                                #列出所有.c 文件

ofile:=$(patsubst %c,%o,$(src))            #在列出的.c文件中寻找符合%c格式的字,用repace代替它。

                                                            #经过替代,$(ofile)=main.o 1.o 2.o 3.o


main: $(ofile)                                        #菜名main,原材料 main.o 1.o 2.o 3.o

    gcc $^ -o $@                                    # $^,所有原材料,$@,菜名。加工方式:把所有原材料 gcc 成菜名main

                                                                #等同于 gcc main.o 1.o 2.o 3.o -o main


%.o: %.c                                                #菜名,%.o的所有菜,原材料,%.o对应所需要的原材料,要分别加工喔。

    gcc -c -g -o $@ $<                            #等同于 gcc -c -g -o 1.o 1.c / gcc -c -g -o 2.o 2.c / gcc -c -g -o 3.o 3.c

                                                                #gcc -c -g -o main.o main.c 分别加工成 1.o 2.o 3.o main.o 几道菜。


testString:

    echo "shuang!shuang!shuang!"


clean:

    rm -rf *.o main

分别试一下。

 

PS: 命令前面加 @,表示静默编译,也就是不打印命令。

把这句改为:

testString:

    @echo "shuang!shuang!shuang!"

再看下,看出区别了么?没有打印 echo "shuang!shuang!shuang"这名了,只打印了 shuang!shuang!shuang!

 

基本上,这个让人又爱又恨的小妖精 Makefile,也就这样了。

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值