在编写内部项目中,往往不同的项目因为历史遗留问题,会有存在多个版本的 golang 编译器共存的情况。

同时,由于 golang 的包管理的短板(虽然在 golang 1.11 以后推出了 go mod),因此不可避免的安装 govendorgo dep 等多个包管理工具,非常的混乱。

本地使用多个 golang 的编译器版本其实更容易造成混乱,同时 env 满天飞也非常不方面维护。解决这块的问题很容易就想到了使用 Docker 去编译和检查本地项目, Docker 官方维护了多个版本的 golang 镜像

那么考虑的目标是:

  1. 使用同一个 Makefile 以及 Dockerfile 去维护项目的编译
  2. 因为 golang 1.11 以后官方推出了 go mod,所以尽量使用官方的包管理工具
  3. 接上条,老版本的依赖和本地编译使用 go mod vendor 去管理
  4. Makefile 不管 golang 编译器的版本问题,编译器版本让 Dockerfile 去管理
  5. 本地环境尽量使用最新版本的 golang 编译器,然后导入到 vendor 中

那么这样子,我们可以简单的使用 Dockerfile 去编译执行,例如下面使用编译器版本 golang 1.9.7 类似:

FROM golang:1.9.7 AS builder
# ...
RUN go build .
# ...

但需要注意的是,golang 1.11 之前必须代码在 $GOPATH 中,所以需要映射

ENV GOPATH /go
ENV GOROOT /usr/local/go
ENV PACKAGE ${YOUR_PACKAGE_NAME}
ENV BUILD_DIR ${GOPATH}/src/${PACKAGE}

# ...
COPY . ${BUILD_DIR}

然后在本地使用 go mod vendor 下载依赖包到项目的 /vendor 就可以在老版本中免去使用 dep、govendor 等依赖工具,统一使用官方的 go mod 去管理和下载依赖。

还有需要关注的是 Makefile 需要加个判断,为了增加通用性,建议根据路径判断 golang 的环境路径,而不是判断是否在 Docker 环境下:

# ...
ifneq ("$(wildcard /go)","")
    GOPATH=/go
    GOROOT=/usr/local/go
endif

GO=env $(GO_ENV) $(GOROOT)/bin/go

# ...

build:$(DIR_SRC)/main.go
    @$(GO) build $(GO_FLAGS) -o $(BIN) $(DIR_SRC)

这样子 Makefile 文件就可以同时同于本地高版本的环境以及 Docker 编译环境了。

详细的示例代码可以参看 这个简单的项目(作用只是用于生成随机密码),其中的 Makefile 以及 Dockerfile

最后,有个讨论 vendor 目录到底要不要纳入到版本控制中?我个人的看法是看情况,大部分情况下不会将这些第三方代码纳入版本控制中。主要的理由有:

  1. 这些代码是第三方库的代码,不会直接更改以及维护;
  2. go.mod 以及 go.sum 文件已经保存了第三方依赖的库信息(版本、hash 等等);
  3. 库文件通常会很多,纳入这些的文件会「污染」本地的 git history;
  4. 不同于开源项目,我们本地的开发环境是可控的。

所以,类似的 Consul 等比较大型的项目还是会将 vendor 目录纳入到版本控制中,个人认为这也是因为兼容方面的考虑更多些,而如果个本地项目往往环境是可控的。

- eof -