【Go学习之 go mod】gomod小白入门,在github上发布自己的项目(项目初始化、项目发布、项目版本升级等)

news2024/11/18 18:07:35

参考

  • Go语言基础之包 | 李文周的博客
  • Go mod的使用、发布、升级 | wei
  • Go Module如何发布v2及以上版本
  • 1.2.7. go mod命令 — 新溪-gordon V1.7.9 文档
  • golang go 包管理工具 go mod的详细介绍-腾讯云开发者社区-腾讯云
  • Go Mod 常见错误的原因 | walker的博客

项目案例

  • oceanweave/testgomod
    • 用于发布 go 包,v1 v2 版本等
  • oceanweave/testhello: test the repo testgomod
    • 用于拉取 testgomod 项目的 go包
  • 具体操作详情,可以查看两个项目的 commit 信息

总结

1. 项目初始化
  • go mod init {项目名},不推荐随便命名项目名,这样不便于以后发布到 github,若本地使用的话可以随意命名项目名

  • 推荐方式:

    • 首先在 github 创建个 repo
    • 然后初始化 go mod init github.com/{your-username}/{repo-name}1
  • go mod init {项目名}

    • 本地创建的项目不上传 github 的话,项目名随便指定

    • 若该项目需要上传 github,并需要后期 go get 下载的话,go mod init 一定要与 github 上存储仓库路径一致

    • 如本地创建一个 hello 项目,同时 github 上创建一个 hello 仓库(repo),那么该 repo 路径对应的 url 就是(github.com/{你的用户名}/{你的仓库名称})

      • 就是确保可以通过此路径找到你的项目
      • 若你在你的 repo 下创建一个二级目录,如 hello repo 下创建个 world 目录,那么如果你想把你的项目传到此目录下
      • 你的 go mod 就要如下设置 go mod init github.com/{你的用户名}/hello/world
    • 你的包如果存放在github上 你的包的go.mod module后面一定是github.com/{username}/xxx不能直接写成 xxx 这样的话 go mod 无法获得包 错误是parsing go.mod: unexpected module path "xxx" go: error loading module requirements

    • 也就是说 go.mod 的module 要跟go get xx/xxx 保持一致

# 举例子
# 1. 首先在 github 上创建个 repo,如 testgomod repo

# 2. 本地创建 go 项目,并发布到 github 上
# - 首先本地创建自己项目目录  testgomod --> 对应 github repo 名 
# 不要将 test 放到最后,若放在最后,其中的 go 文件,将会视此目录为 测试目录,可能编译的时候会有些问题
mkdir testgomod && cd testgomod

# - 与 github repo 进行同步,github 创建 repo 时就有这些提示
echo "# testgomod" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
# 此处我的用户名是 oceanweave
git remote add origin git@github.com:oceanweave/testgomod.git
git push -u origin main
echo "# gomodtest" >> README.md

# 3. 在上面创建的 hello 目录中 go mod 初始化
go mod init github.com/oceanweave/testgomod

# 4. 创建 pkg 目录用于编写你的函数逻辑,创建 main.go 作为程序主入口
$ tree testgomod 
testgomod
├── README.md
└── go.mod


# go.mod 内容
# github.com/oceanweave/hello 就是项目名
module github.com/oceanweave/testgomod

go 1.21.4
2. 程序编写及函数引用
2.1 先把目录结构创建出来
$  tree                                                       
.
├── README.md
├── go.mod
├── main.go           # 函数总逻辑入口
└── pkg
    └── demo1					# 一般来说包名命名和子目录名相同,便于查看
        └── hello1.go	# 编写函数逻辑
2.2 pkg 内创建工作函数(包名和目录一致)
  • 注意:包名要和所在目录名保持一致,否则引用 import 时会混乱
// - 此处 package 关键字就是指定包名
// - 子目录内的函数文件,可以随意命名,如 hello1.go,自己识别就好
// - 注意:子目录内可以创建多个函数文件,但同一个子目录的所有文件只能属于同一个包名,就是必须设置相同的 package
// - 如 demo1 子目录,后续创建的文件,必须添加 package demo1
package demo1

import "fmt"

func Hello1() {
	fmt.Println("this is Hello-1 from demo1")
}
2.3 main函数引用子目录下的函数(import引用及函数调用)
  • 注意:import 引用的是包的路径而不是包名
    • 若开启了 go mod,可以用项目名(module名)+ 包的相对路径表示 (如github.com/oceanweave/testgomod 是项目名,可以理解为是项目的根路径,pkg/demo1 是相对路径,用来指向包),来引用包
  • 函数调用的时候,用的是包名,而不是 import 路径名
    • 但是为什么一致呢,因为在包的定义是,我们定义 package 名保持和目录名一致,也就是上一步操作
    • 保持一致的好处就是,当 demo1 包函数发生错误是,可以通过相同名称在 import 找到对应的项目路径,然后排查并解决错误
package main

import (
  // 可以理解为是 项目名(go module 名) + 包的相对路径(从项目根目录到包的路径)
	"github.com/oceanweave/testgomod/pkg/demo1"
)

func main() {
  // 此处 demo1 是包名,而不是包的路径名
  // 但因为包所在目录和包名一致,所以可能会误认为,此处是上面 import 路径的末尾
  // 这样保持一致的好处就是,当 demo1 包函数发生错误是,可以通过相同名称在 import 找到对应的项目路径,然后排查并解决错误
	demo1.Hello1()
}

2.4 包名与所在目录不一致的情况
// 包名和所在目录不一致情况,如 /pkg/demo1 目录定义的包名为 demox
package demox

import "fmt"

func Hello1() {
	fmt.Println("this is Hello-1 from demo1")
}

// -------------------------------
// 在 main 包应用时的不同,引用方法1
package main

import (
  // 包的路径
	"github.com/oceanweave/testgomod/pkg/demo1"
)

func main() {
  // 包名调用函数
  // 可以看出来,当此处发生问题时,当 import 包过多时,无法定位该包对应上面哪个 import 路径
	demox.Hello1()
}

// -------------------------------
// 在 main 包应用时的不同,引用方法2,利用 import 重命名
package main

import (
  // 有效利用,包的重命名,避免歧义
  // 可以看出来,当此处发生问题时,当 import 包过多时,可以利用 import 重命名的名称,定位该包对应上面哪个 import 路径
  // 但是这种方法仍不如,包名和所在目录保持一致的情况
	demox "github.com/oceanweave/testgomod/pkg/demo1"
)

func main() {
	demox.Hello1()
}
2.5 版本发布
  • 版本发布机下载
# 1. 首先将本地项目开发完成
cd testgomod

# 2. 然后上传到 github
git push

# 此时是未发布版本情况,但上传到了 github
# 此时创建其他项目,比如另创建个项目 testhello,可以如下形式下载此 testgomod 包,然后引用其中的函数
# - 会拉取最新的 commit-id,就是最新的包
go get -u github.com/oceanweave/testgomod
# - 指定某个 commit-id 进行拉取
go get -u github.com/oceanweave/testgomod@commit-id

# 3. 版本发布
git tag -a 版本号  commit-id -m "注释信息"
git push origin 版本号
# 如
git tag -a v1.0.0  87fdf824  -m "Release v1.0.0"
# 之后发布到 github
git push origin v1.0.0
# 此时是发布版本情况
# 此时创建其他项目,比如另创建个项目 testhello,可以如下形式下载此 testgomod 包,然后引用其中的函数
git get -u github.com/oceanweave/testgomod@版本号
git get -u github.com/oceanweave/testgomod@v1.0.0

  • 版本形式要求
- 发布版本,版本形式要求 https://go.dev/ref/mod#versions
    - 形式建议 v1.2.3
    - 1 主版本号:发布了不兼容的版本迭代时递增(breaking changes)。
    - 2 次版本号:发布了功能性更新时递增。
    - 3 修订号:发布了bug修复类更新时递增
  • go get
- go get 有三种拉取方法
    - 版本号形式拉取: 		go get github.com/oceanweave/hello/demo1@v1.0.1
    - 分支形式拉取:  		go get github.com/oceanweave/hello/demo1@git-tag-test  会拉取分支最新的 commit
    - commit-id形式拉取:	go get github.com/oceanweave/hello/demo1@0c74f12d
  • 包的引用
    • 注意 go get 是把另一个项目的所有 go 包都下载下来了
    • 但引用时要指定 go 包的详细路径
// hello 项目
package main

import (
  // go get -u github.com/oceanweave/testgomod 下载了 testgomod 项目所有的包
  // 但引用时,要注意写明 要引用包的详细路径
	"github.com/oceanweave/testgomod/pkg/demo1"
  // 若写成下面这种,程序就不知道该引用哪个包
  // "github.com/oceanweave/testgomod/pkg"
)
func main() {
	demo1.Hello1()
}

2.6 不兼容版本发布
  • 关键点:

    • v2 moudle 名称要和 v1 区分

      • - 因为 v2 进行了重大重构,v1 与 v2 不兼容,所以更改此处 module 进行重命名,避免用户拉取后的不兼容导致失败
          - v1 版本的 module 名称 `module github.com/oceanweave/testgomod`
          - v2 版本的 module 名称 `module github.com/oceanweave/testgomod/v2`
        
    • 同时拉取和使用方式也会有所不同

      • - 更改module名称后,现在拉取方式为,注意多个 v2 路径
          - v1 版本拉取 go get -u github.com/oceanweave/testgomod@v1.0.1
          - v2 版本拉取 go get -u github.com/oceanweave/testgomod/v2@v2.0.0 # v2.0.0 是我们自己手动打的 tag,和上面 v1.0.1 操作方法一致
        - import 使用时,注意多个 v2 路径
          - v1 版本 import "github.com/oceanweave/testgomod/tools"
          - v2 版本 import "github.com/oceanweave/testgomod/v2/tools"
        
  • 方法1:为旧版本在 git 上新建个分支 branch

    • 比如 testgomod 项目
    • main 或 master 分支执行
      • git checkout -b v1
      • git push
  • 方法2:新建一个子目录 v2 保存 v2 代码

    • git push 就好,便于维护
# 仍以 testgomod 项目举例
# 原来 v1 版本 go.mod 文件如下
module github.com/oceanweave/testgomod

go 1.21.4

# 在该项目下,新建一个 v2 目录,将 v2 的所有代码和 go.mod 文件放入其中
# 现在 v2 版本进行了重大重构,很多函数都发生了改变
# 若 v1 版本的老用户误引下载新版本,将会导致其程序无法运行,引发事故
# 所以做了如下改动,避免用户误引用,更改了 go.mod,在后面加了 v2
module github.com/oceanweave/testgomod/v2

go 1.21.4

# 这样拉取包的时候就有所区分了,
# 不过要注意拉取 和 import 方式的不同,注意多个 v2 路径

- 不兼容,更改module名称后,现在拉取方式为,注意多个 v2 路径
  - v1 版本拉取 go get -u github.com/oceanweave/testgomod@v1.0.1
  - v2 版本拉取 go get -u github.com/oceanweave/testgomod/v2@v2.0.0 # v2.0.0 是我们自己手动打的 tag,和上面 v1.0.1 操作方法一致
- import 使用时,注意多个 v2 路径
  - v1 版本 import "github.com/oceanweave/testgomod/tools"
  - v2 版本 import "github.com/oceanweave/testgomod/v2/tools"
2.7 废弃已发布版本

个人测试未生效,没找到原因

如果某个发布的版本存在致命缺陷不再想让用户使用时,我们可以使用retract声明废弃的版本。例如我们在hello/go.mod文件中按如下方式声明即可对外废弃v1.0.1版本。

module github.com/oceanweave/testgomod

go 1.21.4


retract v1.0.1

用户使用go get下载v1.0.1版本时就会收到提示,催促其升级到其他版本。

这个特性是在Go1.16版本中引入,用来声明该第三方模块的某些发行版本不能被其他模块使用;

使用场景:发生严重问题或者无意发布某些版本后,模块的维护者可以撤回该版本,支持撤回单个或多个版本;

这种场景以前的解决办法:

维护者删除有问题版本的tag,重新打一个新版本的tag;

使用者发现有问题的版本tag丢失,手动介入升级,并且不明真因;

引入retract后,维护者可以使用retract在go.mod中添加有问题的版本:

// 严重bug...
retract (
  v0.1.0
  v0.2.0
)

重新发布新版本后,在引用该依赖库的使用执行go list可以看到 版本和"严重bug…"的提醒;

该特性的主要目的是将问题更直观的反馈到开发者的手中;

版本升级

  • 参考自 Go mod的使用、发布、升级 | wei

Go模块中规范了一个重要原则

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

如果旧包和新包具有相同的导入路径,新包必须向后兼容旧包。

v0->v1

假设我们自定义的模块已经稳定了,那么开始要对外发布v1.0.0版本了

  1. 拉取新的分支v1.0.0_branch
  2. git tag v1.0.0 并且推送到远程仓库

v1->v2

v1->v2的升级属于主版本的升级(v2不向后兼容), 这里有两种方式:

  • 创建新的版本目录v2
  • 继续使用v0->v1的升级方式
方法1 创建新的版本目录v2(新建目录)

按照go模块规范的原则,我们不能继续使用github.com/youdw/hello 这个包路径了。当前v1版本目录如下;

.
├── go.mod
├── go.sum
├── hello.go
└── hello_test.go

为了开始开发v2,在hello目录下创建v2目录,然后把.go 和 mod 文件复制到v24 ,然后将v2目录下的mod路径修改为 github.com/youdw/hello/v2

mkdir v2
cp *.go v2/
cp go.mod v2/go.mod
go mod edit -module github.com/youdw/hello/v2 v2/go.mod

完成之后的项目结构如下:

.
├── go.mod
├── go.sum
├── hello.go
├── hello_test.go
└── v2
    ├── go.mod
    ├── hello.go
    └── hello_test.go

1 directory, 7 files

发布v2

git tag v2.0.0
git push origin v2.0.0
方法2 继续使用v0->v1的升级方式(新建分支)

拉取新的分支,修改go.mod路径为github.com/youdw/hello/v2,基于master分支打tag v2.0.0

go mod edit -module github.com/youdw/hello/v2
git push
git tag v2.0.0
git push origin v2.0.0
推荐方式

以上两种方式,第一种是go模块推荐的方式,但是维护和开发的开销较大

go module

  • 参考自 Go语言基础之包 | 李文周的博客

在Go语言的早期版本中,我们编写Go项目代码时所依赖的所有第三方包都需要保存在GOPATH这个目录下面。这样的依赖管理方式存在一个致命的缺陷,那就是不支持版本管理,同一个依赖包只能存在一个版本的代码。可是我们本地的多个项目完全可能分别依赖同一个第三方包的不同版本。

go module介绍

Go module 是 Go1.11 版本发布的依赖管理方案,从 Go1.14 版本开始推荐在生产环境使用,于Go1.16版本默认开启。Go module 提供了以下命令供我们使用:

go module相关命令

命令介绍
go mod init初始化项目依赖,生成go.mod文件
go mod download根据go.mod文件下载依赖
go mod tidy比对项目文件中引入的依赖与go.mod进行比对
go mod graph输出依赖关系图
go mod edit编辑go.mod文件
go mod vendor将项目的所有依赖导出至vendor目录
go mod verify检验一个依赖包是否被篡改过
go mod why解释为什么需要某个依赖

Go语言在 go module 的过渡阶段提供了 GO111MODULE 这个环境变量来作为是否启用 go module 功能的开关,考虑到 Go1.16 之后 go module 已经默认开启,所以本书不再介绍该配置,对于刚接触Go语言的读者而言完全没有必要了解这个历史包袱。

GOPROXY

这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是用于使 Go 在后续拉取模块版本时能够脱离传统的 VCS 方式,直接通过镜像站点来快速拉取。

GOPROXY 的默认值是:https://proxy.golang.org,direct,由于某些原因国内无法正常访问该地址,所以我们通常需要配置一个可访问的地址。目前社区使用比较多的有两个https://goproxy.cnhttps://goproxy.io,当然如果你的公司有提供GOPROXY地址那么就直接使用。设置GOPAROXY的命令如下:

go env -w GOPROXY=https://goproxy.cn,direct

GOPROXY 允许设置多个代理地址,多个地址之间需使用英文逗号 “,” 分隔。最后的 “direct” 是一个特殊指示符,用于指示 Go 回源到源地址去抓取(比如 GitHub 等)。当配置有多个代理地址时,如果第一个代理地址返回 404 或 410 错误时,Go 会自动尝试下一个代理地址,当遇见 “direct” 时触发回源,也就是回到源地址去抓取。

GOPRIVATE

设置了GOPROXY 之后,go 命令就会从配置的代理地址拉取和校验依赖包。当我们在项目中引入了非公开的包(公司内部git仓库或 github 私有仓库等),此时便无法正常从代理拉取到这些非公开的依赖包,这个时候就需要配置 GOPRIVATE 环境变量。GOPRIVATE用来告诉 go 命令哪些仓库属于私有仓库,不必通过代理服务器拉取和校验。

GOPRIVATE 的值也可以设置多个,多个地址之间使用英文逗号 “,” 分隔。我们通常会把自己公司内部的代码仓库设置到 GOPRIVATE 中,例如:

$ go env -w GOPRIVATE="git.mycompany.com"

这样在拉取以git.mycompany.com为路径前缀的依赖包时就能正常拉取了。

此外,如果公司内部自建了 GOPROXY 服务,那么我们可以通过设置 GONOPROXY=none,允许通内部代理拉取私有仓库的包。

使用go module引入包

接下来我们将通过一个示例来演示如何在开发项目时使用 go module 拉取和管理项目依赖。

初始化项目 我们在本地新建一个名为holiday项目,按如下方式创建一个名为holiday的文件夹并切换到该目录下:

$ mkdir holiday
$ cd holiday

目前我们位于holiday文件夹下,接下来执行下面的命令初始化项目。

$ go mod init holiday
go: creating new go.mod: module holiday

该命令会自动在项目目录下创建一个go.mod文件,其内容如下。

module holiday

go 1.16

其中:

  • module holiday:定义当前项目的导入路径
  • go 1.16:标识当前项目使用的 Go 版本

go.mod文件会记录项目使用的第三方依赖包信息,包括包名和版本,由于我们的holiday项目目前还没有使用到第三方依赖包,所以go.mod文件暂时还没有记录任何依赖包信息,只有当前项目的一些信息。

接下来,我们在项目目录下新建一个main.go文件,其内容如下:

// holiday/main.go

package main

import "fmt"

func main() {
fmt.Println("现在是假期时间...")
}

然后,我们的holiday项目现在需要引入一个第三方包github.com/q1mi/hello来实现一些必要的功能。类似这样的场景在我们的日常开发中是很常见的。我们需要先将依赖包下载到本地同时在go.mod中记录依赖信息,然后才能在我们的代码中引入并使用这个包。下载依赖包主要有两种方法。

第一种方法是在项目目录下执行go get命令手动下载依赖的包:

holiday $ go get -u github.com/q1mi/hello
go get: added github.com/q1mi/hello v0.1.1

这样默认会下载最新的发布版本,你也可以指定想要下载指定的版本号的。

holiday $ go get -u github.com/q1mi/hello@v0.1.0
go: downloading github.com/q1mi/hello v0.1.0
go get: downgraded github.com/q1mi/hello v0.1.1 => v0.1.0

如果依赖包没有发布任何版本则会拉取最新的提交,最终go.mod中的依赖信息会变成类似下面这种由默认v0.0.0的版本号和最新一次commit的时间和hash组成的版本格式:

require github.com/q1mi/hello v0.0.0-20210218074646-139b0bcd549d

如果想指定下载某个commit对应的代码,可以直接指定commit hash,不过没有必要写出完整的commit hash,一般前7位即可。例如:

holiday $ go get github.com/q1mi/hello@2ccfadd
go: downloading github.com/q1mi/hello v0.1.2-0.20210219092711-2ccfaddad6a3
go get: added github.com/q1mi/hello v0.1.2-0.20210219092711-2ccfaddad6a3

此时,我们打开go.mod文件就可以看到下载的依赖包及版本信息都已经被记录下来了。

module holiday

go 1.16

require github.com/q1mi/hello v0.1.0 // indirect

行尾的indirect表示该依赖包为间接依赖,说明在当前程序中的所有 import 语句中没有发现引入这个包。

另外在执行go get命令下载一个新的依赖包时一般会额外添加-u参数,强制更新现有依赖。

第二种方式是我们直接编辑go.mod文件,将依赖包和版本信息写入该文件。例如我们修改holiday/go.mod文件内容如下:

module holiday

go 1.16

require github.com/q1mi/hello latest

表示当前项目需要使用github.com/q1mi/hello库的最新版本,然后在项目目录下执行go mod download下载依赖包。

holiday $ go mod download

如果不输出其它提示信息就说明依赖已经下载成功,此时go.mod文件已经变成如下内容。

module holiday

go 1.16

require github.com/q1mi/hello v0.1.1

从中我们可以知道最新的版本号是v0.1.1。如果事先知道依赖包的具体版本号,可以直接在go.mod中指定需要的版本然后再执行go mod download下载。

这种方法同样支持指定想要下载的commit进行下载,例如直接在go.mod文件中按如下方式指定commit hash,这里只写出来了commit hash的前7位。

require github.com/q1mi/hello 2ccfadda

执行go mod download下载完依赖后,go.mod文件中对应的版本信息会自动更新为类似下面的格式。

module holiday

go 1.16

require github.com/q1mi/hello v0.1.2-0.20210219092711-2ccfaddad6a3

下载好要使用的依赖包之后,我们现在就可以在holiday/main.go文件中使用这个包了。

package main

import (
"fmt"

"github.com/q1mi/hello"
)

func main() {
fmt.Println("现在是假期时间...")

hello.SayHi() // 调用hello包的SayHi函数
}

将上述代码编译执行,就能看到执行结果了。

holiday $ go build
holiday $ ./holiday
现在是假期时间...
你好,我是七米。很高兴认识你。

当我们的项目功能越做越多,代码越来越多的时候,通常会选择在项目内部按功能或业务划分成多个不同包。Go语言支持在一个项目(project)下定义多个包(package)。

例如,我们在holiday项目内部创建一个新的package——summer,此时新的项目目录结构如下:

holidy
├── go.mod
├── go.sum
├── main.go
└── summer
    └── summer.go

其中holiday/summer/summer.go文件内容如下:

package summer

import "fmt"

// Diving 潜水...
func Diving() {
fmt.Println("夏天去诗巴丹潜水...")
}

此时想要在当前项目目录下的其他包或者main.go中调用这个Diving函数需要如何引入呢?这里以在main.go中演示详细的调用过程为例,在项目内其他包的引入方式类似。

package main

import (
"fmt"

"holiday/summer" // 导入当前项目下的包

"github.com/q1mi/hello" // 导入github上第三方包
)

func main() {
fmt.Println("现在是假期时间...")
hello.SayHi()

summer.Diving()
}

从上面的示例可以看出,项目中定义的包都会以项目的导入路径为前缀。

如果你想要导入本地的一个包,并且这个包也没有发布到到其他任何代码仓库,这时候你可以在go.mod文件中使用replace语句将依赖临时替换为本地的代码包。例如在我的电脑上有另外一个名为liwenzhou.com/overtime的项目,它位于holiday项目同级目录下:

├── holiday
│   ├── go.mod
│   ├── go.sum
│   ├── main.go
│   └── summer
│       └── summer.go
└── overtime
    ├── go.mod
    └── overtime.go

由于liwenzhou.com/overtime包只存在于我本地,并不能通过网络获取到这个代码包,这个时候应该如何在holidy项目中引入它呢?

我们可以在holidy/go.mod文件中正常引入liwenzhou.com/overtime包,然后像下面的示例那样使用replace语句将这个依赖替换为使用相对路径表示的本地包。

module holiday

go 1.16

require github.com/q1mi/hello v0.1.1
require liwenzhou.com/overtime v0.0.0

replace liwenzhou.com/overtime  => ../overtime

这样,我们就可以在holiday/main.go下正常引入并使用overtime包了。

package main

import (
"fmt"

"holiday/summer" // 导入当前项目下的包

"liwenzhou.com/overtime" // 通过replace导入的本地包

"github.com/q1mi/hello" // 导入github上第三方包
)

func main() {
fmt.Println("现在是假期时间...")
hello.SayHi()

summer.Diving()

overtime.Do()
}

我们也经常使用replace将项目依赖中的某个包,替换为其他版本的代码包或我们自己修改后的代码包。

go.mod文件

go.mod文件中记录了当前项目中所有依赖包的相关信息,声明依赖的格式如下:

require module/path v1.2.3

其中:

  • require:声明依赖的关键字
  • module/path:依赖包的引入路径
  • v1.2.3:依赖包的版本号。支持以下几种格式:
    • latest:最新版本
    • v1.0.0:详细版本号
    • commit hash:指定某次commit hash

引入某些没有发布过tag版本标识的依赖包时,go.mod中记录的依赖版本信息就会出现类似v0.0.0-20210218074646-139b0bcd549d的格式,由版本号、commit时间和commit的hash值组成。

go module生成的版本信息组成示意图

go.sum文件

使用go module下载了依赖后,项目目录下还会生成一个go.sum文件,这个文件中详细记录了当前项目中引入的依赖包的信息及其hash 值。go.sum文件内容通常是以类似下面的格式出现。

<module> <version>/go.mod <hash>

或者

<module> <version> <hash>
<module> <version>/go.mod <hash>

不同于其他语言提供的基于中心的包管理机制,例如 npm 和 pypi等,Go并没有提供一个中央仓库来管理所有依赖包,而是采用分布式的方式来管理包。为了防止依赖包被非法篡改,Go module 引入了go.sum机制来对依赖包进行校验。

依赖保存位置

Go module 会把下载到本地的依赖包会以类似下面的形式保存在 $GOPATH/pkg/mod目录下,每个依赖包都会带有版本号进行区分,这样就允许在本地存在同一个包的多个不同版本。

mod
├── cache
├── cloud.google.com
├── github.com
    └──q1mi
          ├── hello@v0.0.0-20210218074646-139b0bcd549d
          ├── hello@v0.1.1
          └── hello@v0.1.0
...

如果想清除所有本地已缓存的依赖包数据,可以执行 go clean -modcache 命令。

使用go module发布包

在上面的小节中我们学习了如何在项目中引入别人提供的依赖包,那么当我们想要在社区发布一个自己编写的代码包或者在公司内部编写一个供内部使用的公用组件时,我们该怎么做呢?接下来,我们就一起编写一个代码包并将它发布到github.com仓库,让它能够被全球的Go语言开发者使用。

我们首先在自己的 github 账号下新建一个项目,并把它下载到本地。我这里就以创建和发布一个名为hello的项目为例进行演示。这个hello包将对外提供一个名为SayHi的函数,它的作用非常简单就是向调用者发去问候。

$ git clone https://github.com/q1mi/hello
$ cd hello

我们当前位于hello项目目录下,执行下面的命令初始化项目,创建go.mod文件。需要注意的是这里定义项目的引入路径为github.com/q1mi/hello,读者在自行测试时需要将这部分替换为自己的仓库路径。

hello $ go mod init github.com/q1mi/hello
go: creating new go.mod: module github.com/q1mi/hello

接下来我们在该项目根目录下创建 hello.go 文件,添加下面的内容:

package hello

import "fmt"

func SayHi() {
fmt.Println("你好,我是七米。很高兴认识你。")
}

然后将该项目的代码 push 到仓库的远端分支,这样就对外发布了一个Go包。其他的开发者可以通过github.com/q1mi/hello这个引入路径下载并使用这个包了。

一个设计完善的包应该包含开源许可证及文档等内容,并且我们还应该尽心维护并适时发布适当的版本。github 上发布版本号使用git tag为代码包打上标签即可。

hello $ git tag -a v0.1.0 -m "release version v0.1.0"
hello $ git push origin v0.1.0

经过上面的操作我们就发布了一个版本号为v0.1.0的版本。

Go modules中建议使用语义化版本控制,其建议的版本号格式如下:

语义化版本号示意图

其中:

  • 主版本号:发布了不兼容的版本迭代时递增(breaking changes)。
  • 次版本号:发布了功能性更新时递增。
  • 修订号:发布了bug修复类更新时递增。

发布新的主版本

现在我们的hello项目要进行与之前版本不兼容的更新,我们计划让SayHi函数支持向指定人发出问候。更新后的SayHi函数内容如下:

package hello

import "fmt"

// SayHi 向指定人打招呼的函数
func SayHi(name string) {
fmt.Printf("你好%s,我是七米。很高兴认识你。\n", name)
}

由于这次改动巨大(修改了函数之前的调用规则),对之前使用该包作为依赖的用户影响巨大。因此我们需要发布一个主版本号递增的v2版本。在这种情况下,我们通常会修改当前包的引入路径,像下面的示例一样为引入路径添加版本后缀。

// hello/go.mod

module github.com/q1mi/hello/v2

go 1.16

把修改后的代码提交:

hello $ git add .
hello $ git commit -m "feat: SayHi现在支持给指定人打招呼啦"
hello $ git push

打好 tag 推送到远程仓库。

hello $ git tag -a v2.0.0 -m "release version v2.0.0"
hello $ git push origin v2.0.0

这样在不影响使用旧版本的用户的前提下,我们新的版本也发布出去了。想要使用v2版本的代码包的用户只需按修改后的引入路径下载即可。

go get github.com/q1mi/hello/v2@v2.0.0

在代码中使用的过程与之前类似,只是需要注意引入路径要添加 v2 版本后缀。

package main

import (
"fmt"

"github.com/q1mi/hello/v2" // 引入v2版本
)

func main() {
fmt.Println("现在是假期时间...")

hello.SayHi("张三") // v2版本的SayHi函数需要传入字符串参数
}

废弃已发布版本

如果某个发布的版本存在致命缺陷不再想让用户使用时,我们可以使用retract声明废弃的版本。例如我们在hello/go.mod文件中按如下方式声明即可对外废弃v0.1.2版本。

module github.com/q1mi/hello

go 1.16


retract v0.1.2

用户使用go get下载v0.1.2版本时就会收到提示,催促其升级到其他版本。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1226163.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

山西电力市场日前价格预测【2023-11-20】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-11-20&#xff09;山西电力市场全天平均日前电价为255.39元/MWh。其中&#xff0c;最高日前电价为436.50元/MWh&#xff0c;预计出现在18:00。最低日前电价为21.61元/MWh&#xff0c;预计出…

如何查看 class 文件的编译器版本

文章目录 原理分析解决方案其它解决方案javap 命令行工具 在平时的 Java 开发中&#xff0c;有时候我们需要知道某个 class 文件是由哪个版本的 Java 编译器编译生成的 原理分析 class 文件&#xff0c;即字节码文件&#xff0c;它有特定的二进制格式&#xff0c;这种格式是由…

『力扣刷题本』:环形链表(判断链表是否有环)

一、题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&am…

接口调用微信公众号群发功能,绕过微信自身限制

微信群发功能要求要微信认证。微信认证要求要企业账号、而且需要认证费用。 本篇文章教大家非微信认证账号如何群发公众号信息 本篇文章基于python语言开发,其他的语言一样的方式,不需要拘泥于语言 注意事项: 要求有微信公众平台登陆状态,也就是Cookie数据, 如何通过Py…

统信UOS通过源码安装软件提示“configure: error: cannot run C compiled programs.”错误

1. 问题说明 使用源码的方式安装git软件&#xff0c;安装过程中出现两个错误。 编译错误“cannot run C compiled programs” XC:~/Downloads/git-2.42.1$ ./configure --prefix/home/software/git-2.42.1 configure: Setting lib to lib (the default) configure: Will try…

消息消费过程

前言 本文介绍下Kafka消费过程, 内容涉及消费与消费组, 主题与分区, 位移提交&#xff0c;分区再平衡和消费者拦截器等内容。 消费者与消费组 Kafka将消费者组织为消费组, 消息只会被投递给消费组中的1个消费者。因此, 从不同消费组中的消费者来看, Kafka是多播(Pub/Sub)模式…

腾讯云服务器公网带宽速度怎么样?上传下载实测!

腾讯云服务器公网带宽下载速度计算&#xff0c;1M公网带宽下载速度是128KB/秒&#xff0c;5M带宽下载速度是512KB/s&#xff0c;腾讯云10M带宽下载速度是1.25M/秒&#xff0c;腾讯云百科txybk.com来详细说下腾讯云服务器不同公网带宽实际下载速度以及对应的上传速度对照表&…

二十九、W5100S/W5500+RP2040树莓派Pico<Web socket Server>

文章目录 1 前言2 简介2 .1 什么是WebSocket协议&#xff1f;2.2 WebSocket协议工作原理2.3 WebSocket协议优点2.4 WebSocket应用场景 3 WIZnet以太网芯片4 WebSocket示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接…

鸿蒙:Harmony开发基础知识详解

一.概述 工欲善其事&#xff0c;必先利其器。 上一篇博文实现了一个"Hello Harmony"的Demo&#xff0c;今天这篇博文就以Demo "Hello Harmony" 为例&#xff0c;以官网开发文档为依据&#xff0c;从鸿蒙开发主要的几个方面入手&#xff0c;详细了解一下鸿…

macOS 后台项目已添加 “Google Updater添加了可在后台运行的项目。你可以在“登陆项”设置中管理

文章目录 Intro解决查看三个文件夹分析 & 操作确认结果是否生效 Intro 我的macbook上经常弹出这样的通知狂&#xff1a; macOS 后台项目已添加 “Google Updater添加了可在后台运行的项目。你可以在“登陆项”设置中管理 不胜其扰&#xff0c;终于决定禁用它。以下为方法…

算法设计与分析【期中+期末复习知识点总结】(持续更新)

第1章&#xff1a;算法概述 算法&#xff1a;具有输入、输出、确定性、有限性。 程序&#xff08;算法数据结构程序&#xff09;&#xff1a;具有输入、输出、确定性&#xff08;注意&#xff1a;程序可以不满足有限性&#xff0c;如操作系统是在无限循环中执行的程序&#x…

es的优势

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目…

教程:使用 Keras 优化神经网络

一、介绍 在 我 之前的文章中&#xff0c;我讨论了使用 TensorFlow 实现神经网络。继续有关神经网络库的系列文章&#xff0c;我决定重点介绍 Keras——据说是迄今为止最好的深度学习库。 我 从事深度学习已经有一段时间了&#xff0c;据我所知&#xff0c;处理…

橱柜的装修干货|板材、五金、高度、配色4个方面。福州中宅装饰,福州装修

引言 橱柜的装修干货。 橱柜是厨房的核心&#xff0c;一个好的橱柜能让厨房变得实用又美观。以下是关于橱柜装修的几个问题解答。 1. 橱柜的柜门常用的板材有哪些&#xff1f; 橱柜的柜门常用的板材有实木板、防火板、烤漆板、包复框、PVC板、膜压板等。不同板材有不同的特点…

庖丁解牛:NIO核心概念与机制详解 01

文章目录 Pre输入/输出Why NIO流与块的比较通道和缓冲区概述什么是缓冲区&#xff1f;缓冲区类型什么是通道&#xff1f;通道类型 NIO 中的读和写概述Demo : 从文件中读取1. 从FileInputStream中获取Channel2. 创建ByteBuffer缓冲区3. 将数据从Channle读取到Buffer中 Demo : 写…

五、hdfs常见权限问题

1、常见问题 2、案例 &#xff08;1&#xff09;问题 &#xff08;2&#xff09;hdfs的超级管理员 &#xff08;3&#xff09;原因 没有使用Hadoop用户对hdfs文件系统进行操作。 在Hadoop文件系统中&#xff0c;Hadoop用户相当于Linux系统中的root用户&#xff0c;是最高级别用…

win10电脑无法联网,设置IPv4,点击属性无法打开,闪退

win10设置IPv4&#xff0c;点击属性无法打开&#xff0c;闪退 问题:win10设置IPv4&#xff0c;点击属性无法打开&#xff0c;闪退 问题:win10设置IPv4&#xff0c;点击属性无法打开&#xff0c;闪退 第1步&#xff1a;用管理员打开cmd命令窗口&#xff0c;然后输入下面的命令&…

JVM的运行时数据区

Java虚拟机&#xff08;JVM&#xff09;的运行时数据区是程序在运行过程中使用的内存区域&#xff0c;主要包括以下几个部分&#xff1a; 程序计数器虚拟机栈本地方法栈堆方法区运行时常量池直接内存 不同的虚拟机实现可能会略有差异。这些区域协同工作&#xff0c;支持Java…

BUUCTF snake 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 下载附件&#xff0c;解压得到一张snake的图片。 密文&#xff1a; 这里有一张蛇的图片&#xff0c;本人害怕不敢放&#xff0c;想看自己下载附件解压。&#xff08;吐槽一下&#xff0c;我做这道题&#xff0c;全…