Makefile – 快速指南

Makefile – 快速指南


为什么是生成文件?

编译源代码文件可能很累人,尤其是当您必须包含多个源文件并在每次需要编译时键入编译命令时。Makefile 是简化此任务的解决方案。

Makefile 是特殊格式的文件,可帮助自动构建和管理项目。

例如,假设我们有以下源文件。

  • 主程序
  • 你好.cpp
  • 阶乘文件
  • 函数.h

主程序

以下是 main.cpp 源文件的代码 –

#include <iostream>

using namespace std;

#include "functions.h"

int main(){
   print_hello();
   cout << endl;
   cout << "The factorial of 5 is " << factorial(5) << endl;
   return 0;
}

你好.cpp

下面给出的代码用于 hello.cpp 源文件 –

#include <iostream>

using namespace std;

#include "functions.h"

void print_hello(){
   cout << "Hello World!";
}

阶乘文件

factorial.cpp 的代码如下 –

#include "functions.h"

int factorial(int n){
   
   if(n!=1){
      return(n * factorial(n-1));
   } else return 1;
}

函数.h

以下是 fnctions.h 的代码 –

void print_hello();
int factorial(int n);

编译文件并获取可执行文件的简单方法是运行以下命令 –

gcc  main.cpp hello.cpp factorial.cpp -o hello

此命令生成hello二进制文件。在这个例子中,我们只有四个文件,而且我们知道函数调用的顺序。因此,输入上述命令并准备最终的二进制文件是可行的。

但是,对于拥有数千个源代码文件的大型项目,维护二进制构建变得困难。

化妆命令允许你管理大型程序或程序组。当您开始编写大型程序时,您会注意到重新编译大型程序比重新编译短程序需要更长的时间。此外,您会注意到您通常只处理程序的一小部分(例如单个函数),而其余程序的大部分都没有改变。

在接下来的部分中,我们将看到如何为我们的项目准备 makefile。

Makefile – 宏

化妆程序允许您使用宏,这是类似的变量。宏在 Makefile 中定义为 = 对。下面显示了一个示例 –

MACROS  = -me
PSROFF  = groff -Tps
DITROFF = groff -Tdvi
CFLAGS  = -O -systype bsd43
LIBS    = "-lncurses -lm -lsdl"
MYFACE  = ":*)"

特殊宏

在目标规则集中发出任何命令之前,预定义了某些特殊的宏 –

  • $@ 是要制作的文件的名称。

  • $? 是变更后家属的姓名。

例如,我们可以使用如下规则 –

hello: main.cpp hello.cpp factorial.cpp
   $(CC) $(CFLAGS) $? $(LDFLAGS) -o $@

Alternatively:

hello: main.cpp hello.cpp factorial.cpp
   $(CC) $(CFLAGS) [email protected] $(LDFLAGS) -o $@

在这个例子中,$@ 代表hello和 $? [email protected] 选取所有更改的源文件。

隐式规则中使用了两个更特殊的宏。他们是 –

  • $< 导致操作的相关文件的名称。

  • $* 目标文件和相关文件共享的前缀。

常见的隐式规则用于从 .cpp(源文件)构建 .o(对象)文件。

.cpp.o:
   $(CC) $(CFLAGS) -c $<

Alternatively:

.cpp.o:
   $(CC) $(CFLAGS) -c $*.c

常规宏

有各种默认宏。您可以通过键入“make -p”打印出默认值来查看它们。从它们的使用规则来看,大多数都非常明显。

这些预定义的变量,即隐式规则中使用的宏分为两类。它们如下 –

  • 作为程序名称的宏(例如 CC)

  • 包含程序参数的宏(例如 CFLAGS)。

下面是在 makefile 的内置规则中用作程序名称的一些常用变量的表格 –

Sr.No 变量和描述
1

AR

档案维护程序;默认为“ar”。

2

AS

编译汇编文件的程序;默认是`as’。

3

CC

编译C程序的程序;默认为“cc”。

4

CO

从 RCS 中检出文件的程序;默认为“co”。

5

CXX

编译C++程序的程序;默认是`g++’。

6

CPP

运行 C 预处理器的程序,并将结果输出到标准输出;默认为“$(CC) -E”。

7

FC

编译或预处理 Fortran 和 Ratfor 程序的程序;默认为‘f77’。

8

GET

从 SCCS 中提取文件的程序;默认为“获取”。

9

LEX

用于将 Lex 语法转换为源代码的程序;默认是`lex’。

10

YACC

用于将 Yacc 语法转换为源代码的程序;默认是`yacc’。

11

LINT

用于在源代码上运行 lint 的程序;默认是`lint’。

12

M2C

用于编译 Modula-2 源代码的程序;默认是‘m2c’。

13

PC

用于编译 Pascal 程序的程序;默认为“pc”。

14

MAKEINFO

将 Texinfo 源文件转换为 Info 文件的程序;默认是`makeinfo’。

15

TEX

从 TeX 源制作 TeX dvi 文件的程序;默认是`tex’。

16

TEXI2DVI

从 Texinfo 源制作 TeX dvi 文件的程序;默认是`texi2dvi’。

17

WEAVE

将 Web 翻译成 TeX 的程序;默认为“编织”。

18

CWEAVE

将 C Web 翻译成 TeX 的程序;默认是`cweave’。

19

TANGLE

将 Web 翻译成 Pascal 的程序;默认为“缠结”。

20

CTANGLE

将 C Web 翻译成 C 的程序;默认是`ctangle’。

21

RM

删除文件的命令;默认是`rm -f’。

这是一个变量表,其值是上述程序的附加参数。除非另有说明,否则所有这些的默认值都是空字符串。

Sr.No. 变量和描述
1

ARFLAGS

给存档维护程序的标志;默认为“rv”。

2

ASFLAGS

在“.s”或“.S”文件上显式调用时提供给汇编器的额外标志。

3

CFLAGS

提供给 C 编译器的额外标志。

4

CXXFLAGS

提供给 C 编译器的额外标志。

5

COFLAGS

提供给 RCS 合作程序的额外标志。

6

CPPFLAGS

提供给使用它的 C 预处理器和程序(例如 C 和 Fortran 编译器)的额外标志。

7

FFLAGS

提供给 Fortran 编译器的额外标志。

8

GFLAGS

提供给 SCCS get 程序的额外标志。

9

LDFLAGS

当编译器应该调用链接器,`ld’ 时提供给编译器的额外标志。

10

LFLAGS

给 Lex 的额外标志。

11

YFLAGS

给 Yacc 的额外标志。

12

PFLAGS

提供给 Pascal 编译器的额外标志。

13

RFLAGS

为 Ratfor 程序提供给 Fortran 编译器的额外标志。

14

LINTFLAGS

给 lint 的额外标志。

注意– 您可以使用“-R”或“–no-builtin-variables”选项取消隐式规则使用的所有变量。

您还可以在命令行定义宏,如下所示 –

make CPP = /home/courses/cop4530/spring02

在 Makefile 中定义依赖关系

最终的二进制文件依赖于各种源代码和源头文件是很常见的。因为他们让依赖关系是重要的化妆知道关于源的任何目标。考虑以下示例 –

hello: main.o factorial.o hello.o
   $(CC) main.o factorial.o hello.o -o hello

在这里,我们告诉make hello 依赖于 main.o、factorial.o 和 hello.o 文件。因此,只要这些目标文件中的任何一个发生更改,make就会采取行动。

同时,我们需要告诉make如何准备.o文件。因此,我们还需要如下定义这些依赖项 –

main.o: main.cpp functions.h
   $(CC) -c main.cpp

factorial.o: factorial.cpp functions.h
   $(CC) -c factorial.cpp

hello.o: hello.cpp functions.h
   $(CC) -c hello.cpp

在 Makefile 中定义规则

我们现在将学习 Makefile 的规则。

Makefile 目标规则的一般语法是 –

target [target...] : [dependent ....]
[ command ...]

在上面的代码中,括号中的参数是可选的,省略号表示一个或多个。在这里,请注意每个命令前面的选项卡是必需的。

下面给出了一个简单的示例,您可以在其中定义一个规则,以从其他三个文件中创建目标 hello。

hello: main.o factorial.o hello.o
   $(CC) main.o factorial.o hello.o -o hello

注意– 在此示例中,您必须给出规则以从源文件制作所有目标文件。

语义非常简单。当您说“make target”时,make 会找到适用的目标规则;并且,如果任何依赖项比目标更新,则make 一次执行一个命令(在宏替换之后)。如果必须创建任何依赖项,则首先发生(因此您有递归)。

如果任何命令返回失败状态,则Make终止。在这种情况下将显示以下规则 –

clean:
   -rm *.o *~ core paper

Make忽略以破折号开头的命令行上的返回状态。例如,谁在乎没有核心文件?

Make回显命令,在宏替换后显示发生了什么。有时您可能想关闭它。例如 –

install:
   @echo You must be root to install

人们已经开始期待 Makefile 中的某些目标。您应该始终先浏览。但是,期望找到所有(或仅 make)、安装和清理目标是合理的。

  • make all – 它编译所有内容,以便您可以在安装应用程序之前进行本地测试。

  • make install – 它在正确的位置安装应用程序。

  • make clean – 它清理应用程序,清除可执行文件、任何临时文件、目标文件等。

Makefile 隐式规则

该命令应该适用于我们从源代码 x.cpp 构建可执行文件 x 的所有情况。这可以说是一个隐含的规则 –

.cpp:
   $(CC) $(CFLAGS) [email protected] $(LDFLAGS) -o $@

这个隐含规则说明了如何从 xc 中生成 x —— 在 xc 上运行 cc 并调用输出 x。该规则是隐含的,因为没有提到特定的目标。它可以在所有情况下使用。

另一个常见的隐式规则是从 .cpp(源文件)构建 .o(对象)文件。

.cpp.o:
   $(CC) $(CFLAGS) -c $<

alternatively

.cpp.o:
   $(CC) $(CFLAGS) -c $*.cpp

在 Makefile 中定义自定义后缀规则

Make可以自动创建ao文件,在对应的.c文件上使用cc -c。这些规则是内置在make 中的,您可以利用这个优势来缩短您的 Makefile。如果在当前目标所依赖的 Makefile 的依赖项行中仅指示 .h 文件,则make将知道相应的 .cfile 已经是必需的。您不必包含编译器的命令。

这进一步减少了 Makefile,如下所示 –

OBJECTS = main.o hello.o factorial.o
hello: $(OBJECTS)
   cc $(OBJECTS) -o hello
hellp.o: functions.h

main.o: functions.h 
factorial.o: functions.h 

Make使用一个名为.SUFFIXES的特殊目标来允许您定义自己的后缀。例如,参考下面给出的依赖线 –

.SUFFIXES: .foo .bar

它通知make您将使用这些特殊后缀来制定自己的规则。

类似于make已经知道如何.c文件制作.o文件,您可以通过以下方式定义规则 –

.foo.bar:
   tr '[A-Z][a-z]' '[N-Z][A-M][n-z][a-m]' < $< > $@
.c.o:
   $(CC) $(CFLAGS) -c $<

第一条规则允许您.foo文件创建.bar文件。它基本上打乱了文件。第二条规则是make用来.c文件创建.o文件的默认规则

Makefile – 指令

有多种形式的可用指令。您系统上make程序可能不支持所有指令。因此,请检查您的make 是否支持我们在此处解释的指令。GNU make支持这些指令。

条件指令

条件指令是 –

  • IFEQ指令开始的条件,并指定条件。它包含两个参数,用逗号分隔并用括号括起来。对两个参数执行变量替换,然后比较它们。如果两个参数匹配,则遵循 ifeq 之后的 makefile 行;否则它们将被忽略。

  • ifneq指令开始的条件,并指定条件。它包含两个参数,用逗号分隔并用括号括起来。对两个参数执行变量替换,然后比较它们。如果两个参数不匹配,则遵循 ifneq 之后的 makefile 行;否则它们将被忽略。

  • ifdef的指令开始的条件,并指定条件。它包含单个参数。如果给定的参数为真,则条件变为真。

  • IFNDEF指令开始的条件,并指定条件。它包含单个参数。如果给定的参数为假,则条件变为真。

  • 其他指令导致如果以前有条件失败以下行被遵守。在上面的示例中,这意味着只要不使用第一个替代链接命令,就会使用第二个替代链接命令。在条件中使用 else 是可选的。

  • ENDIF指令结束的条件。每个条件都必须以 endif 结尾。

条件指令的语法

没有其他条件的简单条件的语法如下 –

conditional-directive
   text-if-true
endif

text-if-true 可以是任何文本行,如果条件为真,则将其视为 makefile 的一部分。如果条件为假,则不使用任何文本。

复杂条件的语法如下 –

conditional-directive
   text-if-true
else
   text-if-false
endif

如果条件为真,则使用 text-if-true;否则,使用 text-if-false。text-if-false 可以是任意数量的文本行。

无论条件是简单还是复杂,条件指令的语法都是相同的。有四种不同的指令可以测试各种条件。它们是给定的 –

ifeq (arg1, arg2)
ifeq 'arg1' 'arg2'
ifeq "arg1" "arg2"
ifeq "arg1" 'arg2'
ifeq 'arg1' "arg2" 

上述条件的相反指令如下 –

ifneq (arg1, arg2)
ifneq 'arg1' 'arg2'
ifneq "arg1" "arg2"
ifneq "arg1" 'arg2'
ifneq 'arg1' "arg2" 

条件指令示例

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
   $(CC) -o foo $(objects) $(libs_for_gcc)
else
   $(CC) -o foo $(objects) $(normal_libs)
endif

包含指令

include指令允许化妆暂停读取当前的makefile并继续之前读取一个或多个其他的makefile。该指令是 makefile 中的一行,如下所示 –

include filenames...

文件名可以包含 shell 文件名模式。在行的开头允许和忽略额外的空格,但不允许使用制表符。例如,如果您有三个 `.mk’ 文件,即 `a.mk’、`b.mk’ 和 `c.mk’,以及 $(bar) 那么它会扩展为 bish bash,然后是以下内容表达。

include foo *.mk $(bar)

is equivalent to:

include foo a.mk b.mk c.mk bish bash

make处理一个包含指令时,它会暂停读取 makefile 并依次从每个列出的文件中读取。完成后,make继续读取出现该指令的 makefile。

覆盖指令

如果使用命令参数设置了变量,则忽略 makefile 中的普通赋值。如果你想在 makefile 中设置变量,即使它是用命令参数设置的,你可以使用 override 指令,这是一行看起来如下 –

override variable = value

or

override variable := value

Makefile – 重新编译

补充程序是一种智能工具和工程基础上在源文件中,你做的修改。如果你有四个文件 main.cpp、hello.cpp、factorial.cpp 和 functions.h,那么剩下的所有文件都依赖于 functions.h,而 main.cpp 依赖于 hello.cpp 和 factorial.cpp。因此,如果您在 functions.h 中进行任何更改,那么make 将重新编译所有源文件以生成新的目标文件。但是,如果您在 main.cpp 中进行任何更改,因为它不依赖于任何其他文件,那么只会重新编译 main.cpp 文件,而不会重新编译 help.cpp 和 factorial.cpp。

在编译文件时,make检查其目标文件并比较时间戳。如果源文件的时间戳比目标文件更新,那么它会生成新的目标文件,假设源文件已更改。

避免重新编译

可能有一个由数千个文件组成的项目。有时您可能已经更改了一个源文件,但您可能不想重新编译所有依赖于它的文件。例如,假设您将宏或声明添加到其他文件所依赖的头文件中。保守地说,make假设头文件中的任何更改都需要重新编译所有相关文件,但您知道它们不需要重新编译,您宁愿不要浪费时间等待它们编译。

如果您在更改头文件之前就预料到了问题,您可以使用 `-t’ 标志。该标志告诉make不要运行规则中的命令,而是通过更改其上次修改日期来将目标标记为最新。您需要遵循此程序 –

  • 使用命令`make’重新编译真正需要重新编译的源文件。

  • 在头文件中进行更改。

  • 使用命令 `make -t’ 将所有目标文件标记为最新。下次运行 make 时,头文件中的更改不会导致任何重新编译。

如果在某些文件确实需要重新编译时已经更改了头文件,那么再这样做就太迟了。相反,您可以使用 `-o file’ 标志,它将指定的文件标记为“旧”。这意味着,文件本身不会被重新制作,并且不会以其帐户重新制作任何其他内容。您需要遵循此程序 –

  • 使用`make -o header file’重新编译由于独立于特定头文件的原因需要编译的源文件。如果涉及多个头文件,请为每个头文件使用单独的“-o”选项。

  • 用`make -t’ 更新所有的目标文件。

Makefile – 其他功能

在本章中,我们将研究 Makefile 的各种其他特性。

Make 的递归使用

递归使用make意味着在 makefile 中使用make作为命令。当您需要为组成更大系统的各种子系统提供单独的 makefile 时,此技术很有用。例如,假设您有一个名为“subdir”的子目录,它有自己的 makefile,并且您希望包含目录的 makefile在子目录上运行make您可以通过编写以下代码来实现 –

subsystem:
   cd subdir && $(MAKE)

or, equivalently:
 	
subsystem:
   $(MAKE) -C subdir

只需复制此示例即可编写递归make命令。但是,您需要了解它们的工作原理和原因,以及子品牌与顶级品牌之间的关系。

将变量传递给子制作

顶层make 的变量值可以通过显式请求通过环境传递给子 make。这些变量在 sub-make 中定义为默认值。除非您使用`-e’ 开关,否则您不能覆盖子make makefile 使用的makefile 中指定的内容。

要传递或导出变量,make将变量及其值添加到运行每个命令的环境中。反过来,子 make 使用环境来初始化其变量值表。

特殊变量 SHELL 和 MAKEFLAGS 总是被导出(除非你取消导出它们)。如果您将其设置为任何内容,则会导出 MAKEFILES。

如果要将特定变量导出到子制作,请使用导出指令,如下所示 –

export variable ...

如果要防止导出变量,请使用 unexport 指令,如下所示 –

unexport variable ...

变量 MAKEFILES

如果定义了环境变量 MAKEFILES,则make将其值视为要在其他 makefile 之前读取的其他 makefile 的名称列表(由空格分隔)。这很像 include 指令:在各种目录中搜索这些文件。

MAKEFILES 的主要用途是在make 的递归调用之间进行通信

包含来自不同目录的头文件

如果你把头文件放在不同的目录下,并且你在不同的目录下运行make,那么需要提供头文件的路径。这可以使用 makefile 中的 -I 选项来完成。假设functions.h文件在/home/tutorialspoint/header文件夹中可用,其余文件在/home/tutorialspoint/src/文件夹中可用,那么makefile将如下编写:

INCLUDES = -I "/home/tutorialspoint/header"
CC = gcc
LIBS =  -lm
CFLAGS = -g -Wall
OBJ =  main.o factorial.o hello.o

hello: ${OBJ}
   ${CC} ${CFLAGS} ${INCLUDES} -o $@ ${OBJS} ${LIBS}
.cpp.o:
   ${CC} ${CFLAGS} ${INCLUDES} -c $<

将更多文本附加到变量

向已定义的变量值中添加更多文本通常很有用。您可以使用包含“+=”的行来执行此操作,如图所示 –

objects += another.o

它获取变量对象的值,并向其添加文本“another.o”,前面有一个空格,如下所示。

objects = main.o hello.o factorial.o
objects += another.o

上面的代码将对象设置为`main.o hello.o factorial.o another.o’。

使用 `+=’ 类似于:

objects = main.o hello.o factorial.o
objects := $(objects) another.o

Makefile 中的续行

如果您不喜欢 Makefile 中太大的行,那么您可以使用反斜杠“\”断开您的行,如下所示 –

OBJ =  main.o factorial.o \
   hello.o

is equivalent to

OBJ =  main.o factorial.o hello.o

从命令提示符运行 Makefile

如果您已经准备好名为“Makefile”的 Makefile,那么只需在命令提示符下编写 make,它就会运行 Makefile 文件。但是,如果您为 Makefile 指定了任何其他名称,请使用以下命令 –

make -f your-makefile-name

Makefile – 示例

这是用于编译 hello 程序的 Makefile 示例。该程序由三个文件main.cppfactorial.cpphello.cpp 组成

# Define required macros here
SHELL = /bin/sh

OBJS =  main.o factorial.o hello.o
CFLAG = -Wall -g
CC = gcc
INCLUDE =
LIBS = -lm

hello:${OBJ}
   ${CC} ${CFLAGS} ${INCLUDES} -o $@ ${OBJS} ${LIBS}

clean:
   -rm -f *.o core *.core

.cpp.o:
   ${CC} ${CFLAGS} ${INCLUDES} -c $<

现在您可以使用make构建您的程序hello如果您将发出命令make clean则它会删除当前目录中可用的所有目标文件和核心文件。

觉得文章有用?

点个广告表达一下你的爱意吧 !😁