现在的位置: 首页 > 综合 > 正文

第2章 Makefile简介

2017年11月26日 ⁄ 综合 ⁄ 共 7015字 ⁄ 字号 评论关闭
2章 Makefile简介

注:这一章里用到‘目标’一词的频率很高,有关目标一词大体有如下三种含义。第一种是指makefile规则中的目标,即由target一词翻译过来的,第二种是指由goal翻译过来的,有关这一个目标的含义大体和第一种含义一致,不过我对他的理解是,goal是make命令参数中所制定的target名称,所以它和target有对应的关系,第三种是目标文件,即由object翻译过来的,它指的是编译器输出,但是往往每个object也会是一个target,所以也有机会成为goal,例如上例中我如果执行命令‘make main.o’那么这个main.o就具有三种身份。
 
    你需要一个Makefile来通知make程序你想要做什么。通常情况下,Makefile用来告诉Make执行编译和连接一个程序。
    在这一章里我们将讨论一个简单的Makefile文件,它描述了如何将8个C源文件和3个头文件编译并连接为一个文本编辑器。这个Makefile同时也会告诉make程序如何执行各种显示请求(如为进行清理操作的删除某些文件的命令)。《附录C 复杂的Makefile》介绍了一个复杂的Makefile示例。
    make命令重新编译文本编辑器时,所有自上次编译以来发生过变动的源文件都将被重新编译。当一个头文件发生过变动时,为了保险起见所有包含这个头文件的源文件都必须重新编译。如果有任意一个源文件被重新编译过,那么所有的目标文件,无论是刚被更新过的,还是上次编译保留下来的,将被重新连接成为一个新的文本编辑器的可执行文件。
 
2.1 规则的形式
 
    一个简单的Makefile将由如下形式的规则构成
        目标…: 依赖文件...
            命令
        ...
        ...
    目标,通常都是一个程序产生的文件名,例如目标文件(指编译程序的输出,有别于此处所说的目标)、可执行文件。一个目标也可以是要执行一组操作的一个代号,例如‘clean’(参见《4.5 伪目标》)。
    依赖文件,是一个用于产生target所需要的文件。通常产生一个目标需要若干个依赖文件。
命令是一个make程序执行的操作。每条规则中可能包含超过一个的命令,每个命令自成一行,每个命令行都以Tab键开始。这是大家不常注意的地方。
    通常一格规则中的既具有命令又具有依赖文件集,且如果依赖文件集中的文件如果发生变化,命令将生成规则中的目标。然而有些规则并不一定具有依赖文件集,例如携带一条删除命令的目标‘clean’就没有依赖文件集。
    所以,一条规则就是用来解释何时、怎样remake这条规则的目标文件。Make根据依赖文件集执行命令来产生或者更新目标。一条规则也用来解释何时、怎样执行一个操作。有关规则可以参见《第4章 书写规则》。
    一个Makefile可能会包含有除了规则以外的一些其他文本信息,但是一个简单的Makefile这需要包含某些规则即可。实际中很多规则看起来可能要比这里展示的这个模版复杂一点,但是它们也都将或多或少地与这个模版相符合。
 
2.2 一个简单的Makefile
   
    这里介绍一个简单的Makefile,它描述了如何通过8个目标文件生成一个文本编辑器的可执行文件,以及如何根据8个C语言源文件和3个头文件生成这些目标文件。
    在这个例子中,所有的C文件都包含名为“defs.h”的头文件,但是只有那些定义了编辑器命令的C文件将包含头文件“command.h”,那些负责文本编辑器缓冲区处理的底层文件包含头文件“buffer.h”。
 
    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
 
    我们把Makefile中过长的行用‘/’分离成两行,这样有利于提高Makefile的可读性而不改变其作用。
    你需要再命令行下敲入‘make’命令来使用这个Makefile来创建文本编辑器的可执行程序,你可以通过敲入‘make clean’命令来清除上一个命令所生成的可执行文件和目标文件。
    在这个示例中,目标包括可执行文件edit,目标文件main.o,kbd.o等。依赖文件包括main.c,defs.h等。但实际上,每一个‘.o’文件既是依赖文件,又是目标。其中命令包括
`cc -c main.c'、 `cc -c kbd.c'.等。
    如果目标就是一个文件,那么当它的依赖关系集中有文件被修改过时,那么他就需要重新编译或者连接。此外,如果依赖文件集中的某些文件本身就是目标,那么需要确定它们是否应该首先更新。示例中edit目标依赖于8个目标文件,main.o依赖于main.c和defs.h。
Shell命令会在包含目标、依赖文件集的那行文本的后一行出现,命令要以TAB键开始以区别与Makefile中其他类型的文本行(记住make并不知道命令将如何执行,你必须提供给make合适的命令来更新目标。当目标需要更新时,Make所能做到的只是执行拟在规则中所指定的命令)。
目标‘clean’不是一个文件,仅是一个动作的代号。既然你一般情况下并不想执行这个规则里边的操作,所以‘clean’不是任何其他规则的依赖文件。故而除非你明确指定,否则make不会处理这个规则。注意,这个规则的目标非但不是其他任规则的依赖文件,同时它本身也不具有任何依赖文件,所以这一条规则的作用仅是执行其包含的一条命令。不涉及任何文件而仅是用来执行一组操作目的的目标被称作伪目标(phony target),参看《4.5 伪目标》了解有关这种目标的内容。参看《5.4 命令中的错误》了解如何使make程序忽略因rm或者其他命令导致的错误。
 
2.3 Make如何处理Makefile
 
    默认情况下,Make会从Makefile中的第一个目标开始(不是那些名称以‘.’开头的目标),这个目标被称作默认目标。
    在上一节的Makefile示例中,默认目标是生成可执行程序‘edit’,因此我们首先书写了该规则。
    因此,当你敲击make命令时,make程序会解析当前目录下的makefile文件,并由其第一条规则始进行处理。在上节的示例中,第一条规则是用于重新连接生成可执行程序‘edit’的,但是在make程序处理这条规则之前,它必须处理完生成这条规则所依赖的那些文件的那些规则,在这个例子中那些文件就是目标文件(.obj或者.o文件),那些规则就是负责把源文件生成对应的目标文件(.o文件)。如果依赖文件集中指定的源文件、头文件较之由其生成的目标文件更新,或者目标文件不存在,那么就需要进行重新编译的工作。
    其他规则(非第一条规则)能够获得处理,是因为他们的目标出现在了make程序指定的目标的依赖文件集之中了。如果其他规则不会被make目标直接或者间接依赖,那么make程序不会处理它们,除非你明确告知make处理它(例如这样“make clean”)。
    在重新编译目标文件之前,make会考虑更新它的依赖文件,就是那些C源文件和头文件。这个Makefile中并没有指定make要为这些文件执行哪些操作——这些.c和.h文件不是任何规则的目标——所以make不需要为它们作任何事。不过Make也能够自动更新C程序,就像现在Bison和Yacc通过自己的规则所做到的那样。
    在编译完‘edit’所需要的所有的目标文件之后,make程序会确定是否需要重新连接‘edit’。如果有‘edit’依赖的目标文件较其更新,或者edit根本不存在,那么这时make就一定会执行重连接‘edit’的操作。如果有一个目标文件是刚刚经过了重新编译的,那么它就比‘edit’更新,所以‘edit’就要被重连接。
    因此,如果我们改变文件‘insert.c’的内容,随后运行make,make将会编译‘insert.c’来更新‘insert.o’,之后还会连接‘edit’。如果我们改变了‘command.h’的内容,那么‘kbd.o’,‘command.o’,‘files.o’都回被重新编译,之后‘edit’也会被重新连接。
 
2.4 使用变量以简化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
    这种重复带来了错误隐患,如果我们添加了一个新的目标文件到系统,我们或许会只向这两处中的一处添加了这个目标文件而忘记了在另一处添加。我们可以通过使用变量的方法来消除这种错误隐患并同时简化我们的Makefile。变量机制允许一个文本串在一处定义可在多处进行替代(参见《第6章 如何使用变量》了结更多内容)。
    现在,为makefile定义一个命名为objects, OBJECTS,objs, OBJS, obj, 或 OBJ的变量已经成为一种标准化的作法。在这个例子中我们可以定义如下这样一个变量:
    objects = main.o kbd.o command.o display.o /
                insert.o search.o files.o utils.o
    这样一来,在每个我们想要使用上边这些目标文件的地方,我们都可以用$( objects)进行替代(参见《第6章 如何使用变量》了结更多内容)
    下边是我们在示例的makefile使用了变量后的情形:
    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’文件生成对应的‘.o’文件。例如,它将使用命令‘cc –c main.c –o main.o’来把main.c编译为main.o。因此,我们可以省略示例中目标文件所对应的那些命令(参见《第10章隐式规则》)。
    在这种应用下的‘.c’文件也会被自动添加到其目标文件的依赖文件集之中。倘若我们省略了那些命令,我们可以同时省略依赖文件中的那些‘.c’文件。
    在上一节的基础上,我们添加上述的这种方法后,makefile的形式如下:
    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 : 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)
    这就是我们在实践中书写makefile的方法。(有关‘clean’的复杂性将在其他地方进行介绍,参见《4.5 伪目标》《5.4 命令中的错误》)
    既然使用隐式规则如此方便,故而它们非常重要。你将会看到它们被频繁使用。
 
2.6 Makefile的另一种书写格式
 
    当一个makefile中的目标文件都是通过隐式规则产生的时候,那么makefile可以以另外一种格式来书写。在这种格式的makefile之中,每一项规则可以以依赖文件来组织,而不是通过目标来组织。下面是一个例子:
    objects = main.o kbd.o command.o display.o /
            insert.o search.o files.o utils.o
    edit : $(objects)
        cc -o edit $(objects)
    $(objects) : defs.h
    kbd.o command.o files.o : command.h
    display.o insert.o search.o files.o : buffer.h
    这里‘defs.h’作为所有目标文件共有的依赖文件给出,而‘command.h’和‘buffer.h’是作为部分的目标文件共有的依赖文件给出的。
    这种风格好与坏还依赖于个人爱好,这种风格更为简洁,但是有些人觉得把有关一个目标的信息组织到一起的方式更为清晰而更喜欢前一种方式。
 
2.7 用于清除目录的规则
   
    往往编译程序不是我们书写规则的唯一目的。Makefile除了用于编译程序之外通常能告诉make程序完成一些其他的工作:例如,为了清理当前目录,需要删除可执行文件和所有的目标文件。
    下面给出一个例子演示如何清理我们的文本编辑器:
    clean:
        rm edit $(objects)
    在实际编写makefile的过程中,我们往往希望这个规则能够在略为复杂的情况下能够处理一些我们未曾预料的问题。我们可以这样书写:
    .PHONY : clean
    clean :
        rm edit $(objects)
    这样就可以防止在有一个名为‘clean’的文件存在的情况下make程序变得混乱,也能够保证make忽略rm出错而继续执行。(参见《4.5 伪目标》《5.4 命令中的错误》)
    一条这样的规则不要放在makefile中规则的开始部分,因为我们不想它作为默认目标,在我们这个示例makefile中,我们想要负责重新编译文本编辑器工作的edit规则作为默认目标。
    因为clean不是edit的依赖文件之一,当我们执行没有参数的make命令时这个规则根本不会得到处理。如果要执行这个规则,那么我们需要执行‘make clean’这个命令。(参见《第9章 如何运行make》)

抱歉!评论已关闭.