命令记不住

登陆

Go Mod 代码结构化管理

Go Mod

1.Go Mod设置

Go mod模块是相关Go包的集合。modules源代码交换和版本控制的单元。go命令直接支持使用modules,包括记录和解析对其他模块的依赖性。modules替换旧的基于GOPATH的方法来指定在给定构建中使用哪些源文件

设置 GO111MODULE
off
	go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。
on
	go命令行会使用modules,而一点也不会去GOPATH目录下查找。
auto
	默认值,go命令行将会根据当前目录来决定是否启用module功能,当modules 功能启用时,依赖包的存放位置变更为$GOPATH/pkg,允许同一个package多个版本并存,且多个项目可以共享缓存的 module

2.go mod 命令

go mod 有以下命令来管理包

命令 说明
download download modules to local cache(下载依赖包)
edit edit go.mod from tools or scripts(编辑go.mod)
graph print module requirement graph (打印模块依赖图)
init initialize new module in current directory(在当前目录初始化mod)
tidy add missing and remove unused modules(拉取缺少的模块,移除不用的模 块)
vendor make vendored copy of dependencies(将依赖复制到vendor下)
verify verify dependencies have expected content (验证依赖是否正确)
why explain why packages or modules are needed(解释为什么需要依赖)

3.go mod 创建项目

在GOPATH 目录之外新建一个目录,并使用go mod init 初始化生成go.mod文件

go mod init $ProjectName

go.mod文件一旦创建后,它的内容将会被go toolchain全面掌控。go toolchain会在各类命令执行时,比如go get、go build、go mod等修改和维护go.mod文件

go.mod 提供了module, require、replace和exclude 四个命令

  • module 语句指定包的名字(路径)
  • require 语句指定的依赖项模块
  • replace 语句可以替换依赖项模块
  • exclude 语句可以忽略依赖项模块
package main
import "github.com/astaxie/beego"
func main(){
beego.Run()
}
  1. 执行 go run test.go 运行代码会发现 go mod 会自动查找依赖自动下载并完成
  2. go mod tidy,使用mod文件中的内容进行包拉取

4.replace替换依赖项模块

replace protoes => C:\Users\vSphere\go\src\grpc\grpcdemo\protoes

go.mod

module server

go 1.14

replace protoes => C:\Users\vSphere\go\src\grpc\grpcdemo\protoes

require (
	github.com/golang/protobuf v1.4.2 // indirect
	golang.org/x/net v0.0.0-20190311183353-d8887717615a
	google.golang.org/grpc v1.29.1
)

5.解决gomod网络问题

网络问题导致不能正常访问golang.org/x下的包,可以使用replace命令来解决问题

错误信息中会有该包的版本号,如果github正常,golang不正常,使用replace命令将github.com/golang代替golang.org/x,保持两边的版本信息一致

go mod edit -replace=golang.org/x/net@xxxxxxxx119x=github.com/golang/net@xxxxxxxxx119a

重新再运行go mod tidy就会发现这一个包解决了,如果包多的话,重复使用replace命令

go mod edit -replace=old[@v]=new[@v]

整个项目的依赖包都下载在$GOPATH/mod目录下

6.导入外部安装包

包安装的先决条件是要自动处理包自身依赖关系,被依赖的包也会安装到子目录中

使用go install的包安装在$GOPATH/pkg/目录下,此命令一般会生成静态库文件,放在GOPATH/pkg/目录下,文件扩展名为a

7.repalce的使用场景

(1)替换无法下载的包

一般会分别引用多个路径包,使用replace来让项目使用GitHub上相应的镜像包,添加一条新的replace条目

replace (
    github.com/google/uuid  v1.1.1 =>   github.com/google/uuid v1.1.0
    golang.org/x/text v0.3.2 => github.com/golang/text v0.3.2

go.mod文件中定义的module还是module golang.org/x/text,这个module名字直接决定了import的路径

(2)调试依赖包

有时我们需要调试依赖包,此时就可以使用replace来修改依赖:

replace (
github.com/google/uuid v1.1.1 => ../uuid
golang.org/x/text v0.3.2 => github.com/golang/text v0.3.2    
)

使用本地的uuid来替换依赖包,此时,可以任意地修改../uuid目录的内容来进行调试

(3)使用fork仓库

fork仓库仅仅是临时的做法,一旦开源版本变得可用,则需要尽快切换到开源版本

(4)禁止被依赖

module不希望被直接引用,由于不存在0.0.0版本,所以其他项目直接依赖时会因为无法找到版本而无法使用,replace指令在当前模块不是main module时会被自动忽略。

8.依赖包存储

GOPATH模式下不方便使用同一个依赖包的多个版本。在GOMODULE模式下这个问题得到了解决,因为这两种模式下依赖包的存储位置发生了变化。

在GOPATH模式下,依赖包存储在GOPATH/src下,该目录下只保存特定依赖包的一个版本,而在GOMODULE模式下,依赖包存储在GOPATH/pkg/mod下,该目录可以存储特定依赖包的多个版本

需要注意的是,GOPATH/pkg/mod目录下有一个cache目录,它用来存储依赖包的缓存,简单来说,go命令每次下载新的依赖包都会在该cache目录中保存一份。

GO111MODULE环境变量控制具体模式:

  • export GO111MODULE=off 切换到GOPATH模式
  • export GO111MODULE=on 切换到GOMODULE模式
GO111MODULE="on"
GOMODCACHE="/home/administrator/go/pkg/mod"
GOPATH="/home/administrator/go"
1.GOPATH依赖包存储

在GOPATH模式下,go get 命令会将依赖包下载到GOPATH/src/google目录下

该命令等同于在GOPATH/src/google目录下执行git clone https://github.com/google/uuid.git ,也就是说GOPATH/src/google/uuid目录下存储的是完整的仓库

2.GOMODULE依赖包存储

在GOMODULE模式下,go get命令会将依赖包下载到GOPATH/pkg/mod目录下,并且按照依赖包的版本分别存放。注:go get 命令不指定特定版本时,默认会下载最新版

想较于GOPATH模式,GOMODULE有两处不同点:

  • 依赖包的目录中包含了版本号,每个版本占用一个目录
  • 依赖包的特定版本目录中只包含依赖包文件,不包含.git目录

在GOMODULE模式下,只需要下载模块的代码文件,而不必克隆整个仓库

3.包名大小写敏感问题

包名中包含大写字母,在GOMODULE模式下,在存储时会将包名做大小写编码处理,每个大写字母将变成“!+相应的小写字母”,例如“GOPATH/pkg/mod/github.com/!azure”目录中

9.模块代理GOPROXY

GOPROXY,使用 go mod download命令观察请求代理服务器的信息:

go mod download -x -json  github.com/google/uuid@lastes

- x参数表示打印下载细节,打印信息中的“Dir”并非是上面提到的缓存目录,而是GOPATH/pkg/mod.

10.GOSUMDB工作机制

默认是官方的checksum database,既教研和数据库sum.golang.org

11.私有模块

环境变量GOPRIVATE用于指定私有模块的前缀,多个模块前缀间使用逗号分隔:

GOPRIVATE= *.corp.example.com,rsc.io/private

按照此配置,go命令将把所有拥有这些前缀的模块都视为私有,不会从代理服务器下载代码,也不会使用校验服务器来检查下载的代码。比如模块git.corp.example.com/xyzzy、rsc.io/private、rsc.io/private/quux都将被视为私有

私有代理场景

部分企业或许会自建代理服务器,同时代理公开和私有的模块,这时除了使用环境变量GOPRIVATE,可能还需要GONOPROXY和GONOSUMDB来做更精细化 的控制

GONOPROXY和GONOSUMDB的配置格式与GOPRIVATE相同,分别用于指定绕过模块代理、模块校验的前缀。实际上,GOPRIVATE只是GONOPROXY和GONOSUMDB的简便配置,设置GOPRIVATE相当于同时设置了GONOPROXY和GONOSUMDB。

在GO命令行的内部实现中,GONOPROXY和GONOSUMDB的默认值均为GOPRIVATE;

GOPRIVATE  = Getenv("GOPRIVATE")  
GONOPROXY  = envOr("GONOPROXY",GOPRIVATE)
GONOSUMDB  = envOr("GONOSUMDB",GOPRIVATE)

go命令字啊决定是否从代理服务器上下载模块时会参考GONOPROXY的配置,在决定是否校验模块时会参考GONOSUMDB的配置,所以一旦显式地配置了 GONOPROXY和GONOSUMDB,将覆盖GOPRIVATE的配置

12.vendor解决离线工程问题

vendor目录可以简单理解成私有的GOPATH目录。项目编译时,编译器会优先从vendor中寻找依赖包,如果vendor中找不到,那么再到GOPATH中寻找

为了固化使用到的golang.org/x/crypto/ssh和github.com/pkg/sftp版本,可以使用vendor机制

在项目example-gsftp根目录下创建一个vendor目录,并把golang.org/x/crypto/ssh和github.com/pkg/sftp存放到此目录中,让其成为项目的一部分

example-gsftp
|____vendor/
        |____golang.org/x/crypto/ssh/
        |____github.com/pkg/sftp/

编译时不会受到GOPATH目录的影响,即便GOPATH下也有一个同名但不同版本的依赖包

13..Go程序的编译

go install用于编译并安装指定的代码包以及它们的依赖包,并且将编译后生成的可执行文件放到bin目录下(GOPATH/bin),编译后的包文件放在当前工作区的平台相关目录下。

编译加密

对外发布编译文件时,如果不希望被人看到源代码,可使用go build -ldflags命令,设置编译参数-ldflags"-w -s"再编译后发布,这样gdb调试时无法看到源码。

设置编译参数-ldflags "-w -s"。其中-w为去掉调试信息(无法使用gdb调试),-s为去掉符号表.

go build -ldflags "-w -s" ./hello.go

参考:go build -ldflags 参数及 gdb 调试

14.导入项目下的目录包

我们在main目录同级创建一个新的目录cpmpute,在compute目录下新建add.go文件

目录结构:

#tree
.
├── compute
│   └── add.go
├── concurrency
│   ├── channel.go
│   └── first_concurrency.go
├── main
│   ├── hello.go
│   └── main.go
├── reflection
│   └── reflection.go
└── synch
    ├── mutex.go
    ├── rw_mutex.go
    ├── sync_map.go
    └── wait_group.go

add.go

package compute

type AddOperator interface {

	/**
	 * 算术增加
	 */
	Add() interface{}
}

type IntParams struct {
	P1 int // 加数
	P2 int // 被加数
}

func (params *IntParams) Add() interface{} {
	return params.P1 + params.P2
}

ma in.go

package main

import (
	"fmt"
	"github.com/longjoy/micro-go-book/ch4-feature/compute"
)

func main() {

	params := &compute.IntParams{
		P1: 1,
		P2: 2,
	}
	fmt.Println(params.Add())

}

通过import引入了xxx/compute包,接着声明并初始化,调用Add()

.

.

推荐阅读