原文链接:http://www.orlion.ga/816/

一、基本规则

对于一个拥有多个文件的c项目,编译时可能是这样的指令:

gcc main.c stack.c -o main

如果编译之后又对stack.c进行了修改,则又要重新把所有的源文件编译一遍,即使main.c和那些头文件都没有修改也要跟着重新编译,一个大型项目往往上千个源文件组成,全部编译要几个小时,只改一个源文件就要重新编译显然不合理。如果是一下的编译方式会好一些:

gcc -c main.c
gcc -c stack.c
gcc main.o stack.o -o main

如果编译后修改了stack.c要重新编译只需要两步:

gcc -c stack.c
gcc main.o stack.o -o main

这样又有一个问题,如果修改了三个源文件,但是有一个忘记编译了,结果编译完了修改没生效。更复杂的是修改了头文件所有包含该头文件的源文件都需要重新编译。

要解决以上的问题就需要写一个Makefile文件和源码放到同一个目录下:

然后在这个目录下运行make编译:

make命令会自动读取当前目录下的Makefile文件,完成相应的编译步骤。Makefile由一组规则组成,每条规则的格式是:

target ... : prerequisites ...
        command1
        command2
        ...

例如:

main: main.o stack.o is_empty.o pop.o push.o
        gcc main.o stack.o is_empty.o pop.o push.o

main是这条规则的目标(target),main.o、stack.o、is_empty、pop.o、push.o是这条规则的条件(prerequisite)。目标和条件之间的关系是:欲更新目标,必须更新它的所有条件;所有条件中只要有一个条件被更新了,目标也必须跟着被更新。所谓"更新"就是执行一遍规则中的命令列表,命令列表中的每条命令必须以一个Tab开头,注意不能是空格,对于Makefile中的每个一Tab开头的命令,make会创建一个Shell进程去执行它。

对于上边的例子,make执行如下步骤:

    1. 尝试更新Makefile中的第一条规则的目标main,第一条规则的目标称之为缺省目标,只要缺省目标更新了就算完成任务了,其他工作都是为这个目的而做的。由于main文件还没有生成,显然需要更新,但规则说必须先更新了main.o stack.o is_empty.o pop.o push.o这五个条件才能更新main。

    2. 所以make会进一步查找以这五个条件为目标的规则,这些目标文件也没有生成,页需要更新,所以执行相应的命令(gcc -c main.c gcc -c stack.c …)来更新它们。

    3. 最后执行gcc main.o stack.o is_empty.o pop.o push.o -o main更新main。

如果再执行make则不会重新编译,如果源文件修改了则会重新编译更新对应的目标文件然后重新执行gcc main.o stack.o is_empty.o pop.o push.o -o main来更新main

通常Makefile都会有一个clean规则,用于清除编译过程中产生的二进制文件,保留源文件:

clean:
        @echo "cleanning project"
        -rm main *.o
        @echo "clean completed"

把这条规则加到Makefile文件尾,然后执行这条规则:

如果在make的命令行中指定了一个目标(例如clean),则更新这个目标,如果不指定目标则更新Makefile中第一条规则的目标(缺省目标)。

如果make执行的命令前面加了@字符则不显示命令本身而只显示它的结果;如果命令前面加了“-”即使这条命令出错make也会继续执行,(如果不加则会立刻终止,不再执行后续的命令)通常rm命令和mkdir命令前面要加“-”,因为rm要删除的文件可能不存在,mkdir要创建的目录可能已经存在了,而这些错误是可以忽略的,例如上边已经执行过一遍make clean了,再执行一遍就没有文件可以删了这时rm会报错,但是make忽略了这一错误继续执行后边的echo。

如果当前目录下存在一个文件clean会怎么样呢?如果存在一个clean文件,clean目标又不依赖于任何条件,make就认为它不需要更新了,而我们希望把clean当做一个特殊的名字使用,不管它存在不存在都要更新,可以添加一条特殊规则,把clean声明为一个伪目标:

.PHONY: clean

这条规则没有命令列表。(这条规则写在clean:规则之后也行)类似于clean这样的命令常见的就是install了(make install)

二、隐含规则和模式规则

Makefile有很多灵活的写法,可以写得更简洁。

一个目标所依赖的所有条件不一定非要写在一条规则中,可以分开来写:

main.o: stack.h

main.o: main.c
        gcc - c main.c

如果一个目标分来来写则其中只有一条规则允许有命令列表,其他规则应该没有规则列表,否则make会报警并采用最后一条规则的命令列表。

现在我们的Makefile可以改写为:

main: main.o stack.o is_empty.o pop.o push.o
        gcc main.o stack.o is_empty.o pop.o push.o -o main
        
main.o: stack.h

main.o: main.c
        gcc -c main.c

stack.o: stack.c
        gcc -c stack.c
...

clean:
        -rm main *.o
.PHONY: clean

可以看到我们把main.o分开来写,但是这样会显得繁琐了些,这时可以简化:

main: main.o stack.o is_empty.o pop.o push.o
        gcc main.o stack.o is_empty.o pop.o push.o -o main
        
main.o: stack.h

clean:
        -rm main *.o
.PHONY: clean

但是现在main.o\stack.o\is_empty.o…这五个目标连编译命令没了怎么编译的呢?
   

解释一下前五条命令怎么来的:如果一个目标在Makefile中所有规则都没有命令列表,make会尝试在内建的隐含规则数据库中查找适用的规则。make的隐含规则数据库可以用"make -p"命令打印,打印出来的格式也是Makefile的格式,包含很多变量和规则,其中和这个例子有关的隐含规则有:

# default
OUTPUT_OPTION = -o $@
# default
CC = cc
# default
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
%.o: %.c
#  commands to execute (built-in):
        $(COMPILE.c) $(OUTPUT_OPTION) $<

#号在Makefile中表示单行注释,cc是一个Makefile变量,用CC = cc定义和赋值,用$(CC)取它的值,其值应该是cc。Makefile变量像C的宏定义一样,代表一串字符,在取值的地方展开。cc是一个符号链接,通常指向gcc。

CFLAGS这个变量没有定义,$(CFLAGS)展开是空,CPPFLAGS和TARGET_ARCH也是如此。这样$(COMPLIE.c)展开应该是"cc 空 空 空 -c"去掉“空”得到"cc -c",注意中间留下4个空格,所以%.o:%.c规则的命令$(COMPLIE.c) $(OUTPUT_OPTION) $<展开之后是cc -c -o $@ $<,和上面的编译命令已经很接近了。

$@和$<是两个特殊的变量,$@的取值为规则中的目标,$<的取值为规则中的第一个条件。$.o:%.c是一种特殊的规则,称为模式规则。现在回顾一下整个过程,在我们的Makefile中以main.o为目标的规则都没有命令列表,所以make会查找隐含规则,发现隐含规则中有这样一条模式规则适用,main.o符合%.c的模式,现在%就代表main(称为main.o这个名字的Stem),再替换到%.c中就是main.c。所以这条规则就相当于:

main.o: main.c
        cc -c -o main.o main.c

随后在处理stack.o目标时又用到这条模式规则,这时又相当于:

stack.o: stack.c
        cc -c -o stack.o stack.c

其他三个也同样处理。这三条规则可以由make的隐含规则推导出来,所以不必写在Makefile中。

之前我们写的Makefile都是以目标为中心,现在可以以条件为中心:

target1 target2: prerequisite1 prerequisite2
        command $&lt; -o $@

这条规则相当于:

target1: prerequisite1 prerequisite2
        command prerequisite1 -o target1
target2: prerequisite1 prerequisite2
        command prerequisite1 -o target2

三、变量

foo = $(bar) 
bar = Huh? 
all: 
        @echo $(foo)

我们执行make将会打印出Huh?。当make读到foo = $(bar)是,确定foo的值是$(bar),但并不立即展开$(bar),然后读到bar = Huh?,确定bar的值是Huh?,然后在执行规则all:的命令列表时才需要展开$(foo),得到$(bar),再展开$(bar),得到Huh?。因此,虽然bar的定义写在foo之后,$(foo)展开还是能够取到$(bar)的值。、

这种特性有好处就是可以把变量的定义推迟到后边。当然可以使用":="运算符make在遇到变量定义时立即展开:

x := foo
y := $(x) bar
all: 
        @echo "-$(y)-"

还有一个比较有用的赋值运算符是?=,例如foo ?= $(bar)的意思是:如果foo没有定义过,那么?=相当于=,定义foo的值是$(bar),但不立即展开;如果先前已经定义了foo,则什么也不做,不会给foo重新赋值。

+=运算符可以给变量追加值,例如:

objects = main.o
objects += $(foo)
foo = foo.o bar.o

常用的特殊变量:

$@,表示规则中的目标。

$<,表示规则中的第一个条件。

$?,表示规则中所有比目标新的条件,组成一个列表,以空格分隔。

$^,表示规则中的所有条件,组成一个列表,以空格分隔。

四、自动处理头文件的依赖关系

在写main.o、stack.o、is_empty.o、pop.o、push.o这五个目标的规则时要查看源代码,找出它们依赖于哪些头文件,这很容易出错,一是因为有的头文件包含在另一个头文件中,在写规则时很容易遗漏,二是如果以后修改源代码改变了依赖关系,很可能忘记修改Makefile的规则。为了解决这个问题,可以用gcc的-M选项自动生成目标文件和源文件的依赖关系:

如果不需要输出系统头文件的依赖可以使用"-MM"。

接下来是怎么把这些规则包含到Makefile中,GNU make官方手册建议这样写:

all: main
main: main.o stack.o is_empty.o pop.o push.o
        gcc $^ -o $@
clean:
        -rm main *.o
   
.PHONY: clean
sources = main.c stack.c is_empty.c pop.c push.c
include $(sources:.c=.d)
%.d: %.c
        set -e; rm -f $@; \
        $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
        sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
        rm -f $@.$$$$

source变量包含我们要编译的所有.c文件,$(sources: .c=.d)是一个变量替换语法,把sources变量中每一项的.c替换成.d所以include这一句相当于:

include main.d stack.d is_empty.d pop.d push.d

这里include表示包含五个文件。这五个文件也应该符合Makefile的语法。这时候执行make一开始找不到.d文件会报警告,但是make会把include的文件名也当做目标来尝试更新,而这些目标适用模式规则%.d:%.c,所以执行它的命令列表。

不管是Makefile本事还是被包含的文件,只要有一个文件在make过程中给更新了,make就会重新读取整个Makefile以及被它包含的所有文件。

五、常用的make命令行选项

-n选项只打印要执行的命令,而不会真的执行命令,这个选项有助于我们检查Makefile写得是否正确,由于Makefile不是顺序执行的,用这个选项可以先看看命令的执行顺序,确认无误了再真正执行命令。

-C选项可以切换到另一个目录执行那个目录下的Makefile,比如先退到上一级目录再执行我们的Makefile(假设我们的源代码都放在testmake目录下):

$ cd ..
$ make -C testmake

一些规模较大的项目会把不同的模块或子系统的源代码放在不同的子目录中,然后在每个子目录下都写一个该目录的Makefile,然后在一个总的Makefile中用make -C命令执行每个子目录下的Makefile。

在make命令行也可以用=或:=定义变量,如果这次编译我想加调试选项-g,但我不想每次编译都加-g选项,可以在命令行定义CFLAGS变量,而不必修改Makefile编译完了再改回来:

make CFLAGS=-g

Makefile的更多相关文章

  1. 说说Makefile那些事儿

    说说Makefile那些事儿 |扬说|透过现象看本质 工作至今,一直对Makefile半知半解.突然某天幡然醒悟,觉得此举极为不妥,只得洗心革面从头学来,以前许多不明觉厉之处顿时茅塞顿开,想想好记性不 ...

  2. 编写一个通用的Makefile文件

    1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe ...

  3. 编写简单的Makefile文件

    makefile中的编写内容如下: www:hello.c x.h gcc hello.c -o hello clean: rm hello www:hello.c  x.h 表示生成www这个文件需 ...

  4. 简单编写Makefile

    相信很多朋友都有过这样的经历,看着开源项目中好几页的makefile文件,不知所云.在日常学习和工作中,也有意无意的去回避makefile,能改就不写,能用ide就用ide.其实makefile并没有 ...

  5. [转]Linux中configure/makefile

    本文教你如何使用autoconf.automake等来制作一个以源代码形式(.tar.gz)发布的软件.并可在执行configure时使用自定义参数. 一.概述和基础知识 在Linux下得到一个以源代 ...

  6. Linux内核配置、编译及Makefile简述

    Hi,大家好!我是CrazyCatJack.最近在学习Linux内核的配置.编译及Makefile文件.今天总结一下学习成果,分享给大家^_^ 1.解压缩打补丁 首先是解压缩你获取到的Linux内核. ...

  7. make 查找的文件名顺序为:“GNUmakefile”、“makefile”、“Makefile”

    默认的情况下,make会在工作目录(执行make的目录)下按照文件名顺序寻找makefile文件读取并执行,查找的文件名顺序为:“GNUmakefile”.“makefile”.“Makefile”. ...

  8. 实例:对2个Makefile的备注

    实例1:Makefile编译链接简单.c函数 example.c Makefile exe: example.c gcc example.c -o exe clean: rm exe 执行效果: 实例 ...

  9. Linux中C程序调试、makefile

    gcc基本语法格式:gcc [-选项] 源文件 [-选项] 目标文件,GCC编译C程序的过程: 预处理:gcc -E hello.c hello.i.-E指定执行到预处理结束,下面类似. 编译:gcc ...

  10. Linux工具入门:make工具与Makefile文件

    1. make工具 利用make工具可以自动完成编译工作,这些工作包括: 如果修改了某几个源文件,则只重新编译这几个源文件 如果某个头文件被修改了,则重新编译所有包含该头文件的源文件 利用这种自动编译 ...

随机推荐

  1. 怎么在GitHub上寻找开源项目呢

    find projects GitHub Explore: Popular and trending projects. GitHub Stars: Projects starred by other ...

  2. 记lrd的高二上学期第五次调研考试

    河北某某中学的调研考试其实是很好玩的经历呢.可惜没有太多机会了. 背景: NOIP2016回来之后没有好好学文化课-.自习能翘就翘了,衡中特产学案自助没有好好写(说来我好像从来没被老师查到过,上课写学 ...

  3. mySql中IFNULL的使用说明

    IFNULL(expr1,expr2) 如果expr1不是NULL,IFNULL()返回expr1,否则它返回expr2.IFNULL()返回一个数字或字符串值 具体用法如:现有学生表(tbl_stu ...

  4. CentOS安装PHP和mysql

    新生在不会编译的情况下: 1.安装PHP5 yum install php 根据提示输入Y直到安装完成 2.安装PHP组件,使 PHP5 支持 MySQL yum install php-mysql  ...

  5. X32,X64,X86 代表什意义

    X32,X64,X86是什么意思 各代表什么:X86指32位,X64指64位,现在用户最多的是XP,但win7是趋势,发展很快,建议你装个win7 32位的系统,下载的话地方很多,官方安装原版和gho ...

  6. Android程序ToDoList增加配置项页面

    本文要做的事情就是在前面做的简单的ToDoList程序上增加一个配置项页面(Reference).这个Reference页面也非常简单: 这个ToDoList现在有两个页面,主页面能填写待办事项,然后 ...

  7. hdu Random Sequence

    这道题是道规律极强的题...真佩服在赛场上快速找到规律的人. d[i]              a[i]            res[i] 0                 1.000000 C ...

  8. Android 网格布局 计算器

    <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools=&quo ...

  9. hdu 1588(Fibonacci矩阵求和)

    题目的大意就是求等差数列对应的Fibonacci数值的和,容易知道Fibonacci对应的矩阵为[1,1,1,0],因为题目中f[0]=0,f[1]=1,所以推出最后结果f[n]=(A^n-1).a, ...

  10. Careercup - Facebook面试题 - 6026101998485504

    2014-05-02 10:47 题目链接 原题: Given an unordered array of positive integers, create an algorithm that ma ...