搭建可维护的 Golang 开发环境​

2023-03-05 17:16:31 浏览数 (1)

本篇文章将聊聊如何快速搭建 Linux 环境中的 Golang 开发环境。

《基础篇》[1]的内容中,我们聊过了如何基于 Ubuntu 22.04 搭建基础的 Linux 学习环境。接下来的文章里,我们先来聊聊如何在 Linux 环境中,快速安装配置各种可维护的语言环境。

写在前面

在开始聊如何做之前,我们首先要了解为什么要这么做。

我知道有不少同学有安装好环境之后,然后持续使用一个环境,并“天长地久”的用下去的习惯。这样做的好处是“看起来简单省心”,但实际上却埋藏了许多隐患,举几个常见的例子:

•同事说代码、程序有问题,跑不起来,你说程序在你这边是正常的,你觉得他的使用方式不对,他觉得你的程序“兼容性”/“健壮性”有问题。•当你想把半年、一年前的项目跑起来,发现运行的时候多了非常多的“警告”,甚至是“报错”,尤其是当你换了一台电脑的时候。•项目扩充人手,你的同事也需要配置一套环境,你和他折腾的半天,虽然有着重重困难,什么版本不对,配置散落在系统的各个“犄角旮旯”等等,但是你们最终克服了困难,并加深了革命友情。•项目遇到大版本升级,因为一些历史原因,你需要同时使用两个不同的语言版本来做调试,不同版本存在兼容性问题(包括依赖兼容性问题),你的本地环境是升级还是不升呢。

解决这个问题的最佳方案有两个:

•尽可能简化你的环境,简化环境依赖(因为项目的多样性和复杂性,这有一些难)。•尽可能参考“基础架构即代码(IaC)”的思想去维护我们自己的开发环境,让我们所使用的内容,尽可能配置化,透明化,可复现

它除了能够完成 golang 开发环境的快速安装之外,还能够保障多个版本的 golang 共存,不同版本的软件依赖包都保持正常工作。并且,它的实现和社区大名鼎鼎的 nvm-sh/nvm 、shyiko/jabba 是一致的,都是由 BASH 编写,和所需要管理的 Runtime 语言无关,能够更稳定的完成“管理工作”。

Golang 环境安装和配置使用

关于 Golang 的多版本管理和安装,我曾经写过两篇相关的内容,一篇是半年前的内容,分享如何对 “Golang 进行多版本管理[2]”,另外一篇则是这篇的补充内容,分享如何针对 Mac M1 这类 ARM 设备使用 Golang 版本管理工具:《M1 芯片 Mac 上更好的 Golang 使用方案》[3]

如果你希望了解本章节之外的实践内容,或者过程中的思考,可以翻阅上面两篇内容。本篇文章的重点在于如何快速安装和配置,所以就不再展开相关“折腾安装工具”的细节啦。

安装 Golang 版本管理工具:soulteary/gvm

关于 Golang 的安装和版本升降级,因为老牌开源软件 GVM (Go Version Manager)“年久失修”,所以我做了一个修正版:https://github.com/soulteary/gvm

想要正常使用这个工具,我们需要先完成工具的基础依赖的安装:

代码语言:javascript复制
sudo apt install -y binutils bison gcc make

接着,执行下面的命令,通过网络获取安装脚本,然后在本地执行脚本完成安装。(如果你因为网络或其他原因,无法执行这条命令,可以使用下文中的替代方案):

代码语言:javascript复制
curl -sSL https://github.com/soulteary/gvm/raw/master/binscripts/gvm-installer | bash

不出意外的话,你将会看到类似下面的日志输出,意味着工具就此刻已经安装好啦。

代码语言:javascript复制
Cloning from https://github.com/soulteary/gvm.git to /home/soulteary/.gvm
No existing Go versions detected
Installed GVM v1.0.24

Please restart your terminal session or to get started right away run
 `source /home/soulteary/.gvm/scripts/gvm`

这里选择执行或者不执行 source /home/soulteary/.gvm/scripts/gvm 这条命令都可以(注意调整路径中的用户名),因为在接下来的文章中,我们将使用更靠谱的方式来将命令注册到我们所使用的 SHELL 环境中。

使用国内镜像来安装:soulteary/gvm

为了让安装过程更加顺利,我们可以使用从国内镜像下载包含安装脚本的仓库代码,然后直接执行安装脚本,来完成 gvm 这个开源软件的安装。

先使用 git clone 下载完整的软件仓库:

代码语言:javascript复制
git clone https://gitcode.net/soulteary/gvm.git

指定 SRC_REPO 参数为国内镜像地址,然后运行安装脚本:

代码语言:javascript复制
SRC_REPO=https://gitcode.net/soulteary/gvm.git bash gvm/binscripts/gvm-installer

当脚本运行完毕,我们将会看到上文中提到过的日志输出,此刻 gvm 就安装完毕啦。

代码语言:javascript复制
Cloning from https://gitcode.net/soulteary/gvm.git to /home/soulteary/.gvm
No existing Go versions detected
Installed GVM v1.0.24

Please restart your terminal session or to get started right away run
 `source /home/soulteary/.gvm/scripts/gvm`

为了更方便的使用 gvm,我们还需要进行一些配置。

配置 gvm 加速 Golang 下载/切换

gvm 支持使用两种方式来下载 “Golang”,然而不论是“下载源码编译安装”,还是下载适合当前操作系统的“预编译好的二进制文件”,我们都需要访问官方地址。

为了避免下载过程中因为网络问题,出现下载慢,或者无法下载的情况,节约我们的时间,我们需要对 gvm 进行一些简单的配置。

我们可以在当前使用的 “SHELL” 的 “rc” 文件中(比如.bashrc 或者 .zshrc),添加下面的内容,来在当前的环境中让 gvm 命令生效,同时,让我们能够使用更快的下载源来下载我们所需要的 “Golang”:

代码语言:javascript复制
export GO_BINARY_BASE_URL=https://golang.google.cn/dl/
[[ -s "$HOME/.gvm/scripts/gvm" ]] && source "$HOME/.gvm/scripts/gvm"
export GOROOT_BOOTSTRAP=$GOROOT

在“rc” 文件中添加了上述内容后,需要重启终端会话,才能够让会话生效。你可以使用 CTRL D 退出登录,然后再重新使用 SSH 进行终端连接或者直接在本地创建一个新的会话(具体怎么做,取决于你是如何开启的会话)。

为了让配置过程清晰透明,上面的三条命令,我们来依次看看上面的命令都“做了什么事情”。

代码语言:javascript复制
export GO_BINARY_BASE_URL=https://golang.google.cn/dl/

命令中的 GO_BINARY_BASE_URL 变量,定义了我们将从何处下载 Golang 的二进制文件或源码压缩包进行安装。当然,你也可以将其替换为下面的任意一个。

代码语言:javascript复制
# 官方地址
https://go.dev/dl/
# 官方国内镜像地址
https://golang.google.cn/dl/
# 中科大镜像
http://mirrors.ustc.edu.cn/golang/

接下来,我们来看看三条命令中看似最复杂的命令:

代码语言:javascript复制
[[ -s "$HOME/.gvm/scripts/gvm" ]] && source "$HOME/.gvm/scripts/gvm"

这条命令,是根据软件的实际安装情况来选择性加载 gvm。相比较前文中安装完毕 gvm 日志输出内容推荐我们直接使用 source 命令加载 gvm,这样可以更安全的执行命令,当且仅当 ~/.gvm 存在的时候才会加载程序,将 gvm 注册到你当前的 SHELL 环境中。

代码语言:javascript复制
export GOROOT_BOOTSTRAP=$GOROOT

最后一条命令,则是为了确保 Golang 使用源码编译安装时,不会出错(golang 1.14后需要 ),感兴趣可以围观官方开源项目中的这个 issue[4]

gvm 简明实用教程

gvm 是一个特别简单的命令,我们日常使用中其实只需要记得两个命令就好,第一个是 gvm install,第二个是 gvm use

假设我们想安装 Golang 最新版本 1.18.3,那么只需要执行下面的命令:

代码语言:javascript复制
gvm install go1.18.3 -B

在执行完毕命令之后,稍等片刻,当我们看到 Installing go1.18.3 from binary source 这条日志输出结果后,就意味着 Golang 已经被下载完毕了。如果你希望使用编译源码的方式安装 Golang 的话,可以去掉上面命令中的-B 参数:

代码语言:javascript复制
gvm install go1.18.3

虽然我们已经完成了 Golang 1.18.3 的安装,但是目前我们还不能直接使用它,需要再执行一条命令,将这个版本的 Golang “激活”:

代码语言:javascript复制
gvm use go1.18.3 --default

在执行完命令之后,我们能够立刻看到类似 Now using version go1.18.3 的日志输出结果,接下来我们就可以随意的使用 go 这个命令了。

我们可以使用 go version 来验证刚刚下载的程序是否符合我们的诉求:

代码语言:javascript复制
go version
go version go1.18.3 linux/amd64

未来如果 Golang 推出了新版本,我们想升级只需要按照上面的玩法,调整版本号,然后再执行一遍 installuse 命令就好了,是不是很简单!

当然,如果你只是想临时性的使用某个版本,比如 Golang 1.17 这个旧版本,可以稍微调整一下上面的命令,去掉 use 命令中的 --default 参数,只在当前 SHELL 会话中,让这个版本的 Golang 生效,随着我们关闭终端会话,Golang 的版本也会恢复到我们指定的默认版本,再也不需要担心系统环境混乱的问题啦。

代码语言:javascript复制
gvm install go1.17 -B
gvm use go1.17

# 再次执行查看版本,可以看到版本号已经变化了
go version
go version go1.17 linux/amd64

配置 Golang 软件包镜像

在日常开发和学习过程中,我们更多的是使用 Golang 来初始化项目和下载必要的软件包依赖。所以如何快速的下载到各种软件包也很重要,好在 Golang 提供了软件包代理配置选项 GOPROXY,我们可以通过在 “SHELL” 的 “rc” 文件中配置这个参数来完成下载提速:

代码语言:javascript复制
export GO111MODULE=on
export GOPROXY="https://goproxy.cn"

和上文中配置 gvm 一样,我们将上面的内容添加到所使用的 SHELL 的 “rc” 配置后,需要重新创建一个终端会话,让配置生效。

这里有一个题外话,初见“goproxy”的两个域名的时候,觉得域名十分相似,一番搜索,发现这两个域名虽然归属不同的开发者在维护,但是它们之间确实有一段缘分:“goproxy.io 和 goproxy.cn 的关系”[5]

Golang 环境验证:GoJieba

在完成环境配置之后,我们使用一个比较实用的 Golang 项目(https://github.com/yanyiwu/gojieba),来验证环境是否“好用”。

随便创建一个程序目录,然后在其中创建一个名为 main.go 的文件,引用 “gojieba”,并对一些句子和词汇进行处理:

代码语言:javascript复制
package main

import (
    "fmt"
    "strings"

    "github.com/yanyiwu/gojieba"
)

func main() {
    var s string
    var words []string
    use_hmm := true
    x := gojieba.NewJieba()
    defer x.Free()

    s = "北京西站南广场东"
    words = x.CutAll(s)
    fmt.Println(s)
    fmt.Println("全模式:", strings.Join(words, "/"))

    words = x.Cut(s, use_hmm)
    fmt.Println(s)
    fmt.Println("精确模式:", strings.Join(words, "/"))
    s = "向量数据库"
    words = x.Cut(s, use_hmm)
    fmt.Println(s)
    fmt.Println("精确模式:", strings.Join(words, "/"))

    x.AddWord("向量数据库")
    s = "向量数据库"
    words = x.Cut(s, use_hmm)
    fmt.Println(s)
    fmt.Println("添加词典后,精确模式:", strings.Join(words, "/"))

    s = "前门到了,请您后门下车"
    words = x.Cut(s, use_hmm)
    fmt.Println(s)
    fmt.Println("新词识别:", strings.Join(words, "/"))

    s = "小明先去了北京西站南广场东,然后又去了南京东路北大街西"
    words = x.CutForSearch(s, use_hmm)
    fmt.Println(s)
    fmt.Println("搜索引擎模式:", strings.Join(words, "/"))

    s = "朝阳区三里屯优衣库"
    words = x.Tag(s)
    fmt.Println(s)
    fmt.Println("词性标注:", strings.Join(words, ","))

    s = "元宇宙"
    words = x.Tag(s)
    fmt.Println(s)
    fmt.Println("词性标注:", strings.Join(words, ","))

    s = "长江大桥"
    words = x.CutForSearch(s, !use_hmm)
    fmt.Println(s)
    fmt.Println("搜索引擎模式:", strings.Join(words, "/"))

    wordinfos := x.Tokenize(s, gojieba.SearchMode, !use_hmm)
    fmt.Println(s)
    fmt.Println("Tokenize:(搜索引擎模式)", wordinfos)

    wordinfos = x.Tokenize(s, gojieba.DefaultMode, !use_hmm)
    fmt.Println(s)
    fmt.Println("Tokenize:(默认模式)", wordinfos)

    keywords := x.ExtractWithWeight(s, 5)
    fmt.Println("Extract:", keywords)
}

在准备好程序文件之后,我们先执行 go mod init main,完成 Go 项目的初始化:

代码语言:javascript复制
go: creating new go.mod: module main
go: to add module requirements and sums:
    go mod tidy

在执行完上面的命令后,我们的目录中将会多出来 “go.mod” 和 “go.sum” 两个文件,接着,我们来执行 go mod tidy 命令,让程序完成相关依赖的下载:

代码语言:javascript复制
go: finding module for package github.com/yanyiwu/gojieba
go: found github.com/yanyiwu/gojieba in github.com/yanyiwu/gojieba v1.1.2

因为我们配置了软件包镜像,所以应该在几秒内就能够完成项目的初始化。

在完成了项目初始化之后,我们执行 go run main.go 来验证下程序是否能运行,不出意外,将看到类似下面的输出结果:

代码语言:javascript复制
北京西站南广场东
全模式: 北京/北京西/北京西站/京西/西站/南/广场/东
北京西站南广场东
精确模式: 北京西站/南/广场/东
向量数据库
精确模式: 向量/数据库
向量数据库
添加词典后,精确模式: 向量数据库
前门到了,请您后门下车
新词识别: 前门/到/了/,/请/您/后门/下车
小明先去了北京西站南广场东,然后又去了南京东路北大街西
搜索引擎模式: 小明/先去/了/北京/京西/西站/北京西/北京西站/南/广场/东/,/然后/又/去/了/南京/京东/东路/南京东路/北大/大街/北大街/西
朝阳区三里屯优衣库
词性标注: 朝阳区/ns,三里屯/ns,优衣库/x
元宇宙
词性标注: 元/m,宇宙/n
长江大桥
搜索引擎模式: 长江/大桥/长江大桥
长江大桥
Tokenize:(搜索引擎模式) [{长江 0 6} {大桥 6 12} {长江大桥 0 12}]
长江大桥
Tokenize:(默认模式) [{长江大桥 0 12}]
Extract: [{长江大桥 11.1926274509}]

当然,除了 run 之外,我们最常用的命令还有 testbuild,本篇文章暂时不聊如何写单元测试,所以我们就先只验证 build 命令,执行 go build .,我们将在程序目录得到一个名为 main 的可执行文件。

手动执行命令 ./main,不出意外,将得到和上面 run 一样的输出结果。至此,Golang 环境验证也就结束啦。

最后

目前为止,我们已经聊完了“基础 Linux 环境搭建”、“Docker 环境安装和配置”、“Golang 的开发环境搭建”。

接下来的文章中,我会继续完成上篇文章中提到的几种不同的 K8S “发行版”的安装和配置,以及当今世界上流行的编程语言的环境配置。

希望对你有帮助。

--EOF

引用链接

[1] 《基础篇》: https://soulteary.com/2022/06/21/building-a-cost-effective-linux-learning-environment-on-a-laptop-the-basics.html [2] Golang 进行多版本管理: https://soulteary.com/2021/12/15/golang-multi-version-management.html [3] 《M1 芯片 Mac 上更好的 Golang 使用方案》: https://soulteary.com/2022/05/12/better-golang-usage-on-m1-mac.html [4] 这个 issue: https://github.com/golang/go/issues/12214 [5] “goproxy.io 和 goproxy.cn 的关系”: https://github.com/goproxy/goproxy.cn/issues/61

0 人点赞