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

2019/08/19 工具

如果想要在某些文件更新时执行或更新任务,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 类似于其它编程语言中的函数名。这叫做 targetprerequisitesdependencies 就在 target 之后。为了简单起见,在该例中我们没有定义 prerequisites。命令 echo “Hello World” 被称为 reciperecipe 使用 prerequisites 来创建一个 targettargetprerequisitesrecipes 一起组成了 rule

总而言之,下面是一个典型的 rule 语法:

target: prerequisites
<TAB> recipe

例如,一个依赖 prerequisites(源码)的二进制 target 。另一方面,一个 prerequisite 也可以是一个依赖其它 dependenciestarget

final_target: sub_target final_target.c
    Recipe_to_create_final_target
    
sub_target: sub_target.c
    Recipe_to_create_sub_target

target 并不非得是一个文件,它可以仅仅是 recipe 的一个名字,正如我们的示例一样。我们称这种为 phony targets(注:伪目标

回到我们上面的示例,当make执行后,完整的命令 echo “Hello World” 就会紧接着实际的命令后显示。我们通常不希望这样。为了抑制实际的命令,需要在 echo 之前添加 @

say_hello:
    @echo "Hello World"

现在尝试再次运行make。输出应该只显示如下内容:

$ make
Hello World

让我们添加更多 phony targetMakefile中: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中只有第一个 target 是默认的 target。通常这被称为 default goal,这就是你会在大部分项目都看到 all 作为第一个 target 的原因。all 的责任就是调用其它 target。我们也可以通过 phony target .DEFAULT_GOAL 来修改这个默认行为。

让我们在makefile文件开头加上以下内容:

.DEFAULT_GOAL := generate

这样在运行时就会以 generate 作为默认 target

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

正如名字暗示的意思一样,phony target .DEFAULT_GOAL 一次只能运行一个 target。这就是大部分makefile都需要包含 all 作为调用其它 targettarget 的原因。

让我们使用 phony target 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 target.PHONY,这是用来定义所有非文件的 targetmake在运行它的 recipe 时就不用管它是不是文件或者它上次的修改时间了。这是一个完整的makefile

.PHONY: all say_hello generate clean

all: say_hello generate

say_hello:
    @echo "Hello World"
    
generate:
    @echo "Creating empty text files..."
   
clean:
    @echo "Cleaning up..."
    rm *.txt

make会调用 say_hellogenerate

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

all 中不调用 clean 或者不把 clean 放在第一个 target 中是一个很好的做法。clean 应该手动调用,在调用时作为make的第一个参数:

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

现在你应该了解一个基本的makefile是如何工作以及如何写了个简单的makefile了,接下来看了下相对高级的示例。

高级示例

变量

在以上的示例中,大部分 targetprerequisite 值都是硬代码,但在现实的项目中,这些应该用变量和模式来代替。

makefile中定义一个变量,最简单的办法是使用=操作符。例如,把gcc命令赋值给变量CC

CC = gcc

这也叫 recursive expanded variable,它在规则中的用法如下:

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

可能你已经猜到了,recipe 传到终端之后展开如下:

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.

为了避免这种情况,我们可以使用:=操作符(这也叫做 simply expanded variable)。执行以下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 定义了 phony targets allclean
  • 变量 LINKERFLAG 定义了 recipe gcc 的标志。
  • SRCS := $(wildcard *.c): $(wildcard pattern)functions for filenames。在该例中,所有扩展为 .c 的文件都保存到变量 SRCS 中。
  • BINS := $(SRCS:%.c=%): 这叫 substitution reference。在该例中,如果 SRCS 存在值 foo.c bar.c,则 BINS 则为 foo bar
  • all: ${BINS}: phony target alltarget 的方式分别调用 ${BIN} 的值。
  • 规则:

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

    让我们来理解一下这个规则。假设 foo${BINS} 中的一个值,则 % 可以匹配 foo% 可以匹配任何 target 名称)。以下是规则的展开形式:

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

    如上所示,%foo 替换了,$<foo.o 替换了。$< 是匹配 prerequisites 的模式,$@ 匹配了 target。这个规则会被 ${BINS} 中的每一个值调用。

  • 规则:

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

    之前规则中的每个 prerequisite 在这条规则下都被认为是 target。以下是规则展开的形式:

    foo.o: foo.c
        @echo "Creating object.."
        gcc -c foo.c
    
  • 最后,我们用 clean target 把所有的二进制和对象文件删除。

下面重写一下以上的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.c
    @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 manual,这里有完整的手册和示例。

原文:https://opensource.com/article/18/8/what-how-makefile

Search

    Table of Contents