本文部分内容引自:
go-mod
go module是Go1.11版本之后官方推出的版本管理工具,其目标是取代旧的的基于GOPATH方法来指定在工程中使用哪些源文件或导入包。并且从Go1.13版本开始,go module将是Go语言默认的依赖管理工具。
要启用go module支持,首先要设置环境变量GO111MODULE,通过它可以开启或关闭模块支持,它有三个可选值:off、on、auto,默认值是auto。
- GO111MODULE=off:禁用模块支持,编译时会从GOPATH和vendor文件夹中查找包。
- GO111MODULE=on:启用模块支持,编译时会忽略GOPATH和vendor文件夹,只根据 go.mod下载依赖。
- GO111MODULE=auto:当项目在$GOPATH/src外,且项目根目录有go.mod文件时,开启模块支持。
通过以下命令可以进行设置或修改:
go env -w GO111MODULE="auto"
go env -w GO111MODULE="on"
go env -w GO111MODULE="off"
1. 传统的包管理方式-package
在Go1.11之前,如果想要编写Go代码以及引入第三方包,需要将源代码写在GOPATH/src目录下。即开发者只能将研发的项目放到GOPATH目录下。同时,将引入的第三方包会下载到GOPATH/pkg目录下。我们先来看下在这种包管理模式下,使用go get是如何安装依赖包的,然后再分析这种包管理的不足。
1.1 go get的工作流程
我们以在项目中引入http://github.com/mattn/go-sqlite3为例。在项目中使用import导入该包:
import (
"github.com/mattn/go-sqlite3"
)
然后我们需要使用go get命令将该包下载下来:
go get github.com/mattn/go-sqlite3
运行go get命令后,Go会访问http://github.com/mattn/go-sqlite3并下载该包。一旦下载完成,该包就会被保存到$GOPATH/pkg/github.com/mattn/go-sqlite3目录下。
那么从执行go get命令到包被保存到对应的目录期间,go get都经历了哪些过程呢?
首先,Go会将包拼接成https协议的URL地址。这里是http://github.com/mattn/go-sqlite3。Go的第三方包是存储在像GIT或SVN这样的在线版本控制管理系统上的。Go目前支持的在线版本管理类型如下:
Bazaar .bzr
Fossil .fossil
Git .git
Mercurial .hg
Subversion .svn
所以,在示例中,Go首先会解析http://github.com/mattn/go-sqlite3.git。
其次,根据支持的协议依次尝试clone该包。若该在线版本管理系统支持多种协议,那么Go会依次尝试。例如,Git支持https://和git+ssh://协议,那么Go会依次使用对应的协议进行解析该包。如果Go成功解析了对应的URL地址,那么该包将会被clone并保存到$GOPATH/pkg目录下。
最后,若版本管理系统不是Go所支持的,则尝试查找META信息。在这种场景下,Go也会试图使用https或http协议拼装成的URL地址去解析。并从返回的HTML代码中查找META信息:
<meta name="go-import" content="import-prefix type repo-root">
- import-prefix: 这是模块所导入的路径。在我们的示例中是github.com/mattn/go-sqlite3。
- type:在线版本管理系统的类型。可以是上面我们提到的Go支持的类型之一。在我们的示例中是git。
- repo-root:代码仓库在版本控制系统中的根URL地址。例如,在我们的示例中,应该是github.com/mattn/go-sqlite3。
根据读取到的meta信息,Go就可以从http://github.com/mattn/go-sqlite3中克隆该项目代码,并将其保存到本地的$GOPATH/src目录下的github.com/mattn/go-sqlite3。
到此,我们已经了解了传统的包管理的工作方式了。下面我们来看看这种管理方式有哪些缺点。
1.2 传统包管理方式的不足
首先,所有的项目都必须在GOPATH/src指向的目录下,或者必须更改GOPATH环境变量所指向的目录。我们以两个项目A、B来举例说明。假设当前的GOPATH=/usr/local/goworkspace/。如果保持GOPATH不变的话,那么A、B两个项目的源代码都必须要放到GOPATH的目录下,即/usr/local/goworkspace/src目录下。同时,A和B项目引入的第三方包都会在GOPATH/pkg目录下。这样两个项目其实就是混合在一起。
如果不想混合在一起怎么办呢?那就只能更改GOPATH的目录。假设我们现在在研发A项目,并将其工作目录放在/usr/local/goworkspace/a目录下,GOPATH=/usr/local/goworkspace/a。但是在开发B项目时,更改GOPATH的指向,例如我们这里使用/usr/local/goworkspace/b目录下。这样两个项目的源代码以及依赖的第三方包就在各自项目下了。 但同时如果想继续修改A项目的代码时,就需要再将GOPATH目录更改到指向A项目的目录中,即GOPATH=/usr/local/goworkspace/src目录。
其次,对于依赖的同一个包只能从master分支上导入最新的提交,且不能导入包的指定的版本。假设我们有一个第三方包redis,项目A首次引入该包时,使用go get命令从代码库的master分支下载当前最新的代码,并将该包保存在本地的GOPATH/pkg目录下。之后redis包有了新的提交,但同时也引入了一个bug。如果项目A升级或重新安装该包时,使用go get命令并没有指定特定版本的参数,还是从该包的代码库的master分支中下载该包,也就造成了向后不兼容。另外,升级或重新安装的包也会被安装到GOPATH/pkg下的相同目录,因为没有版本的管理,所以会覆盖之前。
好了,以上就是在传统的包管理方式中的两大主要不足之处。那么针对这些不足,我们来看看Go的module是如何解决的。
2. 现代包管理方式-module
2.1 什么是module
一个module就是一个包含多个package的目录,即一个package的集合。其要实现的目标如下:
- 首先,研发者应该能够在任何目录下工作,而不仅仅是在GOPATH指定的目录。
- 可以安装依赖包的指定版本,而不是只能从master分支安装最新的版本。
- 可以导入同一个依赖包的多个版本。当我们老项目使用老版本,新项目使用新版本时会非常有用。
- 要有一个能够罗列当前项目所依赖包的列表。这个的好处是当我们发布项目时不用同时发布所依赖的包。Go能够根据该文件自动下载对应的包。
一个module也是可以像package一样共享的。因此,module也必须是一个git仓库或其他Go可支持的代码控制系统。因此,Go的建议是:
- 一个module必须是一个代码控制系统的仓库,并且一个仓库应该只能包含一个module。
- 一个module应该包含一个或多个package。
- 一个包应该在同一个目录下包含一个或多个go文件。
2.2 如何创建module
第一,我们在GOPATH之外的任何位置创建一个目录。比如:Test_Go_Project。如下所示:
ph@ph-ThinkBook-14-G2-ITL:~$ mkdir Test_Go_Project
ph@ph-ThinkBook-14-G2-ITL:~$ cd Test_Go_Project/
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$ pwd
/home/ph/Test_Go_Project
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$ go env | grep "GOPATH"
GOPATH="/home/ph/go"
可以看到,我们在GOPATH之外建立了一个工程目录。
第二,在本地的目录下执行go mod init 命令来初始化Go module。如下所示:
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$ go mod init main_test
go: creating new go.mod: module main_test
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$
该命令会在Test_Go_Project的根目录下创建go.mod文件,go.mod文件会包含我们定义的module的导入路径和依赖的包及对应的版本。如下所示:
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$ ls
go.mod
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$ cat go.mod
module main_test
go 1.16
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$
由上可知,在生成的go.mod文件中显示了该module可被导入的路径以及Go的版本。因为目前还没有导入任何其他依赖包,所以没有显示导入包的信息。
2.3 如何使用第三方module
在工程路径下创建一个main_test.go文件,在该module下要想使用模块下的包,则需要引入和安装两个步骤。在文件中使用import语句引入包,代码如下:
ackage main
import (
"database/sql"
"fmt"
_ "github.com/mattn/go-sqlite3"
"os"
"flag"
)
const (
dbDriverName = "sqlite3"
)
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func main() {
var dbName string
var SbomFile string
flag.StringVar(&dbName, "db", "/etc/sbom/ding_sbom.db", "-db database file")
flag.StringVar(&SbomFile, "sbom", "/etc/sbom/DingOS-sbom.spdx", "-sbom target sbom file")
flag.Parse()
fmt.Println(dbName)
fmt.Println(SbomFile)
//open database
db, err := sql.Open(dbDriverName, dbName)
checkErr(err)
existed, err := pathExists(SbomFile)
checkErr(err)
if existed == true {
//delete old sbom file first
err = os.Remove(SbomFile)
checkErr(err)
}
db.Close()
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
第一步,使用import引入模块下具体的包。因为在encodex的module中,我们设置的引入路径是http://github.com/goxuetang/encodex,即go.mod文件的第一行。hash包是encodex模块下的一个包。所以我们引入的完整路径是:"github.com/mattn/go-sqlite3"
import "github.com/mattn/go-sqlite3"
第二步,使用go get命令安装引入的包。
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$ go get "github.com/mattn/go-sqlite3"
go: downloading github.com/mattn/go-sqlite3 v1.14.15
go get: added github.com/mattn/go-sqlite3 v1.14.15
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$
同时,go get会将引入的包加在go.mod文件中。require中不仅有包名,还有对应的版本号。如下图所示:
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$ cat go.mod
module main_test
go 1.16
require github.com/mattn/go-sqlite3 v1.14.15 // indirect
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$
好,我们现在来看另外一个问题,下载下来的包存在哪里了。
2.5 module存储位置
当go get将包下载下来后,会将其存储到GOPATH/pkg/mod目录下。通过go env可以查看GOPATH环境变量的具体指向目录,笔者环境下的GOPATH=/home/ph/go,如下是上节中引入的go-sqlite3模块。如下所示:
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$ sudo find /home/ph/ -name "go-sqlite3"
/home/ph/go/pkg/mod/cache/download/github.com/mattn/go-sqlite3
ph@ph-ThinkBook-14-G2-ITL:~/Test_Go_Project$