惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
J
Java Code Geeks
Vercel News
Vercel News
A
About on SuperTechFans
G
Google Developers Blog
C
Check Point Blog
腾讯CDC
N
Netflix TechBlog - Medium
博客园 - 司徒正美
S
SegmentFault 最新的问题
D
DataBreaches.Net
博客园_首页
美团技术团队
Stack Overflow Blog
Stack Overflow Blog
博客园 - 聂微东
量子位
雷峰网
雷峰网
IT之家
IT之家
小众软件
小众软件
Blog — PlanetScale
Blog — PlanetScale
博客园 - 三生石上(FineUI控件)
H
Help Net Security
宝玉的分享
宝玉的分享
博客园 - 叶小钗

Jiajun的技术笔记

你好,2026! TiDB 源码阅读(六):TiDB Coprocessor 源码解析 性能优化的核心思想 TiDB 源码阅读(五):索引 TiDB 源码阅读(四):AST、逻辑计划、物理计划 CockroachDB Serverless Architecture podman 无故退出 Cursor Control-L (CTRL-L) Keyboard Shortcuts in Terminal Replace docker with podman Using xmonad with xfce4 A RC script for freebsd frpc 自己动手写一个k8s controller AI 会取代你的(编程)岗位吗? 自建DERP服务器提升Tailscale连接速度(使用Nginx转发) 自动升级Docker容器 再读《程序员修炼之道-从小工到专家》 让浏览器下载文件 再读《软件随想录》/《黑客与画家》/《软技能》 HTTP 压力测试中的 Coordinated Omission 2的补码 编程语言中的 context 是什么? flutter macOS 构建出错 Flatpak 使用小记 Golang CAS 操作是怎么实现的 PostgreSQL 当MQ来使用 Clash 结合 工作VPN 的网络设计 使用 PostgreSQL 搭建 JuiceFS PostgreSQL 配置优化和日志分析 有GitHub Copilot?那就可以搭建你的ChatGPT4服务 窗口函数的使用(以PG为例)
Go mod 简明教程
Jiajun Huang · 2022-12-16 · via Jiajun的技术笔记

最近认真学习了一下 go mod,整理成文。这篇文章是系统的了解 go mod,并不是简单介绍如何使用。

go mod 出来之前,社区采用的是类似 NodeJS 那种模式,vendor,也就是把所有的包都保存在 vendor 这个目录下,但是 这种方式显然不够优雅,后来有了 go mod,到今天为止,go mod 已然成为了Go社区模块管理的标准。

概念

  • module。有 go.mod 的文件夹,就是一个 module。简单来说,一个module,里面包含了若干个package。
  • package。一个文件夹下,若干个 *.go 文件组成一个包,每个 .go 文件顶部,都会有一个 package xxx 来声明自己是什么包。

版本

module 有版本,Go里遵守 Semantic Versioning 2.0.0,因此,版本基本都会长这样: v0.0.1, v1.2.3, v2.0.0 等等。通常从git仓库中读取的时候,就是以tag为准,有的仓库没有打tag,那么Go会自动生成一个 版本号,称之为 pseudo-version,也就是伪版本号,例如 v0.0.0-20191109021931-daa7c04131f5。大概的格式是:

  • vX.0.0 基础版本
  • 时间戳,格式为 yyyymmddhhmmss,时间值是该次提交的UTC时间
  • 提交哈希值的前12位

上面三个,用 - 连起来。

大版本

也许我们的程序,会出现 2.0, 3.0, 4.0,那么,Go提供的方案是什么呢?答案是,建一个子目录,例如 v2 版本,就建立一个 v2 子目录,v3 就建 v3 子目录。或者如果在顶级目录的话,就要在 go.mod 声明的路径里最后加一个 v2 或者 v3 的后缀。

说实话,不是很优雅。他这个决定,主要是基于这么一条准则:

If an old package and a new package have the same import path, the new package
must be backwards compatible with the old package.

也就是说,同一个大版本号内的代码,必须是兼容的。Go这样做,带来的一个问题是,例如从 v2 升级到 v3 的时候,也许 的确有一些不兼容的地方,但是大部分代码都还是兼容的,又不是完全重写。而这样做了以后呢,调用者原本是导入 github.com/x/aaa,现在要全部改成导入 github.com/x/aaa/v2,并且以前所有引用到的地方都需要改。此外 v2 里的aaa.XXXStructv1 版本里的 aaa.XXXStruct 是不兼容的,无法互相赋值的,这样就会导致调用者需要改很多东西,带来不少负担。

GOPROXY, GOPRIVATE 等常用环境变量

  • 在国内,go get 基本上是无法直接使用的,原因你懂的。因此我们一般都要设置 GOPROXY 这个环境变量:

    $ go env -w GOPROXY="https://goproxy.cn,direct"
    
  • 如果你的项目是私有项目,那么你还需要把对应的路径加到 GOPRIVATE 这个变量里:

    $ go env -w GOPRIVATE="github.com/your_name,git.example.com"
    

如果只有一个,就写一个,多个,就用逗号连起来。对于私有仓库,建议同时设置一个 GONOSUMDB,值和它一样。

go.mod 文件

上面我们说了,有 go.mod 文件的地方,就是一个module。我们来看看 go.mod 的格式,先来看一个例子:

module example.com/my/thing

go 1.12

require example.com/other/thing v1.0.2
require example.com/new/thing/v2 v2.3.4
exclude example.com/old/thing v1.2.3
replace example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5
retract [v1.9.0, v1.9.5]
  • module 声明模块自身的路径
  • go 声明这个模块所用的Go版本
  • require 声明这个模块依赖的模块
  • exclude 让go mod不要加载这个模块
  • replace 声明替换,=> 前是代码中的路径,后是要替换成的路径
  • retract 声明这个模块中,损坏的版本,或者要撤回,要大家别用的版本。作用是,设置了的版本,使用者碰到这个 版本的时候,就会看到对应的提示

里面所有的路径,都是 URL 加 空格,加 版本号的格式,例如 example.com/new/thing/v2 v2.3.4

版本选择

go mod 是怎么做版本选择的呢?一句话:从当前项目 main 开始,构建一颗依赖树,当多个子模块依赖同一个模块时,选最新的。

Go minimal version selection

如上图,main 依赖 A1.2, B1.2A1.2 依赖 C1.3,而 B1.2 依赖 C1.4,他们同时依赖 D1.2。最后,go mod 会选择 main, A1.2, B1.2, C1.4, D1.2

子目录

有的时候,我们想要给子目录打一个版本,那怎么做呢?答案是在版本号前面加上目录的路径就可以,例如:

module/
        - A
        - A/B

那我们执行 git tag A/B/v0.1.2 就可以。这样使用者在使用该包时,就会优先选择子目录的版本,而不是根目录的版本号。

常见命令

我们来看看常见的和版本控制有关系的命令:

  • go mod init github.com/xxx/yyy 声明一个模块
  • go mod tidy 执行模块选择,并且把选择的依赖写到 go.sum 文件里
  • go mod why 解释引入某个依赖的依赖链
  • go build 构建二进制
  • go get 添加依赖
  • go get -u 更新依赖

总结

这篇文章整理了 go mod 的基本概念,然后介绍了它的版本选择方案,以及常见的使用方法和命令,希望能够给读者带来帮助。


ref: