<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          我這樣升級 Go 版本,你呢?

          共 7330字,需瀏覽 15分鐘

           ·

          2021-05-27 21:41

          閱讀本文大概需要 5 分鐘。

          大家好,我是 polarisxu。

          有些人可能注意到,每次 Go 發(fā)布新版本,官方都會提供類似這樣的升級截圖:

          這可以說是官方的 Go 多版本管理,也是升級 Go 的方式。今天就一起聊一聊這種多版本管理方式及其實(shí)現(xiàn)原理。(我之前介紹過一個(gè)第三方多版本管理工具 goup,是我比較推薦的)。

          注意,Windows 用戶應(yīng)該使用 WSL2。

          01 為什么需要多個(gè) Go 版本

          有些人可能覺得沒有這樣的需求。實(shí)際工作中,這樣的需求還是很常見的。以下一些場景,可能會希望有多版本:

          • 一般為了穩(wěn)定,線上版本通常不會激進(jìn)升級到最新版本,但你本地很可能想試用新版本的功能。這時(shí)候就希望能方便的支持多版本;
          • 為了測試或重現(xiàn)特定的問題,希望能夠在特定的版本進(jìn)行,這是為了避免不同版本干擾。
          • 。。。

          多版本并存,讓我們可以更自如的使用 Go。

          02 官方多版本的使用方式

          根據(jù)上面的圖,安裝某個(gè)版本的 Go,跟一般 Go 包安裝一樣,執(zhí)行 go get 命令:

          $ go get golang.org/dl/go<version>  // 其中 <version> 替換為你希望安裝的 Go 版本

          這一步,只是安裝了一個(gè)特定 Go 版本的包裝器,真正安裝特定的 Go 版本,還需要執(zhí)行如下命令:

          $ go<version> download   // 和上面一樣,<version> 是具體的版本

          因此,如果需要安裝 Go1.16.4,執(zhí)行如下兩個(gè)命令即可。

          $ go get golang.org/dl/go1.16.4
          $ go1.16.4 download

          幾個(gè)注意的點(diǎn):

          • 有一個(gè)特殊的版本標(biāo)記:gotip,用來安裝最新的開發(fā)版本;
          • 因?yàn)?golang.org 訪問不了,你應(yīng)該配置 GOPROXY(所以,啟用 Module 是必須的);
          • 跟安裝其他包一樣,go get 之后,go1.16.4 這個(gè)命令會被安裝到 $GOBIN 目錄下,默認(rèn)是 ~/go/bin 目錄,所以該目錄應(yīng)該放入 PATH 環(huán)境變量;
          • 沒有執(zhí)行 download 之前,運(yùn)行 go1.16.4,會提示 go1.16.4: not downloaded. Run 'go1.16.4 download' to install to ~/sdk/go1.16.4;

          可見,最后下載下來的 Go 放在了 ~/sdk/go1.16.4 目錄下。

          現(xiàn)在你是否有這樣的疑問:沒執(zhí)行 download 之前,直接運(yùn)行 go1.16.4 會報(bào)錯(cuò),執(zhí)行之后,它就成了具體的 Go 命令了,怎么做到的?

          03 扒一扒原理

          golang.org/dl/go<version> 對應(yīng)的源碼在 https://github.com/golang/dl(這是一個(gè)鏡像)。

          查看該倉庫代碼,發(fā)現(xiàn)一堆以各個(gè)版本命名的目錄:

          可見,每次發(fā)布新版本,都需要往這個(gè)倉庫增加一個(gè)對應(yīng)的版本文件夾。

          隨便打開一個(gè)(比如 go1.16.4),看看里面包含什么文件:

          就一個(gè) main.go 文件(從 go get 安裝操作,你應(yīng)該猜到一定有一個(gè) main.go 文件)。

          main.go 文件的內(nèi)容如下:(gotip 的內(nèi)容不一樣,它調(diào)用的是 version.RunTip())

          package main

          import "golang.org/dl/internal/version"

          func main() {
           version.Run("go1.16.4")
          }

          所以,關(guān)鍵在于 internal/version 包的 Run 函數(shù)(不同版本,version 參數(shù)不同)。注意以下代碼我給的注釋:

          // Run runs the "go" tool of the provided Go version.
          func Run(version string) {
           log.SetFlags(0)

            // goroot 獲取 go 安裝的目錄,即 ~/sdk/go<version>
           root, err := goroot(version)
           if err != nil {
            log.Fatalf("%s: %v", version, err)
           }

            // 執(zhí)行下載操作
           if len(os.Args) == 2 && os.Args[1] == "download" {
            if err := install(root, version); err != nil {
             log.Fatalf("%s: download failed: %v", version, err)
            }
            os.Exit(0)
           }

            // 怎么驗(yàn)證是否已經(jīng)下載好了 Go?在下載的 Go 中會創(chuàng)建一個(gè) .unpacked-success 文件,用來指示下載好了。
           if _, err := os.Stat(filepath.Join(root, unpackedOkay)); err != nil {
            log.Fatalf("%s: not downloaded. Run '%s download' to install to %v", version, version, root)
           }

            // 運(yùn)行下載好的 Go
           runGo(root)
          }

          這里主要是下載和運(yùn)行 Go。

          下載

          我們先看下載、安裝 Go。

          當(dāng)執(zhí)行 go1.16.4 download 時(shí),會運(yùn)行 install 函數(shù),查看該函數(shù)發(fā)現(xiàn),它調(diào)用了 versionArchiveURL 函數(shù)獲取要下載的 Go 的 URL:

          // versionArchiveURL returns the zip or tar.gz URL of the given Go version.
          func versionArchiveURL(version string) string {
              goos := getOS()

              ext := ".tar.gz"
              if goos == "windows" {
                  ext = ".zip"
              }
              arch := runtime.GOARCH
              if goos == "linux" && runtime.GOARCH == "arm" {
                  arch = "armv6l"
              }
              return "https://dl.google.com/go/" + version + "." + goos + "-" + arch + ext
          }

          也就是從 https://dl.google.com 下載 Go 包,最終的包(是一個(gè)歸檔文件,Wiindows 下是 .zip,其他系統(tǒng)是 .tar.gz)會放到 ~/sdk/go1.16.4 目錄下。

          之后通過 sha256 驗(yàn)證文件的完整性(因?yàn)榉?wù)端放了 sha256 校驗(yàn)文件),最后解壓縮,并創(chuàng)建上面說的 .unpacked-success 空標(biāo)記文件。這樣這個(gè)版本的 Go 就安裝成功了。

          注意,gotip 的下載是通過 git 獲取源碼的方式進(jìn)行的,它會通過源碼構(gòu)建安裝最新的 gotip 版本。具體邏輯在 internal/version/gotip.go 中。

          運(yùn)行

          因?yàn)橄螺d的 Go 是預(yù)編譯好的,因此可以直接使用。

          但是它將 Go 下載到了 ~/sdk/go<version> 目錄下了,我們并沒有將這個(gè)目錄的 bin 目錄加入 PATH,因此直接 go 命令運(yùn)行的還是之前的版本,而不是剛安裝的 go1.16.4。這個(gè)問題我們一會再說,先看看為什么這個(gè)時(shí)候 go1.16.4 命令可以當(dāng)作 go 命令來使用。

          上文說了,go1.16.4 只是一個(gè)包裝器。當(dāng)對應(yīng)的 Go1.16.4 安裝成功后,再次運(yùn)行 go1.16.4,會執(zhí)行 internal/version/version.go 中的 runGo(root) 函數(shù)。

          func runGo(root string) {
              gobin := filepath.Join(root, "bin""go"+exe())
              cmd := exec.Command(gobin, os.Args[1:]...)
              cmd.Stdin = os.Stdin
              cmd.Stdout = os.Stdout
              cmd.Stderr = os.Stderr
              newPath := filepath.Join(root, "bin")
              if p := os.Getenv("PATH"); p != "" {
                  newPath += string(filepath.ListSeparator) + p
              }
              cmd.Env = dedupEnv(caseInsensitiveEnv, append(os.Environ(), "GOROOT="+root, "PATH="+newPath))

              handleSignals()

              if err := cmd.Run(); err != nil {
                  // TODO: return the same exit status maybe.
                  os.Exit(1)
              }
              os.Exit(0)
          }

          該函數(shù)通過 os/exec 包運(yùn)行 ~/sdk/go1.16.4/bin/go 命令,并設(shè)置好響應(yīng)的標(biāo)準(zhǔn)輸入輸出流等,同時(shí)為新運(yùn)行的進(jìn)程設(shè)置好相關(guān)環(huán)境變量,可以認(rèn)為,執(zhí)行 go1.16.4,相當(dāng)于執(zhí)行 ~/sdk/go1.16.4/bin/go。

          所以,go1.16.4 這個(gè)命令,一直都只是一個(gè)包裝器。如果你希望新安裝的 go1.16.4 成為系統(tǒng)默認(rèn)的 Go 版本,即希望運(yùn)行 go 運(yùn)行的是 go1.16.4,方法有很多:

          • ~/sdk/go1.16.4/bin/go 加入 PATH 環(huán)境變量(替換原來的);
          • 做一個(gè)軟連,默認(rèn) go 執(zhí)行 go1.16.4(推薦這種方式),不需要頻繁修改 PATH;
          • 移動 go1.16.4 替換之前的 go(不推薦);

          03 每次升級版本創(chuàng)建一個(gè)包裝器

          手動復(fù)制粘貼代碼做這件事情肯定是很笨的辦法。在 golang.org/dl 中提供了一個(gè)工具,可以快速生成對應(yīng)版本的包裝器:https://github.com/golang/dl/blob/master/internal/genv/main.go。

          $ genv go1.16.4

          就可以生成 go1.16.4 包裝器。這里的實(shí)現(xiàn),有一個(gè)點(diǎn)提一下,它使用了 go list -m -json 命令:

          $ go list -m -json
          {
                  "Path""golang.org/dl",
                  "Main"true,
                  "Dir""<workspace>/dl",
                  "GoMod""<workspace>/dl/go.mod",
                  "GoVersion""1.11"
          }

          可以方便解析相關(guān)信息。

          04 總結(jié)

          官方的 Go 多版本管理就介紹完了??偨Y(jié)一下:

          • 官方通過 genv 命令生成對應(yīng)版本的包裝器;
          • 通過 go get 命令下載安裝對應(yīng)的包裝器;
          • 運(yùn)行包裝器,提供 download 這個(gè) flag,下載對應(yīng)版本的 Go 安裝包并解壓、校驗(yàn);
          • 之后,運(yùn)行包裝器,會執(zhí)行對應(yīng)版本的 go 命令;

          這樣達(dá)到了多版本管理的目的。這個(gè)設(shè)計(jì)思路還是可以的。

          但這種多版本管理,我認(rèn)為存在一些問題:

          • 上面說的,讓某個(gè)版本成為默認(rèn) Go 版本,沒有命令一鍵搞定;
          • 沒法知道有哪些版本,比如無法方便的知曉 1.15.13 是否存在,更無法方便的知曉 1.15.x 系列,x 的最大版本;
          • 刪除某個(gè)版本,得手動進(jìn)行(刪除包裝器和下載的 Go 安裝包);

          你喜歡這種方式管理還是類似 goup 這樣的第三方工具呢?你現(xiàn)在是怎么管理多版本的,歡迎交流!




          往期推薦


          我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗(yàn)!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網(wǎng)!著有《Go語言編程之旅》、開源圖書《Go語言標(biāo)準(zhǔn)庫》等。


          堅(jiān)持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio

          瀏覽 107
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人人操人人摸人人爱 | 国产精品传媒秘 麻豆Hd | 黄色网址中文字幕不卡 | 视频久久久仓井空 | 91天天综合 |