Go Module入门及Golang项目组织

1.什么是Go Module?

简而言之,Go Modul是Go在1.12之后官方发布的包管理工具。

现代的语言都有很多好用的包管理工具,如 pip 之于 python,gem 之于 ruby,npm 之于 nodejs。然而 Golang 早期版本却没有官方的包管理器,直到 go1.5 才添加了实验性的 vendor。尽管官方的不作为(保守),还是无法阻止社区的繁荣。社区诞生了许多工具,比较有代表如 govender,glide,gopm,以及半官方的 dep 等。

2.Go Module带来的改变–淡化了GOPATH

在 go1.12 之前,安装 golang 之后,需要配置两个环境变量即GOROOT 和GOPATH。前者是 go 安装后的所在的路径,后者是开发中自己配置的,用于存放go 源代码的地方。在 GOPATH 路径内,有三个文件夹,分别是

  • bin: go 编译后的可执行文件所在的文件夹
  • pkg: 编译非 main 包的中间连接文件
  • src: go 项目源代码

开发的程序源码则放在src里,可以在src里创建多个项目。每一个项目同时也是一个文件夹。

1.12之后,不需要人工配置,系统会以

~/go

目录为默认GOPATH,可以简单理解为Maven的.m2目录。

3.关于package

golang 的所有文件都需要指定其所在的包(package),包有两种类型,一种是 main 包,使用 package main 在代码的最前面声明。另外一种就是 非main 包,使用 package + 包名 。main 包的可以有唯一的一个 main 函数,这个函数也是程序的入口。也只有 main 包可以编译成可执行的文件。
注意:若一目录中有下一级目录,可以认为是一个内嵌的包,这个包与当前包即使包名相同也不是一个包!包名的完整路径是从项目。如

➜  ~ tree go-mod-test
go-mod-test
├── go.mod
└── src
    ├── main.go
    └── pk1
        ├── pk1
        │   └── pk1.go
        └── pk1.go

3 directories, 4 files

src中有个pk1包,pk1包里面有个嵌入的pk1包。三个go文件如下:

//pk1/pk1.go
package pk1

import "fmt"

func Pk1Func()  {
	fmt.Println("pk1.Pk1Func")

}
//pk1/pk1/pk1.go
package pk1

import "fmt"

func Pk1Func()  {
	fmt.Println("pk1.pk1.Pk1Func")

}
package main

import (
	"go-mod-test/src/pk1"
	pk1pk1 "go-mod-test/src/pk1/pk1"
)

func main() {
	pk1.Pk1Func()
	pk1pk1.Pk1Func()

}

注意:main.go在调用是两个pk1都用了全路径,由于连个包名字都是pk1,main在导入是冲突需要引入别名pk1pk1。

4.关于Module

原理就不赘述,大家留心main.go引入pk1用的

go-mod-test/src/pk1

。注意前面的『“go-mod-test”』这个准确说是当前项目的父Module。在项目路径里通过执行

go mod init go-mod-test

来生成go.mod,即初始化Module,内容就两行,模块名及go版本。

➜  go-mod-test cat go.mod
module go-mod-test

go 1.14

此时我们在main.go加入依赖,如

 _ "github.com/gin-gonic/gin"

然后我们运行

➜  go-mod-test go mod tidy
go: finding module for package github.com/gin-gonic/gin
go: found github.com/gin-gonic/gin in github.com/gin-gonic/gin v1.6.2

就是同该命令来更新依赖,添加需要的,移除多余的,更新结果保存在go.mod以及go.sum。

go.mod可以加入版本管理,go.sum不建议,避免跨平台校验可能出错,而且执行go mod tidy可以自动生成。

➜  go-mod-test tree .
.
├── go.mod
├── go.sum
└── src
    ├── main.go
    └── pk1
        ├── pk1
        │   └── pk1.go
        └── pk1.go

go.mod文件比初始化后多了一行依赖记录,版本号是自动更新的,也可以手动修改该文件指定版本号。

➜  go-mod-test cat go.mod
module go-mod-test

go 1.14

require github.com/gin-gonic/gin v1.6.2

go.sum保存了依赖的传递以及其版本号、hash值

➜  go-mod-test cat go.sum
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
...省略N行...
h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

到目前为止,我们用了两条命令,即

go mod init module-name
go mod tidy

下面看看go mod 有哪些功能

➜  go-mod-test go mod help
Go mod provides access to operations on modules.

Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.

Usage:

	go mod <command> [arguments]

The commands are:

	download    download modules to local cache
	edit        edit go.mod from tools or scripts
	graph       print module requirement graph
	init        initialize new module in current directory
	tidy        add missing and remove unused modules
	vendor      make vendored copy of dependencies
	verify      verify dependencies have expected content
	why         explain why packages or modules are needed

Use "go help mod <command>" for more information about a command.

原来命令也不多,除了刚才的两个另外关注

go mod download

看名字指定是下载依赖到本地缓存,那本地保存在哪呢?我们想起了前面说到的默认GOPATH

➜  ~ tree $GOPATH -L 4
/Users/alexhu/go
└── pkg
    ├── mod
    │   ├── cache
    │   │   └── download
    │   ├── github.com
    │   │   ├── davecgh
    │   │   ├── gin-contrib
    │   │   ├── gin-gonic
    │   │   ├── go-playground
    │   │   ├── golang
    │   │   ├── google
    │   │   ├── json-iterator
    │   │   ├── leodido
    │   │   ├── mattn
    │   │   ├── modern-go
    │   │   ├── pmezard
    │   │   ├── stretchr
    │   │   └── ugorji
    │   ├── golang.org
    │   │   └── x
    │   └── gopkg.in
    │       ├── check.v1@v0.0.0-20161208181325-20d25e280405
    │       └── yaml.v2@v2.2.8
    └── sumdb
        └── sum.golang.org
            └── latest

25 directories, 1 file

果然,都下载到了$GOPATH/pkg/mod目录下了!其它命令试试就知道了!这里提醒下

go mod vendor

会在项目中建立vendor目录,将依赖下载在该目录中,目录结构类似前面说的的pkg/mod目录,整个项目打包的话方便没有网络也可以编译项目。一般情况下不要提交本目录到版本管理中。

6.编译项目

根据老习惯,建立个bin目录,用下面命令生产可执行文件,文件名默认同源文件名:

➜  go-mod-test mkdir .bin
➜  go-mod-test go build -o ./bin/ ./src/main.go
➜  go-mod-test ./bin/main
pk1.Pk1Func
pk1.pk1.Pk1Func

另外,我们将main.go源文件放到项目目录下,然后执行

➜  go-mod-test cp src/main.go .
➜  go-mod-test  go build -o ./bin/ 

这时发现bin中生产的可执行文件不是main而是包名go-mod-test!

7.Go Moudle被墙相关

7.1下载失败

go mod download下载失败包库,增加代理,在.bashr或.zshrc中增加如下export

export GO111MODULE=on
export GOPROXY=https://goproxy.io

https://goproxy.io若不稳定可以用七牛的https://goproxy.cn,或网上搜下其它的代理服务器。

7.2校验失败

如果在运行go mod download,提示Get https://sum.golang.org/lookup/xxxxxx: dial tcp 216.58.200.49:443: i/o timeout,则是因为Go 设置了默认的GOSUMDB=sum.golang.org,这个网站是被墙了的,用于验证包的有效性,可以通过如下命令关闭:

go env -w GOSUMDB=off

但不建议,建议的做法是设置 GOSUMDB=“sum.golang.google.cn”,这个是专门为国内提供的sum 验证服务

go env -w GOSUMDB="sum.golang.google.cn"
7.3 Goland中设置

系统的GOPROXY在Goland中会覆盖,所以Goland中要重新设置:

File-->Preferences-->Go-->Go Modules(vgo)-->Enable Go Modules(vgo) intergration-->proxy-->https://goproxy.cn,direct

即可。

8.项目组织

先看下重构后的本Demo项目结构

➜  go-mod-test tree
.
├── Makefile
├── bin
│   ├── main
│   ├── submain1
│   └── submain2
├── go.mod
├── go.sum
└── src
    ├── main
    │   └── demomain.go
    ├── pk1
    │   ├── pk1
    │   │   ├── pk1.go
    │   │   └── pk1_test.go
    │   ├── pk1.go
    │   └── pk1_test.go
    ├── submain1
    │   └── submain1-main.go
    └── submain2
        └── submain2-main.go

7 directories, 13 files

说明如下:
1.项目根目录下有模块信息,即go.mod。
2.main目录(包)为主模块,需要编译成可执行文件。
3.pk1及其子目录pk1可以理解为公共目录,如放项目的一下公共源文件,不编译成可执行文件。
4.submain1及submain2为子模块(如功能微服务),需要编译成可执行文件,可能引用公共模块里的单元文件。
5.xxx_test.go为测试文件,命名规则就是单元名加后缀_test。
6.Makefile,与make工具搭档是构建系统的鼻祖,简化构建。如本例中

➜  go-mod-test make build
go build -o ./bin/  ./src/main/...
./bin/main
2020/03/30 17:46:15 hahahaha
2020/03/30 17:46:15 pk1.pk1.Pk1Func
go build -o ./bin/ ./src/submain1/...
./bin/submain1
2020/03/30 17:46:16 submain1.main()
2020/03/30 17:46:16  msg from pk1/Pk1Func(): haha
go build -o ./bin/ ./src/submain2/...
./bin/submain2
2020/03/30 17:46:16 submain2.main()
2020/03/30 17:46:16 msg from pk1/pk1/Pk1Func()): pk1.pk1.Pk1Func

一个make build帮我们编译好了并执行了三个(子)项目。再如

➜  go-mod-test make test
go test  ./src/... -v
?   	go-mod-test/src/main	[no test files]
=== RUN   TestPk1Func
=== RUN   TestPk1Func/msg
=== RUN   TestPk1Func/ha
--- PASS: TestPk1Func (0.00s)
    --- PASS: TestPk1Func/msg (0.00s)
    --- PASS: TestPk1Func/ha (0.00s)
=== RUN   TestAdd
=== RUN   TestAdd/1+2
--- PASS: TestAdd (0.00s)
    --- PASS: TestAdd/1+2 (0.00s)
PASS
ok  	go-mod-test/src/pk1	(cached)
=== RUN   TestPk1Func
=== RUN   TestPk1Func/default
--- PASS: TestPk1Func (0.00s)
    --- PASS: TestPk1Func/default (0.00s)
=== RUN   TestSub
=== RUN   TestSub/5-3
=== RUN   TestSub/4-2
    TestSub/4-2: pk1_test.go:40: Sub() = 2, want 1
--- FAIL: TestSub (0.00s)
    --- PASS: TestSub/5-3 (0.00s)
    --- FAIL: TestSub/4-2 (0.00s)
FAIL
FAIL	go-mod-test/src/pk1/pk1	0.204s
?   	go-mod-test/src/submain1	[no test files]
?   	go-mod-test/src/submain2	[no test files]
FAIL
make: *** [test] Error 1

跑完了全部单元测试。
本文结束。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页