什么是 Makefile,它是如何工作的?

使用此便捷的自动化工具更有效地运行和编译程序。
638 位读者喜欢这个。
Open Data Policy

Opensource.com

如果希望在某些文件更新时运行或更新任务,可以使用 make 实用程序。 make 实用程序需要一个文件 Makefile (或 makefile),它定义了要执行的任务集。 您可能使用 make 从源代码编译程序。 大多数开源项目使用 make 来编译最终可执行的二进制文件,然后可以使用 make install 安装该文件。

在本文中,我们将使用基本和高级示例来探讨 makeMakefile。 在开始之前,请确保 make 已安装在您的系统中。

基本示例

让我们首先在终端上打印经典的“Hello World”。 创建一个空目录 myproject,其中包含一个带有以下内容的 Makefile 文件

say_hello:
        echo "Hello World"

现在通过在目录 myproject 中键入 make 来运行该文件。 输出将是

$ make
echo "Hello World"
Hello World

在上面的例子中,say_hello 的行为类似于函数名,就像在任何编程语言中一样。 这被称为 *目标*。 *先决条件* 或 *依赖项* 遵循目标。 为了简单起见,我们在此示例中未定义任何先决条件。 命令 echo "Hello World" 称为 *配方*。 *配方* 使用 *先决条件* 来实现 *目标*。 目标、先决条件和配方一起构成 *规则*。

总而言之,以下是典型规则的语法

target: prerequisites
<TAB> recipe

例如,目标可能是依赖于先决条件(源文件)的二进制文件。 另一方面,先决条件也可以是依赖于其他依赖项的目标

final_target: sub_target final_target.c
	Recipe_to_create_final_target

sub_target: sub_target.c
	Recipe_to_create_sub_target

目标不一定是文件;它可以只是配方的名称,就像我们的例子一样。 我们称这些为“伪目标”。

回到上面的例子,当执行 make 时,整个命令 echo "Hello World" 会被显示,然后是实际的命令输出。 我们通常不希望这样。 要禁止回显实际命令,我们需要在 echo 前面加上 @

say_hello:
        @echo "Hello World"

现在再次尝试运行 make。 输出应该只显示这个

$ make
Hello World

让我们在 Makefile 中添加更多伪目标:generateclean

say_hello:
        @echo "Hello World"

generate:
	@echo "Creating empty text files..."
	touch file-{1..10}.txt

clean:
	@echo "Cleaning up..."
	rm *.txt

如果在更改后尝试运行 make,则只会执行目标 say_hello。 这是因为 makefile 中的第一个目标才是默认目标。 通常称为 *默认目标*,这就是您将在大多数项目中看到 all 作为第一个目标的原因。 all 的职责是调用其他目标。 我们可以使用一个名为 .DEFAULT_GOAL 的特殊伪目标来覆盖此行为。

让我们将其包含在 makefile 的开头

.DEFAULT_GOAL := generate

这将运行目标 generate 作为默认值

$ make
Creating empty text files...
touch file-{1..10}.txt

顾名思义,伪目标 .DEFAULT_GOAL 一次只能运行一个目标。 这就是为什么大多数 makefile 都包含 all 作为可以根据需要调用任意多个目标的目标。

让我们包含伪目标 all 并删除 .DEFAULT_GOAL

all: say_hello generate

say_hello:
	@echo "Hello World"

generate:
	@echo "Creating empty text files..."
	touch file-{1..10}.txt

clean:
	@echo "Cleaning up..."
	rm *.txt

在运行 make 之前,让我们包含另一个特殊的伪目标 .PHONY,我们在其中定义所有不是文件的目标。 无论是否已存在具有该名称的文件或其上次修改时间是什么,make 都会运行其配方。 这是完整的 makefile

.PHONY: all say_hello generate clean

all: say_hello generate

say_hello:
	@echo "Hello World"

generate:
	@echo "Creating empty text files..."
	touch file-{1..10}.txt

clean:
	@echo "Cleaning up..."
	rm *.txt

make 应该调用 say_hellogenerate

$ make
Hello World
Creating empty text files...
touch file-{1..10}.txt

一个好的做法是不在 all 中调用 clean 或将其作为第一个目标。 当需要清理时,应该手动调用 clean 作为 make 的第一个参数

$ make clean
Cleaning up...
rm *.txt

既然您已经了解了基本 makefile 的工作方式以及如何编写简单的 makefile,让我们来看一些更高级的示例。

高级示例

变量

在上面的示例中,大多数目标和先决条件值都是硬编码的,但在实际项目中,这些值会被变量和模式替换。

在 makefile 中定义变量的最简单方法是使用 = 运算符。 例如,将命令 gcc 分配给变量 CC

CC = gcc

这也称为 *递归扩展变量*,它在规则中像下面这样使用

hello: hello.c
    ${CC} hello.c -o hello

您可能已经猜到,当配方传递到终端时,它会像下面这样展开

gcc hello.c -o hello

${CC}$(CC) 都是调用 gcc 的有效引用。 但是,如果尝试将变量重新分配给自己,则会导致无限循环。 让我们验证一下

CC = gcc
CC = ${CC}

all:
    @echo ${CC}

运行 make 将导致

$ make
Makefile:8: *** Recursive variable 'CC' references itself (eventually).  Stop.

为避免这种情况,我们可以使用 := 运算符(这也称为 *简单扩展变量*)。 我们应该可以顺利运行下面的 makefile

CC := gcc
CC := ${CC}

all:
    @echo ${CC}

模式和函数

以下 makefile 可以通过使用变量、模式和函数来编译所有 C 程序。 让我们逐行探讨它

# Usage:
# make        # compile all binary
# make clean  # remove ALL binaries and objects

.PHONY = all clean

CC = gcc			# compiler to use

LINKERFLAG = -lm

SRCS := $(wildcard *.c)
BINS := $(SRCS:%.c=%)

all: ${BINS}

%: %.o
	@echo "Checking.."
	${CC} ${LINKERFLAG} $< -o $@

%.o: %.c
	@echo "Creating object.."
	${CC} -c $<

clean:
	@echo "Cleaning up..."
	rm -rvf *.o ${BINS}
  • # 开头的行是注释。

  • .PHONY = all clean 定义了伪目标 allclean

  • 变量 LINKERFLAG 定义了在配方中与 gcc 一起使用的标志。

  • SRCS := $(wildcard *.c)$(wildcard pattern) 是 *文件名函数* 之一。 在这种情况下,所有带有 .c 扩展名的文件都将存储在变量 SRCS 中。

  • BINS := $(SRCS:%.c=%):这被称为 *替换引用*。 在这种情况下,如果 SRCS 的值为 'foo.c bar.c',则 BINS 将具有 'foo bar'

  • all: ${BINS}:伪目标 all${BINS} 中的值作为单独的目标调用。

  • 规则

    %: %.o
      @echo "Checking.."
      ${CC} ${LINKERFLAG} $&lt; -o $@

    让我们看一个例子来理解这个规则。 假设 foo${BINS} 中的值之一。 那么 % 将匹配 foo (% 可以匹配任何目标名称)。 以下是规则的展开形式

    foo: foo.o
      @echo "Checking.."
      gcc -lm foo.o -o foo

    如图所示,%foo 替换。 $<foo.o 替换。 $< 模式匹配先决条件,而 $@ 匹配目标。 将为 ${BINS} 中的每个值调用此规则

  • 规则

    %.o: %.c
      @echo "Creating object.."
      ${CC} -c $&lt;

    前一个规则中的每个先决条件都被认为是此规则的目标。 以下是规则的展开形式

    foo.o: foo.c
      @echo "Creating object.."
      gcc -c foo.c
  • 最后,我们删除目标 clean 中的所有二进制文件和目标文件。

以下是上述 makefile 的重写,假设它位于具有单个文件 foo.c: 的目录中:

# Usage:
# make        # compile all binary
# make clean  # remove ALL binaries and objects

.PHONY = all clean

CC = gcc			# compiler to use

LINKERFLAG = -lm

SRCS := foo.c
BINS := foo

all: foo

foo: foo.o
	@echo "Checking.."
	gcc -lm foo.o -o foo

foo.o: foo.c
	@echo "Creating object.."
	gcc -c foo.c

clean:
	@echo "Cleaning up..."
	rm -rvf foo.o foo

有关 makefile 的更多信息,请参阅 GNU Make 手册,其中提供了完整的参考和示例。

您还可以阅读我们的 GNU Autotools 简介,了解如何自动生成编码项目的 makefile。

标签
psachin
Sachin 热衷于自由和开源软件。 他是 GNU Emacs 的忠实用户,喜欢谈论和撰写有关开源、GNU/Linux、Git 和 Python 的文章。 他以前曾在 OpenStack、ManageIQ/CloudForms 和 Red Hat Insights 上工作过。 他也喜欢在业余时间探索 Swift 对象存储。 您可以通过 IRC 与他联系,即 psachin@{Libera.Chat, Freenode, OFTC, gnome}。

8 条评论

嗨,

感谢本教程。 真的很有用,而且很容易理解。

有趣的文章。 它激励我玩转 Makefiles。

谢谢

非常感谢!

非常感谢...
非常好的文章...

谢谢!

回复 作者: Neeraj Sharma (未验证)

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© 2025 open-source.net.cn. All rights reserved.