zoukankan      html  css  js  c++  java
  • 怎样写Makefile文件(C语言部分)

      本文摘抄自“跟我一起写Makefile ”,只是原文中我自己感觉比较精要的一部分,并且只针对C语言,使用GCC编译器。 原文请看这里:http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile

            写完之后才发现基本上都是一些比较枯燥的规则,看看一、二、八三个部分就可以了。当作参考工具吧,什么时候用到了再来看看。

    一、概述

            我所使用的make 版本是 GNU Make 3.81,使用的系统是 Ubuntu 10.10,GCC版本为 4.4.5。与原文作者使用的Make 版本很相似。

    1.1  关于程序的编译和链接

            对于C语言的编译,首先要把源文件编译成中间代码文件,即.o文件(在Windows下是.obj文件)。这个动作叫做编译compile)。然后再把大量Object File合成执行文件,这个动作叫做链接link)。更加详细的内容可以参考这里:使用gcc编译C程序的详细过程

            编译时,编译器主要检查语法、函数与变量的声明是否正确。函数的声明通常放在头文件中(头文件中应该只放声明,对于函数的具体实现则应该放到单独的 .c 源文件中),你告诉编译器头文件所在的位置,编译器就会到相应的地方去找函数声明。只要语法正确,编译器就会生成中间文件。一般来说一个源文件(.c)对应一个目标文件(.o)。

            链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序,并不需要源文件的存在。在很多时候,由于中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件

           

    二、Makefile 介绍

    2.1   Makefile的规则

    target ... : prerequisites ...
    	command
    	...
    	...

            target可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。对于标签这种特性,在后续的“伪目标”章节中会有叙述。

            prerequisites就是,要生成那个target所需要的文件或是目标。

            command也就是make需要执行的命令。(任意的shell命令)。注意command之前是TAB键,并非空格键。

    2.2   一个试例

            一个工程有3个头文件,和8个c文件,关系如下图:

    文件关联

            我们的makefile应该是下面的这个样子的:

    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文件中第一个target,并且把它当做最终的目标文件。Makefile所指示出来的关系就是我们在上面的图中所展示的关系。

    2.3   Makefile中使用变量

            makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。

    objects = main.o kbd.o command.o display.o 
    		insert.o search.o files.o utils.o
    
    edit : $(objects)
    	cc -o edit $(objects)

    2.4  让make自动推导

            只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中。

    objects = main.o kbd.o command.o display.o 
    		insert.o search.o files.o utils.o
     cc = gcc
    
    edit : $(objects)
    	cc -o edit $(objects)
    
    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
    
    .PHONY : clean
    clean :
    	rm edit $(objects)
    “.PHONY”表示,clean是个伪目标文件。

    2.5   清空目标文件的规则

    .PHONY : clean
    clean :
    	-rm edit $(objects)
    在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。

    2.6   Makefile中有什么?

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

    1. 显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
    2. 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较简略地书写Makefile,这是由make所支持的。
    3. 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
    4. 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
    5. 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。

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

    2.7   引用其他Makefile

            在include前面可以有一些空字符,但是绝不能是[Tab]键开始。你有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了 e.mk和f.mk。

    include foo.make *.mk $(bar)

    如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

    1. 如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。
    2. 如果目录<prefix>;/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

    如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取, make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”,其表示,无论include过程中出现什么错误,都不要报错继续执行。


    2.8   make的工作方式

    GNU的make工作时的执行步骤入下:(想来其它的make也是类似)

    1. 读入所有的Makefile。
    2. 读入被include的其它Makefile。
    3. 初始化文件中的变量。
    4. 推导隐晦规则,并分析所有规则。
    5. 为所有的目标文件创建依赖关系链。
    6. 根据依赖关系,决定哪些目标要重新生成。
    7. 执行生成命令。

    1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。

            看完这些再看 第八部分:隐含规则,就可以了,中间的都不用看了。我怎么写了这么多。

    三、书写规则

    3.1   文件搜索

            使用特殊变量VPATH 来搜索,如果不指定,make只会在当前的目录中去找寻依赖文件和目标文件。指定了这个变量,在当前目录搜索完而找不到之后会搜索相应目录。

    VPATH = src:../headers

            目录之间用冒号隔开。

            还可以使用vpath关键字,它的使用方法有三种:这里只说一种,详情看原文

    1、vpath <pattern> <directories>

    为符合模式<pattern>的文件指定搜索目录<directories>。

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

    vpath %.h ../headers

    3.2   伪目标
            比如刚开始我们指定的

    .PHONY : clean
    clean :
    	rm *.o temp

            也可以为伪目标指定依赖文件,常见于all,并且把它放在最前,指定为Makefile的终极目标。由于all是伪目标,所以不会生成all文件。

    all : prog1 prog2 prog3
    .PHONY : all

    3.3   静态模式
            静态模式可以更加容易地定义多目标的规则,语法:

    <targets ...>: <target-pattern>: <prereq-patterns ...>
    	<commands>
    	...


            如下面的例子:

    objects = foo.o bar.o
    
    all: $(objects)
    
    $(objects): %.o: %.c
    	$(CC) -c $(CFLAGS) $< -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


            $(objects): %.o: %.c,这里先把$(object) 在这里展开,表示把所有objects之中的.o文件都用.c文件与之对应起来。

            $< 表示依赖文件,这里即.c文件, $@ 表示target文件,这里即.o文件。

    3.4   自动生成依赖性

            在GCC中,我们用gcc –MM main.c 这条命令就可以查看所有main.o 的依赖关系。于是,可以利用这一特性来让Makefile自动生成依赖关系,不用我们自己写了。

            具体看这里

    四、书写命令

            每条规则中的命令和操作系统Shell的命令行是一致的。make会按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头,除非,命令是紧跟在依赖规则后面的分号后的。

    4.1   显示命令

            用“@”字符在命令行前,那么,这个命令将不被make显示出来。

            make执行时,带入make参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令。利于调试Makefile。

            make参数“-s”或“--slient”则是全面禁止命令的显示。

    4.2   命令执行

            如果上一条命令执行的结果需要在下一条指令中使用,那么这两个指令应该放在一行,中间用分号隔开。

    exec:
    	cd /home/hchen; pwd

    4.3   命令出错

            如果想要忽略命令的错误,在它之前加 ‘-’即可。

    4.4   嵌套执行make

            在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。

            例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:

    subsystem:
            cd subdir && $(MAKE)

            等价于:

    subsystem:
            $(MAKE) -C subdir

            具体看这里

    五、使用变量

            在Makefile中的定义的变量,代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。变量是大小写敏感的。“$<”、“$@”等,这些是自动化变量。

    5.1   变量的基础

            变量在定义时要赋初值。使用时在其前加$ 符号,并且用() 或者 {} 把变量名括起来。"$$” 表示真实的 $。

    5.2   变量中的变量

            这里说变量怎样定义,怎样赋值。

            变量赋值有三种方式,一种是 = ,一种是 := ,一种是 ?= 。前一种可以使用延后定义的变量,中间只能按照从上到下的顺序定义变量。最后一种表示如果这个变量已经赋值,就不再对他进行赋值了,如果没有赋值,就对他赋值。详情参看这里

            += 是追加赋值。

            比如定义一个空格,下面的变量space就表示一个空格,其他方法还不好定义空格:

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

    5.3   变量高级用法

          · 变量值的替换:
            我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。例如:

    foo := a.o b.o c.o
    bar := $(foo:.o=.c)

            这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。

    对于静态模式,我们要这样写:   bar := $(foo:%.o=%.c)

          · 把变量的值再当成变量

            即使用变量时,每个变量都展开成一个字符串使用:

    x = y
    y = z
    a := $($(x))

            在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)

            更加详细的描述见这里

    5.4   目标变量

            为某个target指定只会在这条规则以及它所连带的规则中使用的变量:

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

            在这个示例中,不管全局的$(CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是“-g”

    六、使用函数

            在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。

    6.1   函数调用的语法

            函数调用,很像变量的使用,也是以“$”来标识的,其語法如下:

    $(<function> <arguments>)
    或者
    ${<function> <arguments>}


            <arguments>为函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。下面是一个具体的例子:

    comma:= ,
    empty:=
    space:= $(empty) $(empty)
    foo:= a b c
    bar:= $(subst $(space),$(comma),$(foo))

            在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是“a b c”,$ (bar)的定义用,调用了函数“subst”,$(bar)的值是“a,b,c”。

    6.2   字符串处理函数

    $(subst <from>,<to>,<text>) 

    • 名称:字符串替换函数——subst。
    • 功能:把字串<text>中的<from>字符串替换成<to>。
    • 返回:函数返回被替换过后的字符串。
    $(patsubst <pattern>,<replacement>,<text>) 

    • 名称:模式字符串替换函数——patsubst。
    • 功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式< pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符 “%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个 “%”将是<pattern>中的那个“%”所代表的字串。(可以用“”来转义,以“\%”来表示真实含义的“%”字符)
    • 返回:函数返回被替换过后的字符串。

            这和我们前面“变量章节”说过的相关知识有点相似。如:

            “$(var:<pattern>=<replacement>;)” 相当于 “$(patsubst <pattern>,<replacement>,$(var))”,

            而“$(var: <suffix>=<replacement>)” 则相当于 “$(patsubst %<suffix>,%<replacement>,$(var))”。

            例如有:objects = foo.o bar.o baz.o, 那么,“$(objects:.o=.c)”和“$(patsubst %.o,%.c,$(objects))”是一样的。

    $(filter <pattern...>,<text>)

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

            其他更多的函数和示例参看这里

    6.3   文件名操作函数

            这里列出很多对文件名的操作函数。

            还有foreach, if, call这三个比较特殊的函数。

    七、make的运行

    7.1   指定Makefile

            指定某个不叫Makefile的文件运行make命令,加-f选项:

    make –f hchen.mk

    7.2   指定目标

          “all”这个伪目标是所有目标的目标,其功能一般是编译所有的目标。“clean”这个伪目标功能是删除所有被make创建的文件。“install”这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。“print”这个伪目标的功能是例出改变过的源文件。“tar”这个伪目标功能是把源程序打包备份。也就是一个tar文件。“dist”这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。“TAGS”这个伪目标功能是更新所有的目标,以备完整地重编译使用。“check”和“test”这两个伪目标一般用来测试makefile的流程。

           

    7.3   检查规则

            “-n” “--just-print” “--dry-run” “--recon” 不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile很有用处。

    7.4   make的参数

            点击小标题。

    八、隐含规则

    8.1   C语言编译的隐含规则

            “<n>;.o”的目标的依赖目标会自动推导为“<n>;.c”,并且其生成命令是“$(CC) –c $(CPPFLAGS) $(CFLAGS)”。

            其他语言的隐含规则看这里

    8.2   隐含规则使用的变量

            1、关于命令的变量

                 CCC语言编译程序。默认命令是“cc”。

            2、关于命令参数的变量

                 CFLAGSC语言编译器参数。

            更多变量看这里

    8.3   模式规则

            模式规则中都是用%,它表示匹配0个(还是1个?)或多个字符。

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


     

    8.4   自动化变量

            所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在模式规则的命令中。

            在  a: a.o b.o c.o 这个依赖规则中,a 叫做target,即目标; a.o b.o c.o 都叫做 依赖目标。

    $@
    表示规则中的目标文件集(target,比如上面例子中的%.o)。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
    $%
    仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是 "bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
    $< 
    依赖目标(比如上例种的%.c)中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
    $?
    所有比目标新的 依赖目标 的集合。以空格分隔。
    $^
    所有的 依赖目标 的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
    $+
    这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
    $*
    这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。

    多么希望有个实例可以做一下呀,有好多文件,看看怎么把这些文件用Makefile组织起来,并且优化Makefile的文件。只看这些干巴巴的规则,一会儿就累了………………………………

     
     
  • 相关阅读:
    select服务器模型
    网络编程-并发服务器基础
    生产者消费者模型
    readv()和write()sendfile()
    snprintf函数的用法
    struct stat结构体的简介
    half-socket
    结构体struct sockaddr_in, struct sockaddr,struct in_addr
    int main(int argc,char* argv[])的作用
    fgets和fputs,fread和fwrite,fscanf,和fprintf用法小结
  • 原文地址:https://www.cnblogs.com/lanye/p/6149264.html
Copyright © 2011-2022 走看看