Makefile 神奇:驾驭编译的力量

2023年 9月 17日 30.8k 0

一.make和Makefile

当谈到 makeMakefile 时,通常是指构建工具 make 和用于描述编译和构建过程的文本文件 Makefile

make 是一个在类Unix系统中广泛使用的构建工具。它基于文件的时间戳比较,只编译发生了变化的文件,从而提高了编译效率。make 通过读取 Makefile 文件中的规则和命令来执行构建任务。

Makefile 是一个文本文件,其内容定义了一个或多个构建任务的规则。它包含了目标、依赖关系和构建命令。以下是 Makefile 文件中常用的元素:

  • 目标(Target):目标是构建的结果,可以是可执行程序、静态库、对象文件等。每个目标都由一个或多个规则定义。

  • 依赖关系(Dependencies):依赖关系指定了目标所依赖的文件或其他目标。当依赖项发生变化时,make 会自动重新构建相关的目标。

  • 构建命令(Build Command):构建命令是构建目标所需的实际操作。它们通常是编译、链接和其他构建工具的命令。

  • Makefile 的规则由以下形式组成:

    target: dependencies
        build commands
    

    其中:

    • target 是目标的名称,可以是一个文件名或一个逻辑名称。
    • dependencies 是目标依赖的文件或其他目标。
    • build commands 是构建目标所需的一系列命令。

    例如,以下是一个简单的 Makefile 示例:

    hello: main.o utils.o
        gcc -o hello main.o utils.o
    
    main.o: main.c
        gcc -c main.c
    
    utils.o: utils.c
        gcc -c utils.c
    

    上述 Makefile 中定义了一个目标 hello,它依赖于 main.outils.o 这两个文件。对应的构建命令编译并链接这些文件以生成可执行文件 hello。同时,Makefile 定义了 main.outils.o 这两个目标的依赖关系和构建命令。

    当执行 make 命令时,它会读取当前目录下的 Makefile 文件并根据规则执行构建任务。make 会分析目标与依赖关系,并根据文件的时间戳判断是否需要重新构建目标。

    通过使用 makeMakefile,开发者可以方便地管理项目的编译过程,减少重复构建,提高开发效率。

    ![2023-09-17T05:58:27.png][1]

    二.Makefile的重要性

    Makefile具有以下几个重要的作用和重要性:

  • 自动化构建:Makefile定义了项目的构建规则和依赖关系,使得构建过程可以自动化执行。通过执行make命令,构建工具会根据Makefile中的规则和文件的时间戳判断哪些文件需要重新构建,从而提高构建的效率。

  • 管理复杂项目:对于大型和复杂的软件项目,构建过程可能涉及多个源文件、库文件和依赖项。Makefile可以清晰地定义这些依赖关系,确保所有的代码都按照正确的顺序编译和链接,避免遗漏和混乱。

  • 跨平台构建:Makefile不仅适用于Unix和Linux系统,也广泛用于其他操作系统和平台。通过编写通用的Makefile,可以方便地实现不同平台上的构建,从而提高项目的可移植性和可扩展性。

  • 灵活性和可定制性:Makefile提供了丰富的语法和功能,可以处理各种复杂的构建需求。开发者可以根据项目的需要,定制和调整Makefile中的规则和命令,以满足特定的构建要求。

  • 版本控制和协作:Makefile通常与版本控制系统(如Git)结合使用,将构建过程的定义与代码一起进行版本管理。这样可以确保团队成员之间的构建环境一致,并提供一个统一的构建流程,方便项目的协作和迭代开发。

  • 构建的可维护性:Makefile将项目的构建过程抽象出来,使其独立于具体的构建工具和平台。这样,在需要更改构建工具或切换平台时,只需要修改Makefile而不需要修改项目中的代码,提高了构建过程的可维护性和可持续性。

  • 综上所述,Makefile在软件开发中扮演着重要的角色,它提供了一种规范和自动化的方式来管理和执行项目的构建过程,提高了开发效率、可维护性和可移植性,减少了错误和重复工作。

    ![2023-09-17T05:59:52.png][2]

    三.Makefile三要素

    Makefile中包含了三个重要的要素,它们是:

  • 目标(Target):目标是构建的结果,可以是可执行程序、静态库、对象文件等。每个目标都由一个或多个规则定义。例如:
  • hello: main.o utils.o
        gcc -o hello main.o utils.o
    

    在上述示例中,hello 是目标的名称,它依赖于 main.outils.o 这两个文件,通过执行 gcc 命令将它们链接在一起生成可执行文件 hello

  • 依赖关系(Dependencies):依赖关系指定了目标所依赖的文件或其他目标。当依赖项发生变化时,make 会自动重新构建相关的目标。例如:
  • hello: main.o utils.o
    

    上述示例中,hello 这个目标依赖于 main.outils.o 这两个文件,当它们发生变化时,make 将会重新构建 hello 这个目标。

  • 构建命令(Build Command):构建命令是构建目标所需的实际操作。它们通常是编译、链接和其他构建工具的命令。构建命令前需要以一个 Tab 键作为缩进。例如:
  • hello: main.o utils.o
        gcc -o hello main.o utils.o
    

    在上述示例中,gcc -o hello main.o utils.o 是构建命令,它使用 gcc 编译器将 main.outils.o 这两个文件链接在一起生成可执行文件 hello

    通过定义目标、依赖关系和构建命令,Makefile 提供了一种描述项目构建过程的方式,通过执行 make 命令,可以根据 Makefile 中的规则自动执行构建任务。这三个要素共同构成了 Makefile 的基本结构和功能。

    四.伪目标

    伪目标(Phony Target)是在Makefile中定义的一种特殊目标,它并不对应真实的文件,而是表示一个动作或一组命令。

    通常情况下,目标在Makefile中的定义会与一个实际的文件名相关联。但是有些时候,我们可能需要定义一些仅用于执行一些操作或命令的特殊目标,这时就可以使用伪目标。

    伪目标不会检查与其同名的文件是否存在或文件的时间戳,它每次都会执行定义的命令,无论依赖项是否改变。

    定义伪目标时,需要使用 .PHONY 声明。例如:

    .PHONY: clean
    
    clean:
        rm *.o
    

    上述示例中,.PHONY 声明了 clean 为伪目标。当执行 make clean 命令时,Makefile会执行 rm *.o 命令,删除所有 .o 后缀的文件。

    常见的伪目标包括 clean(用于清理生成的文件)、all(执行所有构建操作)等。

    使用伪目标可以方便地扩展Makefile的功能,例如定义一些常用的操作或构建命令的集合。同时,它也提高了Makefile的可读性和维护性,使得构建过程更加灵活和易于管理。

    五.Makefile变量

  • 系统变量
  • 在Makefile中,系统变量是预定义的变量,用于访问系统相关的信息。这些变量可以用于配置编译器、链接器、编译选项等。

    以下是常见的Makefile系统变量:

  • CC:C编译器的路径和名称。默认为cc
  • CXX:C++编译器的路径和名称。默认为g++
  • LD:链接器的路径和名称。默认为cc
  • AR:静态库归档工具的路径和名称。默认为ar
  • RM:删除文件的命令。默认为rm
  • CFLAGS:C编译器的选项和标志。例如,CFLAGS=-O2 -Wall可以设置编译器优化级别和显示警告。
  • CXXFLAGS:C++编译器的选项和标志。
  • LDFLAGS:链接器的选项和标志。可以用来指定库文件的路径和链接其他库等。
  • CPPFLAGS:C和C++编译器的预处理选项和标志。通常用于定义宏或包含路径。
  • SHELL:默认的Shell解释器。默认为系统的默认Shell。
  • 这些系统变量可以在Makefile中进行自定义,以满足项目的需求。通过设置这些变量的值,可以修改编译器、链接器和编译选项,从而灵活地控制项目的编译和构建过程。

    例如,可以通过定义 CC 变量来指定使用的C编译器:

    CC = clang
    

    或者设置 CFLAGS 变量来指定编译选项:

    CFLAGS = -O2 -Wall -I/path/to/includes
    

    利用这些系统变量,可以轻松地管理项目的构建过程,并根据需要进行定制化调整。

    2.自定义变量

    2.1 = 延迟赋值

    当使用 = 运算符进行赋值时,变量的值会在使用时立即展开并计算。这意味着如果在后续的代码中修改了变量的值,那么之前使用该变量的地方也会立即反映这个改变。

    例如:

    VAR = foo
    TARGET = $(VAR)
    
    $(info TARGET: $(TARGET))  # 输出: TARGET: foo
    
    VAR = bar
    
    $(info TARGET: $(TARGET))  # 输出: TARGET: bar
    

    在上述示例中,使用 = 运算符对 TARGET 进行赋值,初始时 VAR 的值为 foo,因此 TARGET 的值也是 foo。然后,修改了 VAR 的值为 bar,因此在下一次使用 TARGET 时,它的值也会变为 bar

    2.2 := 立即赋值

    相反,当使用 := 运算符进行赋值时,变量的值会立即计算并展开,并在定义时就固定下来,不会受其后定义的变量值的影响。

    例如:

    VAR := foo
    TARGET := $(VAR)
    
    $(info TARGET: $(TARGET))  # 输出: TARGET: foo
    
    VAR := bar
    
    $(info TARGET: $(TARGET))  # 输出: TARGET: foo
    

    在上述示例中,使用 := 运算符对 TARGET 进行赋值,初始时 VAR 的值为 foo,因此 TARGET 的值也是 foo。即使在后续将 VAR 的值修改为 barTARGET 的值仍保持不变,仍然是最初展开时的 foo

    通过选择适当的赋值运算符,可以控制变量的延迟赋值行为,从而灵活地管理变量的值。

    2.3 ?= 空赋值
    在Makefile中,?=运算符用于进行空赋值。它的作用是在变量未定义时,将一个新的值赋给该变量。

    当使用?=运算符进行赋值时,如果该变量还未定义,那么它会被赋予指定的值;如果该变量已经定义了,那么赋值操作不会执行,变量的值保持不变。

    以下是一个示例:

    VAR ?= default_value
    
    $(info VAR: $(VAR))
    
    VAR := new_value
    
    $(info VAR: $(VAR))
    

    在上述示例中,首先使用?=运算符进行赋值,将default_value赋给VAR。然后输出VAR的值,结果为default_value

    接着,使用:=运算符进行赋值,将new_value赋给VAR。再次输出VAR的值,结果为new_value

    在这个示例中,?=运算符只在变量未定义时执行赋值操作,因此它并不会覆盖已有的变量值。

    使用?=运算符可以用来设置默认值,确保变量在未定义时有一个合适的初始值。

    需要注意的是,如果变量已经定义了,并且你希望在重新赋值时覆盖原有的值,那么应该使用:==运算符,而不是?=运算符。

    2.4 += 追加赋值
    在Makefile中,+=运算符用于进行追加赋值。它的作用是将一个新的值追加到变量的末尾。

    当使用+=运算符进行赋值时,它会将右边的值追加到左边的变量的末尾。

    以下是一个示例:

    VAR := initial_value
    VAR += appended_value
    
    $(info VAR: $(VAR))
    

    在上述示例中,首先将VAR赋值为initial_value。然后使用+=运算符,将appended_value追加到VAR的末尾。最后输出VAR的值,结果为initial_value appended_value

    通过使用+=运算符,可以方便地将新的值追加到变量中,而不是覆盖原有的值。

    需要注意的是,如果变量之前未定义,那么+=运算符会与=运算符的行为相同,即创建一个新的变量并赋予相应的值。

    VAR += appended_value
    
    $(info VAR: $(VAR))
    

    在上述示例中,由于VAR之前未定义,+=运算符的行为等同于=运算符,创建了一个新的变量VAR并赋予值appended_value。最后输出VAR的值,结果为appended_value

    因此,在使用+=运算符之前,请确保变量已经定义。

    3.自动化变量

    在Makefile中,自动化变量(Automatic Variables)是一些特殊的变量,它们在规则(rule)的命令中具有特殊的含义,用于表示不同的上下文信息。当Makefile执行规则时,自动化变量会根据当前的规则上下文被展开为相应的值。

    以下是一些常用的自动化变量:

    • $@:表示规则的目标(target),即正在生成的文件的名称。
    • $

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论