<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Go | Chia-An Lee</title><link>https://calee0219.github.io/tag/go/</link><atom:link href="https://calee0219.github.io/tag/go/index.xml" rel="self" type="application/rss+xml"/><description>Go</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><copyright>©</copyright><lastBuildDate>Thu, 15 Oct 2020 00:34:57 +0000</lastBuildDate><image><url>https://calee0219.github.io/media/icon_hu_da05098ef60dc2e7.png</url><title>Go</title><link>https://calee0219.github.io/tag/go/</link></image><item><title>Go Module 雜談</title><link>https://calee0219.github.io/blog/go_module_intro/</link><pubDate>Thu, 15 Oct 2020 00:34:57 +0000</pubDate><guid>https://calee0219.github.io/blog/go_module_intro/</guid><description>&lt;p&gt;v3.0.5 之後各 NF 使用 go module 維護所使用的 lib version，這邊簡單紀錄 go module 原理、free5GC 會面臨的問題、debug 手法&lt;/p&gt;
&lt;p&gt;Golang 原本沒有打算推出套件管理系統，因為據說 Google 內部並不會使用舊版 lib，而是使用單一程式庫(Mono Repo)[1]，若任何套件有跟新，就直接讓全部有使用此套件的軟體使用新版套件。可是外部社群不能這樣玩，所以逐漸推出了各自的套件版本管理系統，諸如 go dep, gopkg.in, vgo 等等。但在 go v1.11 Golang 社群 (或是 Google 內部 golang 維護者) 推出了 go module 系統，直接否定所有外部版本管理套件，因此當時也惹來了 go dep 開發者的不滿與爭吵[2]。&lt;/p&gt;
&lt;p&gt;Go module 類似於 npm 的 package.json，用一份 go.mod 檔案維護所使用的 lib 與使用的版本 (require 內部的東西)，在跑起來 (&lt;code&gt;go run&lt;/code&gt;、&lt;code&gt;go mod download&lt;/code&gt;、&lt;code&gt;go install&lt;/code&gt;、&lt;code&gt;go build&lt;/code&gt;) 的時候，就會直接用 &lt;code&gt;go get pakcage@version&lt;/code&gt; 的方式下載 library，並且放到 &lt;code&gt;$GOPATH/pkg/mod&lt;/code&gt; 資料夾下面。&lt;/p&gt;
&lt;h2 id="安裝使用"&gt;安裝使用&lt;/h2&gt;
&lt;p&gt;Go v1.11 之後的版本皆有 go module 可以使用，可以用 &lt;code&gt;go env GO111MODULE&lt;/code&gt; 確認 module 模式設定，有 on / off / auto 三種，on 預設都用 go module，off 都不用，auto 會看資料夾以及資料夾遞迴上去有沒有 go.mod 的檔案，如果有就用 go module，建議用 auto 就好，出問題再視情況 &lt;code&gt;go env -w GO111MODULE=on/off&lt;/code&gt; 設定修改。&lt;/p&gt;
&lt;h3 id="initialization"&gt;Initialization&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;go mod init {module name}&lt;/code&gt; 來初始化 go module，下完此指令後可以在資料夾下看到新增了一個 go.mod 的檔案，內容如下，module name 就是你自己定義的 module name，version 預設會使用目前你的 go 版本&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;此時還不會出現 require 欄位與 go.sum 檔案，如果資料夾下已經有 go file，可以用 &lt;code&gt;go mod tidy&lt;/code&gt; 做 go lib 的 polish，因為此指令會把 go.mod require 中沒用到的 lib 刪掉，同時會把 go file 內部有用到的 lib 放進 go.mod 中，因此可以用此指令幫忙直接更新 go.mod (直接 go run 或其他跑起來的指令，沒有被寫進 go.mod 的 lib 其實好像也會幫忙寫入)。&lt;/p&gt;
&lt;h3 id="載入已使用之-package"&gt;載入已使用之 package&lt;/h3&gt;
&lt;p&gt;下完 &lt;code&gt;go mod tidy&lt;/code&gt; 後，會發現 go.mod 多了 require 欄位，標明所使用的 lib 與所使用的版本，因為之前沒有這個欄位，go 預設會抓下指令時的最新版本，如果那個 lib 有打 git tag，會抓最新的 git tag 當版本，如果沒打，會抓最新的 commit hash 當版本&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gopkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt;&lt;span class="mf"&gt;.3.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;wmnsk&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;gtp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.7.4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;bronze1man&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.0.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20190516032554&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;afd8baec892d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gopkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="mf"&gt;.0.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20200227125254&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;fa46927fb4f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// indirect&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中因為 yaml.v2 有上 git tag，使用的版本是直接用 git tag 的 v2.3.0，而 radius 沒有打任何 git tag，因此版本使用 v0.0.0 後面接 commit 的時間 - {commit hash | cut -c 1-12}[3]。另外也有像是 check.v1 後面有註解 &lt;code&gt;// indirect&lt;/code&gt;，這代表這個 package 不是這份 project 本身有用到，但是是這個 project 所使用的 lib 有使用的 package。&lt;/p&gt;
&lt;h3 id="package-下載位置"&gt;Package 下載位置&lt;/h3&gt;
&lt;p&gt;這些 lib 會被 &lt;code&gt;go get&lt;/code&gt; 到 &lt;code&gt;$GOPATH/pkg/mod/{pakcage path}@{version}&lt;/code&gt; 下 (&lt;code&gt;$GOPATH&lt;/code&gt; 可以用 &lt;code&gt;go env GOPATH&lt;/code&gt; 看到)，例如 yaml 就會被下載到 &lt;code&gt;$GOPATH/pkg/mod/gopkg.in/yaml.v2@v2.3.0/&lt;/code&gt; 下面，若之後有升/降級用到其他版本，會重新抓到 &lt;code&gt;$GOPATH/pkg/mod/gopkg.in/yaml.v2@2.2.8/&lt;/code&gt; 下面。&lt;/p&gt;
&lt;p&gt;lib 檢查順序:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;檢查是否 go module，如果有 (go.mod / GO111MODULE)，看 package prefix 是否跟 go.mod 的 module name 相同，若相同則知道視同個專案，拔除 module name 後，postfix 當作 director path 往下找&lt;/li&gt;
&lt;li&gt;檢查是否有 vendor folder 在這個 project 下面，如果有，檢查 vendor 資料夾下是否有 modules.txt 檔案，若有，使用 modules.txt 所指向的 package 位置 (在 vendor 資料夾裡面)&lt;/li&gt;
&lt;li&gt;檢查 go.mod 或 modules.txt 是否有 replace，優先使用 replace 的 lib 位置&lt;/li&gt;
&lt;li&gt;module name prefix 不相同，檢查 &lt;code&gt;$GOPATH/pkg/mod&lt;/code&gt; 下是否有 lib&lt;/li&gt;
&lt;li&gt;拿 prefix URL git clone 看是否有東西&lt;/li&gt;
&lt;li&gt;檢查 &lt;code&gt;$GOPATH/src&lt;/code&gt; 下是否有 lib&lt;/li&gt;
&lt;li&gt;檢查 &lt;code&gt;$GOROOT&lt;/code&gt; 下是否有 lib&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="gosum"&gt;go.sum&lt;/h3&gt;
&lt;p&gt;另外，有 require 後，也會自動生出 go.sum 的檔案，這份檔案是用來維護 lib 的 check sum 做安全性與完整性的檢查，主要有兩種[4]，差別在於這個 package (lib) 是否有 go.mod 檔案 (是否是 go module 管理)，有 /go.mod 的 package 代表這個 lib 本身沒有用 go module 管理。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;module&amp;gt; &amp;lt;version&amp;gt; &amp;lt;hash&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;module&amp;gt; &amp;lt;version&amp;gt;/go.mod &amp;lt;hash&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;go.sum 上面講的東西其實不太重要，更詳細的內容可以翻[4] 有更多細節。主要需要知道的是 go.sum 是對使用 package 做的 checksum 檢查，因為 go module 對於任何合法的 url 都可以使用 (而不是像 npm 等作中心化管理)，避免被竄改所以需要做 checksum 檢查。另外 go.sum 還會紀錄之前有使用過的版本，因此一份陳年的 go.sum 內容會有很多版本 (transparent log)。&lt;/p&gt;
&lt;p&gt;既然 go.sum 的設計是紀錄 checksum，那就可能會帶出一些麻煩，常見有兩個問題&lt;/p&gt;
&lt;p&gt;在使用的 package 有 force push / rebase / commit &amp;ndash;amend 等強迫性操作時，再配合 git tag, git tag -d (刪除), git tag 的操作 (就是如果你要 re-tag)，會發生同一個 tag 在被 re-tag 後，checksum 不同，可是對 go.sum 來說，只能知道 checksum 不吻合可能被竄改，便會拒絕使用 package，這時候最簡單的作法是手動到 go.sum 內把當個 package 的 checksum 列全部刪掉，重新跑 go run，讓 go.sum 重抓，但更建議的是&lt;strong&gt;盡可能不要做 git 破壞性更新 (套件開發者不要亂搞&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;merge 時因為兩個 branch 更新了同一個套件，又尤其當兩個 branch 跟新到的版本 (commit) 不同，便需要手動解 conflict，大致有兩種解法，一是把曾用到的 commit 都納入 go.sum (branch a 跟 branch b 的 commit 都留著)，因為 go.sum 的 transparent log 性質，理論上曾經用過的版本都要留著；另外一種做法是選其中一個版本，確認可以 work 就好。嫌麻煩更簡單的做法一樣是進到 go.sum 把相關的列都刪掉，重新 go run 讓他自己抓。&lt;/p&gt;
&lt;p&gt;也許你會覺得直接不 commit go.sum 就好，可是因為 go module 不像 npm 信任一個中心化的套件管理倉儲，為了安全性與完整性，go.sum 應該是必須被 commit 的，而不像 npm 的 package-lock.json 應該被 ignore 掉。&lt;/p&gt;
&lt;h3 id="升級-package"&gt;升級 package&lt;/h3&gt;
&lt;p&gt;可以用 &lt;code&gt;go get -u {package}&lt;/code&gt; 來單獨升級 package 的 minor / patch version，可以用 &lt;code&gt;go get -u&lt;/code&gt; 來升級全部 package 的 minor / patch version，可以用 &lt;code&gt;go get -u=patch&lt;/code&gt; 只升級 patch，不過這都是在有打 git tag 的情況。&lt;/p&gt;
&lt;p&gt;可以用 &lt;code&gt;go get -u {package}@{version}&lt;/code&gt; 來使用特定版本，其中 version 可以是 git tag, branch name, commit hash, latest[5]。&lt;/p&gt;
&lt;p&gt;:::warning
go module 本身有一個指令 &lt;code&gt;go mod edit -require=path@version&lt;/code&gt; 可以更新 go.mod 的版本，但是官方不建議這樣使用。&lt;/p&gt;
&lt;p&gt;Note that -require overrides any existing requirements on path. These flags are mainly for tools that understand the module graph. Users should prefer &amp;lsquo;go get path@version&amp;rsquo; or &amp;lsquo;go get path@none&amp;rsquo;, which make other go.mod adjustments as needed to satisfy constraints imposed by other modules.
:::&lt;/p&gt;
&lt;p&gt;:::info
當過了一段時間 (尤其在 release 前)，建議用 go mod tidy 把沒在用的 package polish 掉，避免不必要的下載。
:::&lt;/p&gt;
&lt;h2 id="開發使用"&gt;開發使用&lt;/h2&gt;
&lt;h3 id="private-repo-問題"&gt;Private Repo 問題&lt;/h3&gt;
&lt;p&gt;由於我們內部的專案是 private repo，直接 go get 會 permission denied (403 access denied)，因此要處理權限問題。&lt;/p&gt;
&lt;p&gt;由於 go get 本身是用 git 的方式抓 package (可以用 &lt;code&gt;go get -x&lt;/code&gt; 或 &lt;code&gt;go mod download -x&lt;/code&gt; 看到詳細指令)，因此理論上只要 git fetch 可以抓的到 code 就可以了，常理來想就把 ssh-key 塞進 git server，然後用 git@ 的方式抓 code，或者指定帳號密碼用 https://ac:pw@ 的方式來抓 code，但是可以看出這兩種方法抓 code，都是需要改 upstream 的字串 (從 https:// 變成 git@ 或 https://ac:pw@)，而 go get 或自動加 https:// 在 package name 前面 (ex. github.com/free5gc/aper 會被加成
)，要如何讓他改成加 git@ 等呢? 這時候可以使用 git 的 insteadOf 操作，利用&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config --global url.&lt;span class="s2"&gt;&amp;#34;git@github.com:free5gc/&amp;#34;&lt;/span&gt;.insteadOf https://github.com/free5gc/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;來使 git 在抓 code 的時候自動改變 url，而不需要在 go 上面改變操作。&lt;/p&gt;
&lt;p&gt;其中一定要用 &lt;code&gt;--global&lt;/code&gt; 參數，如果不下，這個參數只會被設定在 local project 裡面，而 go get 的時候是不會吃到這個參數的。這個設定會被寫入 &lt;code&gt;$HOME/.gitconfig&lt;/code&gt; 這份檔案裡，如果需要可以直接去改這份檔案。&lt;/p&gt;
&lt;p&gt;設定完 git 後似乎可行了，可是實際 &lt;code&gt;go get&lt;/code&gt; 時還是會抓不到，那是因為 &lt;code&gt;go env&lt;/code&gt; 內有 &lt;code&gt;GOPROXY&lt;/code&gt;[6] 與 &lt;code&gt;GOSUMDB&lt;/code&gt;[7] 兩個參數，這兩個參數類似 golang 本身的可信任函式庫，非 public 的 project 因為無法透過這兩個 proxy 被獲取，因此會被視為危險被竄改的 url，需要把 private repo url 列入 &lt;code&gt;GONOPROXY&lt;/code&gt; 與 &lt;code&gt;GONOSUMDB&lt;/code&gt; 參數內，而在 go 1.13 版本之後，有 &lt;code&gt;GOPRIVATE&lt;/code&gt;[8] 可以直接使用，它會幫忙同時寫入 &lt;code&gt;GONOPROXY&lt;/code&gt; 與 &lt;code&gt;GONOSUMDB&lt;/code&gt; 兩個參數。&lt;/p&gt;
&lt;p&gt;最後，理論上 &lt;code&gt;GOPROXY&lt;/code&gt; 參數預設就有 direct 在裡面，但是如果沒有，也需要加入&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;In short, 下兩個指令讓 go get 可以抓到 private repo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;`git config --global url.”git@github.com:free5gc/”.insteadOf https://github.com/free5gc/`
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;`go env -w GOPRIVATE=github.com/free5gc/*`
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;也可以直接用現成的 script: `cd infra/ &amp;amp;&amp;amp; ./infra.sh`
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="package-lib-開發問題"&gt;Package (lib) 開發問題&lt;/h3&gt;
&lt;p&gt;在開發時，單獨看一個 package 都是開發完後推上 git server 做 release，才讓別人使用。&lt;/p&gt;
&lt;p&gt;但由於我們同時是 package 的 producer 與 consumer，會出現如果把 package (lib) 開發完，推上 git server，使用的 NF 才能更新 go.mod 的版本來使用，但是這樣會遇到無法快速迭代測試的問題，也就是如果你正在 bugfix aper，你無法 local 修完直接用 amf 來測試 (往好處想就是 unit test 要寫得更詳細 &amp;#x1f914;)，而是需要先推到 remote server 上，然後更新 amf 的 go.mod 中 aper commit hash，才能來測試 amf 是否可以跑得起來，而如果發現 aper 有錯沒修好，就要反覆重複上面的動作。&lt;/p&gt;
&lt;p&gt;:::warning
仔細講這裡會遇到更多討論，像是為了避免 commit message 變得沒有意義，應該用 &lt;code&gt;git commit --amend&lt;/code&gt; 來更新，但是 &lt;code&gt;--amend&lt;/code&gt; 是 force push，會改變 commit hash 與 history，而這時候使用它的 project (amf) 因為有 go.sum 維護 checksum，可能會發現 history 被改變了而不允許跟新，這時候可能還會需要進到 go.sum 內手動改內容…
:::&lt;/p&gt;
&lt;h4 id="1-使用-replace9"&gt;1. 使用 replace[9]&lt;/h4&gt;
&lt;p&gt;在 go.mod 下使用 replace 可以把 go.mod 所使用的 package 替換成指定的路徑下的 package，就不會再從網路上拉並且放到 &lt;code&gt;$GOPATH/pkg/mod/&lt;/code&gt; 資料夾下面，用這個方法在修改指定路徑下的 package 後，更動可以直接 apply 到使用它的 project 上，使用上先 clone 你要改的 package 到你指定的 local path (之後才能 commit 回到 git server)，然後在 go.mod 中 replace package 到剛剛的位置 (可以用相對或絕對路徑)，然後進行修改&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone package /to/local/path
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go mod edit -replace &lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/to/local/path &lt;span class="c1"&gt;# 新增 replace，可以用相對或絕對路徑&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go mod edit -fmt &lt;span class="c1"&gt;# format go.mod，理論上用指令改了話不需要做&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go mod edit -dropreplace package &lt;span class="c1"&gt;# 刪除 replace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但 replace 本身有一個限制，無法 replace indirect 的 package，也就是說你&lt;strong&gt;無法 replace 你用到的 package 所使用到的 package&lt;/strong&gt;，ex. amf 用到 &lt;code&gt;util_3gpp&lt;/code&gt;，而 &lt;code&gt;util_3gpp&lt;/code&gt; 有用到 &lt;code&gt;crypto&lt;/code&gt;，如果 amf 本身沒用到 &lt;code&gt;crypto&lt;/code&gt;，它就不能 replace &lt;code&gt;crypto&lt;/code&gt;，就算把 replace &lt;code&gt;crypto&lt;/code&gt; 寫入 go.mod 中也不會生效。(如果改用 2. vendor 的做法，因為 vendor 等於把所有 package 用跟 &lt;code&gt;pkg/mod&lt;/code&gt; 的方式一樣全部都抓下來，所以應該可以把修改的 &lt;code&gt;crypto&lt;/code&gt; 應用進 amf 中)&lt;/p&gt;
&lt;p&gt;(replace 不只可以把 package 改成 local path，也可以修改 remote package 位置，因此很多 China 的文件有用 replace 來 bypass 牆)&lt;/p&gt;
&lt;h4 id="2-使用-vendor10"&gt;2. 使用 vendor[10]&lt;/h4&gt;
&lt;p&gt;(據說 vendor 會在之後的某一個 go 版本被 outdated)&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;go mod vendor&lt;/code&gt; 會把 go.mod 內所有的 package 下載到 project 內的 vendor 資料夾下，並且建立一個 modules.txt 的檔案，由於 project 有 vendor 資料夾，go lib 會優先看這個資料夾內的 package，其中 package 的資料夾結構與 &lt;code&gt;$GOPATH/src&lt;/code&gt; 的相似，可以透過這個性質，cd 到要修改的 package，&lt;strong&gt;刪掉 package folder 後重新 clone 一個下來&lt;/strong&gt; (因為這個 folder 不會帶 git，無法 commit) (或用其他 git init / set-url 的方式)，再行修改。&lt;/p&gt;
&lt;p&gt;:::warning
要注意每次下 go mod vendor 指令，golang 會直接把 vendor 資料夾刪除後建立新的資料夾，不會檢查內部是否有修改，因此要小心 &lt;strong&gt;local change 一定要 push 上 server&lt;/strong&gt;。
:::&lt;/p&gt;
&lt;h2 id="considering-issue"&gt;Considering issue&lt;/h2&gt;
&lt;h3 id="go-mod-統一版本問題"&gt;Go mod 統一版本問題&lt;/h3&gt;
&lt;p&gt;另外一個 go.mod 需要注意的是，如果有多個 package (包含 project 本身) 有使用相同的 package，而大家又是用不同版本的此 package，golang 會遵守 Minimal Version Selection[11] 的原則來選擇 package 版本，以 Russ Cox 提出的 sample 為例，如果你有 A, B, C, D, E, F, G 不同的 package，而其中 A 用到 B, C，B 用到 D，C 也用到 D, F，D 用到 E，F 與 G 互相依賴，如附圖&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;&lt;img src="https://research.swtch.com/version-select-1.png" alt="" loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;其中用 1, 1.1, 1.2, 1.3, 1.4 等代表不同版本，經過歸納後，會產生一個建構清單&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;&lt;img src="https://research.swtch.com/version-select-list.png" alt="" loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;&lt;img src="https://research.swtch.com/version-select-2.png" alt="" loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;最後會使用 A1, B1.2, C1.2, D1.4, E1.2, F1.1, G1.1 的版本。這樣是否會導致問題? 理論上在 Semantic Versioning 的規範下，只要不是 major 版號更動，API 不應該有所變化，而使用最新的理應可以 work 並且效能或正確性高於舊版。&lt;/p&gt;
&lt;p&gt;然而在 lib 還沒上 Semantic Versioning git tag 前，這不一定適用於我們的專案，目前測試看起來 golang 會選擇主要(最上層)的 go.mod 作為版本依據，這還需要持續觀察以及是否會遇到問題。最理想的做法是以 NF 為單位，每個 NF 所使用的 lib 固定為 NF go.mod 所指定的版號，不同的 NF 可用不同版本的 lib。&lt;/p&gt;
&lt;h3 id="major-版本升級"&gt;Major 版本升級&lt;/h3&gt;
&lt;p&gt;如果之後要升級 major 版號，要如何處理?golang 官方不建議 major 版號升級只生 go.mod 的版號，畢竟理論上 major 升級應該是會有不相容的 API 更新[12]，而是建議在使用時就 import package/name/v2 的方式更新[13]，如果只更新 go.mod 會在 go.sum 出現 +incompatible 的字樣，提醒你有跟新 major 版號，你的 code 使用要檢查。&lt;/p&gt;
&lt;p&gt;如何減少 major 版號更新是設計之初便要思考的，可以參考 Go Blog:
&lt;/p&gt;
&lt;h2 id="trouble-shooting"&gt;Trouble Shooting&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;go.mod 不可以在 &lt;code&gt;$GOPATH&lt;/code&gt; 之中。&lt;/li&gt;
&lt;li&gt;因為
，如果 package 不明原因無法自動獲取不到 (ex. private repo issue)，可以考慮直接到 &lt;code&gt;$GOPATH/pkg/mod&lt;/code&gt; 或 &lt;code&gt;$GOPATH/src&lt;/code&gt; 下面直接做 &lt;code&gt;git clone&lt;/code&gt;，或用 copy folder 方式把東西丟上去，唯需要注意路徑，如果是 &lt;code&gt;pkg/mod&lt;/code&gt; 需要把 folder 名稱重新命名為 &lt;code&gt;path/same/as/url/name@version-time-commit&lt;/code&gt;，並且要把內部的 &lt;code&gt;.git&lt;/code&gt; folder 刪掉 (不刪掉應該也沒差，不過 go 自己抓的好像會沒有)；如果是 &lt;code&gt;$GOPATH/src&lt;/code&gt; 只要路徑對就好。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ref"&gt;Ref:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[1]
,
,
&lt;/li&gt;
&lt;li&gt;[2]
&lt;/li&gt;
&lt;li&gt;[3] &lt;code&gt;git --no-pager show --abbrev=12 --format=&amp;quot;%cd-%h&amp;quot; --date='format-local:%Y%m%d%H%M%S' --quiet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[4]
&lt;/li&gt;
&lt;li&gt;[5]
&lt;/li&gt;
&lt;li&gt;[6]
&lt;/li&gt;
&lt;li&gt;[7]
&lt;/li&gt;
&lt;li&gt;[8]
,
&lt;/li&gt;
&lt;li&gt;[9]
,
,
&lt;/li&gt;
&lt;li&gt;[10]
&lt;/li&gt;
&lt;li&gt;[11]
,
, ,
&lt;/li&gt;
&lt;li&gt;[12]
&lt;/li&gt;
&lt;li&gt;[13]
,
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>