专为GO而作的优秀Makefile(翻译篇)

本文分享为Go Web服务器精炼的Makefile,旨在简化构建与管理。介绍了其包含的环境变量、开发者模式、编译、启停服务器等功能,还说明了各功能的实现方式及作用,最后给出了Makefile最终版本和模板项目地址。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文出自:http://azer.bike/journal/a-good-makefile-for-go/#step-by-step

这是一份为GOweb服务器而精炼的Makefile,主要为了简化构建与管理。

我经常会调整我的Makefile来提升开发速度,而今天早上也是这其中的一次,并且我决定向大家分享结果。
总结一下就是,我使用go搭建web服务,我对makefile的期望如下:

  • 高水平且简单的命令,例如; compile start stop watch 等
  • 管理特定项目的环境变量,其中应包含.env文件
  • 开发者模式,这样每当有改动时,会自动编译
  • 在开发者模式下,编译的错误提示将不会有赘述
  • 特定项目的GOPATH,因此可以保持vender中的依赖
  • 简化文件的watch,如:make watch run=”go test ./…”

    以下是我个人比较喜欢的典型的文件目录:

    .env
    Makefile
    main.go
    bin/
    src/
    vendor

在这个文件中输入的make命令提供以下输出:

$ make
选择一个在my-web-server运行的命令
install 安装缺失的依赖,内部运行 ‘go get’ .
start 开始开发模式.当代码发生改变时自动启动.
stop 停止开发模式.
compile 二进制编译.
watch 当代码发生变化时,运行给定的命令,如:make watch run =”go test ./…”
exec 使用自定义GOPATH包装的给定命令,如:make exec run=”go test ./…”
clean 清楚构建时的文件,内部运行go clean命令.



1. 一步一个脚印

环境变量

首先,我们希望makefile中包含所有我们为工程定义的环境变量,因此,治理我我们的第一行#1:

include .env

在制定项目的环境变量之上,我们将定义其他一些东西,如项目名称,go文件/文件夹,pid文件路径…

PROJECTNAME=\$(shell basename "\$(PWD)")

# 与go相关的变量
GOBASE = \$(shell pwd)
GOPATH = \$(GOBASE)/vendor:\$(GOBASE)
GOBIN = \$(GOBASE)/bin
GOFILES=\$(wildcard *.go)

# 重定向错误输出文件,我们能在开发模式下查看
STDERR = /tmp/.\$(PROJECTNAME)-stderr.txt

# 当在开发模式下运行时,PID文件将存储服务进程id
PID = /tmp/.\$(PROJECTNAME)-api-server.pid

# make在linux中十分冗长,将其设为silent
MAKEFLAGS +=  --silent

在余下的makefile中,我们将着重使用特别GOPATH变量。所有我们使用的命令应该被包装在项目指定的GOPATH中,否则其将不能运行。这是我们的go项目之前有了明确的隔离,也带来了一些复杂性。为了使事情变得更加简单,我们可以添加exec命令,使用上面指定的GOPATH执行任何给定的命令。

//exec:执行包装在GOPATH中给定的命令,如 make exec run="go test ./..."
exec:
    @GOPATH=\$(GOPATH) GOBIN=\$(GOBIN) \$(run)

虽然这个并没有很高级。我们应该使用一些常见的命令来覆盖一些常见的情况,并且只有在我们需要执行一些没有被makefile涵盖的事时,再使用exec。

开发者模式

开发者模式应该做的事:

  • 清楚构建时的缓存
  • 编译代码
  • 后台运行服务
  • 当代码有变动时,重复以上所有步骤

这些听上去很简单,但是很快就会变复杂,因为我们将同时运行服务器和文件观察器。我们需要保证再开启一个新的进程前有合理的关闭,同时也不要破坏常见的命令行行为,例如在按下ctrl+c或者ctrl+d时停止。

start:
    bash -c "trap 'make stop' EXIT; $(MAKE) compile start-server watch run='make compile start-server'"
stop: stop-server

以上的代码解决了以下的一些问题:

  • 在后台编译和运行服务器
  • 由于主进程不在后台执行,因此我们可以使用ctrl+c来进行中断
  • 当主进程被中断时,后台进程也将停止。我们需要trap也主要是因为这个。
  • 代码发生改变时,重新编译并重启服务器

在以下的部分,我将详细解释这些命令。

编译

compile命令在后台处理的不仅仅是调用go compile;它也将清除错误输出并且打印简化版本。

以下是如何做到像是在命令行中做出的重大更改:

compile:
    @-touch $(STDEER)
    @-rm $(STDEER)
    @-$(MAKE) -s go-compile 2>$(STDEER)
        @cat $(STDEER) | sed -e 'ls/.*/\nError:\n' | sed 's/make\[.*/ /' | sed "/^/s/^/  /" 1&2

开始/停止 服务器

start-server 是运行它在后台编译而成的二进制文件,将其PID保存在一个临时文件中。stop-server在有需要时读取PID,并杀死该进程。

start-server:
    @echo " > $(PROJECTNAME) is available at $(ADDR)"
    @-$(GOBIN)/$(PROJECTNAME) 2>&1 &echo &&! > $(PID)
    @cat $(PID) | sed "/^/s/^/ \> PID: /"
stop-server:
    @-touch $(PID)
    @-kill `cat $(PID)` 2> /dev/null || true
    @-rm $(PID)

restart-server: stop-server start-server

监听变化

我们需要一个文件监听器来记录变化。我尝试了很多种但仍旧不是很满意,最终还是创建了我自己的文件监听器,yolo。在系统中安装yolo:

$ go get github.com/azer/yolo

一旦安装完成,我们基本可以监听项目文件中的变化,除了verder和bin文件夹。原因是:

## watch: 当代码发生变化时运行指定命令,如 make watch run = "echo hey"
watch:
    @yolo -i . -e vender -e bin -c $(run) 

现在我们获取到了一个watch命令,可以递归监项目目录中的变化,当然,除了vender目录。我们可以简单地传递任何我们想要执行的命令。如,start命令在代码改变时调用make compile start-server。

make watch run = "make compile start-server"

我们可以使用它来进行测试,或者自动检查这里是否有任何竞争条件(这个不太明白…)。。执行时会设置好环境变量,因此你完全不必担心GOPATH:

make watch run = "go test ./..."

Yolo中包含的一个好东西,那就是web接口。如果你启用它,你可以马上在web接口看到你命令行的输出结果,你所需要做的只是通过-a选项去开启:

yolo -i . -e vender -e bin -c "go run foobar.go" -a localhost:9001

然后你可以在浏览器中打开 localhost:9001 即时得查看结果。

安装依赖

当我们对代码进行修改时,我们希望在编译前下载好缺少的依赖包。install命令将会为我们做这个工作。

install: go-get

当代码发生改动时,我们将自动调用install(编译前),因此依赖会被自动安装。如果你希望手动去安装依赖,那么你可以运行:

make install get = "github.com/foo/bar"

在内部,这段代码会被转化为:

$ GOPATH=~/my-web-server GOBIN=~/my-web-server/bin go get github.com/foo/bar

那么,它是通过什么来工作的呢?请看下一节,我们如何添加GO命令,来实现更高级的命令。

GO命令

由于我们将GOPATH设置为项目目录,希望以此来简化依赖的管理,但这个在Go生态系统中尚未正式解决,因此我们需要将所有Go命令包装在Makefile中。

go-compile:go-clean go-get go-build

go-build:
    @echo " > Building binary..."
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go build -o $(GOBIN)/$(PROJECTNAME) $(GOFILES)

go-generate:
    @echo " > generating dependency files..."
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go generate $(generate)

go-get:
        @echo "  >  Checking if there is any missing dependencies..."
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go get $(get)

go-install:
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go install $(GOFILES)

go-clean:
    @echo "  >  Cleaning build cache"
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go clean

帮助

最后,我们需要一个帮助命令来查看全部的命令。我们可以使用sed和colum命令,自动生成格式优美的输出形式。见下方:

help: Makefile
    @echo " Choose a command run in "$(PROJECTNAME)":"
    @sed -n 's/^##//p' $< | column -t -s ':' |  sed -e 's/^/ /'

下面的命令将扫描makefile中以“##”开头的行,并且输出它们。因此,你可以简单地注释你所定义的命令,这些注释也将被help命令所用到。
如果我们像下面这样加入一些注释:

## install: Install missing dependencies. Runs `go get` internally.
install: go-get

## start: Start in development mode. Auto-starts when code changes.
start:

## stop: Stop development mode.
stop: stop-server

我们将得到:

 $  make help

 Choose a command run in my-web-server:

 install   Install missing dependencies. Runs `go get` internally.
 start     Start in development mode. Auto-starts when code changes.
 stop      Stop development mode.

makefile最终版本

根据以上分享,这使我总结之后的最终版本。今天早上我开始了一个新项目,而这就是我从其中拷贝而来:

include .env
PROJECTNAME = $(shell basename "$PWD")

# Go 相关变量
GOBASE = $(shell pwd)
GOPATH = $(GOBASE)/vendor:$(GOBASE)
GOBIN = $(GOBASE)/bin
GOFILES = (wildcard *.go)

# 将错误输出至指定文件,在开发者模式下可以对其观察
STDERR = /tmp/.$(PROJECTNAME)-stderr.txt

# PID文件将一直一直保存服务的进程ID
PID = /tmp/.$(PROJECTNAME).pid

# make在linux中很冗杂,将其设置为slient模式
MAKEFILES += --silent

## install: 安装缺少的依赖包。在内部将执行`go get`命令。例如:make install get = github.com/foo/bar
install: go-get

## start: 在开发者模式下运行,当代码发生改变时自动给运行。
start: @bash -c "trap 'make stop ' EXIT; $(MAKE) compile start-server watch run='make compile start server'"

## stop: 停止开发者模式
stop: stop-server

start-server: stop server
    @echo " > $(PROJECTNAME) is available at $(ADDR)"
    @-$(GOBIN)/$(PROJECTNAME) 2>&1 & echo $$! > $(PID)
    @cat $(PID) | sed "/^/s/^/ \> PID: /"

stop-server:
    @-touch $(PID)
    @-kill `cat $(PID)` 2> /dev/null || true
    @-rm $(PID)

## watch: 当代码发生改变时,运行指定命令。例如:make watch run = "echo 'hey'"
watch:
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) yolo -i . -e vender -e bin -c "$(run)"

restart-server: stop-server start-server     

## compile: 编译二进制文件
compile:
    @-touch $(STDERR)
    @-rm $(STDERR)
    @-$(MAKE) -s go-compile 2> $(STDERR)
    @cat $(STDERR) | sed -e '1s/.*/\nError:\n/'  | sed 's/make\[.*/ /' | sed "/^/s/^/     /" 1>&2

## exec: 运行包装在GOPATH下的指定命令。例如:make exec run="go test ./..."
exec:
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) $(run)
## 
##
## clean: 清除构建的文件,在内部执行`go clean` 
clean:
    @(MAKEFILE) go-clean

go-compile: go-clean go-get go-build


go-build:
    @echo "  >  Building binary..."
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go build -o $(GOBIN)/$(PROJECTNAME) $(GOFILES)

go-generate:
    @echo "  >  Generating dependency files..."
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go generate $(generate)

go-get:
    @echo "  >  Checking if there is any missing dependencies..."
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go get $(get)

go-install:
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go install $(GOFILES)

go-clean:
    @echo "  >  Cleaning build cache"
    @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go clean

.PHONY: help
all: help
help: Makefile
    @echo
    @echo " Choose a command run in "$(PROJECTNAME)":"
    @echo
    @sed -n 's/^##//p' $< | column -t -s ':' |  sed -e 's/^/ /'
    @echo

模板项目

我搭建了一个web服务,并使用了以上的makefile。你可以看一下这个项目,对其进行设置并可以尝试着运行。地址是:github.com/azer/go-makefile-example.

就是这样!如果你还有其他任何问题、想法或者一些建议能对这个进行优化,请email~

祝好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值