Golang(不存在)的包管理

Golang(不存在)的包管理

    • Golang开发与GOPATH
    • Govendor
    • Go Modules after 1.11
    • go get被墙问题
    • 总结

最近开始上手Golang,竟然一开始在包管理上就碰到不少问题,搜了一大圈,因此开一篇博文记录一下在Golang package managing里爬过的坑。

Golang的包管理不像Python那样健全,无论是使用conda还是pip,都可以很方便的指定版本,也可以非常容易通过tuna更换镜像源来克服GFW。而Golang中的go get命令其实只是一个下载repo代码并install的过程,并且 golang.org 网站是被墙的,所以 golang.org/x/ 底下的所有包基本都是没法下载的,而在下载 github.com 的包时也会经常因为依赖 golang.org 的包而无法下载完成。

Golang开发与GOPATH

一开始不知道,后来搞清楚以后很震惊的一点就是,在默认的Golang开发模式下,所有工程(包括自己写的)都要放在$GOPATH/src里,虽然可以将工作目录加入GOPATH来反向满足这个条件,但感觉这就像每次写一个Python工程就要把其加入PYTHONPATH一样奇怪。如果不放在GOPATH中的话,引用工程内的其他包都做不到(所以Goland可以配置project GOPATH来避免这点)。不过Golang本身就是语言直接定死标准。

而import一个包,实际上就是在若干个PATH中寻找对应的文件夹,比如import "github.com/tidwall/evio"就是寻找到了$GOPATH/src/github.com/tidwall/evio/目录下的所有文件并加载(注意是文件,并不会递归加载文件夹),而在evio的源码中也可以看到当其引用internal包时,使用的也是import "github.com/tidwall/evio/internal",即$GOPATH/src/github.com/tidwall/evio/internal/文件夹,也就是说,Golang是没有relative import的概念。Golang不允许在同一文件夹的文件中定义多个package,但是package name可以和directory name不一样,并且同一个package中的文件都是互相感知的,即一个文件可以直接调用其他文件中定义的函数(比如go编译器可以先扫描同一级目录下的所有文件中的函数定义组成符号表再去编译函数代码)。

Govendor

在Go 1.5及后续版本中,新增了vendor机制,即在项目目录下的vendor目录也会被作为编译时的一个搜索PATH,但这本质上并没有解决依赖包的版本控制问题,也没有内置机制可以管理vendor下的目录,于是就有工具Govendor,通过在vendor目录下增加vendor.json文件用作版本控制,并且govendor作为一个可执行文件提供了command line的管理方式,具体可以参见doc。但是使用govendor的时候项目仍应该位于$GOPATH/src

Go Modules after 1.11

在Go 1.11及后续版本中加入了go modules用于整治其“百花齐放”的包管理市场,对应命令为go mod。使用go modules之后,通过新增go.mod和go.sum文件(用于版本锁定),项目不再必须放在GOPATH中,当调用go build命令时,检测到go.mod文件即会开启module模式,下载go.mod中声明但是本地没有缓存的包,保存位置为$GOPATH/pkg/mod。并且go modules支持使用$GOPROXY环境变量,可以使用export GOPROXY=https://goproxy.io,在下载包的时候就会使用 goproxy.io 的代理绕过GFW(这一点是非常重要的,也是我决定使用go modules的原因)。btw goproxy.io本身也是开源的,可以用于公司内网搭建镜像之类的。

参考链接Using Go Modules和告别GOPATH,快速使用 go mod。

go get被墙问题

go get命令下载包最头疼的问题就是地址被墙,github.com 站点的包有时还可以下载下来,golang.org 站点的就不要想了,虽然一般博客上会说 golang.org 上的包在github上都有对应的repo,可以git clone下载之后做一个软链接,但感觉还是过于原始了。有的博客推荐使用七牛云的gopm工具,但经过我实测,代码里的确是从七牛云的 gopm.io 站点下载包,但实测这个站点也被墙了,无法使用。

一般在Linux中命令行中可以使用proxychains将网络请求代理到ss服务上,因此我也尝试了使用proxychains go get ...来下载包,但可见的是下载过程并没有触发proxychains的输出,也就是说没走socks5。通过export http_proxy=socks5://127.0.0.1:1080是不行的,参见issue,虽然golang在/x/net/proxy包中有socks5的支持,但是标准库中并没有,因此无法基于socks5做http的proxy。通过proxychains不行的原因则是issue,proxychains的工作原理是在动态链接上hook,参见How do those hackers’ tools work? Proxychains,通过使用LD_PRELOAD环境变量覆盖掉原有动态库中的connect函数来建立一个经过proxy的链接,而在issue中说golang使用自己的系统调用封装并且使用的是静态链接,因此无法hook到(虽然我用ldd查看go是有动态链接libc的…)。

$ ldd /usr/loca/go/bin/go
	linux-vdso.so.1 (0x00007ffe58db8000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f38a85c1000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f38a8207000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f38a87df000)

但是http_proxy变量仍然是可用的,只是需要一个http proxy的代理,于是有一种方法便是将socks5代理转换为http代理,参见天朝局域网内go get的正确姿势,文中使用的是polipo,作者说是不维护了,当然这种软件有很多,比如http-proxy-to-socks,无非就是将http协议转换成socks协议,自己写的话用一些第三方包解析一下就行了。

顺便说一下ssh的dynamic port forwarding(即-D选项)使用的也是socks4和socks5协议,本地对某资源的访问可以通过动态端口转发由ssh server来替代发起访问,相当于一个socks服务器(可以用来干什么也都懂的)。以及proxychains的真正目的是proxy chains,即链式代理,利用了HTTP协议的CONNECT请求。

总结

总得来说还是推荐使用GOPROXY的方式,毕竟从1.12开始go modules就被正式引入了,直接使用goproxy.io也比较方便,如果哪天goproxy.io也被墙了,也可以使用其开源项目在vps上搭一个。

你可能感兴趣的:(golang)