<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 版本,你呢?

          共 7279字,需瀏覽 15分鐘

           ·

          2021-06-18 00:48

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

          大家好,我是 polarisxu。

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

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

          注意,Windows 用戶應該使用 WSL2。

          01 為什么需要多個 Go 版本

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

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

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

          02 官方多版本的使用方式

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

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

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

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

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

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

          幾個注意的點:

          • 有一個特殊的版本標記:gotip,用來安裝最新的開發(fā)版本;
          • 因為 golang.org 訪問不了,你應該配置 GOPROXY(所以,啟用 Module 是必須的);
          • 跟安裝其他包一樣,go get 之后,go1.16.4 這個命令會被安裝到 $GOBIN 目錄下,默認是 ~/go/bin 目錄,所以該目錄應該放入 PATH 環(huán)境變量;
          • 沒有執(zhí)行 download 之前,運行 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 目錄下。

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

          03 扒一扒原理

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

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

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

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

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

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

          package main

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

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

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

          // 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)
           }

            // 怎么驗證是否已經下載好了 Go?在下載的 Go 中會創(chuàng)建一個 .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)
           }

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

          這里主要是下載和運行 Go。

          下載

          我們先看下載、安裝 Go。

          當執(zhí)行 go1.16.4 download 時,會運行 install 函數,查看該函數發(fā)現,它調用了 versionArchiveURL 函數獲取要下載的 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 包,最終的包(是一個歸檔文件,Wiindows 下是 .zip,其他系統(tǒng)是 .tar.gz)會放到 ~/sdk/go1.16.4 目錄下。

          之后通過 sha256 驗證文件的完整性(因為服務端放了 sha256 校驗文件),最后解壓縮,并創(chuàng)建上面說的 .unpacked-success 空標記文件。這樣這個版本的 Go 就安裝成功了。

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

          運行

          因為下載的 Go 是預編譯好的,因此可以直接使用。

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

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

          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)
          }

          該函數通過 os/exec 包運行 ~/sdk/go1.16.4/bin/go 命令,并設置好響應的標準輸入輸出流等,同時為新運行的進程設置好相關環(huán)境變量,可以認為,執(zhí)行 go1.16.4,相當于執(zhí)行 ~/sdk/go1.16.4/bin/go

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

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

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

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

          $ genv go1.16.4

          就可以生成 go1.16.4 包裝器。這里的實現,有一個點提一下,它使用了 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"
          }

          可以方便解析相關信息。

          04 總結

          官方的 Go 多版本管理就介紹完了。總結一下:

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

          這樣達到了多版本管理的目的。這個設計思路還是可以的。

          但這種多版本管理,我認為存在一些問題:

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

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




          往期推薦

          福利

          我為大家整理了一份從入門到進階的Go學習資料禮包,包含學習建議:入門看什么,進階看什么。關注公眾號 「polarisxu」,回復 ebook 獲取;還可以回復「進群」,和數萬 Gopher 交流學習。

          瀏覽 97
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  一级日本欧美 | 三级午夜在线无码 | 天天操天天操天天 | 台湾精品在线看 | 这里有精品 |