欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

make工程管理器之makefile详解

程序员文章站 2022-05-13 15:09:15
...

make 工程理器

所谓工程管理器, 顾名思义, 是用于管理较多的文件。读者可以试想一下, 由成百上千个文件构成的项目, 如果其中只有一个或少数几个文件进行了修改, 按照之前所学的gcc 编译工具, 就不得不把这所有的文件重新编译一遍, 因为编译器并不知道哪些文件是最近更新的, 而只知道需要包含这些文件才能把源代码编译成可执行文件, 于是, 程序员就不得不重新输入数目如此庞大的文件名以完成最后的编译工作。编译过程分为编译、汇编、链接阶段, 其中编译阶段仅检查语法错误以及函数与变量是否被正确地声明了, 在链接阶段则主要完成函数链接和全局变量的链接。因此, 那些没有改动的源代码根本不需要重新编译, 而只要把它们重新链接进去就可以了。所以, 人们就希望有一个工程管理器能够自动识别更新了的文件代码, 而不需要重复输入冗长的命令行, 这样,make 工程管理器就应运而生了。实际上、 make 工程管理器也就是个“ 自动编译管理器” , 这里的“ 自动” 是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量, 同时, 它通过读入makefile 文件的内容来执行大量的编译工作。用户只需编写一次简单的编译语句就可以了。它大大提高了实际项目的工作效率.

makefile 基本结构

makefile 是make 读入的惟一配置文件, 因此本节的内容实际就是讲述makefile 的编写规则。在一个makefile 中通常包含如下内容:

1.需要由make 工具创建的目标体( target ) , 通常是目标文件或可执行文件;
2.要创建的目标体所依赖的文件( dependency-file ) ;
3.创建每个目标体时需要运行的命令( command ), 这一行必须以制表符(Tab键)开头。
它的格式为:

target: dependency_files
       command / * 该行必须以TabR 开头*/

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

hello.o: hello.c   hello.h
       gcc -c hello.c -o hello.o

接着就可以使用make 了。使用make 的格式为: make target , 这样make 就会自动读入makefile ( 也可以是首字母大写的Makefile ) 并执行对应target 的command 语句, 并会找到相应的依赖文件。 makefile 执行了"hello .o ” 对应的命令语句, 并生成了“ hello.c ” 目标体。

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

makefile 变量

上面示例的makefile 在实际中是几乎不存在的, 因为它过于徜单, 仅包含两个文件和一个命令, 在这种情况下完全不必要编写makefile 而只需在shell 中直接输入即可, 在实际中使用的makefile 往往是包含很多的文件和命令的, 这也是makefile 产生的原因。下面就可以给出稍微复杂一些的makefile 讲解。

david : kang.o  yul.o
     gcc kang.o  bar.o  -o myprog
kang.o : kang.c kang.h head.h
     gcc  -Wall -o -g -c kang.c -o kang.o
yul.o : bar.c head.h
     gcc  -Wall -o -g -c yul.c -o yul.o

在这个makefile 中有3 个目标体(target) , 分别为david 、kang.o 和yul.o , 其中第一个目标体的依赖文件就是后两个目标体。如果用户使用命令"make david ” , 则make 管理器就是找到david 目标体开始执行。这时make会自动检查相关文件的时间戳。首先, 在检查"kang.o" 、“ yul.o ” 和“ david"3 个文件的时间戳之前, 它会向下查找那些把"kang.o" 或"yul.o ” 作为目标文件的时间戳。

接下来, 为了进一步简化编辑和维护makefile 、 make 允许在makefile 中创建和使用变量。变量是在makefile 中定义的名字, 用来代替一个文本字符串, 该文本字符串称为该变量的值。在具体要求下, 这些值可以代替目标体、依赖文件、命令以及文件中其他部分。在makefile 中的变量定义有两种方式: 一种是递归展开方式, 另一种是简单方式。递归展开方式定义的变量是在引用该变量时进行替换的, 即如果该变量包含了对其他变量的引用, 则在引用该变量时一次性将内嵌的变量全部展开, 虽然这种类型的变量能够很好地完成用户的指令, 但是它也有严重的缺点, 如不能在变量后追加内容( 因为语句:CFLAGS = $(CFLAGS) -O 在变量扩展过程中可能导致无穷循环) 。为了避免上述问题, 简单扩展型变量的值在定义处展开, 并且只展开一次, 因此它不包含任何对其他变量的引用, 从而消除变量的嵌套引用。

递归展开方式的定义格式为: VAR=varo
简单扩展方式的定义格式为: VAR:=varo
make 中的变量使用均使用的格式为: $(VAR)

1、变量名是不包栝“:” 、“#”、“=”以及结尾空格的任何字符串. 同时, 变量名中包含字母、数学以及下划线以外的情况应尽量避免, 因为它们可能在将来被赋予特别的含义。
2、变量名是大小写敏感的, 例如变量名"foo""FOO ” 、和“ F ” 代表不同变量
3、推荐在makefile 内部使用小写字母作为变量名, 预留大写字母作为控制隐含规则参数或用户重载命冷选项参数的变量名。

下面给出了上例中用变量替换修改后的makefile , 这里用OBJS 代替和yul.o , 用CC 代替gcc , 用CFLAGS 代替“ -Wall -o -g” 这样在以后修改时, 就可以只修改变量定义,而不需要修改下面的定义实体, 从而大大简化了makefile 维护的工作量。经变量替换后的makefile 如下所示:

OBJS=kang.o yul.o
CC = gcc
CFLAGS = -Wall -o -g
david ”: $ (OBJS)
     $(CC)  $ ( OBJS ) -o david
kang.o :kang.c kang.h
     $ (CC)  $ (CFLAGS)  -c  kang.c -o kang.o
yul.o:yul.c yul.h
     $(CC)   $( CFLAGS )-c yul.c -o yul.o

可以看到, 此处变量是以递归展开方式定义的。

makefile 中的变量分为用户自定义变量、预定义变量、自动变量及环境变量。如上例中的OBJS 就是用户自定义变量, 自定义变量的值由用户自行设定, 而预定义变量和自动变量为通常在makefile 都会出现的变量, 它们的一部分有默认值, 也就是常见的设定值, 当然用户可以对其进行修改。预定义变量包含了常见编译器、汇编器的名称及其编译选项。

下面列出了makefile 中常见预定义变量及其部分默认值。
make工程管理器之makefile详解
可以看出, 上例中的CC 和CFLAGS 是预定义变量, 其中由于CC 没有采用默认值, 因此, 需要把"CC=gcc ” 明确列出来。
由于常见的gcc 编译语句中通常包含了目标文件和依赖文件, 而这些文件在makefile 文件中目标体所在行己经有所体现, 因此, 为了进一步简化makefile 的编写, 就引入了自动变量。自动变量通常可以代表编译语句中出现目标文件和依赖文件等, 并且具有本地含义( 即下一语句中出现的相同变量代表的是下一语句的目标文件和依赖文件)。
makefile 中常见的自动变量。
make工程管理器之makefile详解
自动变量的书写比较难记, 但是在熟练了之后使用会非常方便, 请读者结合下例中的自动变量改写的makefile 进行记忆。

OBJS = kang.o yul.o
CC = gcc
CFLAGS = -Wall -o -g
david : $ (OBJS )
          $(CC) $^ -o aaa@qq.com
kang.o : kang .c kang.h
          $(CC) $(CFLAGS) -c  $< -o  aaa@qq.com
yul.o : yul.c yul.h
          $(CC) $(CFLAGS)  -c  $<  -o  aaa@qq.com

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

makefile 规则

makefile 的规则是make 进行处理的依据, 它包括了目标体、依赖文件及其之间的命令语句。在上面的例子中, 都显式地指出了makefile 中的规则关系, 如“ $(CC) $(CFLAGS) -c $<0 aaa@qq.com" , 但为了简化makefile 的编写, make 还定义了隐式规则和模式规则, 下面就分别对其进行讲解。
1 . 隐含规则
隐含规则能够告诉make 怎样使用传统的觌则完成任务, 这样, 当用户使用它们时就不必详细指定编译的具体细节, 而只需把目标文件列出即可。make 会自动搜索隐式规则目录来确定如何生成目标文件。如上例就可以写成:

OBJS = kang.o yuI.o
CC = gcc
CFLAGS = -Wall -o -g
david : $(OBJS)
    $(CC) $^ -o aaa@qq.com

为什么可以省略后两句呢? 因为make 的隐式规则指出: 所有“ .o” 文件都可自动由".c ”文件亻吏用命令"S(CC) S(CPPFLAGS) S(CFLAGS) -c file.c -o file.o ” 来生成。这样“ kang.o ”和"yul.o ” 就会分别通过调用“ S(CC) $(CFLAGS) -c kang.c -o kang.o ” 和“ $(CC) $(CFLAGS) -c yul.c -o yul.o " 来生成。

对应语言后缀名 隐式规则
C++编译:.cc或者.c 变为.o $(CC) -c $(CPPFLAGS) $(CFLAGS)
C编译:.c变为.o $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
Pascal编译:.p变为.o $(PC) -c $(PFAGS)
Fortran 编译:.r变为-o $(PC) -c $(FFAGS)

2.模式规则
模式规则是用来定义相同处理规则的多个文件的。它不同于隐式规则, 隐式规则仅仅能够用make 默认的变量来进行操作, 而模式规则还能引入用户自定义变量, 为多个文件建立相同的栽则, 从而简化的编写。模式规则的格式类似于普通规则, 这个规则中的相关文件前必须用“ % ” 标明。, 使用模式规则修改后的makefile 的编写如下:

OBJS = kang .o yul.o
CC = gcc
CFIAGS = -Wall -o -g
david :$(OBJS)
        $(CC) $^ -o  -g
%.o : %.c
        $(CC) $(CFLAGS) -c $< -o  aaa@qq.com

make 管器的使用
使用make 管理器非常简单, 只需在make 命令的后面键入目标名即可建立指定的目标,如果直接运行make , 则建立makefile 中的第一个目标。此外make 还有丰富的命令行选项, 可以完成各种不同的功能。

命令格式 含义
-C dir 读入指定目录下的makefile
-f file 读入当前目录下给file文件作为makefile
-i 忽略所以的命令执行错误
-I dir 指定被包含的makefile所在目录
-n 只打印要执行的命令,但不执行这些指令
-p 显示mkae变量参数库和隐含规则
-s 在执行命令时不显示命令
-w file 如果make在执行过程中改变目录,则打印当前目录

下一章将介绍如何使用autotools