Linux下的Makefiles概述

来源:互联网 发布:三级分销系统源码 编辑:程序博客网 时间:2024/05/22 14:22

Linux下的Makefiles概述

绪论

只有几个模块的C/C++程序是很容易管理的。开发者通常只需要直接用源文件名作为输入参数调用编译器重新编译即可。那是一种简捷的方式。然而,当工程拥有越来越多的源文件而变得越来越复杂的时候,使用工具来帮助开发者管理工程变得十分必要。

 

我要介绍的这个工具就是"make"命令。"make"命令不仅仅能帮助开发者编译程序,而且它还能被用来控制通过多个输入文件来如何产生多个输出文件。

 

这篇文章不是一个完整的make学习指南,只是专注于怎么使用"make"命令和"makefile"来编译用C语言写的程序。本文同时提供了一个zip文件,该文件内部按照不同的目录包含了多个示例的C语言工程。在这些示例文件中,我们关心的是"makefiles"而不是那些C语言的源代码。你需要下载这些示例文件并通过"unzip"命令或其它的工具解压缩这些文件。因此,为了更好的理解这篇文章:

1、下载"make_samples.zip"

2、打开一个终端会话

3、在你的HOME目录创建一个用于实验的目录

4、将"make_samples.zip"移到这个创建的目录下

5、解压缩这个文件:unzip make_samples.zip

 

目录

1、Make工具:语法概述

2、makfiles的基本语法

    测试示例1

    测试示例2

3、Makefile中的假目标、宏和特殊的字符

   测试示例3

4、后缀规则:Makefiles语法简介

    测试示例4-mkfile1

    更多的特殊字符

    测试示例4-mkfile2

    测试示例4-mkfile3和makfile4

5、在一个makefile中调用其它的makefiles

    测试示例5

6、总结

 

1、Make工具:语法概述

make 的命令语法是:

make [选项] [目标]

你可以在终端下输入make --help 来获得make支持的命令行选项。介绍所有的make选项显然以及超出了本文的范围,本文的重点是介绍makefile的结构以及它是如何工作的。出现在makefile中的目标是一个标记(或定义的一个名字),本文的稍后会介绍。

 

make需要由makefile来告诉它你的应用程序是怎么编译出来的。makefile通常和其它源代码文件放在同一个目录下,而且它的文件名可以是任意的。例如:如果你的makefile的文件名时"run.mk" 那么你可以这样执行make命令:

make -f run.mk

-f 选项告诉make命令:make命令需要处理的makefile文件是run.mk

 

-f选项不是必须的,有两个特殊的makefile文件名可以不使用-f选项:"makefile"和"Makefile"。如果你运行"make"命令而没有制定makefile的文件名,那么make就会首先在当前目录下查找是否有"makefile"这个文件。如果"makefile"不存在,那么make会继续寻找是否有"Makefile"这个文件。如果在当前目录下,这两个文件都存在,而且你运行make命令时也没有指定makefile的文件名,如:

make <enter>

那么make命令将会调用"makefile"来作为其输入的makefile文件,忽略掉"Makefile".在这种情况下,你如果想要使用"Makefile"这个文件作为make命令的输入maeifile,那么你可以使用:make -f Makefile

2、makfiles的基本语法

lnxmk_01

图1:Makefile通用语法

 

一个makefile由目标、依赖和规则组成。目标通常是一个将要创建或更新的文件。目标依赖于源代码集或在依赖列表中描述的其它目标。规则是使用依赖列表创建目标的必要命令。

 

正如你在图1中所看到的一样,规则中的在每行命令都必须以<TAB>键开头。如果使用<SPACE>键将会引起错误,同样如果在每个规则行的末尾包含有空格也会引发make的错误。

 

Make命令通过比较makefile中目标所依赖的源文件的日期和时间(时间戳)来决定怎么编译目标文件。如果任何一个依赖目标的时间戳比最后一次通过make构建目标的时间要新,那么make命令就会执行相关的make规则来构建新的目标文件。

2.1测试示例1

下面我们来做一个实验,我们使用源代码中的sample1.这个目录下共有4个文件:

App.c and inc_a.h: 一个简单的C应用程序。

Makefile.r: 一个正确的makefile(扩展名.r表示正确的)

Makefile.w:一个不完整的或书写错误的makefile(扩展名.w表示错误的)。但这不意味着这个文件有语法错误。

 

看下面的示例:

mkfile.r

# This is a very simple makefile

 

app: app.c inc_a.h

cc -o app app.c

and 

mkfile.w 

# This is a very simple makefile

 

app: app.c 

cc -o app app.c

 

表面上来看,这两个文件的差异毫不相干,但在特定情况下它们确实是相关的。首先我们先检查一下makefile的部分:

a、 在makefile中字符"#"表示插入注释内容。从"#"后面开始一直到行结束的内容会全部被make忽略掉。

b、 App是要生成的目标。

c、 App.c和inc_a.h是目标app的依赖文件(inc_a.h只存在于makefile.r中)。

d、 "cc -o app app.c"是目标的依赖列表中有任何改变后重新编译的规则。

 

下面我们通过一系列的命令来举例说明他们是如何工作的(如图2):

lnxmk_02

图2:sample1的命令序列

 

1、 使用make 命令调用mkfile.r 这个makefile来创建app这个可执行性文件。

2、 为了使make重新编译app,我们将app 删除掉。

3、 使用make命令调用mkfile.w这个makefile来创建app这个可执行性文件,获得和步骤1一样的结果。

4、 使用make 命令调用mkfile.r 这个makefile来创建app这个可执行性文件,因为目标是最新的,所以这次不会执行任何规则。

5、 使用make命令调用mkfile.w这个makefile来创建app这个可执行性文件,和步骤4一样的结果。

6、 使用touch命令来改变inc_a.h文件的时间戳,来模拟修改了文件内容。

7、 Mkfile.w这个makefile不能检测到inc_a.h这个文件已经被修改了。

8、 Mkfile.r这个makefile能检测到inc_a.h这个文件已经被修改,它能按照我们的预期重新产生目标app。

9、 使用touch命令来改变app.c文件的时间戳,来模拟修改了文件内容.

10、 Mkfile.w这个makefile能按照预期重新产生目标app。

11、 使用touch命令来改变app.c文件的时间戳,来模拟修改了文件内容。

12、 Mkfile.r这个makefile能按照预期重新产生目标app。

现在你应该知道mkfiel.w这个makefile是如何被认为是一个错误的或不完整的makefile了。因为它所描述的目标app的依赖列表中没有包含inc_a.h这个文件,所以也就不能检测到inc_a.h这个文件的时间戳,从而没能按照我们的预期执行make规则。

2.2测试示例2

Sample2是另外一个简单的makefile例子,但它不只一个目标。同样,还是有两个makefile:mkfile.r和 mkfile.w 来举例说明正确的和错误的编写makefile的方法.

 

正如你所注意到的,最终的可执行文件(目标 app)由三个哦bject文件组成:main.o、 mod_a.o 和mod_b.o。每个object都是其依赖列表中源文件的目标文件。

 

这两个makefile都是完整的。它们最大的不同就是目标在makefiel中出现的顺序不一样而已。

看下面的示例:

mkfile.r 

app: main.o mod_a.o mod_b.o 

cc -o app main.o mod_a.o mod_b.o

main.o: main.c inc_a.h inc_b.h

cc -c main.c 

mod_a.o: mod_a.c inc_a.h

cc -c mod_a.c

mod_b.o: mod_b.c inc_b.h 

cc -c mod_b.c

and

mkfile.w 

main.o: main.c inc_a.h inc_b.h

cc -c main.c 

mod_a.o: mod_a.c inc_a.h

cc -c mod_a.c

mod_b.o: mod_b.c inc_b.h 

cc -c mod_b.c

app: main.o mod_a.o mod_b.o 

cc -o app main.o mod_a.o mod_b.o

下面我们通过一系列的命令来举例说明他们是如何工作的(如图3):

lnxmk_03

图3:sample2的命令序列

 

1、make命令调用mkfile.w这个makefile来产生目标,并且第一个规则被执行。

2、删除所以得.o文件。

3、make命令调用mkfile.r这个makefile来产生目标,并且所有的模块都被正确的创建。

4、目标app也能被执行。

5、删除所有的.o文件。

6、make命令调用mkfile.w这个makefile来产生目标,同时把目标app作为make命令的输入参数,所有的模块都被正确的创建。

 

那么mkfile.w这个makefile到底错在哪里呢?从技术上讲,当你使用步骤6种的方法时mkfile.w也是正确的。然而,当你不显示的指明make的目标时,make命令只会处理makefile中的第一个目标。在mkfile.w中,第一个目标是main.o,而这个目标只是告诉make命令从main.c、inc_a.h和inc_b.h中产生main.o这个目标,而无须作其它的事情。Make命令不会处理接下来的目标。

 

注意:makefile中的第一个目标决定了make如何处理其它的目标以及处理这些目标的顺序。因此,makefile中的第一个目标应该是最主要的目标,这个目标可以使依赖于其它的目标而产生的。

我们再来看目标app,它被放在两个makefile中的不同位置,但它们都具有相同的语法表示。而且图3中的步骤3和步骤6都能产生同样的结果:

a)  目标app告诉make命令它又三个依赖文件:main.o mod_a.o mod_b.o,make应该在产生可执行性的app之前先处理这三个依赖文件。

b) 然后,make卡时查找目标main.o,并处理它的依赖关系和编译规则

c) 接着是处理mod_a.o的依赖关系和编译规则。

d) 最后是处理mod_b.o的依赖关系和编译规则。

e) 这三个目标被创建后,目标app的编译规则被处理,app可执行性文件被产生。

 

3、Makefile中的假目标、宏和特殊的字符

有时候makefiel中的目标并不是要产生一个文件,而只是执行一些其它的动作,那么这样的目标被称为假目标。

例如:

getobj:

mv obj/*.o .  2>/dev/null

 

目标getobj执行一个简单的动作将obj目录下的所有.o文件移到当前目录下。可能你会有疑问:"如果obj目录下没有任何文件执行这样的动作会怎么样的?"这是一个好问题,在这种情况下,mv命令将会向make命令返回一个错误。

 

注意:make命令在执行规则时检测到错误时,其缺省行为将会终止执行make命令。

 

当然,obj目录下的确有可能是空目录。那么这时该如何避免make命令遇到这个错误是不要终止执行makefile中的其它内容呢?

 

你可以使用一个特殊的字符"-"(减号)来处理mv命令,如:

getobj:

-mv obj/*.o .  2>/dev/null

 

"-"告诉make命令忽略错误。这儿还有一个特殊的字符:"@" 它告诉make在执行命令前不要将命令的内容输出到标准输出设备上。你可以将这两个字符组合在一起来处理命令:

getobj:

-@mv obj/*.o .  2>/dev/null

 

还有一个特殊的假目标:all。使用all这个假目标,你可以将make的目标分组,并引导make命令来处理所有这些目标。

例如:

all: getobj app install putobj

 

make命令将会按顺序执行目标:getobj, app, install and putobj. 

另外一个有趣的特性:make命令能支持makefile中的宏定义。我们可以这样定义宏:

MACRONAME=value

并且通过$(MACRONAME) or ${MACRONAME}来访问这个宏的值。例如:

EXECPATH=./bin

INCPATH=./include

OBJPATH=./obj

CC=cc

CFLAGS=-g -Wall -I$(INCPATH)

当上面的内容被执行时,make将$(MACRONAME)替换成实际的定义内容。

3.1测试示例3

Sample3 是一个使用了宏、假目标和特殊字符的较复杂的makefile示例。在sample3的目录下有3个子目录:

a) include -存放所有的.h文件

b) obj -编译后产生的obj文件搬移到这个目录下和重新编译之前将这些obj文件搬回到原来的位置。

c) bin -最终的可执行性文件被拷贝到这里。

 

所有的.c文件和makefile一起都放在sample3的根目录下。当这个makefile的文件名是"makefile"时,调用make命令时就不需要有使用-f参数。

 

所有的文件被分类放在不同的目录下使这个例子更接近实用。Makefile的内容如下:

lnxmk_04

图4:sample3的makefile内容

行号是不会出现在makefile中的。在这里表上行号只是为了更好的分析它。

a) 第7-13行:定义了一些宏

a) INSTPATH,  INCPATH and OBJPATH 表示子目录. 

b) CC and CFLAGS 表示编译器和通用的编译器选项。注:你可以将CC改称gcc。 

c) COND1 and COND2 表示执行一系列的命令。

 

b) 第17行:假目标all作为makefile的第一个目标。目标all按从左到右定义了这些目标的生成顺序。

目标getobj是第一个依赖目标,其次是app install和putobj。

c) 第19-29行:列出了所有的目标是如何产生的。使用CC和CFLAGS这两个宏,在make执行时会替换成这些宏的值,$(CC)被替换成cc,CFLAGS被替换成-g -Wall -I./include.所以第20上被make翻译成如下的内容:

-g -Wll -I./include -o app main.o mod_a.o mod_b.o

d) 第31-34行:列出了目标getobj和putobj。这两个假目标是为了帮助或组织编译过程的:

a) 目标getobj 被目标all作为第一个依赖文件和最先被执行。在编译之前它将所有的obj文件按从 obj 目录($(OBJPATH)) 搬到sample3的根目录下。因此,make 命令可以比较这些object文件的时间戳来重新编译被改变了那些源文件。 

b) 目标putobj 与目标getobj 起相反的作用。在成功编译后,将所有的object文件移到obj目录下。

e) 第38行:目标install是另一个假目标,用来说明如何使用 if 这个shell语句,shell编程已经超出了本文的范围,所以你可以使用google来获得更多有关这方面的信息本文的后续内容会讲解目标isntall的作用。

f) 第47行:目标cleanall 删除所有的目标文件以强迫make命令重新产生这些文件。在正常编译时不会调用到这个目标。你需要通过给make命令传递参数的方式来调用它:

make cleanall

 

你可能也已经注意到在目标getobj, putobj, install and cleanall.前的那些特殊字符(- and @)。如之前的说明,- 告诉make命令即使出现了错误也继续处理余下的makefile内容;@告诉make命令在执行命令前不要将该命令向标准输出设备打印出来。

 

注意:在目标install的每一行都用"/"结尾,且"/"是每行的最后一个字符(其后不允许有空格),否则make命令会出现如下的错误:

line xxx: syntax error: unexpected end of file

其中xxx表示这一行是代码块的开始。

实际上,当你使用"/"来组合一组命令时,"/"必须是其所在行的最后一个字符。

下面我们试验如下的命令序列(图5):

lnxmk_05

图5:sample3的命令序列

1. 因为makefile的文件名是"makefile",所以make 命令不需要输入参数。 

o 1.1 如果 obj  目录下没有任何文件,那么 getobj 将会出现错误. 在mv命令之前的-(减号)阻止了make命令因这个错误而终止执行(第32行)。

o 1.2 这个消息只有当if条件为TRUE (line 39)才会出现。if 语句前的@字符使得该语句下的所有语句都不会被打印出来。这个条件语句比较由COND1 and COND2 宏(lines 12-13)展开的两个字符串。这个条件语句结合了shell命令来判断目标app and ./bin/myapp 的时间戳是否不同。如果不同那么目标app 将以myapp 的名字被拷贝到 ./bin 目录下,同时改变该文件的访问权限。如果没有这个if条件,那么目标 install将会在make执行的每次都会被执行。

2. make 再次被执行,但只有目标 getobj and putobj 被执行,同时没有编译新的目标文件,目标install也没有被执行。

3. touch 命令改变了inc_a.h 文件的时间戳。

4. make 命令被调用,但只有依赖于文件inc_a.h的那些目标被编译。 

5. 列出目录./bin 的内容,注意 myapp 的文件权限。

6. 使用目标cleanall的例子。

4、后缀规则:Makefiles语法简介

当你的工程由于越来越多的源文件和变得越来越复杂的时候,为每一个源文件创建一个目标和依赖关系列表变得不太现实。例如,如果工程中有20个.c的文件来编译出一个可执行性文件,而且每个源文件都是用同样的编译器参数,那么你可以采用一种方式指导make命令向每个源文件使用同样的命令参数。

 

这种方式就是我们将要介绍的后缀规则或基于文件扩展名的规则。例如,下面的后缀规则:

.c.o:

    cc -c $<

这个规则告诉make命令:每个目标文件都以.o为扩展名,而且每个.o都依赖于与他同名的.c文件(同样的文件名,只改变了文件扩展名)。因此,文件main.c将会产生一个main.o的文件。注意:.c.o 不是一个目标,只是两个扩展名(.c 和.o)。

后缀规则的语法如下:

lnxmk_06

图6:后缀规则语法

特殊字符$<将会在后面介绍。

4.1测试示例4-mkfile1

下面我们来试验sample4。这个例子中有几个makefile,第一个mkfile1:

.c.o:

    cc -c $<

这个makfile只包含了一个后缀规则。

我们来试验如下的命令序列(图7):

lnxmk_07

图7:sample4的命令序列-mkfile1

1. make 命令调用mkfile1 这个makefile。由于没有定义目标,所以make不会做任何事情。

2. make 命令再次被调用,并以目标main.o 作为其输入参数。这次make 命令根据.c.o 的后缀规则编译了main.c 。

3. make 命令被调用,并传递了两个目标参数。 

4. make命令被调用,并传递了三个目标参数,由于这些目标已经被编译了,所以这次命令不会再产生它们。 

这里有个问题点需要理解,mkfile1只定义了后缀规则,没有定义目标,那么main.c是如何被编译的呢?

如果mkfile1中定义了如下的main.o的目标,那么make命令将会编译main.o:

main.o: main.c

    cc -c main.c

因此,.c.o 后缀规则告诉make命令:对于每个xxx.o目标都必须依赖于一个xxx.c来编译。

如果make 命令被如此调用:

make -f mkfile1 mod_x.o

那么make命令将会返回一个错误,因为这个目录下没有moc_x.c这个文件。

4.2 更多的特殊字符

在后缀规则中使用的 $< 是什么意思呢? 它的含义是:当前依赖的文件名。在使用.c.o 这个后缀规则时, 当规则被执行时 $< 将被解释成 xxxxx.c 文件. 还有其它的一些特殊字符:

$? 

比当前目标的时间戳更新的依赖文件列表

$@

当前目标的文件名

$<

当前的依赖文件名

$*

当前的依赖文件名(不包含扩展名)

下面的示例将会介绍其它的这些特殊字符。

4.3 测试示例4-mkfile2

mkfile2 展示了后缀规则的另一种是用方法,将file.txt改名为file.log:

.SUFFIXES: .txt .log

.txt.log:

@echo "Converting " $< " to " $*.log

mv $< $*.log

关键字.SUFFIXES: 告诉make 命令哪些扩展名将会在makefile 中使用。在mkfile2中将使用扩展名.txt and .log。有一些扩展名像.c and .o 默认就被makefile支持的,无须使用关键字.SUFFIXES来定义。

我们来试验下面的命令序列(图8):

lnxmk_08

图8:sample4的命令序列-mkfile2

1. 创建一个file.txt 文件。

2. make 命令被调用,同时将file.log 作为其目标参数。

后缀规则.txt.log 告诉make 命令:对于每个xxx.log目标,都必须由一个xxx.txt文件依赖产生。这个命令的执行方式等同于将目标file.log 定义在mkfile2文件中:

file.log: file.txt

    mv file.txt file.log

3. 展示文件file.txt 被重命名为file.log. 

 

 

4.4 测试示例4-mkfile3和makfile4

到目前为止,我们知道了如何定义后缀规则,但还没有在实际项目中使用这个规则。接下来我们就使用一个更接近实际工程应用的例子sample4.

先看文件mkfile3:

.c.o: 

@echo "Compiling" $< "..."

cc -c $<

app: main.o mod_a.o mod_b.o

@echo "Building target" $@ "..." 

cc -o app main.o mod_a.o mod_b.o 

后缀规则被放到了目标app之前。我们来试验如下的命令序列(图9): 

lnxmk_09

图9:sample4的命令序列-mkfile3

1. make 命令调用mkfile3。Make命令读取目标 app 并处理它的依赖关系: main.o, mod_a.o and mod_b.o -根据后缀规则.c.o make 命令知道:对于每一个xxx.o都依赖于xxx.c来产生。

2. make命令再次调用,因目标app 已经是最新的了,所以make不产生任何目标文件。

3. main.c 的访问时间被更新。

4. make 命令按照预期重新编译 main.c 。

5. make命令再次调用,因目标app 已经是最新的了,所以make不产生任何目标文件。

6. inc_a.h 文件的时间戳被更新(它被文件 main.c and mod_a.c所包含) make 命令需要重新编译这些目标文件。

7. make 命令被调用,但没有按照预期的结果执行?

那么步骤7出了什么问题呢?原来我们没有告诉make 命令文件main.c or mod_a.c 依赖于inc_a.h。

一种为每个目标写出依赖关系列表的解决方法:

.c.o: 

@echo "Compiling" $< "..."

cc -c $<

app: main.o mod_a.o mod_b.o

@echo "Building target" $@ "..." 

cc -o app main.o mod_a.o mod_b.o

main.o: inc_a.h inc_b.h

mod_a.o: inc_a.h

mod_b.o: inc_b.h

你可以编辑和添加上面的三行到mkfiel3种,并试验图9中的命令序列。在试验之前采用下面的命令先删除掉object文件:

rm -f *.o app

当然,为每个模块列举出相对应的依赖文件列表示行的,但不实用。想象一下一个拥有50个.c文件和30.h文件的工程,得打多少字!

Mkfile4中提供了一个更实用的解决方法: 

OBJS=main.o mod_a.o mod_b.o

.c.o: 

@echo "Compiling" $< "..."

cc -c $<

app: main.o mod_a.o mod_b.o

@echo "Building target" $@ "..." 

cc -o app main.o mod_a.o mod_b.o

$(OBJS): inc_a.h inc_b.h

下面我们来试验如下的命令序列(图10):

lnxmk_10

图10:sample4命令序列-mkfile4

1. make 命令被调用,同时makefile使用 mkfile4 。

2. 文件mod_a.c 的时间戳被更新。

3. make 命令按照预期重新编译 mod_a.c. 

4. inc_a.h (that is included by main.c and mod_a.c) 的时间戳被更新。 

5. make 命令重新编译了所有的目标文件? 

在步骤5种为什么文件 mod_b.c 会被重新编译呢?

mkfile4 定义了 inc_a.h 是 mod_b.c 的依赖源,而实际上mod_b.c和inc_a.h没有依赖关系(mod_b.c中没有包含inc_a.h)。而makefile却告诉make 命令inc_a.h and inc_b.h 是所有目标的依赖源。这样编写mkfile4 是因为这样更实用,而且不会出错。当然你也可以为每个模块单独列出依赖的头文件。

提示:当处理大型工程时,我更倾向于只将一些主要的头文件放到依赖关系中。也就是说,这些主要的头文件会被其它所有的模块文件按所包含。

5、在一个makefile中调用其它的makefile

当你工作于一个包含了不同模块如:静态库文件,动态链接库,可执行文件的大型工程时,将它们分门别类的放到各自的目录下,不失为一个好主意。这样每个源代码目录就可能有一个各自的makefile ,同时它可以被称为master makefile的makefile所调用。

master makefile 是放在工程源文件根目录下的一个makefile。它能调用每个子目录下的各个模块的makefile,同时它的内容应该很简单。

这里有一个窍门:你需要知道如何强迫make命令改变到其它的目录下。我们先来看一个例子(文件名为makefile):

target1:

@pwd     

cd dir_test     

@pwd

通过make命令调用的结果如下:

lnxmk_11

图11:一种错误的改变当前目录的方法

pwd 命令是用来打印出当前目录的命令。在上面的例子中当前目录是/root .第二个 pwd 命令在执行cd dir_test后打印出和第一个pwd命令同样的结果,这是为什么呢?

你需要更多有关shell命令的知识如(cp, mv and so on),同时要区分在make和shell中调用这些命令的区别(make中调用shell命令的方式如下):

  • 打开一个新的shell实例
  • 执行shell命令
  • 关闭shell实例

实际上,make 在处理上述makefile时make创建了三个shell实例来处理这些shell命令。

cd dir_test 这个shell命令只在make创建的第二个shell实例中被正确执行。 

上面问题的解决方法如下:

target1:

(pwd;cd dir_test;pwd)

执行结果如下:

lnxmk_12

图12:一种正确的改变目录的方法

圆括号保证只在一个shell实例中处理这些命令,make 只会创建一个shell实例来执行这三个命令。

如果不使用圆括号,执行这个makefile会出现什么样的结果呢: 

target1:

    cd dir_test

    make

结果如下:

lnxmk_13

图13:递归的make调用

可以看到make在无休止的递归调用这个maekfile。Make[37]表示这是make命令的第37个实例。

5.1测试示例5

sample5 展示了通过master makefile 怎么调用其它的makefiles 。sample5 的目录结构如下:

  • tstlib目录:包含了一个简单的静态库文件的源代码(tlib.a)。
  • application目录:包含了这个应用程序的源代码,并且这些文件需要链接到tlib.a。
  • makefile:mastermakefile.
  • runmk:一个调用mastermakefile的shell脚本.这个脚本不是必须的,它只是为了避免make在进入或退出一个目录时产生的一个恼人的提示信息。

master makefile 内容如下:

COND1='stat app 2>/dev/null | grep Modify'

COND2='stat ./application/app 2>/dev/null | grep Modify'

all: buildall getexec 

buildall:

@echo "****** Invoking tstlib/makefile"

(cd tstlib; $(MAKE))

@echo "****** Invoking application/makefile"

(cd application; $(MAKE))

getexec:

@if [ "$(COND1)" != "$(COND2)" ];/

then/

echo "Getting new app!";/

cp -p ./application/app . 2>/dev/null;/

chmod 700 app;/

else/

echo "Nothing done!";/

fi

cleanall:

-rm -f app

@echo "****** Invoking tstlib/makefile"

@(cd tstlib; $(MAKE) cleanall) 

@echo "****** Invoking appl/makefile"

@(cd application; $(MAKE) cleanall) 

这个工程相对比较简单,只有4个假目标。目标 all 先调用目标 buildall ,并且make 试图在不同的目录下调用makefile创建不同的目标。注意: $(MAKE) 宏默认是被make支持的,它在执行时被替换成make,无须定义就可以使用。

目标cleanall 被间接的用来删除所有的object文件和可执行性文件。出现在括号前的@字符阻止make 不要输出这些命令。

我们来试验下面的命令序列(图14):

lnxmk_14

图14:sample5命令序列

1. make 命令通过执行shell脚本 runmk 来处理 master makefile. 

o 目标all 调用目标 buildall ,因其是第一个假目标,所以它总是会被执行。首先make调用tstlib目录下的makefile并产生 tlib.a 。该目录下的makefile内容如下:

TLIB=tlib.a

OBJS=tstlib_a.o tstlib_b.o

CC=cc

INCPATH=.

CFLAGS=-Wall -I$(INCPATH)

.c.o:

$(CC) $(CFLAGS) -c $<

$(TLIB): $(OBJS)

ar cr $(TLIB) $(OBJS)

$(OBJS): $(INCPATH)/tstlib.h

cleanall:

-rm -f *.o *.a

o 接下来application目录下的makefile 被调用,同时目标app 被创建。该目录下的makefile 如下:

OBJS=main.o mod_a.o mod_b.o

CC=cc

INCLIB=../tstlib

LIBS=$(INCLIB)/tlib.a

CFLAGS=-Wall -I. -I$(INCLIB) 

.c.o:

$(CC) $(CFLAGS) -c $<

app: $(OBJS) $(LIBS)

$(CC) $(CFLAGS) -o app $(OBJS) $(LIBS)

$(OBJS): inc_a.h inc_b.h $(INCLIB)/tstlib.h

cleanall:

-rm -f *.o app

注意目标app依赖于tlib.a。 因此不管tlib.a 是否被重新编译,目标app都会重新链接它。

o 目标getexec 被master makefile所调用。由于可执行性文件app 不在 sample5的目录下,所以它从application 目录下被拷贝出来。

2. touch 命令改变了tstlib/tstlib_b.c文件的时间戳。

3. make 命令通过执行shell脚本 runmk 来处理 master makefile. 

o tlib.a 被重新编译

o 由于tlib.a被改变所以app 需要重新链接它。

o 目标getexec被 master makefile调用。由于application/app 和app 的时间戳不一样,所以sample5 目录下的app被更新。

4. touch 命令改变了i application/inc_a.h的时间戳。

5. make 命令通过执行shell脚本 runmk 来处理 master makefile. 

o 由于依赖文件没有更新,tlib.a 不需要更新。

o app 的所有模块被重新编译因为所有的.c文件都依赖于inc_a.h 。

o 目标getexec被 master makefile调用。由于application/app 和app 的时间戳不一样,所以sample5 目录下的app被更新。

6. cleanall 目标被执行。 

 

 http://blog.csdn.net/edgar_wu/archive/2009/02/04/3863034.aspx

6、总结

就像你在本文所看到的那样,make命令十分强大的工具,用好它将对你的工程应用提供非常多的帮助。就像开篇所说的,这篇文章不是一个make命令的完全指南,而只是一个如何编写makefile的参考文档。在internet和一些相关的书籍上有许多关于make和makefile的资料,这些资料包含了本文没有提到的一些特性。

希望这篇文章能对你有所帮助。


0 0
原创粉丝点击