如何创建go项目
1、go的环境变量有哪些?分别是什么含义?
go的环境变量可以通过go env
进行查看,查看指定的环境变量是go env GOROOT
(查看go的安装目录),设置环境变量使用 go env -w GOPROXY=....
go中的环境变量有很多,但是很多我们可能都用不到,重点了解以下几个:
环境变量 | 含义 | LINUX默认 | WIN默认 |
---|---|---|---|
GOROOT | go语言的安装目录,用来索引go安装目录下的资源 | $/usr/local/go | $C:\Program Files\Go |
GOPATH | 工作空间,即开发区,存放项目源代码 | $HOME/go | $C:\Users%UserName%\go |
GOBIN | 存放项目编译后生成的可执行文件 | $GOPATH/bin | $GOPATH/bin |
GOOS | 为其编译代码的操作系统,实现跨平台编译 | linux | windows |
GOARCH | 为其编译代码的架构或处理器,实现跨平台编译 | arm(举例) | amd64(举例) |
GOPROXY | 下载依赖时使用的代理地址列表 | proxy.golang.org,direct | proxy.golang.org,direct |
GO111MODULE | go Modules的开关 | 默认空字符串 | 默认空字符串 |
GOMODCACHE | 存储go下载的外部依赖模块文件的目录 | $GOPATH/pkg/mod | $GOPATH/pkg/mod |
GOCACHE | 存放go项目在构建过程中产生的缓存 | $HOME/.cache/go-build | $C:\Users%UserName%\AppData\Local\go-build |
GOENV | 存放go环境变量的值的配置文件 | $HOME/.config/go/env | $C:\Users%UserName%\AppData\Roaming\go\env |
2、go get
、go install
、go build
区别?
(1)go get
用于从远程仓库上下载并安装代码包。获取的包放在$GOPATH/src/
目录下,1.17后,go get仅用来下载库和更新mod文件,并不会安装。
(2)go install
用来编译并安装代码包或者源码文件的。分为三个阶段:
在GOPATH年代,作用分两步:第一步是生成结果文件(可执行文件或.a包),第二步会把编译好的结果移到$GOPATH/pkg/
或$GOPATH/bin/
。其中可执行文件一般是带main函数的文件(简称main包)产生的,放在bin
里;.a应用包一般是不含main文件(简称普通包)产生的,放在pkg
里。
在Go Modules年代,分两种情况:Module没启用时,和GOPATH年代的作用一样;Module启用时,go install对普通包不再安装了,即没有了$GOPATH/pkg/
,对main包会将生成的可执行文件安装到$GOBIN
目录下,默认值就是$GOPATH/bin/
。
1.16及以后,go install 可以接受带有版本后缀的参数(例如 go install example.com/cmd@v1.0.0)。这将导致 go install 以模块感知模式构建和安装包,而忽略当前目录或任何父目录(如果有)中的 go.mod 文件。这对于在不影响主模块依赖性的情况下安装可执行文件很有用。
(3)go build
用来测试编译。如果是普通包,执行命令后不会产生任何文件,如果是main包,执行命令后会在当前目录下生成一个可执行文件。如果需要在$GOPATH/bin
目录下生成相应的exe文件,需要执行go install 或者使用 go build -o 路径/可执行文件。
第三方依赖存放位置
当前比较新的Go版本获取第三方依赖主要有两种方式,一种是go get获取,另一种是go mod获取。需要说明的是依赖包的存储位置:
(1)使用go get获取的包在GO111MODULE="auto" | "off"
模式下是放在$GOPATH/src/
目录下的,但是在GO111MODULE="on"
模式下下载的依赖包存放在$GOPATH/pkg/mod/
目录下;
(2)使用go mod下载的依赖包放在$GOPATH/pkg/mod/
目录下
3、什么是GOPATH?优缺点是什么?
what
GOPATH 是Golang 1.15版本之前一个重要的环境变量配置,是存放 Golang 项目代码的文件路径。在1.18版本之后,如果不指定GOPATH,则是默认的(见上面表格)。GOPATH可以理解为go语言的工作空间,内部有三个文件夹:
(1)bin 存放编译生成的二进制文件;
(2)pkg,其中pkg下面有三个文件夹:
XX_amd64: 其中 XX 是目标操作系统,比如 mac 系统对应的是darwin_amd64, linux 系统对应的是 linux_amd64,存放的是.a结尾的文件。
mod: 当开启go Modules 模式下,go get命令缓存下依赖包存放的位置
sumdb: go get命令缓存下载的checksum数据存放的位置
(3)src存放项目代码的位置
优点
相比于其他语言繁琐的配置,go语言中的工作空间
gopath
配置相对简单,容易理解gopath使得在文件系统组织整个代码更加简洁、结构化,但是限制在单一的工作空间中。
缺点
必须指定目录
go get命令的时候无法指定获取的版本(在GOPATH时代go get没有版本控制概念)
无法处理多版本号
无法同步一致第三方版本号
依赖项列表无法数据化
【解释一下最后一点】在Go Modules之前,没有任何语义化的数据可以知道当前项目的所有依赖项,需要手动找出所有依赖。对项目而言,需要将所有的依赖项全部放入源代码控制中。如果剔除某个依赖,需要在源代码中手工确认某个依赖是否剔除。
4、为什么引入Go Modules?它是什么?
why
为了解决GOPATH的缺陷,Go官方和社区推出许多解决方案,比如godep、govendor、glide等,这些工具要么未彻底解决GOPATH存在的问题要么使用起来繁冗,这才催生了Go Modules的出现。
what
Go modules 是 Go 语言的依赖解决方案,发布于 Go1.11,成长于 Go1.12,丰富于 Go1.13,正式于 Go1.14 推荐在生产上使用。
Go moudles 目前集成在 Go 的工具链中,只要安装了 Go,自然而然也就可以使用 Go moudles 了,而 Go modules 的出现也解决了在 Go1.11 前的几个常见争议问题:
Go 语言长久以来的依赖管理问题。
“淘汰”现有的 GOPATH 的使用模式。
统一社区中的其它的依赖管理工具(提供迁移功能)。
命令
命令 | 作用 |
---|---|
go mod init | 生成 go.mod 文件 |
go mod download | 下载 go.mod 文件中指明的所有依赖 |
go mod tidy | 整理现有的依赖 |
go mod graph | 查看现有的依赖结构 |
go mod edit | 编辑 go.mod 文件 |
go mod vendor | 导出项目所有的依赖到vendor目录 |
go mod verify | 校验一个模块是否被篡改过 |
go mod why | 查看为什么需要依赖某模块 |
开关
Go语言提供了 GO111MODULE 这个环境变量来作为 Go modules 的开关,其允许设置以下参数:
auto:只要项目包含了 go.mod 文件的话启用 Go modules,目前在 Go1.11 至 Go1.14 中仍然是默认值。
on:启用 Go modules,推荐设置,将会是未来版本中的默认值。
off:禁用 Go modules,不推荐设置。
mod文件
在初始化项目时,会生成一个 go.mod 文件,是启用了 Go modules 项目所必须的最重要的标识,同时也是 GO111MODULE 值为 auto 时的识别标识,它描述了当前项目(也就是当前模块)的元信息,每一行都以一个动词开头。
module:用于定义当前项目的模块路径。
go:用于标识当前模块的 Go 语言版本,值为初始化模块时的版本,目前来看还只是个标识作用。
require:用于设置一个特定的模块版本。
exclude:用于从使用中排除一个特定的模块版本。
replace:用于将一个模块版本替换为另外一个模块版本。
indirect 标识表示该模块为间接依赖,也就是在当前应用程序中的 import 语句中,并没有发现这个模块的明确引用,有可能是你先手动 go get
拉取下来的,也有可能是你所依赖的模块所依赖的,情况有好几种。
5、有了Go Modules之后GOPATH是不是可以被舍弃了?
不是的!,GoPath所引出的问题,就是因为第三方类库的包所导致的,所以我们在有了GoModule之后,GoPath和GoModule就分别负责不同的职责,共同为我们的Golang项目服务。
GoPath我们用来存放我们从网上拉取的第三方依赖包。 GoModule我们用来存放我们自己的Golang项目文件,当我们自己的项目需要依赖第三方的包的时候,我们通过GoModule目录下的一个go.mod文件来引用GoPath目录src包下的第三方依赖即可。
这样依赖,既解决了原来只能局限在GoPath目录src包下进行编程的问题,也解决了第三方依赖包难以管理和重复依赖占用磁盘空间的问题。
总而言之,在引入GoModule之后,我们不会直接在GoPath目录进行编程,而是把GoPath作为一个第三方依赖包的仓库,我们真正的工作空间在GoModule目录下。
6、为什么要引入go work?它是什么?
why
目前go都是通过mod进行包管理,包主要分两类,一类是公网上的,直接引入用go mod管理即可。一类是本地包,比如你自己写的包或者下载到本地的包,对于这类包,在go 1.18前,都是通过给go.mod中添加replace来修改引用包的实际路径。这种包引用对于git提交就非常麻烦,每次提交前需要把replace去掉才行。于是go work被发明了出来。 Go 1.18 增加了 workspace 模式的支持,允许开发者同时在多个 module 进行开发。
workspaces 支持开发者同时处理多个 module,而无需为每个 module 编辑 go.mod 文件。解决依赖关系时,工作空间中的每个 module 都被视为 root module。
此前,当我们需要将功能添加到一个 module 并在另一个 module 中使用它,我们有两个选项:
将更改发布到第一个 module;
使用
replace
编辑存在依赖 module 的 go.mod 文件以获取本地未发布的更改。当然,为了随后发布没有错误,我们还需要在发布本地更改后删除replace
。
有了 workspaces,我们可以使用 workspace 根目录中的 go.work 文件来控制所有依赖项。 go.work 文件具有覆盖各个 go.mod 文件的 use
和 replace
指令,因此无需单独编辑每个 go.mod 文件。
what
go work 即工作空间,就是一个目录(文件夹),里边有一个go.work 文件。
运行 go work init
即可创建一个 workspace,用空格分隔的一组module目录作为参数。workspace 不需要包含正在使用的 module。 init
命令会创建一个列出 workspace 中所有 module 的 go.work 文件。如果运行不带参数的 go work init
,该命令会创建一个空工作区。
要将 module 添加到 workspace,请运行 go work use [module directory]
或手动编辑 go.work 文件。运行 go work use -r
将以递归方式将参数中所有带有 go.mod 的目录添加到 workspace 中。如果一个目录没有 go.mod 文件,或者不存在,那么 use
指令将把该目录的从 go.work 文件中删除。
go.work 语法类似于 go.mod 文件,包含以下指令:
go
:go 版本,例如 1.18use
:将磁盘上的 module 添加到 workspace 的 main module 中。它的参数是包含 go.mod 目录的相对路径。replace
:与 go.mod 文件中的 replace 指令类似,go.work 文件中的 replace 指令将 module 的特定版本,或所有版本进行替换。
实战
Golang 1.18 workspaces 尝鲜 - 掘金 (juejin.cn)
7、如何创建Go项目?
开发环境的准备
GOPROXY配置
默认值是:https://proxy.golang.org,direct
因为国内网络原因,需要通过配置Go module镜像代理来加速安装,可以选择下面的值:
七牛CDN
https://goproxy.cn,direct
阿里云
https://mirrors.aliyun.com/goproxy/,direct
官方
https://goproxy.io,direct
direct是什么
“direct” 为特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等),当值列表中上一个 Go module proxy 返回 404 或 410 错误时,Go 自动尝试列表中的下一个,遇见 “direct” 时回源,遇见 EOF 时终止并抛出类似 “invalid version: unknown revision…” 的错误。
为什么不直接配置direct,还配一下代理呢?
代理服务器提供了缓存功能:代理服务器可以缓存已下载的模块文件,当其他开发者或同一开发者的其他项目需要相同的模块时,可以从代理服务器快速获取,而无需再次下载。这可以节省带宽和加快模块下载速度。
代理服务器提供了镜像功能:代理服务器可以配置为从不同的镜像源获取模块文件。这对于在某些地区或网络环境中访问特定源可能受限的情况下非常有用。代理服务器可以从可用的镜像源中选择最快、最稳定的源,并提供给开发者。
安全性和验证:代理服务器可以提供额外的安全性功能,如校验下载的模块文件的完整性和认证性。代理服务器可以验证模块的签名,确保下载的模块文件未被篡改,并提供额外的安全保护。
隔离和可控性:通过使用代理服务器,开发者可以更好地隔离项目的依赖关系。代理服务器允许开发者控制项目所使用的特定模块版本,并确保项目的构建和部署的一致性。
尽管 direct
选项提供了直接从模块源地址获取模块文件的能力,但代理服务器在性能、安全性和可控性方面提供了额外的优势。
创建项目目录
(1)前言
一般我们会把项目放置于 $GOPATH/src
目录下。推荐的做法是将 GitHub 用户名作为命名空间,我的xxx项目存放目录是:
cd $GOPATH/src
mkdir -p github.com/Perryen/xxx
cd github.com/Perryen/xxx
这样做除了将项目推送到 GitHub 时项目地址保持一致外,另一个好处是未来随着 Go 项目增多,src
目录仍可保持有序。
同样的道理,go mod init xxxx,这里的xxxx也推荐使用将Github用户名作为命名空间的方式。在已经创建完的路径下直接go mod init即可,可以查看mod文件下的module名就是那样。但如果没有提前创建就需要在init后加上github.com/Perryen/xxx
来创建mod文件。
当然module名是当前包的索引值,也就是说其他包引入同目录下的本地包时就要以module名作为根目路径。所以重点不在于上面怎么去创建目录,而在于如何创建module。下面就是module的命名规范
(2)go mod命名规范
Go Modules 的命名规范是:
模块名称应该是一个有意义的 URI,通常是一个代码仓库的网址,例如 “github.com/user/repo”。
建议使用域名作为模块名称的前缀,以避免名称冲突。
模块名称应该小写字母,不应该包含大写字母、数字、标点符号等。
模块名称不应该以 “go.” 开头,因为这个前缀是 Go 语言保留的。
模块名称不应该与 Go 语言的关键字相同。
请注意,Go Modules 的命名规范是指导性的,不是强制的。但是遵循规范可以帮助您的项目更容易被其他人理解和使用。
(3)Module名、Package名、目录名(路径名)
在创建项目时,先创建目录,再创建module,最后创建Package,三者之间容易混淆,这里来区分一下。
首先我们要知道一个Module是Package的集合,一个Package是多个go文件的集合。通常Package名跟所在目录名同名,当然不强制,但是要尽量一致。然后Package包中go文件import的包是包的路径或者说目录,路径是Module名加上相对路径。所以Module名跟目录又是什么关系呢?其实也没有强制性的关系,通常是将顶级目录名(一个go模块的顶级目录)与Module名相对应,比如Module名是”github.com/Perryen/goblog”,那么在创建项目目录时通常将go模块的顶级目录设为”goblog”,那么”goblog”的上层目录是什么无所谓,可以是”github.com/Perryen”,这样创建mod时直接go mod init即可,就不用写上Module名了,比较方便;当然也可以是”D:/Code”之类的,只不过需要在go mod init时加上Module名。
(4)Go导入本地包
这里谈论的都是Module模式下的。
如果本地包在同一个模块下(也就是俩包共用一个mod),import时:”Module名+相对路径”;
如果本地包不在同一个模块下(也就是俩包各用各的mod),import时,有两种方式:
replace方法
在需求包的mod中声明:(相对路径比如”../xxx”)
require "本地包的module名" v0.0.0
replace "本地包的module名" => "相对路径"
这种方法有个问题,就是在提交需求包这个module的代码到远程仓库时,需要删除最后的replace指令,否则其他开发者下载后会编译报错,因为他们本地可能没有"本地包"这个目录,或者路径不一样。因此就有了下面的方法
workspace方法
为了解决多个有依赖的Module本地同时开发的问题,Go 1.18引入了工作区模式。
该模式下不再需要在
go.mod
里使用replace指令,而是新增一个go.work
文件。先在workspace目录(就是俩包所在的共同的目录)下执行命令生成go.work:
$ go work init 需求包所在目录名 本地包所在目录名
再在需求包中的mod中声明:
require "本地包的module名" v0.0.0
最后在需求包的go文件中import:
import "本地包的module名"
注意:
go.work
不需要提交到代码仓库中,仅限于本地开发使用。
还需要注意的是如果多个文件夹下有同名 package,其实只是彼此无关的 package,如果需要同时使用不同目录下的同名 package,import 时需要为每个目录指定 package 别名:
package main
import (
package1 "project/folder1"
package2 "project/folder2"
)
func main() {
package1.DoSomething()
package2.DoSomethingElse()
}
虽然在import上的路径是可以区分的。但是我们需要使用相应的别名前缀来访问其中的函数、变量或其他成员,所以才引入了别名。通过在导入语句中使用别名,你可以区分并同时使用来自不同目录的同名包。别名可以根据你的喜好进行命名,这里使用了 package1
和 package2
作为示例别名。
总结
总之,Go项目的顶级目录名通常跟Module的repo名一样,Module名通常是 “github.com/user/repo”格式,Package名通常跟所在目录名一样,import同一个模块下的本地包时记得”Module名+相对路径”。
除此之外,同一个项目有多个Module时用go work模式。
本作品采用《CC 协议》,转载必须注明作者和本文链接