如果希望在某些文件更新时运行或更新任务,可以使用 make
实用程序。 make
实用程序需要一个文件 Makefile
(或 makefile
),它定义了要执行的任务集。 您可能使用 make
从源代码编译程序。 大多数开源项目使用 make
来编译最终可执行的二进制文件,然后可以使用 make install
安装该文件。
在本文中,我们将使用基本和高级示例来探讨 make
和 Makefile
。 在开始之前,请确保 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
中添加更多伪目标:generate
和 clean
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_hello
和 generate
$ 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
定义了伪目标all
和clean
。 -
变量
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} $< -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 $<
前一个规则中的每个先决条件都被认为是此规则的目标。 以下是规则的展开形式
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。
8 条评论