专注细节
努力进步

makefile学习

1,makefile文件的组成内容  

  • 显式规则
  • 隐式规则
  • 变量的意义
  • 文件指示
  • 注释

2,make工具的退出码

  • 0——表示makefile文件成功执行
  • 1——表示makefile文件执行时出现了错误
  • 2——如果用户使用了-q选项,并且make使一些目标不需要更新,则返回2

3,规则的使用

3.1,基本规则(显式规则)

  • a, targets : prerequisites
    • command
    • /…
  • b, targets : prerequisites ; command
    • command

    edit : main.o kbd.o command.o display.o /
    insert.o search.o files.o utils.o
    cc -o edit main.o kbd.o command.o display.o /
    insert.o search.o files.o utils.o

    main.o : main.c defs.h
    cc -c main.c
    kbd.o : kbd.c defs.h command.h
    cc -c kbd.c
    command.o : command.c defs.h command.h
    cc -c command.c
    display.o : display.c defs.h buffer.h
    cc -c display.c
    insert.o : insert.c defs.h buffer.h
    cc -c insert.c
    search.o : search.c defs.h buffer.h
    cc -c search.c
    files.o : files.c defs.h buffer.h command.h
    cc -c files.c
    utils.o : utils.c defs.h
    cc -c utils.c
    clean :
    rm edit main.o kbd.o command.o display.o /
    insert.o search.o files.o utils.o

3.2,隐式规则

  • make工具有自己的推导规则,例如:make工具会自动使用gcc -c命令,将一个扩展名为.c的c语言源程序编译成一个同名的.o的目标文件。因此当编译一个单独的c文件到o文件时,可以使用隐含规则,让make工具自己推导规则。

    edit : main.o kbd.o command.o display.o /
    insert.o search.o files.o utils.o
    cc -o edit main.o kbd.o command.o display.o /
    insert.o search.o files.o utils.o

    main.o : defs.h
    kbd.o : defs.h command.h
    command.o : defs.h command.h
    display.o : defs.h buffer.h
    insert.o : defs.h buffer.h
    search.o : defs.h buffer.h
    files.o : defs.h buffer.h command.h
    utils.o : defs.h
    clean :
    rm edit main.o kbd.o command.o display.o /
    insert.o search.o files.o utils.o

3.3,伪目标

  • 上面的makefile中,多次提到一个称为clean的目标,那就是一个伪目标。
  • 使用clean目标,执行后面的命令,清除所生成的目标文件,为下次编译工作做准备
  • 因此,make并不生成clean这个目标文件,这个文件称为伪目标文件。
  • 为了避免和已存在的文件重名,必须使用一个特殊的标记.phony来显示地指明一个目标
  • 是伪目标。如:.phony : clean.表示不管makefile文件是否有clean文件,都将其声明为
  • 一个伪目标。
  • 伪目标也可以作为默认目标,其位置必须是第一个目标

    all : main.o kbd.o command.o display.o /
    insert.o search.o files.o utils.o
    cc -o edit main.o kbd.o command.o display.o /
    insert.o search.o files.o utils.o

    .phony all

    main.o : main.c defs.h
    cc -c main.c
    kbd.o : kbd.c defs.h command.h
    cc -c kbd.c
    command.o : command.c defs.h command.h
    cc -c command.c
    display.o : display.c defs.h buffer.h
    cc -c display.c
    insert.o : insert.c defs.h buffer.h
    cc -c insert.c
    search.o : search.c defs.h buffer.h
    cc -c search.c
    files.o : files.c defs.h buffer.h command.h
    cc -c files.c
    utils.o : utils.c defs.h
    cc -c utils.c

    这个makefile,默认目标为伪目标即不生成目标文件,所以这个makefile可以用来每次更新多个目标文件而生成目标文件。

3.4,使用通配符

  • make工具支持3种通配符*,?,[…].   ~表示用户的根目录,该目录通常都是用$home环境变量所保存。
  • *表示匹配所有字符,如*.c表示所有扩展名为c的文件。?表示匹配一个字符,[]表示一个模式。

3.5,搜索源文件

  1. 1,设置vpath变量

      1. makefile文件中使用特殊特殊变量vpath就是实现搜索源文件的功能,若没有指明该变量,make就默认在当前目录中去寻找依赖文件和目标文件。如vpath=src:../include。vpath变量被定义为2个目录,分别是src和../include。表示make工具会顺序搜索当前目录、当前目录下的src目录和父目录下include目录。

  1. 2,使用vpath关键字


    • 使用vpath的方法有三种:a,指定特定模式的文件的搜索目录vapth <pattern> <dirs>;b,指定文件的模式 vpath<pattern>表示清楚符合模式pattern的文件的搜索目录;c,清除所有的文件的搜索目录vpath.eg: vpath %.h ../include表示在当前目录和父目录下的include搜索所有的以.h结尾的文件

4,使用命令

4.1,显示命令

  1. make工具会把要执行的命令行在命令执行之前输出到屏幕上,当使用@字符在命令行前,这个命令将不被make工具显示出来,如:echo compling命令会先显示echo compling然后显示compling,而@echo compling不会显示echo compling而显示compling。
  2. 另外,make执行时,使用参数-n或者–just-print时,只是显示命令,不会执行命令。使用-s或者——slient,禁止所有命令的显示,不论命令前面是否有@。

4.2,执行命令

  1. makefile:
  2. all:
  3.         ls –l
  4.         ./hello
  5. .phony: all
  6. make –s all
  7. hello            hello.c      makefile
  8. hello world
  9. makefile:
  10. exec:
  11.          cd /home/admin
  12.          pwd
  13. .phony:  exec
  14. make –s exec
  15. /home/admin/make                    ####注意这里不是/home/admin
  16. makefile:
  17. exec:
  18.           cd /home/admin;pwd       ####如果需要让上一条命令的结果应用在下一条命令时,应该用
  19.                                                  ####   ;分割这两条命令
  20. .phony: exec
  21. make –s exec
  22. /home/admin/

4.3,命令出错

  1. 一般一个命令运行完成后,make工具就会检测每个命令的返回码。如果命令返回成功,make会执行下一条命令。当规则中所有的命令返回成功后,这个规则就运行完成。如果某个出现错误,make工具就会终止。但是在命令前加“-”可以忽略对该民联执行结果的判断。如:
  2. clean:
  3.         -rm –f *.c
  4. 或者make使用-i(–ignore-errors)是makefile所有命令都忽略错误。又或者规则以.ignore声明为目标。如:
  5. clean:
  6.           rm –f *.c
  7. .ignore

5,使用变量

5.1,使用普通变量

  1. 可以在makefile中使用变量,makefile中的变量代表一个字符串。makefile变量在声明的时候需要对其赋值,需要在变量前加上$符号(需要使用$符号,则用$$表示),对3.1中makefile使用变量后:
  2. objects=main.o kbd.o command.o display.o /
    insert.o search.o files.o utils.o edit : $(objects)
    gcc -o edit $(objects) main.o : main.c defs.h
    gcc -c main.c
    kbd.o : kbd.c defs.h command.h
    gcc -c kbd.c
    command.o : command.c defs.h command.h
    gcc -c command.c
    display.o : display.c defs.h buffer.h
    gcc -c display.c
    insert.o : insert.c defs.h buffer.h
    gcc -c insert.c
    search.o : search.c defs.h buffer.h
    gcc -c search.c
    files.o : files.c defs.h buffer.h command.h
    gcc -c files.c
    utils.o : utils.c defs.h
    gcc -c utils.c
    clean :
    rm edit $(objects)
    在很多情况下,用户将编译器以及编译器选项定义为变量,能够增强makefile的灵活性,便于移植。

    cc=gcc
    flags=-c
    objects= main.o kbd.o command.o display.o
            insert.o search.o files.o utils.o
    edit: $(objects)
        $(cc) -o edit $(objects)
    main.o: main.c defs.h
        $(cc) $(flags) main.c
    kbd.o: kbd.c defs.h command.h
        $(cc) $(flags) kbd.c
    command.o: command.c defs.h command.h
        $(cc) $(flags) command.c
    display.o: display.c defs.h buffer.h
        $(cc) $(flags) display.c
    insert.o: insert.c defs.h buffer.h
        $(cc) $(flags) insert.c
    search.o:search.c defs.h buffer.h
        $(cc) $(flags) search.c
    files.o:files.c defs.h buffer.h command.h
        $(cc) $(flags) files.c
    utils.o: utils.c defs.h
        $(cc) $(flags) utils.c
    clean :
        rm edit $(objects)
    .phony: clean

    当移植到其他的平台时,只需要更改变量cc和变量flags即可,其他内容都不需要更改。

5.2,变量中的变量

  1. 在makefile中使用变量定义变量值的方式有3种
  2. 1,使用“=”操作符
  3. foo=$(bar)
  4. bar=$(ugh)
  5. ugh=hub?
  6. all:
  7.       echo $(foo)
  8. 执行后显示,hub?     ps:防止递归定义
  9. 2,使用:=操作符
  10. 使用:=操作符可以避免递归定义,使用:=定义时,只能使用前面已经定义好的变量。
  11. 3,使用?=操作符
  12. ?=表示如果变量之前未被定义,那么变量的值就被定义。如变量之前的值已经定义则赋值语句不做任何操作

5.3,追加变量的值

  1. makefile允许给变量追加一个值,操作符是+=
  2. objects=main.o foo.o bar.o utils.o
  3. objects+= $(objects) another.o
  4. 例子:
  5. cc =gcc
    flags=-c
    flags+= –o2
  6. objects =main.o kbd.o command.o display.o
        insert.o search.o fles.o utils.o
    objects+=new.o
    edit: $(objects)
        $(cc) -o -o2 edit $(objects)
  7. main.o: main.c defs.h
        $(cc) $(flags) main.c
       
  8. kbd.o: kbd.c defs.h command.h
        $(cc) $(flags) kbd.c
    command.o: command.c defs.h command.h
        $(cc) $(flags) command.c
    display.o: display.c defs.h buffer.h
        $(cc) $(flags) display.c
    insert.o: insert.c defs.h buffer.h
        $(cc) $(flags) insert.c
    search.o:search.c defs.h buffer.h
        $(cc) $(flags) search.c
    files.o:files.c defs.h buffer.h command.h
        $(cc) $(flags) files.c
    utils.o: utils.c defs.h
        $(cc) $(flags) utils.c
    new.o: new.c
        $(cc) $(flags) new.c
       
  9. clean :
        rm edit $(objects)
  10. .phony clean

5.4,自动化变量

  1. makefile一共有7个自动化变量$@,$%,$<,$?,$^,$+,$*。

    $@
        表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
    $%
        仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(unix下是[.a],windows下是[.lib]),那么,其值为空。
    $<
        依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
    $?
        所有比目标新的依赖目标的集合。以空格分隔。
    $^
        所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
    $+
        这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
    $* 
       这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo"。这个特性是gnu make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。

6,使用条件判断

6.1,条件表达式

  1. 条件关键字有4个:ifeq,ifneq,ifdef,ifndef
  2. 1,ifeq:表示相等则执行,其格式如下:
  3.   ifeq(<arg1>,<arg2>)
  4.   ifeq ‘<arg1>’’<arg2>’
  5.   ifeq “<arg1>”“<arg2>”
  6.   ifeq “<arg1>”’<arg2>’
  7.   ifeq ‘<arg1>’”<arg2>”
  8. 2,ifneq:表示不相等则执行
  9.   ifeq(<arg1>,<arg2>)
  10.   ifeq ‘<arg1>’’<arg2>’
  11.   ifeq “<arg1>”“<arg2>”
  12.   ifeq “<arg1>”’<arg2>’
  13.   ifeq ‘<arg1>’”<arg2>”
  14. 3,ifdef:表示值非空则执行
  15.   ifdef <variable-name>
  16. 4,ifndef:表示值为空则执行
  17.   ifndef<variable-name>
  18. 例子:
  19. var1 =
    var2 = hello
  20. all:
    ifdef $var1
        $var1=hello
    endif
  21. ifeq($(var1),$(var2))
        echo "they are equal!"
    else
  22.     echo "they are not equal"
    endif
    .phony:all

6.2,表达式实例

    cc=gcc

    libs_for_gpu=-lgpu
    normal_libs=
    objects= main.o list.o my_lib.o
    app: $(objects)
        ifeq ($(cc),gcc)
            $(cc) -o app $(objects) $(libs_for_gpu)
        else
            $(cc) -o app $(objects) $(normal_libs)
        endif

    main.o : main.c
        $(cc) -c main.c
    list.o : list.c
        $(cc) -c list.c
    my_lib.o : my_lib.c
        $(cc) -c my_lib.c

7,使用函数

7.1,函数调用的语法

  1. makefile文件中的函数以$标识,其语法如下:
  2. $(<function> <arguments>)
  3. or
  4. ${<function> <arguments>}
  5. <function>表示函数名,<arguments>表示函数的参数列表。参数间以逗号”,”分隔,函数名与参数间用空格分隔。例子:
  6. comma :=,
    empty :=
    space := $(empty) $(empty)
    var :=a b c
    bar :=
    all:
        $bar:=$(subst $(space),$(var),$(comma))
    .phony: all

7.2,字符串处理函数

    1. $(subst <from>;,<to>;,<text>;)
    2. $(subst ee,ee,feet on the street) 把”feet on the street”中的“ee“替换成“ee“
    3. $(patsubst <pattern>;,<replacement>;,<text>;) 
    4. $(patsubst %.c,%.o,x.c.c bar.c) 把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”
    5. $(strip <string>;)去空格函数
    6. 去掉<string>字串中开头和结尾的空字符。
    7. $(strip a b c ) 把字串“a b c ”去到开头和结尾的空格,结果是“a b c”
    8. $(findstring <find>,<in>;)查找字符串函数
    9. 在字串<in>中查找<find>;字串。
    10. $(findstring a,a b c) 
      $(findstring a,b c) 第一个函数返回“a”字符串,第二个返回“”字符串(空字符串)
    11. $(filter <pattern…>,<text>)过滤函数
    12. 以<pattern>;模式过滤<text>;字符串中的单词,保留符合模式<pattern>;的单词。可以有多个模式。
  1. sources := foo.c bar.c baz.s ugh.h 
    foo: $(sources) 
            cc $(filter %.c %.s,$(sources)) -o foo
  2. $(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。
  3.  
    $(filter-out <pattern…>,<text>;) 反过滤函数
  4. 以<pattern>模式过滤<text>;字符串中的单词,去除符合模式<pattern>;的单词。可以有多个模式。
  5. objects=main1.o foo.o main2.o bar.o 
            mains=main1.o main2.o  
            $(filter-out $(mains),$(objects)) 返回值是“foo.o bar.o”
  6. $(sort <list>) 排序函数
  7. 给字符串<list>;中的单词排序(升序)。$(sort foo bar lose)返回“bar foo lose” 。
  8. $(word <n>,<text>;)取单词函数
  9. 取字符串<text>中第<n>个单词。(从一开始) $(word 2, foo bar baz)返回值是“bar”。
  10. $(wordlist <s>,<e>,<text>;)取单词串函数
  11.            从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字。
  12.            $(wordlist 2, 3, foo bar baz)返回值是“bar baz”。
  13. $(words <text>) 单词个数统计函数
  14.           统计<text>;中字符串中的单词个数,$(words, foo bar baz)返回值是“3”
  15. $(firstword <text>)首单词函数
  16.            取字符串<text>中的第一个单词。$(firstword foo bar)返回值是“foo”。

7.3,文件名操作函数

  1. $(dir <names…>),从文件名序列<names>中取出目录部分
  2. result:=
    all:
        $(result)=$(dir test.c /home/admin/test.c)
        echo -n "the result is :"
        echo $(result)
    .phony: all
  3. 结果是./ /home/admin
  4. $(notdir <names…>)从文件名序列<names…>中取出非目录部分。非目录部分是指最后一个“/”之后的部分
  5. $(result)=$(notdir test.c /home/admin/test.c)
  6. result为test.c test.c
  7. $(suffix <name…>)取后缀函数
  8. $(result)=$(suffix test.txt test.c)得到.txt .c
  9. $(basename <names…>)从文件名序列取各个文件名的前缀部分
  10. $(addsuffix <suffix>,<names…>) 把后缀<suffix>加到序列中每个单词的后面
  11. $(addprefix <prefix>,<names…>)把前缀<prefix>加到序列中每个单词的前面
  12. $(join <list1>,<list2>)把<list2>中的单词对应地加到<list1>的单词的后面

7.4,foreach函数

  1. foreach函数是用来控制循环。$(foreach <var> ,<list> ,<text>) <var>最好是一个变量名,<list>可以是一个表达式,而<text>一般会使用<var>参数来一次枚举<list>中的单词。
  2. files :=
    names := a b c d
  3. all:
        $(files) :=$(foreach n, $(names),$(n).c)
        echo -n "the file is : "
        echo $(files)
    .phony: all

7.5,if 函数

  1. if函数很像makefile文件中所支持的条件语句——ifeq。$(if <condition>,<then-part>) or $(if <condition> ,<then-part> ,<else-part>)
  2. a := 1
    b := 1
    result :=
    all:
        $(result) :=$(if ifeq($a,$b), $a=10,$b=10)
    echo -n "the result is :"
    echo $(result)
    .phony: all

7.6,call 函数

  1. call函数为用户创建一个自己定义的函数。用户可以写一个非常复杂的表达式,每次用到这个表达式的时候,使用call函数跳转到该表达式出执行即可。$(call <expression> ,<parm1> ,<parm2> ,<parm3> …),当make执行这个函数时,<expression>参数中的变量,如$(1),$(2),$(3)等会被参数<parm1>, <parm2>,<parm3>依次取代。
  2. add = $(1)+$(2)
    result :=
    all:
    $(result) := $(call add,1,2)
    echo -n "the result is : "
    echo $(result)
    .phony: all

7.7,origin函数

  1. origin函数操作变量的值,该函数可以将变量的定义返回给用户,其语法如下所示:$(origin <variable>) <variable>表示变量的名字。

7.8,shell函数

  1. shell函数用来执行操作系统shell的命令。该函数把执行操作系统命令后的输出作为函数返回。用户可以通过调用shell函数,使用操作系统命令来生成一个变量。格式如下:
  2. $(shell <command> ,<parm1> ,<parm2> ,<parm3>…) <command>表示需要执行的shell的命令,所以该函数的返回值为执行的shell命令的输出结果。
  3. files :=
    all:
    $(files) := $(shell ls)
    echo -n "the files is : "
    echo $(files)
    .phony: all

8,makefile实例

8.1 makefile实例——项目中的总makefile

  1. 若某个项目的源代码按模块分类分别存储在多个目录下,每个模块的目录内都有各自的的makefile文件。其源程序存储的目录结构如下所示:
  2. hello(目录)     include(目录)    list(目录)     makefile(makefile文件)
  3. project/hello目录下。
  4. hello.c(源代码文件)      makefile(makefile文件)
  5. project/include目录下。
  6. hello.h(头文件)     list.h(头文件)
  7. project/list目录下。
  8. list.c(源代码文件)     makefile(makefile文件)
  9. hello目录下的makefile如下:
  10. hello: hello.o
        gcc hello.o -o hello
    hello: hello.c
        hcc -c hello.c
    clean:
        rm -rf hello *.c
    .phony: clean
  11. list目录下的makefile文件如下:
  12. list: list.o
        gcc list.o -o list
    list.o: list.c
        gcc -c list.o
    clean:
        rm -rf list *.o
    .phony: clean
  13. project目录中的makefile文件如下:
  14. subdirs := list hello
    all: modules
    .phony all
    modules:
        for n in $(subdirs)
        do
            exit= make –directory=$$n

            if [$exit="1"];then
                exit 1
            fi
        done
    .phony modules
    clean:
        for n in $(subdirs)
        do
            make –directory=$$n clean
        done
    .phony: clean

8.2 makefile实例——makefile模板

  1. 一个比较规范的makefile文件模板:
  2. cc = xxx-gcc
    cflags+= xxx
    ldflags+= xxx
    exec = xxx
  3. all: $(exec)
    .phony all
  4. xxx: xxx.c
        $(cc) $(cflags) $(ldflags) xxx.c -o $@
  5. clean:
        -rm -f $(exec) *.elf *.gdb *.o *.a
    .phony: clean
  6. 使用该模板创建hello程序的makefile文件:
  7. cc = gcc
    cflags+=
    ldflags+=
    exec =
  8. all: $(exec)
    .phony all

    hello: hello.c
        $(cc) $(cflags) $(ldflags) hello.c -o $@

    clean:
        -rm -f $(exec) *.elf *.gdb *.o *.a
    .phony: clean

    如果此时该程序需要使用pthread线程库,并且使用做高的优化编译选项o3,则修改后的makefile文件如下:

    cc = gcc
    cflags+= -o3
    ldflags+= -pthread
    exec = hello

    all: $(exec)
    .phony all

    hello: hello.c
        $(cc) $(cflags) $(ldflags) hello.c -o $@

    clean:
        -rm -f $(exec) *.elf *.gdb *.o *.a
    .phony: clean

 

分享到:更多 ()