Golang1.5到Golang1.12包管理:golang vendor 到 go mod

栏目: Go · 发布时间: 5年前

内容简介:我从2016年毕业的时候,第一份工作,就接触了之后2017年新年刚过,我跑到了深圳,在华为外包做

1. 前言

Golang 是一门到如今有十年的静态高级语言了,2009年的时候算是正式推出了,然后到最近的一两年,2017-2018年的时候,突然直线上升,爆火了,得益于容器化运维/直播/短视频/区块链...

我从2016年毕业的时候,第一份工作,就接触了 Golang 语言,那时公司用 Java 处理数据流,然后用 Golang 写高性能的网络中间件。那时, Golang 还很简单,那时 Docker 刚火, Kubernates 还在发力中,基本在中国,还算小众语言。

之后2017年新年刚过,我跑到了深圳,在华为外包做 Kubernates 二次开发,全职了 Golang ,接触了专业的开发模式。那年,区块链火了,那年,编排系统,容器化运维火了。到了2018,越来越多的人用了 Golang ,越來越多的大公司开始招 Golang

Golang 语法简单,简单即是复杂,软件构建的核心在于将复杂的东西简单化,处理好复杂度。

作为一个 gopher ,我们要知道他的包管理,这样才能合理化代码结构,做好工程管理。( gopher :地鼠)

2. GOPATH/ Golang 1.5之前

Golang 的包管理一直让人口病,一开始它用 GOPATH 来进行依赖库管理,特别简单粗暴。

如果环境变量:

export GOROOT=/home/love/go
export GOPATH=/home/love/code
export GOBIN=$GOROOT/bin

上面 GOROOT 是指 Golang编译器以及其 工具 链,基础源码库 所在的目录, GOPATH 是用户自定义的代码所在位置。

以下 GOPATH 的结构如下:

├── src
    └── github.com
        └── hunterhug
            └── rabbit
                └── a
                    └── a.go
                └── main.go
├── bin
├── pkg

我们写的开发包有简单易懂的路径之分,比如我的包叫 github/hunterhug/rabbit ,那么结构如上面一样。

我们进入到 rabbit 目录, main.go 代码:

package main
import "github/hunterhug/rabbit/a"

func main(){
  ...
}

然后 go build 的话,找包时,就会从 GOPATH src 下面开始找,比如 rabbit 包下的 main.go 依赖了 github/hunterhug/rabbit/a ,那么它首先从 src 下面按路径往下拼接查找,然后就找到了,最后生成和包名 github/hunterhug/rabbit 一样的一个叫 rabit 的二进制。

如果我们 go install 的话,这个二进制就会保存在 GOBIN 下(如果不存在 GOBIN ,会保存在 GOPATH bin 下)。如果我们要编译时,缺少包,那么 go get -v 将会下载依赖包源码到 GOPATH src 下,然后在 GOPATH pkg 目录下生成该包的静态库(下次用就不用再从源码编译了,算缓存)。

但是我们包找不到时:

love@love:~/code/src/github.com/hunterhug/fafacms$ go build
core/server/server.go:4:2: cannot find package "github.com/gin-contrib/cors" in any of:
        /home/love/go/src/github.com/gin-contrib/cors (from $GOROOT)
        /home/love/code/src/github.com/gin-contrib/cors (from $GOPATH)

我们发现,原来,其实是先去 GOROOT 下找包,找不到包,再去 GOPATH 找,流下了感动的泪水!比如我们的 GOPATH 下建了一个 fmt 包:

package fmt

func PPrintln() {
    print("i am diy fmt")
}

但是我们想引用这个库, main.go 使用:

package main
import fmt
func main(){
  fmt.PPrintln()
}

发现引用不了,2333! 所以, GOPATH 下的包最好不要和 GOROOT 下的标准库重名!

你再看下 GOROOT 的结构:

├── src
    └── time
    └── fmt
├── bin
├── pkg

这不和我们的 GOPATH 很像吗,对,现在的 Golang编译器 是自编译的,就是用 Golang 来写 Golang编译器 ,它的编译器及中间产物,基础库等,保持和 GOPATH 一毛一样,无缝衔接。

但是不同依赖包是有版本的,版本变了怎么办?这就需要人工管理了。

3. Golang vendor/Golang1.5以后

自己管理库版本,想想都不太可能,毕竟 Javamaven , Pythonpip , PHPcomposeNodeJsnpm

于是从 Golang1.5 开始推出 vendor 文件夹机制( vendor :供应商/小贩)。

Golang1.6 正式开启这个功能。

比如我们的包叫 awesomeProject ,在 GOPATH 下结构:

├── src
    └── awesomeProject
        └── vendor
            └── fmt
                └── fmt.go
        └── main.go
├── pkg

其中 main.go

package main

import "fmt"

func main() {
    fmt.PPrintln()
}

我们进入 awesomeProject 目录,并且 go build , 偶也成功。

这下子不会像上面没 vendor 时直接引用 GOROOT 的标准包了,我们终于可以用和标准包重名的包了,那就是放在和 main.go 同目录的 vendor 下面!

这下子,我们 import 的包会先在同级 vendor 下找,找不到再按照以前的方式。

如果我们将 main 改成引用一个不存在的包 b :

package main

import (
    "b"
)

func main() {
    b.P()
}

然后 go build 提示:

main.go:4:2: cannot find package "b" in any of:
        /home/love/code/src/awesomeProject/vendor/b (vendor tree)
        /home/love/go/src/b (from $GOROOT)
        /home/love/code/src/b (from $GOPATH)

如果此时我们再任性一点,在 GOPATH src 下建立一个空的 vendor 文件夹,则会提示:

main.go:4:2: cannot find package "b" in any of:
        /home/love/code/src/awesomeProject/vendor/b (vendor tree)
        /home/love/code/src/vendor/b
        /home/love/go/src/b (from $GOROOT)
        /home/love/code/src/b (from $GOPATH)

好了,我们发现现在的加载方式是:

包同目录下的vendor
GOPATH src 下的vendor
GOROOT src
GOPATH src

如果在 GOROOTGOPATH 下建 vendor 会怎么样?我们就不止疼了,233。。

好了,现在问题就是 vendor 是怎么冒泡的,如果我 main.go 引用了 vendor/b ,而 b 包里面引用了一个 c 包。此时 vendor/b 会怎么找库?

├── src
    └── awesomeProject
        └── vendor
            └── b
                └── b.go
        └── main.go
├── pkg

现在 vendor/b/b.go 的内容:

package b

import "c"

func P() {
    print(" i am vendor b\n")
    c.P()
}

我们进入 awesomeProject 项目 go build ,出现:

vendor/b/b.go:3:8: cannot find package "c" in any of:
    /home/love/code/src/awesomeProject/vendor/c (vendor tree)
    /home/love/code/src/vendor/c
    /home/love/go/src/c (from $GOROOT)
    /home/love/code/src/c (from $GOPATH)

现在加载流程是:

包同目录的包(即b包同目录看看有没有c包)
GOPATH src 下的vendor
GOROOT src
GOPATH src

此时我们在 vendor/b 下建一个空 vendor

├── src
    └── awesomeProject
        └── vendor
            └── b
                └── vendor
                └── b.go
        └── main.go
├── pkg

进入 awesomeProject 项目再 go build 会出现:

vendor/b/b.go:3:8: cannot find package "c" in any of:
    /home/love/code/src/awesomeProject/vendor/b/vendor/c (vendor tree)
    /home/love/code/src/awesomeProject/vendor/c
    /home/love/code/src/vendor/c
    /home/love/go/src/c (from $GOROOT)
    /home/love/code/src/c (from $GOPATH)

如果我们再满足上面的 c 包,同理在 c 包建一个空 vendor

├── src
    └── awesomeProject
        └── vendor
            └── b
                └── vendor
                    └── c
                        └── vendor
                        └── c.go
                └── b.go
        └── main.go
├── pkg

cc.go 引用了不存在的 d 包:

package c

import "d"

func P() {
    d.P()
}

进入 awesomeProject 项目再 go build 会出现:

vendor/b/vendor/c/c.go:3:8: cannot find package "d" in any of:
    /home/love/code/src/awesomeProject/vendor/b/vendor/c/vendor/d (vendor tree)
    /home/love/code/src/awesomeProject/vendor/b/vendor/d
    /home/love/code/src/awesomeProject/vendor/d
    /home/love/code/src/vendor/d
    /home/love/go/src/d (from $GOROOT)
    /home/love/code/src/d (from $GOPATH)

发现, 查找包 vendor 是往上冒泡的, 一个包引用另一个包,先看看 同目录 vendor 下有没有这个包, 没有的话一直追溯到上一层 vendor 看有没有,没有的话再上一层

vendor ,直到 GOPATH src/vendor

所以现在的加载流程是:

包同目录下的vendor
包目录向上的最近的一个vendor
...
GOPATH src 下的vendor
GOROOT src
GOPATH src

总结: vendor 向上冒泡!!!!

这样的话, 我们可以把包的依赖都放在 vendor 下,然后提交到仓库,这样可以省却拉取包的时间,并且相对自由,你想怎么改都可以,你可以放一个已经被人删掉的 github 包在 vendor 下。这样,依然手动,没法管理依赖版本。

所以很多第三方,比如 glide , godep , govendor 工具出现了, 使用这些工具, 依赖包必须有完整的 git 版本, 然后会将所有依赖的版本写在一个配置文件中。

比如 godep

go get -v github.com/tools/godep

在包下执行

godep save

会生成 Godeps/Godep.json 记录依赖版本,并且将包收集于 当前 vendor 下。

3. go mod/Go1.11以后

Golang 1.11 开始, 实验性出现了可以不用定义 GOPATH 的功能,且官方有 go mod 支持。 Golang 1.12 更是将此特征正式化。

现在用 Golang1.12 进行:

go mod init
go: modules disabled inside GOPATH/src by GO111MODULE=auto; see 'go help modules'

其中 GO111MODULE=auto 是一个开关,开启或关闭模块支持,它有三个可选值: off / on / auto ,默认值是 auto

  1. GO111MODULE=off ,无模块支持,和之前一样。
  2. GO111MODULE=on ,模块支持,忽略 GOPATHvendor 文件夹,只根据 go.mod 下载依赖。
  3. GO111MODULE=auto ,该项目在 GOPATH src 外面且根目录有 go.mod 文件时,开启模块支持。

在使用模块的时候, GOPATH 是无意义的,不过它还是会把下载的依赖储存在 GOPATH/src/mod 中,也会把 go install 的结果放在 GOPATH/bin (如果 GOBIN 不存在的话)

我们将项目移出 GOPATH ,然后:

go mod init

出现:

go: cannot determine module path for source directory /home/love/awesomeProject (outside GOPATH, no import comments)

现在 main.go 改为:

package main // import "github.com/hunterhug/hello"

import (
    "b"
)

func main() {
    b.P()
}

将会生成 go.mod :

module github.com/hunterhug/hello

go 1.12

此时我们:

go build
build github.com/hunterhug/hello: cannot load b: cannot find module providing package b

这下没法查找 vendor 了,我们加上参数再来:

go build -mod=vendor
build github.com/hunterhug/hello: cannot load c: open /home/love/awesomeProject/vendor/c: no such file or directory

流下了感动的泪水, vendor 冒泡呢?原来启用了 go.modvendor 下的包 b 无法找到 b/vendor 下的包 c ,只能找到一级,2333333,这是好还是坏?

一般情况下, vendor 下面有 vendor 是不科学的, godep 等工具会将依赖理顺,确保只有一个 vendor

那么 go.mod 导致 vendor 无法冒泡产生的影响,一点都不大,流下感动的泪水。

现在我们来正确使用 go mod , 一般情况下:

待写

待写


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C++数值算法(第二版)

C++数值算法(第二版)

William T.Vetterling、Brian P.Flannery、Saul A.Teukolsky / 胡健伟、赵志勇、薛运华 / 电子工业出版社 / 2005年01月 / 68.00

本书选材内容丰富,除了通常数值方法课程的内容外,还包含当代科学计算大量用到的专题,如求特殊函数值、随机数、排序、最优化、快速傅里叶变换、谱分析、小波变换、统计描述和数据建模、常微分方程和偏微分方程数值解、若干编码算法和任意精度的计算等。 本书科学性和实用性统一。每个专题中,不仅对每种算法给出了数学分析和比较,而且根据作者的经验对算法做出了评论和建议,并在此基础上给出了用C++语言编写的实用程......一起来看看 《C++数值算法(第二版)》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试