GNU make(一):GNU make概述

一、GNU make概述

1. GNU make是什么?

GNU make是Linux环境下著名的工程构建和管理工具,使得我们可以使用一个命令就完成编译、链接以至于执行,自动地帮我们完成构建工作。目前,大量的C/C++项目使用make作为工程构建工具,大量的IDE使用与make相同的工程构建策略。

2. GNU make如何工作?

Make的本质是一个解释器,用于解释一种名为Makefile的脚本。这个Makefile脚本告诉make以何种方式编译源代码和链接程序。从本质上来看,make通过比较相关文件的最后修改时间,来决定哪些文件需要更新,哪些文件不需要更新,之后执行以下操作:

  • 对于那些需要更新的文件,make就使用在Makefile中预定义的命令来重新构建这些文件;
  • 对于那些无需更新的文件,make就什么也不做。

二、Makefile简介

编写Makefile脚本是一项相对复杂的工作,它有自己的书写格式、关键字函数。而且,在Makefile脚本中可以使用宿主操作系统所提供的任何Shell命令来完成想要完成的操作。

1. Makefile的规则介绍

在Makefile中,规则是最重要的组成部分,通过定义一条或多条规则来描述文件如何被生成。Makefile一个最基本的Makefile规则如下所示:

# 这里是单行注释。
Target : Pre[1] Pre[2] ... Pre[N]; Command[0]
    Command[1]
    Command[2]
    ...
    Command[M]

其中所包含的主要成员如下:

  • Target规则的目标。它通常是以下两种内容:

    • 需要生成的文件的名称
    • 一个make执行的动作的名称

    【注意】

    1. 目标可以有多个,中间用空格分开

    2. 动作名称是指:有时make希望将一组特定的指令放在一起,但其目的并不是为了生成目标文件。

      例如,make经常需要清理之前所生成的文件,然而这项清理工作很显然是不应该生成文件的。

      此时,可以将clean(或任何其他名字)作为一个目标,并且在其所有命令行中不生成任何文件即可。

  • Pre规则的依赖。它通常是以下两种内容:

    • 生成目标所需要的文件名列表
    • 生成目标所需要的其他目标列表
    • 以上两种情况的杂揉

    【注意】依赖可以有多个,中间用空格分开

  • command规则的命令行。指的是为了生成目标所需要执行的Shell指令,可以出现在以下为止:

    • 在所有的依赖后面添加一个"",然后写命令。
    • 从目标和依赖所出现的下一行开始,以【TAB】键开头,然后编写多条命令。

    【注意】

    1. 命令一般有多个,以行为单位。
    2. 不推荐编写在依赖行后面紧跟的命令,因为它影响可读性。
  • 注释:和其他脚本相同,使用"#"作为注释的标志。

在所有的规则中,默认的第一个规则的目标为最终目标,也就是make默认生成的目标;如果第一个规则是一个多目标规则,那么第一个目标被作为最终目标。

当然,Makefile中通常还包含了除了规则之外的很多东西(后续我们来展开讨论)。但无论它怎样复杂,它都符合以上介绍的最基本的格式

2. make如何解释规则

(1) make判断需要生成目标的依据

当make进行维护工作时,它按照如下顺序检查是否需要生成目标:

  1. 目标不存在,就生成它;
  2. 目标存在,但在目标的所有依赖中,至少存在一个依赖,在时间戳上比当前的目标更新 (这表明在上一次维护后,依赖被修改了),则重新生成它;
  3. 什么也不做。

同时,对于规则,make还具有以下特性:

  1. 如果目标不是一个文件,并且没有依赖或者命令,那么它的时间戳被认为是最新的(确保它一定被执行)
  2. 如果一个目标不是最终目标的依赖,那么即便它被定义了,也不会被执行。

(2) make维护的抽象数据结构

当make解释器发现了一条规则时,它将按照顺序读取其目标、依赖和所有命令,然后维护一个数据结构来存储它们。这个数据结构的实现各有不同,但是为了帮助理解上面的特性,我们可以按照下面的样子来抽象并理解它:

其中,所有的依赖和目标组成了一个循环链表,而所有的命令是一个单链表。当make发现需要维护一个目标时,它将进行以下工作:

  1. 如果这个目标不存在,就生成它;

  2. 如果这个目标存在,就从第一个依赖开始顺序检查所有依赖,

  • 如果当前依赖是一个文件,那么检查它的时间戳来判定是否重新生成;
  • 如果当前依赖是一个目标,那么寻找这个"依赖目标"的数据结构,并尝试维护它(将这个依赖作为目标,递归地进行工作)。

3. 如何执行make

在终端执行make命令即可。当执行make命令时,有几个可选项:

  • -f:指定Makefile的文件名。
  • 目标名:将这个目标作为最终目标。

如果没有使用-f选项,那么make程序执行默认的缺省选择:

  • makefile
  • Makefile(推荐使用,因为能够和README、CHANGELIST等文件放在一起)

三、编写一个最简单的HelloWorld脚本

作为HelloWorld脚本,它应该尽可能地简单。因此,我们不生成任何文件,仅仅让其在终端输出字符串即可。

【示例】一个最简单的Makefile脚本

# Makefile
HelloWorld:
    echo "hello, world"

在这个Makefile脚本中,我们仅编写了一条规则:HelloWorld,它不具有任何依赖,并且只有一条命令:echo "hello, world"。当Make解释器在解释这个脚本时,它将开始如下的工作:

  1. 读取这个脚本文件;
  2. 构建所有规则的数据结构;
  3. 找到最终目标HelloWorld
  4. 发现HelloWorld目标在当前文件系统中不存在(也就不是一个文件),并且没有依赖,因此一定被执行。
  5. 执行命令:echo "hello, world"

【运行结果】

[scott@localhost 0000]$ make
echo "hello, world"
hello, world
[scott@localhost 0000]$ make -f Makefile
echo "hello, world"
hello, world
[scott@localhost 0000]$ make HelloWorld
echo "hello, world"
hello, world
[scott@localhost 0000]$ make -f Makefile HelloWorld
echo "hello, world"
hello, world

四、使用make来编译代码

【示例】使用make来维护代码

/*
 * foo.h
 */
#ifndef _FOO_H_
#define _FOO_H_

void foo();

#endif
/*
 * foo.c
 */
#include <stdio.h>
#include "foo.h"

void foo()
{
        printf("hello, makefile\n");
}
/*
 * main.c
 */
#include "foo.h"

int main(int argc, char *argv[])
{
        foo();
        return 0;
}
# Makefile
HelloMakefile.elf: main.o foo.o
        gcc -o HelloMakefile.elf main.o foo.o
                
main.o: main.c
        gcc -c main.c -o main.o

foo.o: foo.c
        gcc -c foo.c -o foo.o

【运行结果】

[scott@localhost 0000]$ ls
foo.c  foo.h  main.c  Makefile
[scott@localhost 0000]$ make
gcc -c main.c -o main.o
gcc -c foo.c -o foo.o
gcc -o HelloMakefile.elf main.o foo.o
[scott@localhost 0000]$ ls
foo.c  foo.h  foo.o  HelloMakefile.elf  main.c  main.o  Makefile
[scott@localhost 0000]$ ./HelloMakefile.elf 
hello, makefile

猜你喜欢

转载自www.cnblogs.com/rosefinch/p/11313808.html