提示:本节课最终代码为:feature/s01

因为本课程是一个从 0 开始,一步一步教你构建起一个完整 Go 项目的课程,所以这里我会从项目初始化开始,告诉你项目初始化时一般需要执行的操作。

初始化代码仓库

开始 Go 项目开发的第一步便是:初始化一个项目仓库,对于 Go 项目来说主要包括以下 3 内容:

  1. 创建项目目录;

  2. 初始化目录为 Go 模块;

  3. 初始化目录为 Git 仓库。

创建项目目录

首先,我们要给我们的项目仓库起一些名字:

  • 项目名称: 项目名要具有一定语义,说明该项目的功能等,建议的格式为纯小写的精短名字,如果项目名字过长,可以按单词用 - 分割,但最好不用到 -。这些是一些合格的名字:apicontrollermanagercontroller-manager。不建议这样命名:controller_manager

  • 确认大小写格式: 还要确认项目名在代码中的大小写格式,统一大小写格式,可以使整个代码看起来更加一体。例如:controller-manager / controllermanager 项目的小写格式为 controllermanager,大写格式为 ControllerManager

  • 确认项目名在代码中的简写格式: 有些项目名字出于易读目的,会比较长。在编写代码时,如果引用了项目名,可能会使代码行过长,为了使代码行简短易读,通常会使用简写模式。带 - 分割的项目名的简短模式,一般为每个单词的首字母,例如:controller-managercm。不带 - 分割的简短模式,需要你根据具体名字确定,并没有统一命名规则,例如: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 文件:

  1. $ mkdir -p $GOPATH/src/github.com/marmotedu/miniblog
  2. $ cd $GOPATH/src/github.com/marmotedu/miniblog
  3. $ echo "## miniblog 项目" >> README.md

提示:你可以使用 readme.so 工具来协助生成 README 文件,通常 README 文件需要包含以下部分:Features、Installation、Usage/Examples、Documentation、Feedback、Contributing、Authors、License、Related。

初始化目录为 Go 模块

因为我们这是一个 Go 项目,根据 Go 语法要求,还需要将该项目初始化为一个 Go 模块。初始化命令如下:

  1. $ go mod init # 初始化当前项目为一个 Go 模块
  2. $ go work use . # 添加当前模块到 Go 工作区

接下来,你还要将当前目录初始化成一个 Git 仓库,通过 Git 来管理项目代码。

初始化目录为 Git 仓库

初始化为 Git 仓库的第一步,就是在当前目录添加一个 .gitignore 文件,里面包含不期望 Git 跟踪的文件,例如:临时文件等。你可以使用生成工具 gitignore.io 来生成 .gitignore

4. 开发第一步:如何初始化 Go 项目? - 图1

基于生成的 .gitignore 文件,我们又自定义了需要忽略的文件:

  1. # Custom
  2. /_output

接下来,执行初始化命令:

  1. $ git init # 初始化当前目录为 Git 仓库
  2. $ git add . # 添加所有被 Git 追踪的文件到暂存区
  3. $ git commit -m "feat: 第一次提交" # 将暂存区内容添加到本地仓库中

之后,我们就可以在该目录下开发代码,并根据需要提交代码。提交后的源码目录内容如下:

  1. $ ls -A
  2. .git .gitignore go.mod README.md

创建需要的目录

初始化完代码仓库后,我们可以根据前面设计的目录规范创建一些空目录,例如:

  1. $ mkdir -p api configs docs scripts
  2. $ ls
  3. api configs docs go.mod README.md scripts

虽然创建空目录看似是一种空操作,但它可以带来以下好处:

  • 提前规划好目录,等于提前规划好以后的功能,起到一种提示功能:这部分功能需要补充;

  • 提前创建好目录,可以使后来的文件按目录功能存放在预先规划好的目录中,使项目更加规范。

比如,这时候,我们就可以将之前设计好的规范存放在 docs/devel/zh-CN/conversions 目录中,内容如下:

  1. $ ls docs/devel/zh-CN/conversions/
  2. api.md commit.md directory.md error_code.md go_code.md log.md README.md version.md

提示:

  1. 错误规范(error_code.md)和日志规范(error_code.md)可以在具体设计错误包和日志包的时候制定,这里先留空占位。
  2. Git 不追踪空目录,为了让 Git 追踪空目录,我们可以在空目录下创建一个空文件 .keep,并在适当的时候执行以下命令删除这些临时的 .keep 文件:find . -name .keep | xargs -i rm {}

创建 Hello World 程序

一个全新的项目,首先需要编写一个最简单的 Hello World 程序,用来检查开发、编译环境是否就绪。根据目录规范,需要在 cmd/miniblog 目录下创建 main.go 文件,内容如下:

  1. package main
  2. import "fmt"
  3. // Go 程序的默认入口函数(主函数).
  4. func main() {
  5. fmt.Println("Hello MiniBlog!")
  6. }

然后,编译并运行,命令如下:

  1. $ gofmt -s -w ./
  2. $ go build -o _output/miniblog -v cmd/miniblog/main.go
  3. command-line-arguments
  4. $ ls _output/
  5. miniblog
  6. $ ./_output/miniblog
  7. Hello MiniBlog

可以看到,成功编译并运行了 miniblog 二进制程序,说明我们的开发、编译环境是就绪的!后面就可以愉快地进行代码开发了。

这里,有两点你需要注意:

  • 很多项目中会将 main 文件名跟应用程序名字保持一致,例如:miniblog.go,从效果上来说,其实完全没有任何影响,但我日常开发时,更喜欢将 main 文件命名为 main.go,原因是这样的文件名,能够明确告诉其他开发者,这是一个 main 文件。

  • 我们通常需要将构建产物、临时文件等存放在一个单独的目录中,例如:_output。这样的好处:一方面,我们能够很方便地找到并使用这些构建产物或者方便、安全地清理这些产物,例如只需要执行 rm -rf _output 即可清理这些产物。另一方面,我们还可以将 _output 目录加入到 .gitignore 文件中,告诉 Git 不要追踪这些临时产物。

程序实时加载、构建、启动

在开发过程中,我们经常需要修改代码、编译代码、重新启动程序,然后测试程序。这个过程如果每次都手工操作,无疑效率是比较低的,那么有没有什么手段或者工具能够解放双手,提高开发效率呢?答案是:可以使用一些程序热加载工具。

业界当前有很多好用的程序热加载工具,在 Go 项目开发中,比较受欢迎的是 air 工具。关于如何使用 air 工具,你可以直接参考官方文档 Air 官方文档

接下来,我们就来配置、使用 air 工具。具体分为以下几步:

  1. 安装 air 工具。
  1. $ go install github.com/cosmtrek/air@latest
  1. 配置 air 工具。

这里我们使用 air 官方仓库中给出的示例配置:air_example.tomlair_example.toml 里面的示例配置基本能满足绝大部分的项目需求,一般只需要再配置 cmdbinargs_bin 3 个参数即可。

在 miniblog 项目根目录下创建 .air.toml 文件,内容见 .air.toml

.air.toml 基于 air_example.toml 文件修改了以下参数配置:

  1. # 只需要写你平常编译使用的 shell 命令。你也可以使用 `make`.
  2. cmd = "make build"
  3. # 由 `cmd` 命令得到的二进制文件名.
  4. bin = "_output/miniblog"

参数介绍:

  • cmd:指定了监听文件有变化时,air 需要执行的命令,这里指定了 make build 重新构建 miniblog 二进制文件;

  • bin:指定了执行完 cmd 命令后,执行的二进制文件,这里指定了编译构建后的二进制文件 _output/miniblog

  1. 启动 air 工具。

配置好后,在项目根目录下运行 air 命令:

  1. $ air # 默认使用当前目录下的 .air.toml 配置,你可以通过 `-c` 选项指定配置,例如:`air -c .air.toml`
  2. ...
  3. mkdir /home/colin/workspace/golang/src/github.com/marmotedu/miniblog/tmp
  4. watching .
  5. watching _output
  6. ...
  7. watching scripts
  8. !exclude tmp
  9. building...
  10. running...
  11. Hello MiniBlog

修改 cmd/miniblog/main.go 文件,MiniBlog 变更为 MiniBlog!,观察 air 终端窗口:

  1. ...
  2. running...
  3. Hello MiniBlog
  4. cmd/ miniblog /main.go has changed
  5. building...
  6. running...
  7. 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/usersPOST ``/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 文档,例如:

4. 开发第一步:如何初始化 Go 项目? - 图2

提示:SwaggerHub 是一个 API 设计和文档平台,为团队打造,以推动其 API 开发工作流程的一致性和规范性。

推荐编写方法:

  • 如果 API 接口文档可以暴露在 SwaggerHub 平台,建议基于 SwaggerHub 平台来编写,因为 SwaggerHub 平台具备 API 编辑、展示、Mock、保存等功能,并能很方便地进行 API 共享。

  • 如果 API 接口文档比较敏感,则可以基于本地编辑器编辑,并将 API 接口文档粘贴在 Swagger Editor 进行正确性校验。

  • 不建议基于代码标注生成,因为基于代码标注生成,往往说明接口实现已经开发好,再基于接口生成 API 文档,违反接口定义先行的原则,不利于并行开发以提高开发效率;

  • 文档编写工具也在不断发展,如果你有更好编辑方式,也可以采用。

miniblog API 文档编写和展示

因为 OpenAPI 文档内容比较多,并且网上有很多易读的文档,所以本课程不再介绍,具体语法可参考官方文档进行学习:OpenAPI Specification(一个不错的中文翻译文档:开放 API 规范中文翻译)。

无需从零编写 OpenAPI 文档,可以基于模板进行修改编写,步骤如下:

  1. 打开 Swagger Editor 在线编辑器;

  2. 选择 Edit -> Load OpenAPI 3.0 Petstore Fixture,载入 Swagger Petstore 文档模板,

  3. 为了防止在线编辑内容丢失,可以选择 File -> Save (as YAML) 将模板保存在本地,使用本地编辑器进行编辑;

  4. 编辑之后,可以将内容粘贴在 Swagger Editor 中以检查文档是否正确。

miniblog OpenAPI 文档为 api/openapi/openapi.yaml。可以通过 swagger 工具,渲染并在线展示,具体步骤如下:

  1. 安装 swagger 工具,安装命令如下:
  1. $ go install github.com/go-swagger/go-swagger/cmd/swagger@latest
  1. 运行 swagger 命令:
  1. $ swagger serve -F=swagger --no-open --port 65534 ./api/openapi/openapi.yaml
  2. 2022/11/22 21:19:49 serving docs at http://localhost:65534/docs
  1. 在浏览器中打开 http://localhost:65534/docs,效果如下图所示:

4. 开发第一步:如何初始化 Go 项目? - 图3

这里需要注意:使用 swagger serve 渲染 OpenAPI 文档需要确保 OpenAPI 文档版本为:swagger: "2.0",例如:

  1. swagger: "2.0"
  2. servers:
  3. - url: http://127.0.0.1:8080/v1
  4. description: development server
  5. info:
  6. version: "1.0.0"
  7. title: miniblog api definition

否则 swagger serve 命令会渲染失败。

编写后的 OpenAPI 文档需要根据目录规范存放在:api/openapi 目录下。

有很多开发者会觉得编写 OpenAPI 文档是一个复杂、耗时的工作,其实只要你熟悉 OpenAPI 文档的语法和编写方式,编写 OpenAPI 文档是一个高效、简单的工作。

添加版权声明

如果你的项目是一个开源项目或者未来准备开源,那么还需要给项目添加一些版权声明,主要包括:

  1. 存放在项目根目录下的 LICENSE 文件,用来声明项目遵循的开源协议;

  2. 项目源文件中的版权头信息,用来说明文件所遵循的开源协议。

添加版权声明的第一步就是选择一个开源协议,当前有上百种开源协议可供选择,常用的有 6 种,miniblog 项目使用了最宽松的 MIT 协议。更多协议可参考 附录 C:开源规范协议介绍

miniblog 添加 LICENSE 文件

一般项目的根目录下会存放一个 LICENSE 文件用来声明本项目所遵循的协议,所以我们这里也要为 miniblog 初始化一个 LICENSE 文件。LICENSE 文件怎么写?不用慌,作为一个懒惰的程序员,我们可以使用 license 工具来生成,具体操作命令如下:

  1. $ go install github.com/nishanths/license/v5@latest
  2. $ license -list # 查看支持的代码协议
  3. $ license -n 'colin404(孔令飞) <colin404@foxmail.com>' -o LICENSE mit # 在 miniblog 项目根目录下执行
  4. $ ls LICENSE
  5. LICENSE

可以看到上述命令成功生成了一个内容为 MIT 协议的 LICENSE 文件。

给源文件添加版本声明

接下来,我们还需要给项目中的源文件添加版权头信息,用来声明文件所遵循的开源协议。miniblog 的版权头信息保存在 boilerplate.txt 文件中。

提示:版权头信息保存的文件名,通常命名为:boilerplate。

有了版权头信息,我们在新建文件时,就需要将这些信息放在文件头中,如果手动添加,不仅容易出错,还容易漏文件。最好的方法是通过自动化的方法来追加版权头信息。追加方法如下:

  1. 安装 addlicense 工具。

安装命令如下:

  1. $ go install github.com/marmotedu/addlicense@latest
  1. 运行 addlicense 工具添加版权头信息。

运行命令如下:

  1. $ addlicense -v -f ./scripts/boilerplate.txt --skip-dirs=third_party,vendor,_output .
  2. cmd/miniblog/main.go added license

可以看到 main.go 文件已经被追加上了版权头信息,内容如下:

  1. // Copyright 2022 Innkeeper Belm(孔令飞) <nosbelm@qq.com>. All rights reserved.
  2. // Use of this source code is governed by a MIT style
  3. // license that can be found in the LICENSE file. The original repo for
  4. // this file is https://github.com/marmotedu/miniblog.
  5. package main
  6. import "fmt"
  7. // Go 程序的默认入口函数(主函数).
  8. func main() {
  9. fmt.Println("Hello MiniBlog!")
  10. }

使用构建工具提高开发效率

提示:构建工具(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 编程:

  1. 学习 Makefile 基本语法:可参考文档 Makefile基础知识.md

  2. 学习 Makefile 高级语法(如果有时间/感兴趣):陈皓老师编写的 跟我一起写 Makefile (PDF 重制版)

编写后的 Makefile 文件位于项目根目录下,内容为:

  1. # ==============================================================================
  2. # 定义全局 Makefile 变量方便后面引用
  3. COMMON_SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
  4. # 项目根目录
  5. ROOT_DIR := $(abspath $(shell cd $(COMMON_SELF_DIR)/ && pwd -P))
  6. # 构建产物、临时文件存放目录
  7. OUTPUT_DIR := $(ROOT_DIR)/_output
  8. # ==============================================================================
  9. # 定义 Makefile all 伪目标,执行 `make` 时,会默认会执行 all 伪目标
  10. .PHONY: all
  11. all: add-copyright format build
  12. # ==============================================================================
  13. # 定义其他需要的伪目标
  14. .PHONY: build
  15. build: tidy # 编译源码,依赖 tidy 目标自动添加/移除依赖包.
  16. @go build -v -o $(OUTPUT_DIR)/miniblog $(ROOT_DIR)/cmd/miniblog/main.go
  17. .PHONY: format
  18. format: # 格式化 Go 源码.
  19. @gofmt -s -w ./
  20. .PHONY: add-copyright
  21. add-copyright: # 添加版权头信息.
  22. @addlicense -v -f $(ROOT_DIR)/scripts/boilerplate.txt $(ROOT_DIR) --skip-dirs=third_party,vendor,$(OUTPUT_DIR)
  23. .PHONY: swagger
  24. swagger: # 启动 swagger 在线文档.
  25. @swagger serve -F=swagger --no-open --port 65534 $(ROOT_DIR)/api/openapi/openapi.yaml
  26. .PHONY: tidy
  27. tidy: # 自动添加/移除依赖包.
  28. @go mod tidy
  29. .PHONY: clean
  30. clean: # 清理构建产物、临时文件等.
  31. @-rm -vrf $(OUTPUT_DIR)

为了使你理解 Makefile 的内容,这里会介绍一些涉及到的重点语法和内容。

Makefile 规则语法

Makefile 的规则语法如下:

  1. target ...: prerequisites ...
  2. command ...
  3. ...
  • 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 核心内容解读

  1. 获取项目根目录绝对路径。

以下两行 Makefile 代码,最终获取了项目根目录的绝对路径:

  1. COMMON_SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
  2. ROOT_DIR := $(abspath $(shell cd $(COMMON_SELF_DIR)/ && pwd -P))
  1. 设置 Makefile 默认目标。

通过 all 伪目标,指定执行 make 命令时默认需要执行的规则目标,例如:

  1. .PHONY: all
  2. all: add-copyright format build

以上 all 伪目标指定了当我们在执行 make(不带参数)命令时所默认执行的目标(按执行顺序):add-copyright(添加版权头信息)、format(格式化 Go 源码)、build(编译源码)。

  1. 实现幂等删除。

其次,建议在清理临时目录时使用 @-rm -vrf $(OUTPUT_DIR)语法,- 语法可以确保在临时目录不存在时,Makefile 执行结果仍然是成功的(实现幂等清理的效果)。

  1. 使用绝对路径。

建议你在编写 Makefile 脚本中,通过 $(ROOT_DIR) 变量来引用文件的绝对路径,例如:

  1. .PHONY: build
  2. build: tidy # 编译源码,依赖 tidy 目标自动添加/移除依赖包.
  3. @go build -v -o $(OUTPUT_DIR)/miniblog $(ROOT_DIR)/cmd/miniblog/main.go

这里的 $(OUTPUT_DIR)/miniblog$(ROOT_DIR)/cmd/miniblog/main.go 其实都是绝对路径,通过引用绝对路径,可以确保路径是符合预期的,避免使用相对路径带来的潜在问题。

小结

本节课带你初始化了一个 Go 项目,初始化的内容其实可以根据项目需要各不相同,但以上初始化内容和方法,对于大部分企业级 Go 项目都是适用的。