提示:本节课最终代码为:feature/s01。
因为本课程是一个从 0 开始,一步一步教你构建起一个完整 Go 项目的课程,所以这里我会从项目初始化开始,告诉你项目初始化时一般需要执行的操作。
初始化代码仓库
开始 Go 项目开发的第一步便是:初始化一个项目仓库,对于 Go 项目来说主要包括以下 3 内容:
创建项目目录;
初始化目录为 Go 模块;
初始化目录为 Git 仓库。
创建项目目录
首先,我们要给我们的项目仓库起一些名字:
项目名称: 项目名要具有一定语义,说明该项目的功能等,建议的格式为纯小写的精短名字,如果项目名字过长,可以按单词用
-
分割,但最好不用到-
。这些是一些合格的名字:api
、controllermanager
、controller-manager
。不建议这样命名:controller_manager
;确认大小写格式: 还要确认项目名在代码中的大小写格式,统一大小写格式,可以使整个代码看起来更加一体。例如:
controller-manager
/controllermanager
项目的小写格式为controllermanager
,大写格式为ControllerManager
;确认项目名在代码中的简写格式: 有些项目名字出于易读目的,会比较长。在编写代码时,如果引用了项目名,可能会使代码行过长,为了使代码行简短易读,通常会使用简写模式。带
-
分割的项目名的简短模式,一般为每个单词的首字母,例如:controller-manager
为cm
。不带-
分割的简短模式,需要你根据具体名字确定,并没有统一命名规则,例如:controller
可以为ctrl
。
一个好的名字是一个好的开始,本实战项目的项目名字为 miniblog
,意为:微型博客。代码中的大写格式为 Miniblog
,小写格式为 miniblog
。因为 miniblog
本身较简短,所以简短格式就是 miniblog
。
确定了项目名字,接下来我们就可以在指定的组织目录下创建项目目录,这里我创建在 marmotedu
组织下。
marmotedu
是一个专门用来进行 Go 实战教学的组织,里面包含了众多可以提升你 Go 编码能力的实战项目:
- miniblog:一个可供快速学习的入门课程。课程入门但不简单,学完后,你已经具备足够的知识使用 Go 语言进行企业应用开发。Go 基础语法带你掌握一门工具,
miniblog
助你携带 Go 工具,进入企业参加工作。- iam:一个大型的 Go 语言实战课;
- superproj:一个大型 Kubernetes 编程实战课,计划未来 2 年完成编写(当前未完工)。
- awesome-books:推荐了一些优质的 IT 书籍。
我们可以执行以下命令来创建目录,并在 miniblog 项目中添加一个README.md
文件:
$ mkdir -p $GOPATH/src/github.com/marmotedu/miniblog
$ cd $GOPATH/src/github.com/marmotedu/miniblog
$ echo "## miniblog 项目" >> README.md
提示:你可以使用 readme.so 工具来协助生成 README 文件,通常 README 文件需要包含以下部分:Features、Installation、Usage/Examples、Documentation、Feedback、Contributing、Authors、License、Related。
初始化目录为 Go 模块
因为我们这是一个 Go 项目,根据 Go 语法要求,还需要将该项目初始化为一个 Go 模块。初始化命令如下:
$ go mod init # 初始化当前项目为一个 Go 模块
$ go work use . # 添加当前模块到 Go 工作区
接下来,你还要将当前目录初始化成一个 Git 仓库,通过 Git 来管理项目代码。
初始化目录为 Git 仓库
初始化为 Git 仓库的第一步,就是在当前目录添加一个 .gitignore
文件,里面包含不期望 Git 跟踪的文件,例如:临时文件等。你可以使用生成工具 gitignore.io 来生成 .gitignore
:
基于生成的 .gitignore
文件,我们又自定义了需要忽略的文件:
# Custom
/_output
接下来,执行初始化命令:
$ git init # 初始化当前目录为 Git 仓库
$ git add . # 添加所有被 Git 追踪的文件到暂存区
$ git commit -m "feat: 第一次提交" # 将暂存区内容添加到本地仓库中
之后,我们就可以在该目录下开发代码,并根据需要提交代码。提交后的源码目录内容如下:
$ ls -A
.git .gitignore go.mod README.md
创建需要的目录
初始化完代码仓库后,我们可以根据前面设计的目录规范创建一些空目录,例如:
$ mkdir -p api configs docs scripts
$ ls
api configs docs go.mod README.md scripts
虽然创建空目录看似是一种空操作,但它可以带来以下好处:
提前规划好目录,等于提前规划好以后的功能,起到一种提示功能:这部分功能需要补充;
提前创建好目录,可以使后来的文件按目录功能存放在预先规划好的目录中,使项目更加规范。
比如,这时候,我们就可以将之前设计好的规范存放在 docs/devel/zh-CN/conversions
目录中,内容如下:
$ ls docs/devel/zh-CN/conversions/
api.md commit.md directory.md error_code.md go_code.md log.md README.md version.md
提示:
- 错误规范(
error_code.md
)和日志规范(error_code.md
)可以在具体设计错误包和日志包的时候制定,这里先留空占位。- Git 不追踪空目录,为了让 Git 追踪空目录,我们可以在空目录下创建一个空文件
.keep
,并在适当的时候执行以下命令删除这些临时的.keep
文件:find . -name .keep | xargs -i rm {}
。
创建 Hello World 程序
一个全新的项目,首先需要编写一个最简单的 Hello World 程序,用来检查开发、编译环境是否就绪。根据目录规范,需要在 cmd/miniblog
目录下创建 main.go
文件,内容如下:
package main
import "fmt"
// Go 程序的默认入口函数(主函数).
func main() {
fmt.Println("Hello MiniBlog!")
}
然后,编译并运行,命令如下:
$ gofmt -s -w ./
$ go build -o _output/miniblog -v cmd/miniblog/main.go
command-line-arguments
$ ls _output/
miniblog
$ ./_output/miniblog
Hello MiniBlog
可以看到,成功编译并运行了 miniblog
二进制程序,说明我们的开发、编译环境是就绪的!后面就可以愉快地进行代码开发了。
这里,有两点你需要注意:
很多项目中会将 main 文件名跟应用程序名字保持一致,例如:
miniblog.go
,从效果上来说,其实完全没有任何影响,但我日常开发时,更喜欢将 main 文件命名为main.go
,原因是这样的文件名,能够明确告诉其他开发者,这是一个 main 文件。我们通常需要将构建产物、临时文件等存放在一个单独的目录中,例如:
_output
。这样的好处:一方面,我们能够很方便地找到并使用这些构建产物或者方便、安全地清理这些产物,例如只需要执行rm -rf _output
即可清理这些产物。另一方面,我们还可以将_output
目录加入到.gitignore
文件中,告诉 Git 不要追踪这些临时产物。
程序实时加载、构建、启动
在开发过程中,我们经常需要修改代码、编译代码、重新启动程序,然后测试程序。这个过程如果每次都手工操作,无疑效率是比较低的,那么有没有什么手段或者工具能够解放双手,提高开发效率呢?答案是:可以使用一些程序热加载工具。
业界当前有很多好用的程序热加载工具,在 Go 项目开发中,比较受欢迎的是 air 工具。关于如何使用 air 工具,你可以直接参考官方文档 Air 官方文档。
接下来,我们就来配置、使用 air 工具。具体分为以下几步:
- 安装 air 工具。
$ go install github.com/cosmtrek/air@latest
- 配置 air 工具。
这里我们使用 air 官方仓库中给出的示例配置:air_example.toml。air_example.toml
里面的示例配置基本能满足绝大部分的项目需求,一般只需要再配置 cmd
、bin
、args_bin
3 个参数即可。
在 miniblog 项目根目录下创建 .air.toml
文件,内容见 .air.toml。
.air.toml
基于 air_example.toml
文件修改了以下参数配置:
# 只需要写你平常编译使用的 shell 命令。你也可以使用 `make`.
cmd = "make build"
# 由 `cmd` 命令得到的二进制文件名.
bin = "_output/miniblog"
参数介绍:
cmd
:指定了监听文件有变化时,air 需要执行的命令,这里指定了make build
重新构建miniblog
二进制文件;bin
:指定了执行完cmd
命令后,执行的二进制文件,这里指定了编译构建后的二进制文件_output/miniblog
。
- 启动 air 工具。
配置好后,在项目根目录下运行 air
命令:
$ air # 默认使用当前目录下的 .air.toml 配置,你可以通过 `-c` 选项指定配置,例如:`air -c .air.toml`
...
mkdir /home/colin/workspace/golang/src/github.com/marmotedu/miniblog/tmp
watching .
watching _output
...
watching scripts
!exclude tmp
building...
running...
Hello MiniBlog
修改 cmd/miniblog/main.go
文件,MiniBlog
变更为 MiniBlog!
,观察 air 终端窗口:
...
running...
Hello MiniBlog
cmd/ miniblog /main.go has changed
building...
running...
Hello MiniBlog!
可以看到 air 根据我们指定的命令,重新编译并启动了 miniblog 服务。
编写 API 文档
Go 项目绝大部分是后端 API 服务,当前一般采用前后端分离的软件架构,前后端分离有很多好处,例如:使前后端功能迭代互不影响,可以减小代码维护的复杂度、加快发布速度;使前后端可以并行开发,提高开发效率,前后端通过 API 文档进行耦合;使专业的人做专业的事,确保代码质量。
前后端并行开发,需要依赖于 API 接口文档,所以在项目初期,首先要编写好 API 文档,编写 API 文档时需要关注:API 文档规范和编写方式。
API 文档规范
理论来说,只要在团队内约定好 API 编写和呈现规范,那么你可以根据需要创建任意规范,但不建议这么做,更建议使用社区已经成熟的 API 文档规范。好处有很多:
使用社区已经存在的、成熟的 API 文档规范,可以提高 API 文档的质量、减少工作量、提高编写效率;
使用社区的 API 文档规范,也天然可以使用社区针对改 API 规范所开发的各种工具,例如:文档编辑器、代码生成器、代码校验器等;
使用社区通用的 API 规范,可以使其他开发者不需要额外的理解便能够理解、使用 API 接口。
根据 API 风格的不同,API 规范也不同,具体如下:
REST API 风格:OpenAPI 接口规范;
RPC API 风格:Protobuf 接口规范;
GraphQL API 风格:GraphQL 接口规范。
本实战项目,采用了 REST API 风格 和 RPC API 风格,所以接下来我会介绍什么是 OpenAPI 接口规范、如何编写以及围绕 OpenAPI 的工具集。Protobuf 接口规范在 第 16 节 会详细介绍。
提示:业界知名的 Go 项目(例如:Kubernetes、Etcd 等)基本也都采用了 OpenAPI 规范,OpenAPI 规范已经是 REST API 文档的事实标准。
OpenAPI 规范和 Swagger
OpenAPI 规范(以前称为 Swagger 规范)是 REST API 的 API 描述格式。 OpenAPI 规范兼容文件允许我们描述完整的 REST API。 它通常以 YAML 或 JSON 文件格式编写。目前最新的 OpenAPI 规范是OpenAPI 3.0(也就是 Swagger 2.0 规范)。
OpenAPI 规范文档包含以下信息:
描述所有可用的 API 端点(例如
/v1/users
、/v1/users/{name}
);端点中的操作,例如
GET /v1/users
、POST ``/v1``/users
;每个操作的输入和输出参数;
认证机制;
联系信息、API 许可、使用条款和其他信息。
Swagger 是一组围绕 OpenAPI 规范构建的开源工具,可以帮助我们设计、构建、记录和使用 REST API。Swagger 不仅可以帮助我们设计和记录 REST API,还可以让我们构建(服务器存根)和使用(REST 客户端)REST API。主要的 Swagger 工具包括:
Swagger Editor:基于浏览器的编辑器,可以在其中编写 OpenAPI 规范;
Swagger UI:将 OpenAPI 规范呈现为交互式 API 文档;
Swagger Codegen:根据 OpenAPI 规范生成服务器存根和客户端库。
API 文档编写工具
如果采用 OpenAPI 规范,你可以采用以下 3 种 API 文档编写方式:
- 基于本地编辑器 / IDE 编写 YAML 格式的 OpenAPI 文档;
- 基于 Swagger Editor 编写 OpenAPI 文档;
- 基于 Swagger Hub 编写 OpenAPI 文档;
- 基于代码标注生成 OpenAPI 文档,例如:
提示:SwaggerHub 是一个 API 设计和文档平台,为团队打造,以推动其 API 开发工作流程的一致性和规范性。
推荐编写方法:
如果 API 接口文档可以暴露在 SwaggerHub 平台,建议基于 SwaggerHub 平台来编写,因为 SwaggerHub 平台具备 API 编辑、展示、Mock、保存等功能,并能很方便地进行 API 共享。
如果 API 接口文档比较敏感,则可以基于本地编辑器编辑,并将 API 接口文档粘贴在 Swagger Editor 进行正确性校验。
不建议基于代码标注生成,因为基于代码标注生成,往往说明接口实现已经开发好,再基于接口生成 API 文档,违反接口定义先行的原则,不利于并行开发以提高开发效率;
文档编写工具也在不断发展,如果你有更好编辑方式,也可以采用。
miniblog API 文档编写和展示
因为 OpenAPI 文档内容比较多,并且网上有很多易读的文档,所以本课程不再介绍,具体语法可参考官方文档进行学习:OpenAPI Specification(一个不错的中文翻译文档:开放 API 规范中文翻译)。
无需从零编写 OpenAPI 文档,可以基于模板进行修改编写,步骤如下:
打开 Swagger Editor 在线编辑器;
选择 Edit -> Load OpenAPI 3.0 Petstore Fixture,载入 Swagger Petstore 文档模板,
为了防止在线编辑内容丢失,可以选择 File -> Save (as YAML) 将模板保存在本地,使用本地编辑器进行编辑;
编辑之后,可以将内容粘贴在 Swagger Editor 中以检查文档是否正确。
miniblog OpenAPI 文档为 api/openapi/openapi.yaml。可以通过 swagger 工具,渲染并在线展示,具体步骤如下:
- 安装 swagger 工具,安装命令如下:
$ go install github.com/go-swagger/go-swagger/cmd/swagger@latest
- 运行 swagger 命令:
$ swagger serve -F=swagger --no-open --port 65534 ./api/openapi/openapi.yaml
2022/11/22 21:19:49 serving docs at http://localhost:65534/docs
- 在浏览器中打开
http://localhost:65534/docs
,效果如下图所示:
这里需要注意:使用 swagger serve
渲染 OpenAPI 文档需要确保 OpenAPI 文档版本为:swagger: "2.0"
,例如:
swagger: "2.0"
servers:
- url: http://127.0.0.1:8080/v1
description: development server
info:
version: "1.0.0"
title: miniblog api definition
否则 swagger serve
命令会渲染失败。
编写后的 OpenAPI 文档需要根据目录规范存放在:api/openapi
目录下。
有很多开发者会觉得编写 OpenAPI 文档是一个复杂、耗时的工作,其实只要你熟悉 OpenAPI 文档的语法和编写方式,编写 OpenAPI 文档是一个高效、简单的工作。
添加版权声明
如果你的项目是一个开源项目或者未来准备开源,那么还需要给项目添加一些版权声明,主要包括:
存放在项目根目录下的
LICENSE
文件,用来声明项目遵循的开源协议;项目源文件中的版权头信息,用来说明文件所遵循的开源协议。
添加版权声明的第一步就是选择一个开源协议,当前有上百种开源协议可供选择,常用的有 6 种,miniblog 项目使用了最宽松的 MIT 协议。更多协议可参考 附录 C:开源规范协议介绍。
miniblog 添加 LICENSE
文件
一般项目的根目录下会存放一个 LICENSE
文件用来声明本项目所遵循的协议,所以我们这里也要为 miniblog 初始化一个 LICENSE
文件。LICENSE
文件怎么写?不用慌,作为一个懒惰的程序员,我们可以使用 license
工具来生成,具体操作命令如下:
$ go install github.com/nishanths/license/v5@latest
$ license -list # 查看支持的代码协议
$ license -n 'colin404(孔令飞) <colin404@foxmail.com>' -o LICENSE mit # 在 miniblog 项目根目录下执行
$ ls LICENSE
LICENSE
可以看到上述命令成功生成了一个内容为 MIT 协议的 LICENSE 文件。
给源文件添加版本声明
接下来,我们还需要给项目中的源文件添加版权头信息,用来声明文件所遵循的开源协议。miniblog 的版权头信息保存在 boilerplate.txt 文件中。
提示:版权头信息保存的文件名,通常命名为:boilerplate。
有了版权头信息,我们在新建文件时,就需要将这些信息放在文件头中,如果手动添加,不仅容易出错,还容易漏文件。最好的方法是通过自动化的方法来追加版权头信息。追加方法如下:
- 安装
addlicense
工具。
安装命令如下:
$ go install github.com/marmotedu/addlicense@latest
- 运行
addlicense
工具添加版权头信息。
运行命令如下:
$ addlicense -v -f ./scripts/boilerplate.txt --skip-dirs=third_party,vendor,_output .
cmd/miniblog/main.go added license
可以看到 main.go
文件已经被追加上了版权头信息,内容如下:
// Copyright 2022 Innkeeper Belm(孔令飞) <nosbelm@qq.com>. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. The original repo for
// this file is https://github.com/marmotedu/miniblog.
package main
import "fmt"
// Go 程序的默认入口函数(主函数).
func main() {
fmt.Println("Hello MiniBlog!")
}
使用构建工具提高开发效率
提示:构建工具(build tool)是用来从源代码生成用户可以使用的目标(targets)的自动化工具。目标可以包括库、可执行文件、或者生成的脚本等等。
上面我们为了初始化项目执行了多个操作,其中一些操作在整个 Go 项目开发周期中会被反复执行,如果每次都手动运行命令来完成,效率低下,还容易出错;而且,每个人执行的命令、参数可能不同,并造成不一致的结果,导致项目不规范。
那么如何解决这些问题呢?最佳的实践就是使用构建工具来管理你的项目,将这些高频的操作集成在构建工具中。业界当前有许多构建工具,比较受欢迎的有:
Make: Make 是 GNU 软件包中的一个构建工具,可用于构建并管理项目的目标文件和可执行文件。
- 优点:普及度高、自动化编译、语法简单,易学习;
- 缺点:不能实现增量式构建,不支持缓存,当项目规模很大时,构建速度很慢,并且依赖管理会比较复杂。
Bazel: Bazel 是一个类似于 Make 的工具,是 Google 为其内部软件开发的特点量身定制的工具,如今 Google 使用它来构建内部大多数的软件。
- 优点:多语言支持、多平台支持、可重复性、可伸缩性、局部和增量编译(编译速度快)、显式的编译声明等;
- 缺点:构建系统相对比较复杂,需要花费大精力学习。
CMake: CMake 是一种跨平台编译工具,比 make 更为高级,使用起来要方便得多。CMake 主要是编写
CMakeLists.txt
文件,然后用cmake
命令将CMakeLists.txt
文件转化为 make 所需要的 makefile 文件,最后用make
命令编译源码生成可执行程序或共享库。优点:跨平台发现依赖的系统库、自动发现和工具链的配置、
CMakeLists.txt
文件编写语法易于阅读和理解;缺点:比 make 更复杂、自动生成的 makefile 太臃肿。
提示:当然还有一些其他构建工具,例如:ninja、maven 等,这些工具不在选择范围之内,这里不再介绍。
3 种工具各有优缺点,要选择一个最合适的管理工具,需要你对 3 种工具有比较熟悉的了解,了解每个工具的优缺点和使用场景。这里我有以下建议:
如果没有特殊需求,建议 make 和 cmake 中,选择 make(原因:make 更普适);
对于一般的项目(应该是绝大多数的 Go 项目),可以使用更加通用的 make 工具;
对于超大型的项目(例如:公司级别的 Git 大仓)可以考虑使用 bazel。
miniblog 项目选择 make 作为构建工具。业界优秀的项目基本都是采用 make 来管理的,例如:Kubernetes、Docker、Istio 等等。
编写简单的 Makefile
要用 Makefile 来管理项目,就要学会编写 Makefile 脚本,因为 Makefile 语法较多,网上也有较多优秀的课程,所以本课程不会详细介绍。这里建议你通过以下方式来学习 Makefile 编程:
学习 Makefile 基本语法:可参考文档 Makefile基础知识.md;
学习 Makefile 高级语法(如果有时间/感兴趣):陈皓老师编写的 跟我一起写 Makefile (PDF 重制版) 。
编写后的 Makefile 文件位于项目根目录下,内容为:
# ==============================================================================
# 定义全局 Makefile 变量方便后面引用
COMMON_SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
# 项目根目录
ROOT_DIR := $(abspath $(shell cd $(COMMON_SELF_DIR)/ && pwd -P))
# 构建产物、临时文件存放目录
OUTPUT_DIR := $(ROOT_DIR)/_output
# ==============================================================================
# 定义 Makefile all 伪目标,执行 `make` 时,会默认会执行 all 伪目标
.PHONY: all
all: add-copyright format build
# ==============================================================================
# 定义其他需要的伪目标
.PHONY: build
build: tidy # 编译源码,依赖 tidy 目标自动添加/移除依赖包.
@go build -v -o $(OUTPUT_DIR)/miniblog $(ROOT_DIR)/cmd/miniblog/main.go
.PHONY: format
format: # 格式化 Go 源码.
@gofmt -s -w ./
.PHONY: add-copyright
add-copyright: # 添加版权头信息.
@addlicense -v -f $(ROOT_DIR)/scripts/boilerplate.txt $(ROOT_DIR) --skip-dirs=third_party,vendor,$(OUTPUT_DIR)
.PHONY: swagger
swagger: # 启动 swagger 在线文档.
@swagger serve -F=swagger --no-open --port 65534 $(ROOT_DIR)/api/openapi/openapi.yaml
.PHONY: tidy
tidy: # 自动添加/移除依赖包.
@go mod tidy
.PHONY: clean
clean: # 清理构建产物、临时文件等.
@-rm -vrf $(OUTPUT_DIR)
为了使你理解 Makefile 的内容,这里会介绍一些涉及到的重点语法和内容。
Makefile 规则语法
Makefile 的规则语法如下:
target ...: prerequisites ...
command ...
...
target:可以是一个 object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。可使用通配符,当有多个目标时,目标之间用空格分隔;
prerequisites:生成该 target 所需要的依赖项,当有多个依赖项时,依赖项之间用空格分隔;
command:该 target 要执行的命令(任意的 shell 命令)。
在执行 command 之前,默认会先打印出该命令,然后再输出命令的结果;如果不想打印出命令,可在各个 command 前加上
@
;command 可以为多条,可以分行写,但每行都要以 TAB 键开始。另外,如果后一条命令依赖前一条命令,则这两条命令需要写在同一行,并用分号进行分隔;
如果要忽略命令的出错,需要在各个 command 之前加上减号
-
。
提示:只要 targets 不存在或 prerequisites 中有一个以上的文件比 targets 文件新,command 所定义的命令就会被执行。command 会产生我们需要的文件或执行我们期望的操作。
Makefile 中使用到的语法列表
Makefile 中使用到的语法列表如下:
MAKEFILE_LIST
变量是 Makefile 的内置变量,表示:make 所需要处理的 makefile 文件列表,当前 makefile 的文件名总是位于列表的最后,文件名之间以空格进行分隔;函数
$(lastword <text>)
取字符串<text>
中的最后一个单词,并返回字符串<text>
的最后一个单词;函数
$(dir <names...>)
从文件名序列<names>
中取出目录部分。目录部分是指最后一个反斜杠(/
)之前的部分。如果没有反斜杠,那么返回./
;函数
$(shell cat foo)
执行操作系统命令,并返回操作结果;函数
$(abspath <text>)
将<text>
中的各路径转换成绝对路径,并将转换后的结果返回。
Makefile 核心内容解读
- 获取项目根目录绝对路径。
以下两行 Makefile 代码,最终获取了项目根目录的绝对路径:
COMMON_SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(abspath $(shell cd $(COMMON_SELF_DIR)/ && pwd -P))
- 设置 Makefile 默认目标。
通过 all
伪目标,指定执行 make
命令时默认需要执行的规则目标,例如:
.PHONY: all
all: add-copyright format build
以上 all
伪目标指定了当我们在执行 make
(不带参数)命令时所默认执行的目标(按执行顺序):add-copyright
(添加版权头信息)、format
(格式化 Go 源码)、build
(编译源码)。
- 实现幂等删除。
其次,建议在清理临时目录时使用 @-rm -vrf $(OUTPUT_DIR)
语法,-
语法可以确保在临时目录不存在时,Makefile 执行结果仍然是成功的(实现幂等清理的效果)。
- 使用绝对路径。
建议你在编写 Makefile 脚本中,通过 $(ROOT_DIR)
变量来引用文件的绝对路径,例如:
.PHONY: build
build: tidy # 编译源码,依赖 tidy 目标自动添加/移除依赖包.
@go build -v -o $(OUTPUT_DIR)/miniblog $(ROOT_DIR)/cmd/miniblog/main.go
这里的 $(OUTPUT_DIR)/miniblog
,$(ROOT_DIR)/cmd/miniblog/main.go
其实都是绝对路径,通过引用绝对路径,可以确保路径是符合预期的,避免使用相对路径带来的潜在问题。
小结
本节课带你初始化了一个 Go 项目,初始化的内容其实可以根据项目需要各不相同,但以上初始化内容和方法,对于大部分企业级 Go 项目都是适用的。