Linux入职基础-7.4_Makefile文件使用入门很简单(实例讲解)

Makefile使用入门很简单(实例讲解)

一、引言

如开发一个小项目,在当前目录下(~/myproject),内含3个c源码文件源码和2个h头文件,依赖关系如下:

f1.c-->def1.h

f2.c-->def2.h

mian.c-->f1.cf2.c

/*f1.c*/

#include "stdio.h"

#include"def1.h"

voidfunc_one()

{

        printf("This is func_one() ,it isin f1.c!\n");

}

/*f2.c*/

#include "stdio.h"

#include"def2.h"

voidfunc_two()

{

        printf("This is func_two(),it isin f2.c!\n");

}

/*main.c*/

#include"stdio.h"

#include"def1.h"

#include"def2.h"

intmain()

{

        printf("main() isruning!\n");

        func_one();

        func_two();

        return 1;

}

上节我们了解gcc编译器简单使用,知道先分别编译源文件(*.c)、再链接所有目标文件(*.o)可生成执行文件,演示过程如下:

[root@localhostmyproject]# ls

def1.h  def2.h f1.c  f2.c  main.c 

//编译

[root@localhostmyproject]# gcc -c f1.c -o f1.o

[root@localhostmyproject]# gcc -c f2.c -o f2.o

[root@localhostmyproject]# gcc -c main.c -o main.o

//链接

[root@localhostmyproject]# gcc main.o f1.o f2.o -o main

[root@localhostmyproject]# ls

def1.h  def2.h f1.c  f1.o  f2.c f2.o  main  main.c main.o

//执行

[root@localhostmyproject]# ./main

main()is runing!

Thisis func_one(),it is in f1.c!

Thisis func_two(),it is in f2.c!

如果软件项目工程大,有成百上千个c源代码程序,上述过程会把人累晕!这时我们介绍另外一个工具,make命令及其配置文件Makefile

为了初步体会Makefile,我们使用它同样来实现上述小项目的编译链接。

//先清理文件

[root@localhostmyproject]# rm -rf *.o

[root@localhostmyproject]# rm -rf main

//编辑Makefile文件,如下:

 [root@localhost myproject]# vim Makefile

/*Makefile*/

main:main.o f1.o f2.o

        gcc main.o f1.o f2.o -o main

main.o:main.c def1.h def2.h

        gcc -c main.c -o main.o

f1.o:f1.c def1.h

        gcc -c f1.c -o f1.o

f2.o:f2.c def2.h

        gcc -c f2.c -o f2.o

这就是Makefile文件结构,清晰简单,每项由两部分组成:依赖关系与一些命令。它们一步一步的告诉make命令如何去生成执行文件main。

[root@localhostmyproject]# ls

def1.h  def2.h f1.c  f2.c  main.c Makefile

//make命令

[root@localhostmyproject]# make

gcc-c main.c -o main.o

gcc-c f1.c -o f1.o

gcc-c f2.c -o f2.o

gccmain.o f1.o f2.o -o main

[root@localhostmyproject]# ls

def1.h  def2.h f1.c  f1.o  f2.c f2.o  main  main.c main.o  Makefile

程序源代码一旦写好,只需要一个make命令,整个工程完全自动编译,提高了软件开发的效率。即依据Makefile文件“自动化编译”!

二、make命令与Makefile关系

Makefile文件,就是告诉make命令需要怎么样的去编译和链接程序,决定检查哪些模块,以及如何构建软件。

make 命令,就是用于自动编译、链接程序的实用工具。能够根据程序中模块的修改情况,自动判断应该对那些模块重新编译,从而保证软件是由最新的模块构成。

同时,make程序的命令行选项可以对Makefile进行即时配置。

三、make命令的语法与常用选项

make [options] [target]…

选项

含义

-f FILE

以指定的FILE 文件作为makefile。

-n

只打印要执行的命令,但不执行这些命令。

-s

在执行命令时不显示命令。

-p

输出所有宏定义和目标文件描述

-i

忽略命令执行返回的出错信息。

d

显示调试信息

-c dir

在读取 makefile 之前改变到指定的目录dir

-I dir

当包含其他 makefile文件时,利用该选项指定搜索目录

-w

在处理 makefile 之前和之后,都显示工作目录。

 

 

 

 

 

 

四、make命令的工作原理

当输入make命令之后,会默认的在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到就解析这个文件。

解析过程就是一层又一层地去找文件的“规则”(规则:由依赖关系和其所定义的一些命令组成),这些“规则”会指示 make 如何进行编译,直到最终编译出第一个目标文件。

在解析过程中,比如,规则的目标[ target ]冒号后面的被依赖的文件找不到(伪目标除外),那么make就会报错退出;

而对于所定义的命令的错误(或是编译不成功),make命令会忽视后继续下一行的解析。

归纳成执行步骤如下:

①、读入所有的Makefile。

②、读入被include的其它Makefile。

③、初始化文件中的变量。

④、推导隐晦规则,并分析所有规则。

⑤、为所有的目标文件创建依赖关系链。

⑥、根据依赖关系,决定哪些目标要重新生成。

⑦、执行生成命令。

五、Makefile文件的基本结构

Makefile文件的结构是由包含一系列的“规则”组成,其样式如下:

目标(target) :依赖(prerequiries)

命令(command)

命令(command)

……

//或者

目标(target):依赖(prerequiries);命令(command)

命令(command)

……

//又或者

伪目标(target) :

命令(command)

命令(command)

……

基本语义如下:

规则的目标(target):通常是要产生的文件的名称或者为了实现这个目标

而必需的中间过程文件名。目标可以是可执行文件或OBJ文件,也可是一个执行的动作名称(这目标称为伪目标(PHONY),如‘clean’)。

规则的依赖(prerequiries):生成规则目标所需要的文件名列表,一个目标生成经常依赖一个或几个文件。

规则的命令(command):是规则所要执行的动作(任意的shell命令或者

是可在shell下执行的程序),一个规则可以含有几个命令,每个命令占一行。

:每一个命令行必须以[Tab]字符开始,[Tab]字符告诉make此行是一个命令行,这是不小心容易出错的地方。此外,如果在makefile文件中的行尾加上空格键的话,也会导致make命令运行失败。

六、隐式规则

Make命令会自动使用gcc -c命令,将一个扩展名为.cc语言程序源文件编译成一个同名的.o目标文件。

因此,在上例中,我们将Makefile文件使用隐式规则重新编辑,如下:

[root@localhost myproject]#vim Makefile1

/*Makefile1*/

main:main.o f1.o f2.o

        gcc main.o f1.o f2.o -o main

main.o: def1.h def2.h

        gcc -c main.c

f1.o:def1.h

        gcc -c f1.c

f2.o:def2.h

        gcc -c f2.c

[root@localhostmyproject]# make –f  Makefile1

使用了隐式规则,隐式规则只是节省了敲代码的数量,但是可读性差些,一般大项目中不建议使用。

七、伪目标

有些目标(target)区别于普通的,它不是为了编译生成文件,而是实现一些小功能的作用,比如,清除之前编译时生成的“.o”目标文件。这种类型目标(target:)便叫做伪目标。请看下面的示例:

示例:在上述范例的Makefile1文件末尾中加入clean伪目标,清除“.o”目标文件。

[root@localhost myproject]#vim Makefile1

/*Makefile1*/

main:main.o f1.o f2.o

        gcc main.o f1.o f2.o -o main

main.o: def1.h def2.h

        gcc -c main.c

f1.o:def1.h

        gcc -c f1.c

f2.o:def2.h

        gcc -c f2.c

.PHONY clean

clean:

             -rm  *.o

[root@localhost myproject]#make –f  Makefile1 clean

我们可以直接输入:“make clean”(有些教程使用默认的Makefile文件)来运行这个伪目标。显然,此伪目标可以清除之前生成的.o目标文件。

加入“.PHONY clean”,是为了防止命名的重复,可以使用.PHONY来显式地指明一个伪目标,这样就不会对clean这个目标的性质定义产生混乱。

项目中常见的伪目标,实现编译、安装、打包等功能,如下:

“all”     这个伪目标是所有目标的目标,其功能一般是编译所有的目标。

“clean”   这个伪目标功能是删除所有被make创建的文件。

“install” 这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。

“print”   这个伪目标的功能是例出改变过的源文件。

“tar”     这个伪目标功能是把源程序打包备份。也就是一个tar文件。

“dist”    这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。

“TAGS”    这个伪目标功能是更新所有的目标,以备完整地重编译使用。

“check”和“test” 这两个伪目标一般用来测试makefile的流程。

八、Makefile常用的语法

为了增强Makefile的功能与增加程序的可移植性,Makefile中使用很多语法格式,如变量(宏)、通配符、函数、条件表达式、关键字等等。

①内部定义的宏

符号

含义

$

宏引用,如$(CC)

$@

代表目标文件

$^

代表所有的依赖文件

$<

代表第一个依赖文件

$?

代表比目标的修改时间更晚的那些依赖文件。

$*

代表去掉后缀的当前目标名。例如,若当前目标是pro.o,则$*表示pro

 

 

宏的作用类似于C语言中的define。Makefile文件中的宏分为两种类:

系统内部定义的宏,上表已经列出,可以直接使用。

用户自己定义的宏,必须在makefile或命令行中明确定义。

格式1:宏标识符 = 值列表  //这个格式很少用

格式2:宏标识符 := 值列表  //这个使用正常

格式3:宏标识符 ?= 值列表

上述三个方式赋值有些区别的,大家自己根据需要使用!一般就用格式2吧!

示范:用户自定义宏

main:main.o f1.o f2.o

        gcc main.o f1.o f2.o -o main

上述规则可转化成如下:

OBJFS:=main.o f1.of2.o

main: $(OBJFS)

gcc main.o f1.o f2.o -omain

由于Makefile当中的宏在本质上是一个字符串,因此可以追加宏的值,用‘+=’操作符。例如:

objects := main.o foo.o

则objects +=another.o 结果为:

objects := main.o foo.o another.o

②关键符的含义

符号

含义

#

注释以#为开头,至行尾结束。

\

将一个较长行使用反斜线(\)来分解为多行,反斜线之后不能有空格。

@

不要显示执行的指令。

-

表示即使该行指令出错,也不会中断执行。

 

 

 

 

 

 

 

 

示范:反斜线(\)

main: main.o f1.o f2.of3.o f4.o \

f5.o f6.o f7.o

                          gcc mian.o f1.o f2.o f3.o f4.o \

f5.o f6.o f7.o

clean:

                          -rm *.o

③通配符

通配符,如’*’,’?’。它只在规则的目标、依赖关系、命令中会自动扩展。但是,其它情况下,比如,在规则中的文件名可以包含统配符,除非显式使用’wildcard’函数,否则统配符不会扩展开来的。

示范:可执行文件’main’是从当前目录中的所有’.o’文件生成的:

objects = *.o

main : $(objects)

             gcc  $(objects) -o  main

如果当前目录删除了所有的’.o’文件,此时通配符不匹配任何文件,就保持原样(无法扩展了),则’main’依赖于一个叫做’*.o’的文件,make命令找不到这个文件就会报错,把上述代码代入Makefile1中,测试下便知!

 

④函数语法与常用函数(详见范例讲解)

几种常用的函数介绍,详解见《Linux_编程_02:Makefile常用函数》,一般中等项目软件够用了!

⑤目录与文件的搜索(详见范例讲解)

make只会在当前的目录中去找寻依赖文件和目标文件。但是,在大的工程中,许多的源文件分类存放在不同的目录中。关于目录与文件的搜索,Makefile 提供了两种方式:

第一种是设置全局访问路径VAPTH。

第二种是设置关键字vpath。

先介绍第一种方式:Makefile文件中有个特殊变量“VPATH”,如果定义了,则make就会在当前目录找不到的情况下,到所指定的目录中去找寻文件了。

语法:VPATH =src:../headers

上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。

注意:定义了VPATH或vpath,这仅仅是对于makefile来说搜索目标和依赖文件的路径,但是对于命令行来说是无效的,也就是说在执行g++或者gcc时不会自动从VPATH 或者vpath中自动搜索要包含的头文件等信息文件。

此时要用到了 -I 或者--incude +路径。这也容易出错的地方。

第二种方式:当需要为不类型的文件指定不同的搜索目录时需要这种方式。

语法:

#为符合模式的文件指定搜索目录

vpath

#清除符合模式的文件的搜索目录。

vpath

#清除所有已被设置好了的文件搜索目录。

vpath

指定了要搜索的文件集,而则指定了的文件集的搜索的目录。

示范

vpath  %.h  ../headers

vpath  %.c  foo

要求make在“../headers”目录下搜索所有以“.h”结尾的文件;在“foo”目录下搜索所有以“.c”结尾的文件。

⑥条件表达式

条件编译:如果满足一定的条件,将执行一定的动作。

常见的条件编译指令:

ifeq(arg1, arg2) .. endif  如果arg1和arg2相等的话,将执行该区域中的内容。

ifneq(arg1, arg2) .. endif        如果arg1和arg2不相等的话,将执行该区域内的内容。

ifdef ARG .. endif    判定ARG变量是否已经定义,已定义则将执行该区域内的内容。

ifndef ARG .. endif  和ifdef相反

上面的语句可以和else一起来使用,如下:

ifneq ("same", "diff")

        @echo "same !=diff"

else                       

        @echo "same ==diff"

endif   

九、自动产生依赖关系表

在一个含有大量源文件的项目中,很可能每个源文件都包含一组头文件,而头文件有时又会包含其它头文件,这时正确区分依赖关系就比较难了,了防止遗漏,gcc还有一个-MM选项可用,该选项会为make生成一个依赖关系表,如下:

[root@localhost makepro]# gcc -MM main.cf1.c f2.c f3.c

main.o: main.c def1.h

f1.o: f1.c def1.h

f2.o: f2.c def1.h def2.h

f3.o: f3.c def2.h def3.h

将它们保存到一个临时文件内,然后将其插入makefile即可。


 


你可能感兴趣的:(Linux,Linux入职基础)