用 Makefile 构建 go 程序

基本命令

基本规则

Makefile 基本规则如下:

<target>: <prerequisites>
[tab]   <commands>

: 前面的部分成为目标,我们通常会指定特定的目标进行特定的任务。如:build:clean:install:等,通过 make buildmake cleanmake install命令执行指定目标。

直接运行 make 命令且不带目标,Makefile 默认执行第一个目标。所以我们通常将 all: 目标作为约定放在第一位,用于编译整个程序。

第二行的命令部分前面必须用 tab 开头,否则会出错。
而 commands 中可以直接执行 shell 命令。

所以可以简单执行以下命令:

hello:
    echo hello world

执行 make hello 就会看到:

echo hello world
hello world

会发现执行的命令也会输出,如果不想看到命令本身,只要在前面加一个 @ 符号就可以了。

hello:
    @echo hello world

伪目标

编写 C语言程序时我们经常会清理 .o 目标文件、或者其他临时文件。这时我们通常会写:

clean:
    rm *.o temp

但是,如果当前目录下已经存在一个叫 clean 的文件,那么这个目标不会执行。因为 Makefile 会认为 clean 这个文件已经存在路,不需要重新构建。

为了避免这种情况发生,可以声明 clean 为"伪命令。

.PHONY: clean
clean:
    rm *.o temp

命令

每行命令之前必须有一个tab键。如果想用其他键,可以用内置变量.RECIPEPREFIX声明。

.RECIPEPREFIX = >
all:
> echo Hello, world

需要注意的是,每行命令在一个单独的shell中执行。这些Shell之间没有继承关系。

所以为了继承上一个命令可以将:

  1. 两个命令卸载一行并用 ; 隔开。
  2. 在换行符前面加一个反斜杠 \
  3. .ONESHELL: 命令。

变量与控制

Makefile 还支持变量、条件判断和循环。

txt = Hello World
test:
    @echo $(txt)
LIST = one two three
all:
    for i in $(LIST); do \
        echo $$i; \
    done

# 等同于

all:
    for i in one two three; do \
        echo $i; \
    done
ifeq ($(CC),gcc)
  libs=$(libs_for_gcc)
else
  libs=$(normal_libs)
endif

还有一些自动变量:

$*   不包含扩展名的目标文件名称。
$+   所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。
$<   第一个依赖文件的名称。
$?   所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。
$@   目标的完整名称。
$^   所有的依赖文件,以空格分开,不包含重复的依赖文件。
$% 如果目标是归档成员,则该变量表示目标的归档成员名称。

用 Makefile 构建 go 程序

以下是一个 go 程序的 Makefile 可以作为参考

.PHONY: all clean test docker latest init

export CGO_ENABLED=0
export GOOS=linux
export GOARCH=amd64

BINARY=go-realworld-clean
VERSION=$(shell git describe --abbrev=0 --tags 2> /dev/null || echo "0.1.0")
BUILD=$(shell git rev-parse HEAD 2> /dev/null || echo "undefined")
LDFLAGS=-ldflags "-X main.Version=$(VERSION) -X main.Build=$(BUILD)"

all:
	go build -o $(BINARY) $(LDFLAGS)

init:
	git config core.hooksPath .githooks

docker:
	docker build \
		-t $(BINARY):latest \
		-t $(BINARY):$(VERSION) \
		--build-arg build=$(BUILD) --build-arg version=$(VERSION) \
		-f Dockerfile --no-cache .

latest:
	docker build \
		-t $(BINARY):latest \
		--build-arg build=$(BUILD) --build-arg version=$(VERSION) \
		-f Dockerfile --no-cache .

test:
	go test ./...

clean:
	if [ -f $(BINARY) ] ; then rm $(BINARY) ; fi