|
@@ -0,0 +1,469 @@
|
|
|
+---
|
|
|
+title: Golang高阶:Golang1.5到Golang1.12包管理
|
|
|
+date: 2020-06-29 12:09:08
|
|
|
+---
|
|
|
+`Golang` 是一门到如今有十年的静态高级语言了,2009年的时候算是正式推出了,然后到最近的一两年,2017-2018年的时候,突然直线上升,爆火了,得益于容器化运维/直播/短视频/区块链...
|
|
|
+
|
|
|
+`Golang` 语法简单,简单即是复杂,软件构建的核心在于将复杂的东西简单化,处理好复杂度。
|
|
|
+
|
|
|
+作为一个 `gopher`,我们要知道他的包管理,这样才能合理化代码结构,做好工程管理。( `gopher`:地鼠)
|
|
|
+
|
|
|
+`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` 一毛一样,无缝衔接。
|
|
|
+
|
|
|
+但是不同依赖包是有版本的,版本变了怎么办?这就需要人工管理了。
|
|
|
+
|
|
|
+自己管理库版本,想想都不太可能,毕竟 `Java` 有 `maven`, `Python` 有 `pip`, `PHP` 有 `compose`, `NodeJs` 有 `npm`。
|
|
|
+
|
|
|
+于是从 `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
|
|
|
+```
|
|
|
+
|
|
|
+如果在 `GOROOT` 和 `GOPATH` 下建 `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
|
|
|
+```
|
|
|
+
|
|
|
+但 `c` 包 `c.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` 看有没有,没有的话再上一层<br> `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`下。
|
|
|
+
|
|
|
+`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`。
|
|
|
+
|
|
|
+在使用模块的时候, `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.mod`, `vendor` 下的包 `b` 无法找到 `b/vendor` 下的包 `c`,只能找到一级,2333333,这是好还是坏?
|
|
|
+
|
|
|
+一般情况下, `vendor` 下面有 `vendor` 是不科学的, `godep` 等工具会将依赖理顺,确保只有一个 `vendor`。
|
|
|
+
|
|
|
+那么 `go.mod` 导致 `vendor` 无法冒泡产生的影响,一点都不大,流下感动的泪水。
|
|
|
+
|
|
|
+现在我们来正确使用 `go mod`, 一般情况下:
|
|
|
+
|
|
|
+```
|
|
|
+省略N步
|
|
|
+```
|
|
|
+
|
|
|
+到了这里,我们很遗憾的说再见了,现在 `go mod` 刚出来, 可能还会再更新,您可以谷歌或者其他方式搜索这方面的文章,或者:
|
|
|
+
|
|
|
+```
|
|
|
+go help modules
|
|
|
+```
|
|
|
+
|
|
|
+这一部分可能隔一段时间再细写。
|
|
|
+
|
|
|
+目前生产环境用 `go mod` 还不太现实, 我还是先推荐定义 `GOPATH` 和 `vendor` 用法。
|
|
|
+
|
|
|
+装环境太难, 我的天啊, 我每次都要装环境, 我们可以用下面的方法 `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
|
|
|
+```
|
|
|
+
|
|
|
+管理依赖,到如何将代码编译成二进制,是一个过程,还有许多细节。 上面是我的经验,感谢阅读。
|