Chinaunix首页 | 论坛 | 博客
  • 博客访问: 265012
  • 博文数量: 107
  • 博客积分: 305
  • 博客等级: 二等列兵
  • 技术积分: 417
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-22 09:42
文章分类

全部博文(107)

文章存档

2014年(3)

2013年(41)

2012年(34)

2011年(28)

2008年(1)

分类:

2011-06-13 00:32:13

原文地址:makefile入门实践 作者:fireaxe

 
本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接,严禁用于任何商业用途。
作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net 

 

一、    学习makfile Example000基础makefile

这是最基本的makefile工程框架,其中包括一个main.c文件、六个source文件与六个header文件,还有一个Makefile文件与一个.批处理文件。每个source文件包含一个header文件,所有的header文件都是空的,而main文件则包含所有的header文件。

/* c001.c */

#include "../include/c001.h"

 

void c001(void)

{

 

}

main.c文件是工程的入口函数。

/* mian.c */

#include "../include/c001.h"

#include "../include/c002.h"

#include "../include/c003.h"

#include "../include/c004.h"

#include "../include/c005.h"

#include "../include/c006.h"

 

int main(void)

{

    return 0;

}

 

run.bat的作用是设置环境变量,当然也可以用bat自动运行makefile文件。注意,等号与前后命令之间不能有空格存在,否则dos无法识别

echo run.bat

@echo off

set path=C:\Tornado2.2ppc\host\x86-win32\bin;%path%

echo %path%

 

工程没有makefile文件也可以编译,而批处理文件完全是为了是为了实现自动化而编写的,利用批处理文件也可以一次完成工程的编译,但是批处理文件的功能太过简单,自动化程度很低,难以实现工程的维护。而且批处理文件一般不能识别文件是否需要编译,因此它每次都是编译所有文件,对于大型项目会浪费大量的时间。

Makefile的基本原理是建立文件之间的依赖关系,利用时间戳来实现文件的自动更新,而不是把所有的文件都重新编译一次。

下面是example000中的makefile的结构:

# example000

OBJS = main.o c001.o c002.o c003.o c004.o c005.o c006.o

CC = ccppc

LD = ldppc

myedit : $(OBJS)

       $(LD) -e main -o $@ main.o $(OBJS)

main.o : main.c c001.h c002.h c003.h c004.h c005.h c006.h

       $(CC) –c $^

c001.c : c001.c c001.h

       $(CC) –c $^

c001.c : c001.c c001.h

       $(CC) –c $^

c001.c : c001.c c001.h

       $(CC) –c $^

c001.c : c001.c c001.h

       $(CC) –c $^

c001.c : c001.c c001.h

       $(CC) –c $^

c001.c : c001.c c001.h

       $(CC) –c $^

 

.PHONY: clean

clean:

       -rm myedit $(OBJS)

 

 

Example001 工程层次化(F

Example000中把所有的源文件都放到了当前目录下,这样不利于工程文件的组织,因此在Example001中建立了sourceinclude两个文件夹,暂时还无法把便以后的文件放到指定文件夹中的功能。如果是直接利用cc命令编译文件名,那么就需要明确指明被编译文件的位置,如include/c001.h,而定义为makefile的变量后只需利用VPATHvpath两个命令指定搜索路径就可以直接找到所需文件。

另外我们看到$(CC) –c $^重复出现了多次,它的规则都是相同的,因此即使去掉该命令行makefile也是可以自动进行的,这也就是makefile中的默认规则,这样我们的makefile得到了极大简化。

 

# example001

 

CC = ccppc # 新加入

LD = ldppc # 新加入

 

vpath %.c source

vpath %.h include

 

OBJS = main.o c001.o c002.o c003.o c004.o c005.o c006.o

 

myedit : $(OBJS)

       $(LD) -e main -o $@ $(OBJS)

main.o : main.c c001.h c002.h c003.h c004.h c005.h c006.h

c001.o : c001.c c001.h

c002.o : c002.c c002.h

c003.o : c003.c c003.h

c004.o : c004.c c004.h

c005.o : c005.c c005.h

c006.o : c006.c c006.h

 

.PHONY: clean

clean :

       -rm myedit $(wildcard *.o) # 有改动

 

在学习makefile是,我曾经使用过一种极端简化的格式,但是实践证明该方法只能保证第一次编译时工程能够被正确编译,但他们的依赖关系并没有建立起来,也就是说之是由于工程比较简单,makefile利用默认规则就完成了编译,当修改了某一个头文件时,makefile并不能重新编译依赖它的文件。

# wrong example

 

CC = ccppc

LD = ldppc

 

vpath %.c source

vpath %.h include

OBJS = c001.o c002.o c003.o c004.o c005.o c006.o c007.o c008.o c009.o c010.o

 

myedit : main.o $(OBJS)

       $(LDFLAGS) -e main -o $@ main.o $(OBJS)

main.o : $(@: .o=.c) $(OBJS: .o=.h)

$(OBJS) : $(@: .o=.c) $(@: .o=.h)

 

.PHONY: clean

clean :

       -rm myedit $(wildcard *.o)

通过对该错误的总结,得到了一下结论:

²  makefile 中除了定义变量、建立环境和包含其它make文件外,其结构是非常单一的。依赖关系与依赖关系下的各种命令组成了一个个单元,这一个个的单元组成了makefile文件。单元间没有太多联系

²  单元内部是一脸关系与命令组成的双层结构。当依赖关系建立后,下面的命名可以使用$@$<等自动变量,而依赖关系中是不能使用他们的。

 

模块化(From001):

为了实现模块化结构,要求把实现不同内容的makfile放到不同的文件中,在需要的时候才包含如总makefile文件中,下面是总makefile文件的内容。

# example002 Makefile

 

CC = ccppc

LD = ldppc

 

include m001.mak

 

.PHONY: clean001

clean001 :

       -rm myedit $(wildcard *.o)

 

下面是子模块的makefile文件,一般以makmk为后缀。在上层makefile中定义的变量是可以直接在子makefile文件中使用的。

# example002 m001.mak

 

vpath %.c source

vpath %.h include

 

OBJS = c001.o c002.o c003.o c004.o c005.o c006.o有改动

 

myedit : main.o $(OBJS)

       $(LD) -e main -o $@ main.o $(OBJS)

main.o : main.c $(OBJS:.o=.h) # 有改动

c001.o : c001.c c001.h

c002.o : c002.c c002.h

c003.o : c003.c c003.h

c004.o : c004.c c004.h

c005.o : c005.c c005.h

c006.o : c006.c c006.h

 

Example003 自动生成依赖关系(From002):

Gnu的编译器提供了自动推导依赖关系的功能,其语句是 $(CC) –M c001.c。在例子中这句命令的输出是c001.o : c001.c c001.h。利用该功能得到了如下的改进后的makefile

# example003 m001.mak

OBJS = main.o c001.o c002.o c003.o c004.o c005.o c006.o

 

 

.PHONY: m001

m001: myedit

myedit : $(OBJS)

       $(LD) -e main -o $@ $(OBJS)

 

%.d: %.c

       $(CC) -M $< | \

       sed "s/$*.o[ :]/$*.o $*.d :/g" \

       > $@

 

include $(OBJS:.o=.d)

 

这个文件与Example002相比区别就是下面这些代码。

%.d: %.c 的作用是所有以“.d”结尾的文件都依赖于与它同名的以“.c”结尾的文件。

后三句其实是一句命令,只是分割成三段。$(CC) -M $< 作用是利用编译器推导依赖关系。后面的“|”符号是管道连接符,用于把前一行命令产生的结果传给后一个命令。sed "s/$*.o[ :]/$*.o $*.d :/g" 用来修改编译器产生的结果,实际上就是把“.d”结尾的文件也加入了目标集。例如c001.o : c001.c c001.h就变为了c001.o c001.d : c001.c c001.h    >”用于把结果输出道文件。“$@”表示目标文件,也就是“%.d”。

$*makefile中的自动化变量,用于模式规则中的%号。

通过上面这一段代码,就生成了依赖关系并送入相应的“%.d”文件,然后只需把这些规则加入makefile即可。当然其中只有依赖关系没有编译命令。也就是结果Example002中的一样。因此通过makefile的默认规则就可以自动编译。

到此为止,我们的makefile已经基本成型了,当加入新的自文件时,只需修改极少的地方能够就可以完成工程架构。

Example004  make的递归执行(From003):

下面试Example003中的总makefile的内容,该文件实质的内容就是一个submake的伪目标。调用该为目标后,就会自动执行cd subprj && $(MAKE) -f submake.mak,从而调用subprj目录下的submake.mak文件。

# example004 Makefile

 

include variable.mak

 

.PHONY: submake

submake:

       cd subprj && $(MAKE) -f submake.mak

 

.PHONY: clean

clean :

       $(RM) myedit $(wildcard *.o *.d)

执行cd命令后,make的当前目录变量CURDIR会改变为subprj(注意此变量不要手动赋值,否则它就是去了自动更新的功能)。这样所有的工作目录就转到了subprj中,而且除非用export声明,父makefile中的变量都不能在子makefile中使用。因此本文中把新的变量声明放到了variable.mak文件中,当进入子目录后,再次包含该文件就完成了变量的设置。

# example004 variable.mak

 

MAKE = make

CC = ccppc

LD = ldppc

RM = rm -f

SOURCEPATH = source

INCLUDEPATH = include

OUTPUTPATH = output

 

vpath %.c source

vpath %.h include

可以利用如下方法验证该规则:

可以看到,引入variable.mak后,$(CC)的内容变成了我们定义的ccppc,而未引入时的内容则是make默认的cc

为了不改变原有工程的结构,对与不涉及工程目录编译的实践(如:变量)将在subprj目录中进行。

Example005  变量(From004):

GNU make中有两种定义变量的方法:

名称

递归展开式变量

直接展开式变量

方式

VAR = var

VAR := var(多一个冒号)

特点

在引用的地方是严格的文本替换,相当于c语言中的宏定义

更类似于c语言中变量的定义方式

展开时机

只有在被引用的时候才回被展开

在定义时被展开

递归调用的特点

根据起展开时机,当发生递归时,会根据当前变量的值来赋值

由于是定义时展开,变量的值只与定义时变量的值有关

例子

foo = hello

bar = $(foo)

foo = world

.PHONY: sub

sub :

       @echo $(bar)

foo := hello

bar := $(foo)

foo := world

.PHONY: sub

sub

       @echo $(bar)

结果

world

hello

分析

变量 barecho调用时会被展开成$(foo),而此时foo的值为world,因此输出world

变量定义时会自动展开$(foo),此时foo的值为hello,因此bar的的值也变为了hello,当echo调用变量bar时,就会自动替换为hello

变量有如下三种类型:

²  全局变量:一般形式的比那辆

²  目标指定变量:target : VAR = var

²  模式指定变量:%.d : VAR = var

后两种变量相当于局部变量,只有条件满足时才会起作用

 

本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接,严禁用于任何商业用途。
作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net 
阅读(1363) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~