Golang1.5到Golang1.12包管理

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

内容简介:版权所有,转载请注明:

版权所有,转载请注明: http://www.lenggirl.com/language/govendor.html

1. 前言

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

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 , 一般情况下:

省略N步

到了这里,我们很遗憾的说再见了,现在 go mod 刚出来, 可能还会再更新,您可以谷歌或者其他方式搜索这方面的文章,或者:

go help modules

这一部分可能隔一段时间再细写。

目前生产环境用 go mod 还不太现实, 我还是先推荐定义 GOPATHvendor 用法。

4. 使用 Docker 来多阶段编译 Golang

装环境太难, 我的天啊, 我每次都要装环境, 我们可以用下面的方法 So easy 随时切换 Golang 版本。

如果你的 Golang 项目依赖存于 vendor 下,那么我们可以使用多阶段构建并打包成容器镜像, Dockefile 如下:

FROM golang:1.12-alpine AS go-build

WORKDIR /go/src/github.com/hunterhug/fafacms

COPY core /go/src/github.com/hunterhug/fafacms/core
COPY vendor /go/src/github.com/hunterhug/fafacms/vendor
COPY main.go /go/src/github.com/hunterhug/fafacms/main.go

RUN go build -ldflags "-s -w" -o fafacms main.go

FROM alpine:3.9 AS prod

WORKDIR /root/

COPY --from=go-build /go/src/github.com/hunterhug/fafacms/fafacms /bin/fafacms
RUN chmod 777 /bin/fafacms
CMD /bin/fafacms $RUN_OPTS

其中 github.com/hunterhug/fafacms 是你的项目。使用 golang:1.12-alpine 来编译二进制,然后将二进制打入基础镜像: alpine:3.9 ,这个镜像特别小。

编译:

sudo docker build -t hunterhug/fafacms:latest .

我们多了一个镜像 hunterhug/fafacms:latest , 而且特别小, 才几M 。

运行:

sudo docker run -d  --net=host  --env RUN_OPTS="-config=/root/fafacms/config.json" hunterhug/fafacms

可是,如果我们用了 cgo , 那么请将 Dockerfile 改为:

FROM golang:1.12 AS go-build

WORKDIR /go/src/github.com/hunterhug/fafacms

COPY core /go/src/github.com/hunterhug/fafacms/core
COPY vendor /go/src/github.com/hunterhug/fafacms/vendor
COPY main.go /go/src/github.com/hunterhug/fafacms/main.go

RUN go build -ldflags "-s -w" -o fafacms main.go

FROM bitnami/minideb-extras-base:stretch-r165 AS prod

WORKDIR /root/

COPY --from=go-build /go/src/github.com/hunterhug/fafacms/fafacms /bin/fafacms
RUN chmod 777 /bin/fafacms
CMD /bin/fafacms $RUN_OPTS

5. 总结

管理依赖,到如何将代码编译成二进制,是一个过程,还有许多细节。 上面是我的经验,感谢阅读。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

大型网站系统与Java中间件开发实践

大型网站系统与Java中间件开发实践

曾宪杰 / 电子工业出版社 / 2014-4-24 / 65.00

本书围绕大型网站和支撑大型网站架构的 Java 中间件的实践展开介绍。从分布式系统的知识切入,让读者对分布式系统有基本的了解;然后介绍大型网站随着数据量、访问量增长而发生的架构变迁;接着讲述构建 Java 中间件的相关知识;之后的几章都是根据笔者的经验来介绍支撑大型网站架构的 Java 中间件系统的设计和实践。希望读者通过本书可以了解大型网站架构变迁过程中的较为通用的问题和解法,并了解构建支撑大型......一起来看看 《大型网站系统与Java中间件开发实践》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

在线进制转换器
在线进制转换器

各进制数互转换器