golang modules问题的理解与踩坑记

golang 在1.11版本开始增加modules的功能,解决包管理的问题,之前包管理方案废除了。这个方案感觉都几好,不过最近使用的时候。发觉都点问题,可能是我对它的理解不是很清晰。导致的出问题。

现在讲讲我遇到的问题。如果大家想跟着我的项目试验,请去

github下载并切换到这个commit。这里我列一下我项目的go.mod的主要部分

require (
github.com/deepch/av v0.0.0-20160612005306-c437a98c9300 // indirect
github.com/deepch/rtsp v0.0.0-20180827192050-70ae0abf31bd // indirect
github.com/deepch/sample_rtsp v0.0.0-20180827191708-90250a0f88be // indirect
github.com/gorilla/mux v1.7.1 // indirect
github.com/pion/webrtc/v2 v2.0.9 // indirect
)

目前项目有点问题,我需要升级下github.com/pion/webrtc/这个库的版本。升级这个库的版本有几个方面。

  1. 使用命令go get github.com/pion/webrtc/v2@v2.0.12
  2. 使用命令go get -u github.com/pion/webrtc/v2@v2.0.12
  3. 直接编辑go.mod文件,把版本号修改。

大家是否会觉得用以上三个方法结果都一样。我开始以为这个三个方法都一样的。不过后发觉他们直接还是有点不一样的。

方法1,go get github.com/pion/webrtc/v2@v2.0.12命令之后。go.mod文件只是修改了github.com/pion/webrtc/v2的版本号。go.sum增加了2.0.12依赖的包对应的新的版本库。

require (
	github.com/deepch/av v0.0.0-20160612005306-c437a98c9300 // indirect
	github.com/deepch/rtsp v0.0.0-20180827192050-70ae0abf31bd // indirect
	github.com/deepch/sample_rtsp v0.0.0-20180827191708-90250a0f88be // indirect
	github.com/gorilla/mux v1.7.1 // indirect
	github.com/pion/webrtc/v2 v2.0.12 // indirect
)

方法2. go get -u github.com/pion/webrtc/v2@v2.0.12命令后,go.mod文件多了很多依赖包。go.sum也出现了很多更新包。这些依赖包都是次级依赖的包的升级版本,可能是github.com/pion/webrtc库需要。都列在go.mod哪里了。

require (
	github.com/deepch/av v0.0.0-20160612005306-c437a98c9300 // indirect
	github.com/deepch/rtsp v0.0.0-20180827192050-70ae0abf31bd // indirect
	github.com/deepch/sample_rtsp v0.0.0-20180827191708-90250a0f88be // indirect
	github.com/golang/mock v1.3.0 // indirect
	github.com/gorilla/mux v1.7.1 // indirect
	github.com/lucas-clemente/quic-go v0.11.1 // indirect
	github.com/onsi/ginkgo v1.8.0 // indirect
	github.com/onsi/gomega v1.5.0 // indirect
	github.com/pion/datachannel v1.4.3 // indirect
	github.com/pion/dtls v1.3.4 // indirect
	github.com/pion/webrtc/v2 v2.0.12 // indirect
	github.com/stretchr/objx v0.2.0 // indirect
	golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 // indirect
	golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect
	golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c // indirect
	golang.org/x/text v0.3.2 // indirect
	golang.org/x/tools v0.0.0-20190509014725-d996b19ee77c // indirect
	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
	gopkg.in/yaml.v2 v2.2.2 // indirect
)

方法3。直接编辑go.mod,然后编译程序和或者运行,得到的结果是和方法1一样的。

现在来一条思考题:,如果命令是go get -u github.com/pion/webrtc/v2@v2.0.9版本号不变化。你会觉得go.mod会有变化么?

结果来了。

require (
	github.com/deepch/av v0.0.0-20160612005306-c437a98c9300 // indirect
	github.com/deepch/rtsp v0.0.0-20180827192050-70ae0abf31bd // indirect
	github.com/deepch/sample_rtsp v0.0.0-20180827191708-90250a0f88be // indirect
	github.com/golang/mock v1.3.0 // indirect
	github.com/gorilla/mux v1.7.1 // indirect
	github.com/lucas-clemente/quic-go v0.11.1 // indirect
	github.com/onsi/ginkgo v1.8.0 // indirect
	github.com/onsi/gomega v1.5.0 // indirect
	github.com/pion/datachannel v1.4.3 // indirect
	github.com/pion/dtls v1.3.4 // indirect
	github.com/pion/ice v0.2.6 // indirect
	github.com/pion/srtp v1.2.4 // indirect
	github.com/pion/webrtc/v2 v2.0.9 // indirect
	github.com/stretchr/objx v0.2.0 // indirect
	golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 // indirect
	golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect
	golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c // indirect
	golang.org/x/text v0.3.2 // indirect
	golang.org/x/tools v0.0.0-20190509014725-d996b19ee77c // indirect
	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
	gopkg.in/yaml.v2 v2.2.2 // indirect
)

二级依赖包都更新了。所以平时更新项目的依赖包要小心,最好别用go get -u来更新,只要用go get来更新具体某个依赖包就好了,带-u的会把次级的依赖都升价到新版本,不理会你直接引用的库是否有升级。这个问题不是很好的。因为有时候回归问题根源的时候,会把依赖包进行升级和降级处理进行查找一些问题。加了-u的选项,它把次级依赖包都升价了。不能很好地回归问题。
假设,你使用go get -u github.com/pion/webrtc/v2@v2.0.12升价了包,然后用go get  github.com/pion/webrtc/v2@v2.0.9进行降级。其他依赖包也是被升价了。

所以大家以后升价依赖包,要不手动改文件。要不只用go get。千万别用go get -u了。要不然降级的时候就麻烦了。另外这个go的依赖包的升级是根据语义化版本来决定升级的。如果你原来是1.X.X的版本,不会知道升级到2.X.X的版本的。而且go现在的包的import语句引入2.X.X版本的时候需要跟一个v2的尾巴。这个超级不习惯。跟其他语言很大不同。

我目前使用的go的版本号:go version go1.12.4 darwin/amd64

golang 与cgo的跨平台编译

golang语音本身是支持跨平台编译的,只要在编译的时候添加相应的选项就可以了。不过有一个问题是不支持带有cgo的程序。如果需要支持cgo还需要配置编译cgo的环境,如果一个带cgo的程序需要编译成多个平台的支持,那是一件痛苦的事情,或者需要编译成非开发机本机运行版本,也是一件麻烦事。特别是开发嵌入式arm程序的时候就编译环境的配置就特别麻烦。不过现在有万能的docker对配置进行了简化。

由于工作需要,之前自己做了一个docker编译go 和cgo在android上运行的images,不过当时是手工制作images,并没有记录制作步骤和写下dockerfile。由于目前go和android的版本升级比较快。所以原来的docker images已经不合时宜了。不想再自己去做这个images了。在网上搜索了一个Go CGO cross compiler这个docker images比自己做的强大很多,支持很多平台的编译。使用起来比较方便。大家可以看看说明就可以了。非常方便。

由于这个docker的操作非常自动,可以说太自动了。例如,编译的项目必须在git上,自动下载项目和项目相关的库。对于一些不在git上的公司内部项目不是很友好。而且好像每次都重新下载项目和关联的代码库。导致编译速度非常慢,本来就是比较慢了。所以我没有使用它提供的的xgo工具。只使用它的docker images。

这个docker images的使用方法官方并没有说明。不过通过其dockerfile可以窥探其秘密。

进入这个docker的命令是:

docker run -it –entrypoint /bin/bash  karalabe/xgo-latest

因为这个docker设置了entrypoint是根目录的build.sh,需要把它替换成bash。这个docker的go path位于/go目录中,代码需要放在/go/src目录中。然后可以运行/build.sh XXXX 进行项目编译。当然可以归宿主机的代码映射到docker目录中并自动运行编译那时最方便的。

docker run –rm -it \
-v “$PWD”:/build \
-v “$PWD”/vendor:/go/src \
-v “$PWD”:/go/src/XXXXX \
karalabe/xgo-latest XXXXX

#XXXX代表你的项目目录名称。这里把项目需要包含的依赖包也一并映射到docker的/go/src中。

这样就可以很方便编译出各个平台运行的版本可执行文件,包括windows,macOS,iOS,android,linux,arm等。不过大多数我们并不需要编译这么多平台的版本,很多时候我们只需一个版本就够了。这样可以节省很多的编译时间。

docker run –rm -it \
-v “$PWD”:/build \
-v “$PWD”/vendor:/go/src \
-v “$PWD”:/go/src/XXXXX \
-e TARGETS=android/arm \
karalabe/xgo-latest XXXXX

这样写就可以只编译android的arm的版本。具体TARGETS支持什么参数,请参考官方说明。

golang cgo编译问题与坑

golang这个语言最大的好处就是和c结合得非常好,非常方便与c互相调用。虽然其他语言也可以调用c的库,但是调用没有golang这个语言这么方便。其实想讲,golang就是现代版的c语言。

首先golang如何调用c的库,文档写得比较清楚。请参照文档cgo编译cgo命令。这个看来之后基本就大概明白了。而且讲解了一些基本的类型转换。例如string的转换等。不过真实使用的时候,会用到char**的。这个问题如何解决。stackoverflow上有解,请自行观看。

看完这些问题,大家觉得是不是就没有问题。我也是这样想的,不过真实的时候就遇到一个非常恼火的问题。就是golang对格式的一些严格要求。

问题来源,在调试cgo的程序的时候。发觉本来调试好的程序,后来也没有改什么,突然编译不了。找了很久也没有找出原因。由于是测试代码,没有放入代码管理,回滚不了。看来看去也没有看出什么问题。最后只能从最开始重新编写这个代码。最后发现是一个空行引发的血案。悲哀啊,搞了我大半天。

看下边的代码吧:

package main

/*
#cgo CFLAGS: -I .
#cgo LDFLAGS: -L . -lclibrary

#include "clibrary.h"

int callOnMeGo_cgo(int in); // Forward declaration.
*/
import "C"  //这里不能与上边注释的c代码有任何空行。

import (
        "fmt"
        "unsafe"
)

//export callOnMeGo
func callOnMeGo(in int) int {
        fmt.Printf("Go.callOnMeGo(): called with arg = %d\n", in)
        return in + 1
}

func main() {
        fmt.Printf("Go.main(): calling C function with callback to us\n")
        C.some_c_func((C.callback_fcn)(unsafe.Pointer(C.callOnMeGo_cgo)))
}

大家可以尝试在import “C”上边插入空行。你就发现会不能编译了。真是不太明白golang为什么搞怎么严格的格式检查。真的服了它了。多一个空行而已。

还有一个问题是,目前我使用cgo与ffmpeg的库进行编译,经常出现编译问题。但是重新编译多一次就成功了。也搞不懂什么原因。