在本教程中,我们将研究Go开发人员如何使用Makefile开发自己的应用程序。
什么是Makefile?
Makefile是非常有用的自动化工具,不仅可以在Go上运行,而且可以在大多数其他编程语言中运行和构建应用程序。
通常可以在Github和Gitlab上许多Go应用程序的根目录中看到它。 它广泛用作开发人员经常执行的自动化任务的工具。
如果使用Go创建Web服务,则Makefile将帮助解决以下任务:
- 自动调用简单命令,例如:编译,启动,停止,监视等。
- 管理特定于项目的环境变量。 它应包含.env文件。
- 一种开发模式,可根据更改自动编译。
- 显示编译错误的开发模式。
- 为特定项目定义GOPATH,以便我们可以将依赖项存储在vendor文件夹中。
- 简化的文件监视,例如,使watch run =“ go test。 / ...”
这是项目的典型目录结构:
.env Makefile main.go bin/ src/ vendor/
如果在此目录中调用make命令,则会得到以下输出:
$ make 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. compile Compile the binary. watch Run given command when code changes. eg; make watch run="go test ./..." exec Run given command, wrapped with custom GOPATH. eg; make exec run="go test ./..." clean Clean build files. Runs `go clean` internally.
环境变量
我们需要从Makefile中获得的第一件事是包括我们为项目定义的环境变量。 因此,第一行将如下所示:
include .env
接下来,我们定义项目名称,转到文件夹/文件,PID的路径...
PROJECTNAME=$(shell basename "$(PWD)")
在Makefile的其余部分,我们将经常使用GOPATH变量。 我们所有的团队都必须与特定项目的GOPATH相关联,否则它们将无法工作。 这样可以完全隔离我们的项目,但同时会使工作复杂化。 为了简化任务,我们可以添加一个exec命令,该命令将使用GOPATH执行任何命令。
但是,值得记住的是,仅当您要执行makefile中无法编写的操作时才需要使用exec。
开发模式
开发模式应:
- 清除构建缓存
- 编译代码
- 在后台运行服务
- 代码更改时,请重复这些步骤。
听起来很简单。 但是,困难在于我们同时运行服务和文件监视程序。 在开始新进程之前,我们必须确保其正确停止,并且在按Control-C或Control-D时不违反命令行的通常行为。
start: bash -c "trap 'make stop' EXIT; $(MAKE) compile start-server watch run='make compile start-server'" stop: stop-server
上面描述的代码解决了以下任务:
- 在后台编译并运行服务。
- 主进程不在后台运行,因此我们可以使用Control-C中断它。
- 当主进程被中断时,停止后台进程。 仅为此需要陷阱。
- 代码更改后,重新编译并重新启动服务器。
在以下各节中,我将更详细地说明这些命令。
合编
compile命令不仅会在后台调用go 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
服务器启动/停止
启动服务器启动在后台编译的二进制文件,并将其PID保存到临时文件中。 停止服务器读取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 。 使用以下命令安装它:
$ go get github.com/azer/yolo
安装后,我们可以观察到项目目录中的更改,供应商和bin文件夹除外。
现在,我们有了一个watch命令,该命令以递归方式跟踪对项目目录的更改,但供应商目录除外。 我们可以传递任何命令来运行。
例如,当代码更改时,开始调用make-start-server:
make watch run="make compile start-server"
我们可以使用它来运行测试或自动检查比赛条件。 环境变量将在运行时设置,因此您不必担心GOPATH:
make watch run="go test ./..."
Yolo的一个不错的功能是它的Web界面。 如果启用它,则可以立即在Web界面中看到命令的输出。 您需要做的就是传递-a选项:
yolo -i . -e vendor -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命令以实现更高级别的命令。
去命令
由于我们想在项目目录中安装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
帮忙
最后,我们需要help命令来查看可用命令列表。 我们可以使用sed和column命令自动生成格式精美的帮助输出:
help: Makefile @echo " Choose a command run in "$(PROJECTNAME)":" @sed -n 's/^
以下命令扫描Makefile中以##开头的行并显示它们。 这样,您只需在特定命令上添加注释,注释就会与help命令一起显示。
如果我们添加一些评论:
我们会得到:
$ 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.
最终版本
include .env PROJECTNAME=$(shell basename "$(PWD)")