美文网首页想法
GNU_Make中文手册

GNU_Make中文手册

作者: ElephantKing | 来源:发表于2019-11-05 20:33 被阅读0次

    make执行的两个阶段

    1. 读取所有makefile文件(include指定的,命令行选项-f指定的,其余不用考虑,不常用的),内建所有变量,明确规则和隐含规则,建立目标和依赖之间的关系链表
    2. 根据阶段1建立的关系依赖链表,决定哪些目标需要更新。

    在make执行第一阶段中如果变量和函数被展开,那么称此展开为立即展开,在第二阶段的展开称为延后展开。区别如下:
    立即展开就是变量定义时,就确定变量的值,比如a=1,b=a+1,如果是立即展开,那么b就确定为2了,而延后展开不同,同样a=1,b=a+1,需要等到使用变量b的时候才展开,但是如果在b=a+1之后,且在使用变量b之前,a的值变为2了,那么使用b的时候就变为b=2+1=3了。那么哪些情形是立即展开,哪些是延后展开呢?定义变量的三种方法,如果变量a通过a+=的方式赋值,且之前a是一个简单变量,即通过a:=的方式定义,那么a立即展开的,其余情况都是延后展开

    a=val1
    b:=val2      #仅这种方式定义的变量称为简单变量
    c+=$(b)
    
    .PHONY:all
    x=foo           #x不是简单变量
    y=$(x) b        #这种方式定义y,是延后展开
    z := $(x) b     #z是简单变量,立即展开为foo b
    w += $(y) b     #w不是简单变量,因为之前没有定义,延后展开
    u := sample     #u是简单变量
    u += $(x) b     #u在此之前是简单变量,使用+=赋值时立即展开,u=sample foo b
    x = later
    all:
      @echo "x is $(x)"
      @echo "y is $(y)"
      @echo "z is $(z)"
      @echo "w is $(w)"
      @echo "u is $(u)"
    
    #output:
    #x is later
    #y is later b
    #z is foo b
    #w is later b b
    #u is sample foo b
    

    makefile的规则

    • 一个简单的栗子
      本部分比较复杂,前后勾连,我们先看一个例子
    foo.o:defs.h foo.c
      cc -c foo.c
    

    foo.o是这个规则的目标,def.h foo.c是目标的依赖,第二行是规则的命令。上述规则告诉我们如何重建目标文件,以及何时重建。

    • 通配符
      makefile中可以使用的通配符有三个(*, ? [...]),其意义和shell中的一样。统配符可以直接用在如下两种情形下:
    1. 规则的目标或依赖
    2. 规则的命令中,此时通配符的处理是在shell执行的时候

    其余情况不能直接使用,需要配合wildcard函数来使用。

    • 通配符使用
    objs = *.o
    foo : $(objs)
      cc -o foo $(objs)
    

    本意是foo依赖目录下所有的.o文件,如果所有的.o文件都存在,那么 能正确编译出foo,但是如果删掉所有.o后,就会报错,说没有重建*.o的规则,这里是因为使用通配符的时候出了错。应该改为

    objs = $(wildcard *.o)
    foo : $(objs)
      cc -o foo $(objs)
    

    自动化变量

    仅列出常用的,其余的暂时不需要。

    $@:规则目标名
    $<:规则第一个依赖的文件名
    $^:规则所有依赖的文件列表
    $(@D):目标文件目录部分,不包括最后的斜杠,如目录不存在斜杠,则其值为".",表示当前目录
    $(@F):目标文件的文件名部分
    $(<D):依赖的第一个文件的目录部分
    $(<F):依赖的第一个文件的文件名部分
    

    库文件的搜索目录

    当重建一个目标时,所依赖的文件名为-INAME,make将执行以下搜索:

    1. 在当前目录搜索名为libNAME.so文件
    2. 搜索VPATH或vpath指定的目录
    3. 系统库文件默认路径,"/lib,/usr/lib,/usr/local/lib"
    4. 上述路径下名为libNAME.a的文件
    foo : foo.c -lcurses 
      cc $^ -o $@
    

    一般情况下,我们的库文件是不会在make的时候重建的,所以只要能在上述4个步骤中的某一个找到它就行。下面是一个具体的栗子。

    tree.png

    在目录dynamic_lib中有一个简单的动态库,在static_lib中有一个简单的静态库,静态库和动态库的生成见你的动态库还在报错?。文件main.c引用了两个库中的函数,我们的Makefile应该怎么写呢?

    INCLUDE = -I./dynamic_lib -I./static_lib
    all:main
    main:main.c
      gcc $(INCLUDE) main.c -L./dynamic_lib -L./static_lib \
      -ldynamic_help -lstatic_lib -o main
    .PHONY:clean
    clean:
      rm main
    

    有一下几点需要格外注意:

    1. 如要使用动态库,在编译的时候需要指定库的位置,用-L./dynamic_lib指定,如果要使用的库已经在默认搜索目录(/lib,/usr/lib,/usr/local/lib)则不需要再指定目录,只需要用-lXXX来引用库libXXX.so。静态库也一样,只不过静态库名一般为libXXX.a
    如果不指定库的位置,将编译报错
    1. 如要使用动态库或静态库,在编译的时候,需要指定相应头文件的位置,用-I./dynamic_lib来指定,即上面的INCLUDE变量。
    如果不指定头文件位置,编译报错
    1. 在运行使用了动态库的程序时,程序需要知道动态库的位置在哪,如果动态库不在默认路径上,则需要用LD_LIBRARY_PATH环境变量来指定。而由于静态库在编译的时候就相当于把整个库联编到可执行程序(本例中为main这个可执行程序),在运行的时候就不需要再指定静态库的位置了。
    直接运行,找不到动态库文件,运行报错,需要用LD_LIBRARY_PATH指定

    我们写一个运行脚本,把环境变量LD_LIBRARY_PATH设置好,进行验证。

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./dynamic_lib/
    ./main
    

    执行结果如下:

    正确的运行结果

    伪目标

    使用场景有两个,这里只介绍一个,那就是如果我们的规则是执行一段命令,而不是重建某个文件,如clean。

    .PHONY : clean
    clean:
      rm *.o
    #执行make clean就能清楚所有的.o文件
    
    .PHONY : cleanall cleanobj cleandiff
    cleanall : cleanobj cleandiff
      rm program
    cleanobj:
      rm *.o
    cleandiff:
      rm *.diff
    

    强制目标:FORCE

    FORCE仅仅是一个符号,也可以用其他任意未用过的符号替代,FORCE出现在依赖列表中,如果FORCE是一个真正存在的文件名,那么将根据文件的新旧来决定是否重建target,但是FORCE是一个不存在的文件,且FORCE本身的依赖是空,规则命令也是空,在这种情下,依赖与FORCE的目标将无条件被重建,看例子。

    #FORCE充当目标all的依赖
    all:FORCE
      @echo "must execute here"
    
    #相当于FORCE是一个目标,其依赖和规则命令都为空
    FORCE:
    

    下面是一个具体的栗子:下面这个例子中,终极目标gen_file,这个目标依赖文件test.log,即如果文件test.log不存在或比gen_file文件新,将会执行echo "gen_file",所以在第一次执行make时,由于test.log文件不存在,会执行一次,并生成文件test.log,此后再执行将不会有任何输出,因为文件test.log并没有变得更新,所以make觉得不需要执行。

    file_name=test.log
    all:gen_file
    gen_file:$(file_name)
      @echo "gen_file"
    $(file_name):
      @echo "echo file_name $(file_name)"
      @rm -rf $(file_name) && echo `date` > $(file_name)
    

    要想每次都执行日期更新,那么需要文件test.log依赖于一个永远是最新的东西,那么就是FORCE,如下所示,由于FORCE并不存在,所以每次都需要更新test.log也就会执行echo和rm命令了。

    file_name=test.log
    all:gen_file
    gen_file:$(file_name)
      @echo "gen_file"
    $(file_name):FORCE
      @echo "echo file_name $(file_name)"
      @rm -rf $(file_name) && echo `date` > $(file_name)
    FORCE:
    .PHONY:FORCE
    

    包含其他makefile文件

    在makefile1中使用include包含另一个makefile2相当于把makefile2中的内容贴到include处合并为一个makefile文件。然后该怎么执行就怎么执行。

    #makefile1
    include makefile2
    all:
      @echo "target all"
    
    #makefile2
    __all:
      @echo "target __all"
    

    如果执行make -f makefile1,那么先包含文件makefile2,那么__all成为第一目标,也就是终极目标,所以默认重建__all,所以输出target __all

    静态模式

    静态模式定义了一种比较简约的目标依赖的书写方法,有三种不同的写法,意义等价。完全不用纠结其中的意义。

    objs = a.o b.o c.o d.o
    $(objs):%.o:%.c
      cc -c $< -o $@
    
    #或者是如下写法
    %.o : $.c
      cc -c $< -o $@
    
    #或者
    .c.o:
      cc -c $< -o $@
    

    函数

    • subset
    $(subset ee,EE,feet on the street)
    #得到fEET on the strEEt
    
    • patsubset
    s=a b acb adb
    $(patsubst a%b,sss,$(s))
    #输出:a b sss sss,其中a b不会替换为sss
    
    • strip
    s =       a b      c      d
    $(strip $(s))
    #返回无前导、无后缀空白,单词间用一个空格分隔的字符串
    
    • join
    $(join a b c, .c .o)
    #a.c b.o c
    
    • wildcard
    #列出当前目录下符合模式的文件名
    $(wildcard *.c)
    
    • foreach
    dirs = a b
    files = $(foreach dir, $(dirs), $(wildcard $(dir)/*))
    #输出目录a b下的所有文件名,用空格分隔
    
    • if
    SUBDIR=$(if $(src_dir) $(src_dir),/home/)
    #如果src_dir不空,那么SUBDIR就是src_dir否则是/home/
    
    • shell函数:执行时用``包括起来就行。

    相关文章

      网友评论

        本文标题:GNU_Make中文手册

        本文链接:https://www.haomeiwen.com/subject/ekjsbctx.html