什么是 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 条评论

您好,

感谢您的教程。 非常有用且易于理解。

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

谢谢您

非常感谢!

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

谢谢!

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

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.