Makefile学习

一直以来对于所学的东西都是会而不精,今天从makefile开始,深入的学习一下一些基础的内容.

参考:gunmake.htm

 

Makefile

make 可以自动判断一个大程序中哪些文件需要编译,并编译它们.
    如何判断?
      make程序根据Makefile文件中的数据和每个文件更改的时间戳决定哪些文件需要更新.

使用make 必须要有对应的Makefile文件
  它根据makefile文件来决定干什么

makefile的规则:
  目标 target : 依赖1 prerequiries ....()
  <tab>编译的命令1(cmd)
  ....
  以tab开始的行,make会认为是命令行.

  目标是要产生的文件名称.比如可执行文件或者obj文件.当然,目标也可以是一个可执行的动作名称,比如"clean"

  依赖是用来输入从而产生目标的文件,一个目标可以有很多依赖

  命令是make执行的具体动作,一个规则可以含有多个命令
    注意:每个命令行前面必须是一个tab字符,这里比较容易出错

通常,如果一个依赖发生变化,make会根据makefile的依赖关系,自动更新需要更新的目标
  但是目标不一定都需要依赖,比如"clean", 它只包含命令

规则即用来解释怎样创建文件.比如编译顺序,编译内容.
  一个Makefile可以包含规则以外的其他文本,但一个简单的makefile文件仅仅需要包含规则.

具体例子:
一个文本编译器(名字为edit)的可执行文件生成方法,其依赖8个obj文件(.o文件),这8个obj依赖8个C文件和3个头文件.

  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

make 处理makefile的过程
  默认状态下,make开始于第一个目标,如上例,自动执行于第一个目标edit.

使用变量简化makefile文件
  上例中每个文件都被列举了两次,这样,如果对于一个大的工程,很有可能在需要列举的地方都要列举出来
  很容易遗漏.而使用变量则可以简化makefile文件并排除这种出错可能,变量是定义一个字符串一次,而能多处替代改字符串使用

一般在makefile中使用名为objects,OBJECTS,objs,OBJS,obj,OBJ的变量代表所有obj文件.此处我们定义obj变量,如下
  obj = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
  然后再每个需要列举obj的地方,使用$(obj)来代替
  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 -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 $(objects)

2.5 让make推断命令
    编译单独的c语言并不需要写出命令,因为make可以把它推断出来:
    make有一个使用cc -c把C语言源程序编译更新为相同文件名的obj文件的隐含规则.
      例如
      main.o : main.c
        cc -c main.c -o main.o
      可写作
      main.o:
2.7 在目录中删除文件的规则
    make 可以执行一个清除工作.如clean
    实际应用中,应该编写较为复杂的规则以防不能预料的情况发生.更接近实用的规则样式如下:
      .PHONY: clean
      clean:
        -rm edit $(obj)
    这样可以预防因为存在clean的文件而发生混乱,并导致它在执行rm命令时发生错误.

3 编写makefile
    命名: make可以自动寻找一下文件'GNUmakefile'、‘makefile’和‘Makefile’
    推荐命名为Makefile
      如果要使用自定义名字的makefile,可以使用-f name或 --file=name 来告诉make读名字为name的文件为makefile文件.
      如果指定多个文件,则按顺序执行.
3.1 包含其他的makefile文件
    include指令告诉make暂停读取当前makefile文件,先读完include指令指定的makefile文件后再继续,指令在makefile文件占单独一行,其格式如下:
      include filenames.... //该行不能以tab开始,可以有空格,两个文件名也以空格隔开
    使用include指令的一种情况: 几个程序分别有单独的makefile文件
      另一种使用include指令情况是需要自动从源文件为目标产生依赖的情况

    如果makefile文件名不以'/'开头,且在当前目录找不到,则会搜寻以'-I'或'--include-dir'参数指定的目录,最后会一次搜寻下面的目录(如果存在)
    ‘prefix/include' (通常为 ‘/usr/local/include') ‘/usr/gnu/include',‘/usr/local/include', ‘/usr/include'。

4 通配符
    make中的通配符和shell中的通配符一样是"*","?","[...],例如*.c指当前目录中所有以.c结尾的文件. 注意,当 定义一个变量时通配符不会扩展,如obj = *.o,并不会扩展.
    在目标、依赖和命令中的通配符自动扩展.

4.1 函数wildcard
  通配符在规则中可以自动扩展,但设置在变量中活在函数的参数中通配符一般不能正常扩展,如果需要扩展,可以使用如下格式:
    $(wildcard pattern...)
  可以在makefile文件的任何地方使用该字符串,应用时该字符串被一列在指定目录下存在的且
    文件名和给出的文件名的格式相符合的文件所代替,文件名中间由空格隔开.
  如果没有指定格式一致的文件,则函数wildcard的输出将会省略.
  如使用函数wildcard得到指定陌路下所有的c语言源程序文件名的命令格式为:
    $(wildcard *.c)
  我们可以把所获得的C语言源程序文件名的字符串通过".C"后缀变为".o"转换为obj文件名的字符串,其格式为:
    $(patsubst %.c,%.o,$(wildcard *.c))
  这里使用了另一个函数patsubst(字符串替换和分析函数)
  这样,一个变异特定目录下所有C语言源程序,并把它们连接在一起的makefile文件可以写成如下格式:
    obj:=$(patsubst %.c,%.o,$(wildcard *.c))

    foo : $(obj)
      cc -o foo $(obj)


4.4 假想目标
  假想目标不是一个真正存在的文件名,它仅仅是您制定的一个具体规则所执行的一些命令的名称.使用假想目标有两个原因:
  避免和具有相同名称的文件冲突和改善性能.
  如果您写一个其命令不创建目标文件的规则,一旦由于重建而提及该目标,则该规则的命令就会执行.比如:
    clean:
      rm *.o temp
  因为rm命令不创建名为clean的文件,所以不应有名为clean的文件存在,
  注意:但如果目标clean文件存在,则因为其没有依赖,所以文件clean始终会认为已经更新,因此它的命令将永不会执行.
  为了避免这种情况,应该使用.PHONY目标格式声明其为假想目标
    .PHONY : clean
    clean:
      rm *.o temp
  另一个使用假想目标的例子是使用make的递归调用进行连接的情况:此时,makefile文件常常包含列举一系列需要创建的子目录的变量.

4.7 内建的特殊目标名
  .PHONY
      特殊目标.PHONY的依赖是假想目标.
      假想目标是这样一些目标:make 无条件的执行它命令,和目录下是否存在该文件以及它最后一次更新的时间没有关系.

  .SUFFIXES
      特殊目标.SUFFIXES的依赖是一列用于后缀规则检查的后缀.

  .IGNORE
      如果您特别为目标.IGNORE指明依赖,则make将会忽略处理这些依赖文件时执行命令产生的错误.

4.11 双冒号规则
    双冒号规则是在目标名后使用::代替:的规则,当同一个目标在一条以上的规则中出现时,双冒号规则和平常的规则处理有所差异.

4.12 自动生成依赖
  在为一个程序编写的makefile文件中,常常需要写许多仅仅是说明一些obj文件依靠头文件的规则.例如如果main.c通过一条include使用defs.h,则您需要写入下列规则:
    main.o:defs.h
  您需要这条规则让make知道如果defs.h一旦改变必须重构main.o.所以在一个大的工程中,必须多次注明,且如果删除一个include则makefile也要改写.
  为避免这种烦恼,现代C编译器根据源程序中include语句可以为您编写这些规则,即如果需要这种功能,通常在编译源程序是加入 '-M'开关,如下命令
    cc -M main.c
  产生如下输出:
    main.o : main.c defs.h
  这样您就不必再亲自写这些规则,编译器可以为您完成这些工作.

  我们推荐使用自动生成依赖的习惯是把makefile文件和源文件程序文件一一对应起来.
    如,对每一个源程序文件"name.c"有一名为name.d的makefile文件和它对应,该makefile文件中列出了名为name.o的obj文件所依赖的文件.这种方式的优点是仅在源程序文件改变的情    况下才有必要重新扫描生成新的依赖.
  这里有一个根据C语言源程序name.c生成名为name.d依赖文件的格式规则:
    %.d:%.c
    set -e: $(CC) -M $(CPPFLAGS) $< | sed 's/\($*\)\.o[ :]*/\l.o $@ : /g' > $@: [ -s $@] || rm -f $@

常用的自动变量 
  下面是自动变量列表:
    $@
      规则的目标文件名。如果目标是一个档案成员,则变量‘$@’ 档案文件的文件名。对于有多个目标的格式规则(参阅格式规则简介),变量‘$@’是那个导致规则命令运行的目标文件名。

    $<

      第一个依赖的文件名。如果目标更新命令来源于隐含规则,该变量的值是隐含规则添加的第一个依赖。参阅使用隐含规则。

    $?

      所有比目标‘新’的依赖名,名字之间用空格隔开。对于为档案成员的依赖,只能使用已命名的成员。参阅使用make更新档案文件。

    $^

      所有依赖的名字,名字之间用空格隔开。对于为档案成员的依赖,只能使用已命名的成员。参阅使用make更新档案文件。对同一个目标来说,一个文件只能作为一个依赖,不管该        文件的文件名在依赖列表中出现多少次。 所以,如果在依赖列表中,同一个文件名出现多次,变量‘$^’的值仍然仅包含该文件名一次。

posted on 2013-09-11 15:56  joseph_伽拉  阅读(773)  评论(0编辑  收藏  举报