Makefile

如果读者经常泡在 GitHub 上浏览他人的 C 语言专案,应该很常会看到名为 Makefile 的档案。
本文会介绍 Make 这套建构工具以及撰写 Makefile 档案的技巧。

Make

Make 是一套自动化建构软件,它会读取名为 Makefile 的档案,并根据使用者输入的命令找到 target 後生成命令转交给 shell 去执行,以生成开发者需要的可执行档案。
不只如此, Make 还具有依赖关系的检查系统,可以帮助开发者高效的进行测试与编译工作。

Make 的历史

该软件最早由贝尔实验室的 Stuart Feldman 开发,随後也被各种 UNIX Like 的分支进行无数次的改良 (包含改良演算法与功能扩充),衍生出了多种版本:

  • BSD Make
  • GNU Make
  • Microsoft nmake

撰写 Makefile

要撰写 Makefile ,我们需要先建立对 TargetDependenciesCommands 的认知,让我们先看看基本的 Makefile 档案:

output: main.o
	gcc -o output main.o
main.o: main.c def.h
	gcc -c main.c

在这个 Makefile 中:

  • 有两个 Target ,分别是 output 以及 main.o
  • 在 Target 的右方,我们可以看到它的依赖,以 output 为例: 它的依赖为 main.o
    假设 output 这个 Target 被执行,Make 会去检查 main.o 是否存在或是否被修改,如果有的话便会先执行 main.o target 底下的命令产生 main.o,再执行 output 底下的命令产生 output 档案。
    至於要如何执行 Target 呢?我们可以这样做:
make output

或是:

make

如果不指定 target 的话,Make 会执行 Makefile 当中的第一个 Target 。

使用巨集

Make 也提供了巨集功能,可以帮助开发者写出更精简的 Makefile 档案。
以刚刚的 Makefile 为例,我们可以使用巨集对它稍作梳理:

OBJ = main.c def.h
output: main.o
	gcc -o output main.o
main.o: $(OBJ)
	gcc -c main.c

条件式

如果有撰写 C 程序的经验,想必都已经熟悉了前置处理器的使用。Make 让我们能够在 Makefile 中使用类似的条件式,以下内容取在 xv6-riscv 的 Makefile:

ifndef TOOLPREFIX
TOOLPREFIX := $(shell if riscv64-unknown-elf-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
	then echo 'riscv64-unknown-elf-'; \
	elif riscv64-linux-gnu-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
	then echo 'riscv64-linux-gnu-'; \
	elif riscv64-unknown-linux-gnu-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
	then echo 'riscv64-unknown-linux-gnu-'; \
	else echo "***" 1>&2; \
	echo "*** Error: Couldn't find a riscv64 version of GCC/binutils." 1>&2; \
	echo "*** To turn off this error, run 'gmake TOOLPREFIX= ...'." 1>&2; \
	echo "***" 1>&2; exit 1; fi)
endif

包含上面使用的 ifndef,Make 还提供了:

  • ifdef
  • ifndef
  • ifeq
  • ifneq

给开发者使用,使用这些条件式都必须在结尾处加上 endif

Shell Script

除了巨集功能,我们还可以将 Shell script 的条件式应用到 Makefile 中:

forfun:
    @if [ "test" = "test" ]; then\
        echo "Hello world";\
    fi

需要注意的有以下几点:

  1. 在 Shell script 中,使用 if 後需要接上 fi。
  2. 在上面的范例中, ;\ 都是必要的。

接着,我们可以尝试使用这个方法简化版本推送的流程:

push: 
    @if [ "x$(MSG)" = 'x' ]; then\
        echo "Commit message is necessary."; fi
    @test "x$(MSG)" != 'x'
    git commit -a -m "$(MSG)"
    git push origin master
  • @if 帮助我们判断 MSG 是否为空
  • @test 如果 MSG 不为空,继续执行,否则 make 会被暂停。
    改写脚本後,使用下列命令呼叫 push target:
MSG="init commit" make push

Reference


<<:  Day13 [实作] 把视讯及音讯内容录制下来

>>:  Day 15: Inspector 布建

[Day08] Dependency Injection Part2 - 依赖介面

依赖介面而不是特定的 Service 昨天我们介绍了怎麽在 .NET Web API 的专案里实现依...

Day35 | WebView元件开发 - Webpack打包工具整合地雷陷阱排除

大家好,今天继续来开发元件,并动手解决实务上我们遇到的设定配置的问题。在昨天的练习里,我们可以使用b...

Day29:【技术篇】初探打包工具的存在?

一、前言   先前有写过两篇关於 Webpack 的文章(文章1、文章2),回顾起自己学习到 Web...

[Golang]panic是什麽?-心智图总结

1. panic是什麽? 程序在运行时,发生意料之外的程序异常。例如: 访问,不存在的array。 ...

Day 38 - 在 AWS Lambda 中使用 YOLO 推估 (Inference)

Day 38 - 在 AWS Lambda 中使用 YOLO 推估 (Inference) 在 Da...