makefile1

730阅读 0评论2013-03-16 cherish568
分类:LINUX

包,在Windows下这种包叫库文件Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。

 

首先,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNUmake使用手册,在这个示例中,我们的工程有8C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
    1
)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
    2
)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
    3
)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

 

target ... : prerequisites ...
            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

 

.PHONY : clean
        clean :
                -rm edit $(objects)

前面说过,.PHONY意思表示clean是一个伪目标,。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事

 

 

Makefile中的命令,必须要以[Tab]键开始

当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”“Make.Solaris”“Make.AIX”等,如果要指定特定的Makefile,你可以使用make“-f”“--file”参数,如:make -f Make.Linuxmake --file Make.AIX

 

 

print: *.c
         lpr -p $?
         touch print

    
上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的[.c]文件。其中的“$?”是一个自动化变量,我会在后面给你讲述。

    objects = *.o

    
上面这个例子,表示了,通符同样可以用在变量中。并不是说[*.o]会展开,不!objects的值就是“*.o”Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的文件名的集合,那么,你可以这样:

    objects := $(wildcard *.o)

 

VPATH = src:../headers

上面的的定义指定两个目录,“src”“../headers”make会按照这个顺序进行搜索。目录由冒号分隔。(当然,当前目录永远是最高优先搜索的地方)‘

 

vpath %.c foo
    vpath %   blish
    vpath %.c bar

其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。

    vpath %.c foo:bar
    vpath %   blish

而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。

 

  all : prog1 prog2 prog3
    .PHONY : all

    prog1 : prog1.o utils.o
            cc -o prog1 prog1.o utils.o

    prog2 : prog2.o
            cc -o prog2 prog2.o

    prog3 : prog3.o sort.o utils.o
            cc -o prog3 prog3.o sort.o utils.o

 

/////////

bigoutput littleoutput : text.g
            generate text.g -$(subst output,,$@) > $@

    
上述规则等价于:

    bigoutput : text.g
            generate text.g -big > bigoutput
    littleoutput : text.g
            generate text.g -little > littleoutput

 

 

/////

objects = foo.o bar.o

    all: $(objects)

    $(objects): %.o: %.c
            $(CC) -c $(CFLAGS) $< -o $@

 

 

 

二、命令执行

当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如:

    
示例一:
        exec:
                cd /home/hchen
                pwd

    
示例二:
        exec:
                cd /home/hchen; pwd

当我们执行“make exec”时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第二个例子中,cd就起作用了,pwd会打印出“/home/hchen”

 

 

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。 

1
、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。 

2
、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。 

3
、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。 

4
、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。 

5
、注释。Makefile中只有行注释,和UNIXShell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“\#” 

最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。

 

   -include ; 
    
其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。

 

 

 

    objects = *.o 

    
上面这个例子,表示了,通符同样可以用在变量中。并不是说[*.o]会展开,不!objects的值就是“*.o”Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的文件名的集合,那么,你可以这样: 

    objects := $(wildcard *.o)

 

 

 

 

四、文件搜寻 

在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。 

Makefile
文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。 

    VPATH = src:../headers 

上面的的定义指定两个目录,“src”“../headers”make会按照这个顺序进行搜索。目录由冒号分隔。(当然,当前目录永远是最高优先搜索的地方) 

另一个设置文件搜索路径的方法是使用make“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种: 

    1
vpath ; 

    
为符合模式;的文件指定搜索目录; 

    2
vpath ; 

    
清除符合模式;的文件的搜索目录。 

    3
vpath 

    
清除所有已被设置好了的文件搜索目录。 

vapth
使用方法中的;需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。;指定了要搜索的文件集,而;则指定了;的文件集的搜索的目录。例如: 

    vpath %.h ../headers 

该语句表示,要求make“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话) 

我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的;,或是被重复了的;,那么,make会按照vpath语句的先后顺序来执行搜索。如: 

    vpath %.c foo 
    vpath %   blish 
    vpath %.c bar 

其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。 

    vpath %.c foo:bar 
    vpath %   blish 

而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。

 

 

 

  .PHONY: clean 
    clean: 
            rm *.o temp 

伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为默认目标,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用伪目标这个特性: 

    all : prog1 prog2 prog3 
    .PHONY : all 

    prog1 : prog1.o utils.o 
            cc -o prog1 prog1.o utils.o 

    prog2 : prog2.o 
            cc -o prog2 prog2.o 

    prog3 : prog3.o sort.o utils.o 
            cc -o prog3 prog3.o sort.o utils.o

 

 

 

 

多目标:

    bigoutput littleoutput : text.g 
            generate text.g -$(subst output,,$@) >; $@ 

    
上述规则等价于: 

    bigoutput : text.g 
            generate text.g -big >; bigoutput 
    littleoutput : text.g 
            generate text.g -little >; littleoutput 

    
其中,-$(subst output,,$@)中的“$”表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是截取字符串的意思,“$@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。

静态模式

  objects = foo.o bar.o 

    all: $(objects) 

    $(objects): %.o: %.c 
            $(CC) -c $(CFLAGS) $< -o $@ 


上面的例子中,指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。而命令中的“$<”“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则: 

    foo.o : foo.c 
            $(CC) -c $(CFLAGS) foo.c -o foo.o 
    bar.o : bar.c 
            $(CC) -c $(CFLAGS) bar.c -o bar.o

------------------------------------------------------------------------------------------------

   files = foo.elc bar.o lose.o 

    $(filter %.o,$(files)): %.o: %.c 
            $(CC) -c $(CFLAGS) $< -o $@ 
    $(filter %.elc,$(files)): %.elc: %.el 
            emacs -f batch-byte-compile $< 


$(filter %.o,$(files))
表示调用Makefilefilter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不用多说了吧。这个例字展示了Makefile中更大的弹性。

 

命令执行:

当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如: 

    
示例一: 
        exec: 
                cd /home/hchen 
                pwd 

    
示例二: 
        exec: 
                cd /home/hchen; pwd 

当我们执行“make exec”时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第二个例子中,cd就起作用了,pwd会打印出“/home/hchen”

命令出错:

clean: 
            -rm -f *.o 

还有一个全局的办法是,给make加上“-i”或是“--ignore-errors”参数,那么,Makefile中所有命令都会忽略错误。而如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。 

还有一个要提一下的make的参数的是“-k”或是“--keep-going”,这个参数的意思是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。

 

 export variable += value 

        
其等价于: 

        variable += value 
        export variable 

如果你要传递所有的变量,那么,只要一个export就行了。后面什么也不用跟,表示传递所有的变量。

 

定义命令包:

如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始,以“endef”结束,如: 

    define run-yacc 
    yacc $(firstword $^) 
    mv y.tab.c $@ 
    endef 

这里,“run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在“define”“endef”中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序,因为Yacc程序总是生成“y.tab.c”的文件,所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看看吧。 

    foo.c : foo.y 
            $(run-yacc) 

我们可以看见,要使用这个命令包,我们就好像使用变量一样。在这个命令包的使用中,命令包“run-yacc”中的“$^”就是“foo.y”“$@”就是“foo.c”(有关这种以“$”开头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。

 

变量

foo = $(bar) 
    bar = $(ugh) 
    ugh = Huh? 

    all: 
            echo $(foo) 

我们执行“make all”将会打出变量$(foo)的值是“Huh?” $(foo)的值是$(bar)$(bar)的值是$(ugh)$(ugh)的值是“Huh?”)可见,变量是可以使用后面的变量来定义的。

 

面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来: 

    nullstring := 
    space := $(nullstring) # end of the line 

nullstring
是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意这里关于“#”的使用,注释符“#”的这种特性值得我们注意,如果我们这样定义一个变量: 

    dir := /foo/bar    # directory to put the frobs in 

dir
这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我们这样使用这样变量来指定别的目录——“$(dir)/file”那么就完蛋了。

 

 

变量替换

面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来: 

    nullstring := 
    space := $(nullstring) # end of the line 

nullstring
是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意这里关于“#”的使用,注释符“#”的这种特性值得我们注意,如果我们这样定义一个变量: 

    dir := /foo/bar    # directory to put the frobs in 

dir
这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我们这样使用这样变量来指定别的目录——“$(dir)/file”那么就完蛋了。

 

 x = variable1 
    variable2 := Hello 
    y = $(subst 1,2,$(x)) 
    z = y 
    a := $($($(z)))

 

    a_objects := a.o b.o c.o 
    1_objects := 1.o 2.o 3.o 

    sources := $($(a1)_objects:.o=.c) 

这个例子中,如果$(a1)的值是“a”的话,那么,$(sources)的值就是“a.c b.c c.c”;如果$(a1)的值是“1”,那么$(sources)的值是“1.c 2.c 3.c” 

再来看一个这种技术和函数条件语句一同使用的例子: 

    ifdef do_sort 
    func := sort 
    else 
    func := strip 
    endif 

    bar := a d b g q c 

    foo := $($(func) $(bar)) 

这个示例中,如果定义了“do_sort”,那么:foo := $(sort a d b g q c),于是$(foo)的值就是“a b c d g q”,而如果没有定义“do_sort”,那么:foo := $(sort a d b g q c),调用的就是strip函数。 

当然,把变量的值再当成变量这种技术,同样可以用在操作符的左边: 

    dir = foo 
    $(dir)_sources := $(wildcard $(dir)/*.c) 
    define $(dir)_print 
    lpr $($(dir)_sources) 
    endef 

这个例子中定义了三个变量:“dir”“foo_sources”“foo_print”

 

 

追加变量

 

我们可以使用“+=”操作符给变量追加值,如: 

    objects = main.o foo.o bar.o utils.o 
    objects += another.o 

于是,我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”another.o被追加进去了) 

使用“+=”操作符,可以模拟为下面的这种例子: 

    objects = main.o foo.o bar.o utils.o 
    objects := $(objects) another.o 

所不同的是,用“+=”更为简洁。 

如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符,如: 

    variable := value 
    variable += more 

等价于: 

    variable := value 
    variable := $(variable) more

条件判断

  libs_for_gcc = -lgnu 
    normal_libs = 

    foo: $(objects) 
    ifeq ($(CC),gcc) 
            $(CC) -o foo $(objects) $(libs_for_gcc) 
    else 
            $(CC) -o foo $(objects) $(normal_libs) 
    endif

 

条件关键字

  ifeq ($(strip $(foo)),) 
    ; 
    endif

 

ifneq (;, ;)  
    ifneq ';' ';'  
    ifneq ";" ";"  
    ifneq ";" ';'  
    ifneq ';' ";" 

 

 

  示例一: 
    bar = 
    foo = $(bar) 
    ifdef foo 
    frobozz = yes 
    else 
    frobozz = no 
    endif 

    
示例二: 
    foo = 
    ifdef foo 
    frobozz = yes 
    else 
    frobozz = no 
    endif

 

 

ifndef ;

 

函数

 $(patsubst %.c,%.o,x.c.c bar.c)

$(strip ;) 
    
名称:去空格函数——strip 
    
功能:去掉;字串中开头和结尾的空字符。 
    
返回:返回被去掉空格的字符串值。 
    
示例: 
        $(strip a b c ) 
      
把字串“a b c ”去到开头和结尾的空格,结果是“a b c”

(strip ;) 

    
名称:去空格函数——strip 
    
功能:去掉;字串中开头和结尾的空字符。 
    
返回:返回被去掉空格的字符串值。 
    
示例: 
         
        $(strip a b c ) 

        
把字串“a b c ”去到开头和结尾的空格,结果是“a b c” 

$(findstring ;,;) 

    
名称:查找字符串函数——findstring 
    
功能:在字串;中查找;字串。 
    
返回:如果找到,那么返回;,否则返回空字符串。 
    
示例: 

        $(findstring a,a b c) 
        $(findstring a,b c) 

        
第一个函数返回“a”字符串,第二个返回“”字符串(空字符串) 

$(filter ;,;) 

    
名称:过滤函数——filter 
    
功能:以;模式过滤;字符串中的单词,保留符合模式;的单词。可以有多个模式。 
    
返回:返回符合模式;的字串。 
    
示例: 

        sources := foo.c bar.c baz.s ugh.h 
        foo: $(sources) 
                cc $(filter %.c %.s,$(sources)) -o foo 

        $(filter %.c %.s,$(sources))
返回的值是“foo.c bar.c baz.s” 

$(filter-out ;,;) 

    
名称:反过滤函数——filter-out 
    
功能:以;模式过滤;字符串中的单词,去除符合模式;的单词。可以有多个模式。 
    
返回:返回不符合模式;的字串。 
    
示例: 

        objects=main1.o foo.o main2.o bar.o 
        mains=main1.o main2.o 
         
        $(filter-out $(mains),$(objects)) 
返回值是“foo.o bar.o” 
         
$(sort ;) 

    
名称:排序函数——sort 
    
功能:给字符串;中的单词排序(升序)。 
    
返回:返回排序后的字符串。 
    
示例:$(sort foo bar lose)返回“bar foo lose”  
    
备注:sort函数会去掉;中相同的单词。 

$(word ;,;) 

    
名称:取单词函数——word 
    
功能:取字符串;中第;个单词。(从一开始) 
    
返回:返回字符串;中第;个单词。如果;;中的单词数要大,那么返回空字符串。 
    
示例:$(word 2, foo bar baz)返回值是“bar” 

$(wordlist ;,;,;)   

    
名称:取单词串函数——wordlist 
    
功能:从字符串;中取从;开始到;的单词串。;;是一个数字。 
    
返回:返回字符串;中从;;的单词字串。如果;;中的单词数要大,那么返回空字符串。如果;大于;的单词数,那么返回从;开始,到;结束的单词串。 
    
示例: $(wordlist 2, 3, foo bar baz)返回值是“bar baz” 

$(words ;) 

    
名称:单词个数统计函数——words 
    
功能:统计;中字符串中的单词个数。 
    
返回:返回;中的单词数。 
    
示例:$(words, foo bar baz)返回值是“3” 
    
备注:如果我们要取;中最后的一个单词,我们可以这样:$(word $(words ;),;) 

$(firstword ;) 

    
名称:首单词函数——firstword 
    
功能:取字符串;中的第一个单词。 
    
返回:返回字符串;的第一个单词。 
    
示例:$(firstword foo bar)返回值是“foo” 
    
备注:这个函数可以用word函数来实现:$(word 1,;) 

以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。这里,举一个现实中应用的例子。我们知道,make使用“VPATH”变量来指定依赖文件的搜索路径。于是,我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数CFLAGS,如: 

    override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH))) 

    
如果我们的“$(VPATH)”值是“src:../headers”,那么“$(patsubst %,-I%,$(subst :, ,$(VPATH)))”将返回“-Isrc -I../headers”,这正是ccgcc搜索头文件路径的参数。 

 

Foreach

   names := a b c d 

    files := $(foreach n,$(names),$(n).o) 

  

上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”

call

   reverse =  $(1) $(2) 

    foo = $(call reverse,a,b) 



那么,foo的值就是“a b”。当然,参数的次序是可以自定义的,不一定是顺序的,如: 

  

    reverse =  $(2) $(1) 

    foo = $(call reverse,a,b) 
shell

shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awksed等等命令来生成一个变量,如: 

  

    contents := $(shell cat foo) 

  

    files := $(shell echo *.c)


此时的foo的值就是“b a”

上一篇:DHCP服务配置2
下一篇:C++ 工作运用