Golang
是一门到现在有十年的静态高级语言了,2009年的时候算是正式推出了,而后到最近的一两年,2017-2018年的时候,忽然直线上升,爆火了,得益于容器化运维/直播/短视频/区块链...git
Golang
语法简单,简单便是复杂,软件构建的核心在于将复杂的东西简单化,处理好复杂度。github
做为一个 gopher
,咱们要知道他的包管理,这样才能合理化代码结构,作好工程管理。(gopher
:地鼠)golang
Golang
的包管理一直让人口病,一开始它用 GOPATH
来进行依赖库管理,特别简单粗暴。docker
若是环境变量:npm
export GOROOT=/home/love/go export GOPATH=/home/love/code export GOBIN=$GOROOT/bin
上面 GOROOT
是指 Golang编译器以及其工具链,基础源码库
所在的目录, GOPATH
是用户自定义的代码所在位置。json
如下 GOPATH
的结构以下:缓存
├── src └── github.com └── hunterhug └── rabbit └── a └── a.go └── main.go ├── bin ├── pkg
咱们写的开发包有简单易懂的路径之分,好比个人包叫 github/hunterhug/rabbit
,那么结构如上面同样。cors
咱们进入到 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
的二进制。maven
若是咱们 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
看有没有,没有的话再上一层 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
。
GO111MODULE=off
,无模块支持,和以前同样。GO111MODULE=on
,模块支持,忽略 GOPATH
和 vendor
文件夹,只根据 go.mod
下载依赖。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.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
管理依赖,到如何将代码编译成二进制,是一个过程,还有许多细节。 上面是个人经验,感谢阅读。