golang工程建立

本文介绍了Go语言的包管理方式,包括传统的基于GOPATH的包管理和现代的go module。传统方式有项目目录受限、无法指定包版本等不足。go module是Go 1.11后官方推出的版本管理工具,可在任意目录工作、指定包版本等,还介绍了其创建、使用及存储位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文部分内容引自:

手把手教你如何创建及使用Go module - 知乎

ubuntu下GoMod和GoVendor命令-爱码网

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$ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝天居士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值