bgo: 讓構建 go 程式更容易

hedzr發表於2022-01-30

bgo

一句話瞭解:bgo 管 exe 不管 lib

前言

前言之前:

這次的前言很長,可以直接跳過。

bgo 是一個構建輔助工具,它和已知的那些為 go 應用程式構建服務的工具沒有多大的不同,都能支援在你的工作環境中交叉編譯構建出 go 應用程式的執行檔案。<!--MORE-->


儘管這個能力本來是 go build 自帶所有,但這樣外包裝一次後的輔助性的構建工具可以讓你節省大量的精力,無論是那些難以記憶的 go build 命令列引數也好,還是那些我們不得不用到的前準備,後處理步驟,例如 go generate,複製到某個公共地點,等等。

所以這就是 bgo 這樣的工具的用途,省力。

緣起

當然,為什麼我要開發又一個輔助工具呢?

原因也不復雜,有這麼幾個:

我現在常常在一個大目錄中弄一大堆小專案,也許只是試驗一下某個功能,做某件事情,也許是因為我需要羅列一大堆 examples 給使用者睇,總之很多就是了。但一個小的週期裡,我通常聚焦在其中的某一個或者某兩三個,為此,切換目錄,構建就很疲憊了。Goland 倒是很貼心能讓我省去這些命令列環境裡的工作,但它的執行結果視窗不能支援全功能的鍵盤互動,所以有的行為我就只能開出一個終端來跑。當然還有很多別的是 Goland 一定做不了的,就不列舉了,總之這是一個具體的問題。對此,為什麼不 vscode 呢?你用過嗎?你要知道 vscode 跑 goapp 只有除錯跑某一個主程式的能力,想要不除錯,或者想多個 apps,需要去它的嵌入終端裡敲命令列的。另外,如果你不在 go.mod 那裡開啟工作區的話,vscode 會有很多問題——換句話說,vscode 頂多支援單個 go.mod 代表的模組。

另一個原因,是作為較為正式的釋出,我可能頻繁需要用到 -X 或者 go generate。對此,我原本有一個複雜的 Makefile 可以自動解決這些問題。不過他們對於我在大規模專案中也不怎麼好用,因為我可能會有很多不同的目錄對應不同的子模組。

再一個原因是構建時的順序問題,我有時候需要有序地做一系列構建,而有時候我需要很快地構建單個 target 在 exeutable 模式下執行和檢視結果。

當然還有其它原因,不過拉拉雜雜的就不說了。

你可以注意到主要的理由還是在於我在維護大型專案結構。為什麼不切分成很多很多的 repos 呢?這又涉及到另一個問題:go modules 不支援多個巢狀的 go.mod。也就是說,如果你的上級目錄有 go.mod,那麼下級就不可以再有,這是個很複雜的情況,但終歸對於你來說,這個限制是很硬的,所以這樣的結構行不通:

/complex-system
  go.mod
  /api
    /v1
      go.mod
  /common
    go.mod
  /account
    go.mod
  /backends
    /order
      go.mod
    /ticket
      go.mod

這裡只列舉了部分目錄結構,而且僅僅是個示意。

同樣地,還是會有人說,每個 go.mod 都一個 repo 就好了丫。恩,是這樣的,確實這樣是可以的,也只能這樣做。

完了之後就需要想辦法解決依賴關係了。

go 1.18 的 go.work 對此沒有什麼幫助,甚至於很難用。

git submodules?很麻煩,很難用,會忘記狀態,然後就杯具。

再來有人就會說,那就頂級一個 go.mod,其它的都不要用,只分出目錄結構就好了。對的,在我幾年前做類似的大規模專案時就是這麼做的,但 api 的 protobuf 參考,common 的 etcd 參考,等等,混雜在一起,而且都是解決起來很麻煩的那種,最後弄得整個巨型專案臃腫不堪,而且毫無必要地混亂。

其實,曾經有一段時間,多個 go.mod 巢狀存在是可以馬馬虎虎運作的,但那個階段很短,我忘記是在 1.13 還是哪個時期了,總之這是一個遙遠的懷念了。那段時光裡專案的構型我就滿意,但後來重新順應單 go.mod 也更痛苦。

所以

理由說了一堆,但是 bgo 只解決了一部分問題,也就是很多小 apps 分佈在一堆複雜的子目錄中時的構建問題。

採用 bgo 的話,我們就可以用一個大的頂級目錄來管理若干的子專案了,當然,由於 go modules 的限制在頂級目錄是不能建立 go.mod 的(以免子目錄中的 go.mod 出現問題),它只是起到聚集的作用,而我們利用 Goland 或者 vscode 開啟這種結構時也都會很 OK。

現在的構型:

/atonal
  .bgo.yml
  /api
    /v1
      go.mod
  /common
    go.mod
  ...

你可以注意到,在頂級目錄中包含一個 .bgo.yml 檔案,這就是 bgo 的配置檔案了,它可以通過 bgo init 經過掃描子目錄之後自動生成,然後在這個基礎上你可以進一步地調整。

唯一的問題是,它只收集 main 包所在的目錄,也就是能夠產出 executable 的那些目錄,至於說你的帶有 go.mod 的目錄並不真的被納入管理範圍。

這是 bgo 的限制,但本也就是 bgo 的設計目標:我們是掃描和管理一系列的 CLI apps 並以一種較輕便的方式完成相應的構建。我們並不提供以 go.mod 為標誌的多個 modules 的管理,這種功能要麼我們另行設計一款工具來說,要麼不做,免得哪天 google 發神經有搞一套 go.zone 出來。

當然,你可以為每個 go.mod 配一個 main.go 來讓 bgo 兼管它。但總的來說,bgo 管 exe 不管 lib

令人失望的 go 演進

話說 go.mod go.work 都有了,卻全都不是對大型專案做解決的有效方案,而是僅僅侷限在的某一個 module 之中。

在 Go 的工程管理中,總是有很多問題:

  • 私有 repos 的解決
  • 問題 repo 或者遺失的 repo 的解決
  • 複雜的多模組建設與組織
  • 可憐的泛型,還不如沒有呢
  • 等等

講真,我曾抱有很高的期待。但我和 yaml 那些人的心情估計是相似的,哇,泛型來了,哦,泛型走好。在這個領域裡(自動型別解決,未知型別解決),golang 的泛型算得上是一無是處。按照目前的演進態勢,也不可能期望它解決自由預判未知型別。

如何開始?

安裝

首先當然是要安裝 bgo。

Releases

bgo 被設計為只需單個可執行檔案即可開始工作,所以請在 Releases 下載預編譯版本並放到你的搜素路徑中。

go get

或者,你可以通過 go get 系統:

go get -u -v github.com/hedzr/bgo
From source code

image-20220130101919648

Homebrew

homebrew 是可以的:

brew install hedzr/brew/bgo

其它平臺的包管理暫不支援,因為這就是個小工具而已。

Docker

也可以使用 docker 方式執行,但稍微有點複雜,因為需要搭載本機捲上去:

docker run -it --rm -v $PWD:/app -v /tmp:/tmp -v /tmp/go-pkg:/go/pkg hedzr/bgo

這樣就和執行原生 bgo 作用一致。

docker 容器可以從這些地方獲取:

docker pull hedzr/bgo:latest
docker push ghcr.io/hedzr/bgo:latest

執行

在你想要構建的目錄,執行 bgo。

例如我們在 ini-op 的原始碼中執行它:

image-20220128104835837

這就是最簡便的開始。

特點

不過如果就是這樣,有什麼價值呢?命令列縮短器?

bgo 所能做的,特別在於幾處:

  • tags 等等 go build 命令列太長太難於編輯
  • 想要編排構建指令及其前後處理行為。例如加入後處理器,等等。
  • 有一大堆子目錄中都有 CLIs,但不想逐個構建、或者構建引數各有區別而不能一次性處理。
  • 有一系列的 go modules,但想要統一編排。

這些目標,都可以通過配置檔案來解決。

建立構建配置檔案

bgo 以當前目錄為基準,它會尋找當前目錄下的 .bgo.yml 檔案,載入其中的 projects 配置資訊,然後按照特定的順序依次構建。

由於我們強調有序性,因此 bgo 並不支援多個專案並行編譯。

事實上,這種功能是無價值的,因為它只會降低整體的編譯速度。

所以問題的第一個是,.bgo.yml 如何準備?

方法是,選擇你的根目錄來首次執行 bgo。

bgo -f --save          # Or: bgo init
mv bgo.yml .bgo.yml

這個命令會掃描全部子目錄,然後編制一個構建配置表並儲存為 bgo.yml 檔案。

然後你可以選擇重新命名配置檔案(這是被推薦的,防止錯誤執行了 --save 後帶來的可能的副作用)。但如果你想,也可以保持檔名原樣不變。

同義詞

bgo init
bgo init --output=.bgo.yml

如果你需要一份帶註釋的示例,請查閱:

https://github.com/hedzr/bgo/blob/master/.bgo.yaml

開始構建

配置檔案準備好之後,就可以跑了:

bgo

這就會將配置檔案中的全部 projects 都給構建了。

事實上,如果沒有配置檔案,bgo 會自動掃描出第一個 main cli 並構建它。

採用不同的構建範圍

bgo 支援三種範圍,它們的區別在於如何處理配置檔案中的 projects 以及是否掃描當前資料夾中的 projects:

  [Scope?]
  -a, --auto      ⬢ Build all modules defined in .bgo.yaml recursively (auto mode) [env: AUTO] (default=true)
  -f, --full      ⬡ Build all CLIs under work directory recursively [env: FULL](default=false)
  -s, --short     ⬡ Build for current CPU and OS Arch ONLY [env: SHORT] (default=false)

平時 bgo 在自動模式(--auto),此時僅配置檔案中的 projects 被考慮納入構建序列中。如果沒有找到配置檔案的話,bgo 會嘗試掃描當前資料夾找出第一個 main app。

完整掃描模式(--full)會掃描當前資料夾中的全部可能的 projects。

而簡短模式(--short) 只會從配置檔案中提取第一個專案,不過,如果你的根目錄中沒有配置檔案的話,它會掃描到首個 project 來構建。它的特別之處在於只有工作機的當前 GOOS+GOARCH 才會被構建。

除此之外,你可以顯式指定一個名字來執行 bgo。bgo 會在配置表中檢索同名的project 並單獨對其執行構建序列。

bgo -pn whoami   # Or `--project-name`

在配置檔案中,你可以設定 disabled: true 來禁止一個專案,或者禁止一個專案組。

對 projects 分組

多個 projects 可以劃分為一個分組,好處是構建引數可以統一指定。例如:

---
app:
  bgo:
    build:
      cgo: false

      projects:
        000-default-group:
          # leading-text:
          cgo: false
          items:
            001-bgo:   # <- project name
              name:    # <- app name
              dir: .
              gen: false
              install: true
              cgo: false
            010-jsonx: # <- project name
              name:    # <- app name
              dir: ../../tools/jsonx

例子中定義了一個 default-group 組,然後定義了 bgojsonx 兩個 projects。

它們的名字字首(如 001- 片段)被用於排序目的,在構建行為中會被忽略,不會被當作是名稱的一部分。

專案組的構建配置設定(例如 cgo 引數)會被下拉應用到每個專案,除非你顯式指定它;類似地,在 app.bgo.build 這一級也可以定義 cgo, for, os, arch 等等引數,它們具有更高一級的優先性。

示例片段中,cgo 這個引數在各級都有定義,專案級的 cgo 優先順序最高。

Project 的配置引數

除了 name, dir, package 欄位之外,其它的引數都是公共的。也就是說,其它引數都可以被用於頂級或者專案組一級。

name, dir, package

每個 project 需要 dir 作為基本的定義,這是必須的:

---
app:
  bgo:
    build:

      projects:
        000-default-group:
          # leading-text:
          items:
            001-bgo:   # <- project name
              name:    # <- app name
              dir: .
              package: # optional

可以採用 ../ 的相對路徑的語法,跳出當前目錄的限制。理論上說,你可以納入一切專案。

bgo 會檢測該目錄中的 go.mod 以決定應否啟動 go modules 編譯。

每當具體執行 go build 時,總是會切換到 dir 所指向的位置,以此為基準,除非你指定了 keep-workdir,這個引數告訴 bgo 保持當前工作目錄。你也可以使用 use-workdir 來為某個 project 特別指明一個 go build 時的基準工作目錄。

name 可以被用於顯式地指定專案的 app name,如果不指定,那麼會從 project name 取值。project name 是開發者的工程管理用名,而 app name 是用給 end user 看的(以及,將被作為輸出的 executable 的基本名字)。

package 是可選的,通常你不必手工指定它。通過 bgo init 掃描得來的結果裡這個欄位會被自動填寫,但實際構建時它總是會被重新提取。

disabled

這個欄位使得相應專案被跳過

disable-result

bgo 在完成了構建之後,自動 ll 結果的 executable。這個欄位可以禁止 bgo 的這一行為。

keep-workdir, use-workdir

一般情況下,bgo 跳轉到專案所在目錄之後開始 go build .,然後再返回當前目錄。

keep-workdir 允許你停留在當前目錄(也即啟動 bgo 時所在的目錄),使用 go build ./tools/bgo 這樣的句法進行構建。此時你可以設定 keep-workdir 為 true。

如果你想某個專案使用特定的一個基準目錄(多半是因為那個基準目錄才有 go.mod)的話,可以使用 use-workdir 欄位指定一個。

    use-workdir: ./api/v1
    keep-workdir: false

gen, install, debug, gocmd, cgo, race, msan,

gen 決定了是否在 go build 前執行 go generate ./...

install 決定了是否將執行檔案複製到 $GOPATH/bin 中,如同 go install 做的那樣。

debug 會產出更大的可執行檔案,而預設時會使用 -trimpath -s -w 這些典型配置來削減執行檔案的尺寸。

gocmd 在你要使用不同的 go 版本執行構建時很有用,把它設定為指向你的特定版本的 go 執行檔案即可。當大多數情況下你可能都會保持它為空。但如果你在哦某個資料夾中編制了一段需要泛型的試驗程式碼時 gocmd 可能很有用。

cgo 決定了環境變數 CGO_ENABLED 的取值以及構建時是否使能 gcc 環節。注意 CGO 特性僅可用於當前 GOOS/GOARCH,對於交叉編譯它不能被很好地支援。

race 表示是否開啟競態條件檢測。

msan 被原樣傳遞給 go build,以便產出記憶體診斷和審計程式碼。

目標平臺:for, os, arch

在配置檔案中,可以指定要對哪些目標平臺進行構建。

---
app:
  bgo:
    build:
      # the predefined limitations
      # for guiding which os and arch will be building in auto scope.
      #
      # If 'bgo.build.for' is empty slice, the whole available 'go tool dist list'
      # will be used.
      #
      #for:
      #  - "linux/amd64"
      #  - "windows/amd64"
      #  - "darwin/amd64"
      #  - "darwin/arm64"

      # the predefined limitations
      os: [ linux ]

      # the predefined limitations
      #
      arch: [ amd64,"386",arm64 ]

注意這些鍵值也可以被用於 project-group 或者 project,例如這樣:

      projects:
        000-default-group:
          # leading-text:
          items:
            001-bgo:   # <- project name
              name:    # <- app name
              dir: .
              gen: true
              install: true
              # os: [ "linux","darwin","windows" ]
              # arch: [ "amd64" ]
              # for: [ "linux/riscv64" ]

for 會指定一個目標平臺陣列,每一個條目都是 os/arch 對。

但如果你指明瞭 os 和 arch 陣列的話,它們倆會做笛卡爾積來產生最終的目標平臺 martrix。

post-action,pre-action

可以指定 shell 指令碼在 go build 之前或者之後執行。

一個 post-action 可能是像這樣的:

post-action: |
  if [[ "$OSTYPE" == *{ {.OS}}* && "{ {.Info.GOARCH}}" == { {.ARCH}} ]]; then
    cp { {.Output.Path}} $HOME/go/bin/
  fi
  echo "OS: $OSTYPE, Arch: { {.Info.GOARCH}}"

它使用了模板展開功能。相應的資料來源來自於 我們的 build.Context 變數,這個變數的定義在文末會有一個描述。

具體而言,{ {.Info.GOARCH}} 代表著正在執行的 go runtime 值,即 runtime.GOARCH,而 { {.OS}}{ {.ARCH}} 是正在構建的目標的相應值。

由於 jekyll 模板展開的原因,所以所有的 { { 都被插入了空格以防止文章釋出失敗。
post-action-file,pre-action-file

如果你想要,可以使用指令碼檔案。

注意

這些設定僅用於每個 project,不支援被應用到 project-group 這一級。原因在於我們的程式碼實現中在最後階段刪除了 group 這個層級。

ldflags, asmflags, gcflags, tags

這些選填的引數將被傳遞給 go build 的相應命令列引數。

但是在我們這裡,你應該將它們指定為陣列形式,例如:

---
app:
  bgo:
    build:
      ldflags: [ "-s", "-w" ]

指定了全域性的 ldflags 引數,將被用到所有的 projects 構建時,除非你在某個 project 明確地指定了專屬的 ldflags 版本。

extends

向正在構建的程式碼的特定包寫入變數值,是通過 go build -ldflags -X ... 來達成的。同樣的也有配置檔案條目來簡化這個問題:

            001-bgo: # <- project name
              name:    # <- app name
              dir: tools/bgo
              gen: false
              install: true
              cgo: true
              extends:
                - pkg: "github.com/hedzr/cmdr/conf"
                  values:
                    AppName: "{ {.AppName}}"
                    Version: "{ {.Version}}"
                    Buildstamp: "{ {.BuildTime}}" # or shell it
                    Githash: "`git describe --tags --abbrev=16`"
                    # Githash: "{{.GitRevision}}"  # or shell it: "`git describe --tags --abbrev=9`"
                    GoVersion: "{ {.GoVersion}}"  # or shell it
                    ServerID: "{ {.randomString}}"

可以採用模板展開,也可以嵌入小的 shell 指令碼。

但不鼓勵寫很多的指令碼在這裡。

示例中給出的是針對 hedzr/cmdr CLI app 的構建引數,但實際上它們是多餘的:作為一家人,我們會自動嘗試識別你的 go.mod 是不是包含了到 cmdr 的引用,從而決定了我們要不要自動填寫這組引數。

很顯然,在構建時寫入包變數並不是 cmdr 專屬的東西,所以你可以用它來做你的專屬配置。

頂級的特有配置引數

在配置檔案中,app.bgo.build 是配置項的頂層,在這一層可以指定排除目錄和輸出檔名模板:

---
app:
  bgo:
    build:
      output:
        dir: ./bin
        # split-to sample: "{ {.GroupKey}}/{ {.ProjectName}}"
        #
        # named-as sample: "{ {.AppName}}-{ {.Version}}-{ {.OS}}-{ {.ARCH}}"

      # wild matches with '*' and '?'
      # excludes patterns will be performed to project directories.
      # but projects specified in .bgo.yml are always enabled.
      excludes:
        - "study*"
        - "test*"

output 塊中可以指定 named-as 作為輸出可執行檔名的模板,預設時 bgo 會採用 { {.AppName}}-{ {.OS}}-{ {.ARCH}}

dir 指明輸出資料夾,可執行檔案被指向這裡。

當你還可以指明 split-to 為每個 project 設定額外的子檔案層次,例如可以是 { { .ProjecName}},等等。

excludes 是一個字串陣列,提供一組檔名萬用字元模板,和這些模板匹配的資料夾將不會被掃描。

Build Context

在一些欄位中我們允許你嵌入動態變數值,它們會根據每個專案的構建時而因應變化。例如 { {.AppName}} 可以被展開為當前正在被構建的專案的 app name。

這些值被包含在 build.Context 的宣告中。

下面給出 bgo 原始碼的相應程式碼片段,因此我就不必再做解釋了。

這裡並非最新版本,目前仍在迭代中
type (
    Context struct {
        WorkDir    string
        TempDir    string
        PackageDir string

        // Output collects the target binary executable path pieces in building
        Output PathPieces

        *Common
        *Info
        *DynBuildInfo
    }
)

type PathPieces struct {
    Path    string
    Dir     string
    Base    string
    Ext     string
    AbsPath string
}

type (
    Info struct {
        GoVersion   string // the result from 'go version'
        GitVersion  string // the result from 'git describe --tags --abbrev=0'
        GitRevision string // revision, git hash code, from 'git rev-parse --short HEAD'
        BuildTime   string //
        GOOS        string // a copy from runtime.GOOS
        GOARCH      string // a copy from runtime.GOARCH
        GOVERSION   string // a copy from runtime.Version()

        RandomString string
        RandomInt    int
        Serial       int
    }
  
    DynBuildInfo struct {
        ProjectName         string
        AppName             string
        Version             string
        BgoGroupKey         string // project-group key in .bgo.yml
        BgoGroupLeadingText string // same above,
        HasGoMod            bool   //
        GoModFile           string //
        GOROOT              string // force using a special GOROOT
        Dir                 string
    }
)

type (
    CommonBase struct {
        OS   string `yaml:"-"` // just for string template expansion
        ARCH string `yaml:"-"` // just for string template expansion

        Ldflags    []string `yaml:"ldflags,omitempty,flow"`    // default ldflags is to get the smaller build for releasing
        Asmflags   []string `yaml:"asmflags,omitempty,flow"`   //
        Gcflags    []string `yaml:"gcflags,omitempty,flow"`    //
        Gccgoflags []string `yaml:"gccgoflags,omitempty,flow"` //
        Tags       []string `yaml:"tags,omitempty,flow"`       //
        // Cgo option
        Cgo bool `yaml:",omitempty"` //
        // Race option enables data race detection.
        //        Supported only on linux/amd64, freebsd/amd64, darwin/amd64, windows/amd64,
        //        linux/ppc64le and linux/arm64 (only for 48-bit VMA).
        Race bool `yaml:",omitempty"` //
        // Msan option enables interoperation with memory sanitizer.
        //        Supported only on linux/amd64, linux/arm64
        //        and only with Clang/LLVM as the host C compiler.
        //        On linux/arm64, pie build mode will be used.
        Msan          bool   `yaml:",omitempty"`               //
        Gocmd         string `yaml:",omitempty"`               // -gocmd go
        Gen           bool   `yaml:",omitempty"`               // go generate at first?
        Install       bool   `yaml:",omitempty"`               // install binary to $GOPATH/bin like 'go install' ?
        Debug         bool   `yaml:",omitempty"`               // true to produce a larger build with debug info
        DisableResult bool   `yaml:"disable-result,omitempty"` // no ll (Shell list) building result

        // -X for -ldflags,
        // -X importpath.name=value
        //    Set the value of the string variable in importpath named name to value.
        //    Note that before Go 1.5 this option took two separate arguments.
        //    Now it takes one argument split on the first = sign.
        Extends      []PackageNameValues `yaml:"extends,omitempty"` //
        CmdrSpecials bool                `yaml:"cmdr,omitempty"`
    }

    PackageNameValues struct {
        Package string            `yaml:"pkg,omitempty"`
        Values  map[string]string `yaml:"values,omitempty"`
    }

    Common struct {
        CommonBase     `yaml:"base,omitempty,inline,flow"`
        Disabled       bool     `yaml:"disabled,omitempty"`
        KeepWorkdir    bool     `yaml:"keep-workdir,omitempty"`
        For            []string `yaml:"for,omitempty,flow"`
        Os             []string `yaml:"os,omitempty,flow"`
        Arch           []string `yaml:"arch,omitempty,flow"`
        Goroot         string   `yaml:"goroot,omitempty,flow"`
        PreAction      string   `yaml:"pre-action,omitempty"`       // bash script
        PostAction     string   `yaml:"post-action,omitempty"`      // bash script
        PreActionFile  string   `yaml:"pre-action-file,omitempty"`  // bash script
        PostActionFile string   `yaml:"post-action-file,omitempty"` // bash script
    }
)

最新版本請直接前往 或者 go.dev 處查閱。

命令列的使用

bgo 是一個基於 hedzr/cmdr 的命令列程式,帶有 cmdr 所支援的基本特性,例如自由的多級子命令引數輸入與識別,等等。

image-20220128180556300

總的來說,你應該以兩個步驟來使用 bgo:

  1. 利用 bgo init 生成一個 bgo.yml 模板,它組織了掃描到的 cli apps 到這個配置模板中,請將其改名為 .bgo.yml 便於 bgo 自動裝載。

    bgo 會從若干自動位置查詢 .bgo.yml 檔案的存在。實際上它也會檢視 bgo.yml,並且還會自動擴充到相應的 conf.d 資料夾中做自動裝載與合併,所以你可以在 conf.d 中分離地為每個 project 編寫一個 yaml 片段,這對於持續整合是很有好處的。

  2. .bgo.yml 就緒之後(你可以手工編輯它以增添定製屬性),直接使用 bgo 就能一次性構建。

在上述自動模式之外,也有一些離開自動模式臨時特定操作的方式。

  1. 如果 bgo 沒有發現配置檔案,我們會試著掃描當前資料夾來嘗試構建。
  2. 如果不想發生預期之外的構建行為,請帶上 --dry-run 引數。

一些可能有用的命令列在下面列舉出來,它們都是不必強求配置檔案在場的——但是如前所述,配置檔案能夠以一種易於調整的格式讓你充滿了控制力,而命令列無論如何優化,也不能掩蓋像 -ldflags 這樣的控制引數的內容如此複雜與難以編輯。

為了縮短命令列輸入,執行 bgo 時隱含著等價於執行 bgo build 子命令。也就是說,bgo -s 實質上實在執行 bgo build -s,將啟動一個簡短構建模式。

所以為了檢視可能的命令列引數,你應該使用 bgo build --help

針對當前 GOOS/GOARCH 構建

bgo -s
bgo -s -pn project-one

其中後一種形式的目的是隻針對當前 GOOS/GOARCH 編譯 project-one 這個專案,忽略配置檔案中的其它 projects。

如果沒有配置檔案,它會自動查詢首個 main cli 然後進行構建。

完整掃描模式

bgo -f

這時除了配置檔案中定義的 projects 之外,bgo 會再次掃描資料夾下的所有 cli apps。

指定構建目標平臺

例如僅編譯指定目標平臺 linux/386 執行檔案,忽略配置檔案中可能存在的其它目標平臺定義:

bgo build --for linux/386
bgo -os linux -arch 386

兩條命令的用途相同。

同時,可以通過多次指定來指明一個陣列:

bgo -os linux --arch 386 --arch amd64,arm64

並且可以隨意使用逗號分隔符(,)來告訴 bgo 識別陣列列表,例如上例中實際上給出了一個包含三個 arch 的陣列作為引數:[ "386", "amd64", "arm64"]

此外,foros 以及 arch 同時適用於長短引數形式,--for 或者 -for 都沒問題,目的是為了降低記憶負擔。

類似的,這樣也是有效的:

bgo --for linux/386 --for darwin/am64
bgo --for linux/386,darwin/amd64

指定構建專案名

bgo -pn bgo
bgo 用作構建簡稱,是 bgo build 的縮短方式。

你可以同時限定專案名以及目標平臺:

bgo -os linux,windows -arch 386,amd64 -pn project-one

啟用 Shell 自動完成

當前 bgo 能夠提供 Shell 自動完成功能,輸入 bgo 之後鍵擊 TAB 即可。

image-20220130092618399

以及

image-20220130092701837

image-20220130092728651

如果你是通過下載二進位制執行檔案的方式執行 bgo 的,需要一點步驟來啟用 Shell 自動完成功能。

zsh

對於 zsh 環境,這樣生成自動完成指令碼:

$ bgo gen sh --zsh

# "/usr/local/share/zsh/site-functions/_bgo" generated.
# Re-login to enable the new zsh completion script.

如果 bgo 沒有能找到放置 _bgo 完成指令碼的位置,它會將指令碼輸出到控制檯,你需要自行儲存為 _bgo 然後放入你的 zsh 自動完成指令碼搜尋位置中。

zsh 使用環境變數 fpath 來指示自動完成指令碼應該放在什麼地方。例如:

❯ print -l $fpath
/Users/hz/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting
/Users/hz/.oh-my-zsh/custom/plugins/zsh-autosuggestions
/Users/hz/.oh-my-zsh/plugins/z
/Users/hz/.oh-my-zsh/plugins/docker
/Users/hz/.oh-my-zsh/plugins/git
/Users/hz/.oh-my-zsh/functions
/Users/hz/.oh-my-zsh/completions
/Users/hz/.oh-my-zsh/cache/completions
/usr/local/share/zsh/site-functions
/usr/share/zsh/site-functions
/usr/share/zsh/5.7.1/functions

bgo 將會自動解釋這些路徑位置並尋找最佳放置路徑。然而如果由於寫入許可權或其它問題導致 bgo 無法成功寫入的話,那麼你需要手工操作。

你也可以生成該指令碼到指定位置:

bgo gen sh --zsh -o /some/where/for/_bgo
bash

bash 自動完成指令碼

後記

bgo 看起來似乎有點用,但是它對你可能也並沒有用處。

bgo 如像是個 modules 管理器和構建輔助工具,但實際上它並不是 modules 管理器,頂多只能算是 main packages 自動構建器吧。

所以重要的事說三遍:bgo 管 exe 不管 lib。

最後

最後不得不說,bgo 做的是很粗暴的,因為最初的念頭只是想有一個 Disabled 標誌可以斃掉某些 projects,然後就想有個 --project-name 做篩選,然後是 -os, -arch,然後又發覺有必要有 --for。

然後其實幾乎已經失控了。

但總之它現在可以跑了。

題圖

image-20220130110150772

題外話

還有誰記得新蛋網嗎?

突然看到些 news 是關於 newegg 的,原來它的美國母公司活得好好的,遠不至於說是倒閉衰敗,去年還上市了。

在這個年歲交替的時候,回首二十年來網際網路上的變遷,心生感慨卻難以揮發。

REFs

?

相關文章