《嵌入式 Linux C 语言应用程序设计(修订版)》——2.5 make工程管理器

本节书摘来自异步社区《嵌入式 Linux C 语言应用程序设计(修订版)》一书中的第2章,第2.5节,作者 华清远见嵌入式培训中心,孙琼,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.5 make工程管理器

前面几节主要讲解如何在嵌入式Linux下使用编辑器编写代码,如何使用GCC把代码编译成可执行文件,以及如何使用GDB来调试程序,那么,所有的工作看似已经完成了,为什么还需要make这个工程管理器呢?

工程管理器用来管理较多的文件。读者可以试想一下,有一个上百个文件的代码构成的项目,如果其中只有一个或少数几个文件进行了修改,按照之前所学的GCC编译工具,就不得不把这所有的文件重新编译一遍,因为编译器并不知道哪些文件是最近更新的,而只知道需要包含这些文件才能把源代码编译成可执行文件,于是,程序员就不能不再重新输入数目如此庞大的文件名以完成最后的编译工作。

人们希望有一个工程管理器能够自动识别更新了的文件代码,同时又不需要重复输入冗长的命令行,于是make工程管理器也就应运而生了。

实际上,make工程管理器就是个自动编译管理器,能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件的内容来执行大量的编译工作。

用户只需一次编写简单的编译语句即可。它大大提高了实际项目的工作效率,几乎所有嵌入式Linux下的项目编程均会涉及它,希望读者能够认真学习本节内容。

2.5.1 Makefile基本结构

Makefile用来告诉make怎样编译和连接成一个程序,是make读入的惟一配置文件,本节主要讲解Makefile的编写规则。

在一个Makefile中通常包含如下内容。

  • 需要由make工具创建的目标体(target),目标体通常是目标文件、可执行文件或是一个标签。
  • 要创建的目标体所依赖的文件(dependency_file)。
  • 创建每个目标体时需要运行的命令(command)。

它的格式为:

target: dependency_files
     command

例如,有两个文件分别为hello.c和hello.h,希望创建的目标体为hello.o,执行的命令为gcc编译指令:gcc –c hello.c,那么,对应的Makefile就可以写为以下形式:

#The simplest example
hello.o: hello.c hello.h
gcc –c hello.c–o hello.o

接着就可以使用make了。使用make的格式为:make target,这样make就会自动读入Makefile(也可以是首字母小写makefile)执行对应target的command语句,并会找到相应的依赖文件,如下所示:

[root@localhost makefile]# make hello.o
gcc –c hello.c –o hello.o
[root@localhost makefile]# ls
hello.c  hello.h  hello.o  Makefile

可以看到,Makefile执行了“hello.o”对应的命令语句,并生成了“hello.o”目标体。

注意 在Makefile中的每一个command前必须有“Tab”符,否则在运行make命令时会出错。

上面示例的Makefile在实际中是几乎不存在的,因为它过于简单,仅包含两个文件和一个命令,在这种情况下完全不需要编写Makefile而只需在Shell中直接输入即可,在实际中使用的Makefile往往是包含很多的文件和命令的,这也是Makefile产生的原因。

下面就对较复杂的Makefile进行讲解,以下这个工程包含有3个头文件和8个C文件,其Makefile如下所示:

edit : main.o kbd.o command.o display.o \
           insert.o search.o files.o utils.o
           gcc -o edit main.o kbd.o command.o display.o \
                             insert.o search.o files.o utils.o
main.o : main.c defs.h
           gcc -c main.c –o main.o
kbd.o : kbd.c defs.h command.h
           gcc -c kbd.c –o kbd.o
command.o : command.c defs.h command.h
           gcc -c command.c –o command.o
display.o : display.c defs.h buffer.h
           gcc -c display.c –o display.o
insert.o : insert.c defs.h buffer.h
           gcc -c insert.c –o insert.o
search.o : search.c defs.h buffer.h
           gcc -c search.c –o search.o
files.o : files.c defs.h buffer.h command.h
           gcc -c files.c –o files.o
utils.o : utils.c defs.h 
           gcc -c utils.c –o utils.o
clean :
           rm edit main.o kbd.o command.o display.o \
               insert.o search.o files.o utils.o

这里的反斜杠“”是换行符的意思,用于增加Makefile的可读性。读者可以把这些内容保存在文件名为“Makefile”或“makefile”的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果想要删除执行文件和所有的中间目标文件,那么,只需要简单地执行一下“make clean”即可。

在这个makefile中,目标文件(target)包含以下内容:执行文件edit和中间目标文件“*.o”,依赖文件(dependency_file)就是冒号后面的那些“.c”文件和“.h”文件。

每一个“.o”文件都有一组依赖文件,而这些“.o”文件又是执行文件“edit”的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。

在定义好依赖关系后,后续的那一行命令定义了如何生成目标文件的系统命令。请读者注意,这些命令都是以一个Tab键作为开头的。

另外值得注意的是,make工程管理器其实并不处理命令是具体如何工作的,它只负责执行用户所定义的命令。同时,make还会比较目标文件和依赖文件的修改日期,如果依赖文件的日期要比目标文件的日期更新,或者目标文件并不存在的话,那么,make就会执行后续定义的命令。

这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,也可称其为标签,其冒号后什么也没有。这样,make就不会自动去查找文件之间的依赖性,因此也就不会自动执行其后所定义的命令。

若用户想要执行其后的命令,就要在make命令后显示地指出这个标签的名字。这个方法非常有用,通常用户可以在一个Makefile中定义不用的编译或是和编译无关的命令,比如程序的打包、程序的备份命令等。

2.5.2 Makefile变量

为了进一步简化编辑和维护Makefile,make允许在Makefile中创建和使用变量。变量是在Makefile中定义的名字,用来代替一个文本字符串,该文本字符串称为该变量的值。

在具体要求下,这些值可以代替目标体、依赖文件、命令以及Makefile文件中其他部分。在Makefile中的变量定义有两种方式:一种是递归展开方式,另一种是简单方式。

递归展开方式定义的变量是在引用在该变量进行替换的,即如果该变量包含了对其他变量的引用,则在引用该变量时一次性将内嵌的变量全部展开。虽然这种类型的变量能够很好地完成用户的指令,但是它也有严重的缺点,如不能在变量后追加内容,因为语句“CFLAGS = $(CFLAGS) –O”在变量扩展过程中可能导致无穷循环。

为了避免上述问题,简单扩展型变量的值在定义处展开,并且只展开一次,因此它不包含任何对其他变量的引用,从而消除了变量的嵌套引用。

递归展开方式的定义格式为:VAR=var。

简单扩展方式的定义格式为:VAR:=var。

Make中的变量使用均使用格式为:$(VAR)

注意 变量名是不包括‘:’,‘#’,‘=’、结尾空格的任何字符串。同时,变量名中包含字母、数字以及下划线以外的情况应尽量避免,因为它们可能在将来被赋予特别的含义,

变量名是大小写敏感的,例如变量名‘foo’、‘FOO’和‘Foo’代表不同的变量。

推荐在Makefile内部使用小写字母作为变量名,预留大写字母作为控制隐含规则参数或用户重载命令选项参数的变量名。

在上面的例子中,先来看看edit这个规则:

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

读者可以看到“.o”文件的字符串被重复了两次,如果在工程需要加入一个新的“.o”文件,那么用户需要在这两处分别加入(其实应该是有3处,另外一处在clean中)。

当然,这个实例的Makefile并不复杂,所以在这两处分别添加也没有太多的工作量,但如果Makefile变得复杂,那么用户就很有可能会忽略一个需要加入的地方,从而导致编译失败。所以,为了使Makefile易维护,推荐在Makefile中尽量使用变量这种形式。

这样,用户在这个实例中就可以按以下的方式来定义变量:

OBJS = main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o

这里是以递归展开的方式来进行定义的。在此之后,用户就可以很方便地在Makefile中以“$(objects)”的方式来使用这个变量了,于是改良版Makefile就变为如下所示:

OBJS = 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 –o main.o
kbd.o : kbd.c defs.h command.h
            gcc -c kbd.c –o kbd.o
command.o : command.c defs.h command.h
            gcc -c command.c –o command.o
display.o : display.c defs.h buffer.h
            gcc -c display.c –o display.o
insert.o : insert.c defs.h buffer.h
            gcc -c insert.c –o insert.o
search.o : search.c defs.h buffer.h
            gcc -c search.c –o search.o
files.o : files.c defs.h buffer.h command.h
            gcc -c files.c –o files.o
utils.o : utils.c defs.h 
            gcc -c utils.c –o utils.o
clean :
            rm edit $(OBJS)

可以看到,如果这时又有新的“.o”文件需要加入,用户只需简单地修改一下“OBJS”变量就可以了。

Makefile中的变量分为用户自定义变量、预定义变量、自动变量及环境变量。如上例中的OBJS就是用户自定义变量,自定义变量的值由用户自行设定,而预定义变量和自动变量为通常在Makefile都会出现的变量,其中部分有默认值,也就是常见的设定值,当然用户可以对其进行修改。

预定义变量包含了常见编译器、汇编器的名称及其编译选项,表2.12列出了Makefile中常见预定义变量及其部分默认值。


screenshot


screenshot

上例中的CC和CFLAGS是预定义变量,其中由于CC没有采用默认值,因此,需要把“CC=gcc”明确列出来。

由于常见的gcc编译语句中通常包含了目标文件和依赖文件,而这些文件在Makefile文件中目标体的一行已经有所体现,因此,为了进一步简化Makefile的编写,引入了自动变量。

自动变量通常可以代表编译语句中出现目标文件和依赖文件等,并且具有本地含义(即下一语句中出现的相同变量代表的是下一语句的目标文件和依赖文件),表2.13列出了Makefile中常见自动变量。


screenshot

自动变量的书写比较难记,但是在熟练了之后会非常地方便,请读者结合下例中的自动变量改写的Makefile进行记忆。

OBJS = main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
CC = gcc
CFLAGS = -Wall -O -g
edit : $(objects)
        $(CC) $^ -o $@
main.o : main.c defs.h
        (CC) $(CFLAGS) -c $< -o $@
kbd.o : kbd.c defs.h command.h
        (CC) $(CFLAGS) -c $< -o $@
command.o : command.c defs.h command.h
        (CC) $(CFLAGS) -c $< -o $@
display.o : display.c defs.h buffer.h
        (CC) $(CFLAGS) -c $< -o $@
insert.o : insert.c defs.h buffer.h
        (CC) $(CFLAGS) -c $< -o $@
search.o : search.c defs.h buffer.h
        (CC) $(CFLAGS) -c $< -o $@
files.o : files.c defs.h buffer.h command.h
        (CC) $(CFLAGS) -c $< -o $@
utils.o : utils.c defs.h 
        (CC) $(CFLAGS) -c $< -o $@
clean :
          rm edit $(OBJS)

另外,在Makefile中还可以使用环境变量。使用环境变量的方法相对比较简单,make在启动时会自动读取系统当前已经定义了的环境变量,并且会创建与之具有相同名称和数值的变量。但是,如果用户在Makefile中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量。

2.5.3 Makefile规则

Makefile的规则包括目标体、依赖文件及其间的命令语句,是make进行处理的依据。Makefile中的一条语句就是一个规则。

在上面的例子中显示地指出了Makefile中的规则关系,如“$(CC) $(CFLAGS) -c $< -o $@”,为了简化Makefile的编写,make还定义了隐式规则和模式规则,下面就分别对其进行讲解。

1.隐式规则

隐含规则能够告诉make怎样使用传统的技术完成任务,这样,当用户使用它们时就不必详细指定编译的具体细节,而只需把目标文件列出即可。make会自动搜索隐式规则目录来确定如何生成目标文件,如上例可以写成:

OBJS = main.o kbd.o command.o display.o \
             insert.o search.o files.o utils.o
CC = gcc
CFLAGS = -Wall -O -g
edit :$(objects)
            $(CC) $^ -o $@
main.o : main.c defs.h
kbd.o : kbd.c defs.h command.h
command.o : command.c defs.h command.h
display.o : display.c defs.h buffer.h
insert.o : insert.c defs.h buffer.h
search.o : search.c defs.h buffer.h
files.o : files.c defs.h buffer.h command.h
utils.o : utils.c defs.h 
clean :
            rm edit $(OBJS)

为什么可以省略“(CC) $(CFLAGS) -c $< -o $@”这句呢?

因为make的隐式规则指出:所有“.o”文件都可自动由“.c”文件使用命令“$(CC) $(CPPFLAGS) $(CFLAGS) -c file.c –o file.o”生成。因此,Makefile就可以进一步地简化了。

注意 在隐式规则只能查找到相同文件名的不同后缀名文件,如“kang.o”文件必须由“kang.c”文件生成。

表2.14给出了常见的隐式规则目录。


screenshot

2.模式规则

隐式规则仅仅能够用make默认的变量来进行操作。

模式规则不同于隐式规则,是用来定义相同处理规则的多个文件的,模式规则能引入用户自定义变量,为多个文件建立相同的规则,简化Makefile的编写。

模式规则的格式类似于普通规则,这个规则中的相关文件前必须用“%”标明,然而在这个实例中,并不能使用这个模式规则。

2.5.4 make使用

使用make管理器非常简单,只需在make命令的后面键入目标名即可建立指定的目标,如果直接运行make,则建立Makefile中的第一个目标。

此外make还有丰富的命令行选项,可以完成各种不同的功能,表2.15列出了常用的make命令行选项。


screenshot

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值