Go1.13のGOPATHとgoコマンドの挙動の関係を図で整理した @Tomofiles
目次
はじめに
こんにちは、Tomofilesです。
Go1.13でGo Modulesがデフォルトで利用可能になってから、半年以上経ちました。
Go Modulesのモジュール対応モードで開発するときに、私は以下がイメージできなくて困ることがありました。
- PATHやGOPATHに設定する内容
- 依存するモジュールがどこにインストールされるのか
- go build、go installしたときの成果物の生成
この記事では、モジュール対応モードでGoのコードを書いて、ビルド、実行するときの、環境のイメージと各成果物の生成について、図で表現して整理しています。
Goを学習されている方の、理解の助けになれれば、幸いです。
環境
- OS:Ubuntu Desktop 18.04.4 LTS
- Go:1.13.7
- パッケージ管理:Go Modules
モジュール対応モードでの環境イメージ
Goのモジュール対応モードでの環境の全体像をイメージ図にしてみると、下図のようになります。
ディレクトリーツリーについて
それぞれのディレクトリーツリーの概要は、以下の通りです。
ディレクトリーツリーに付けている名前は、公式の名称ではなく私が便宜的に付けた名前です。
Goインストールディレクトリー
Go公式のインストール手順に従うと、Goのバイナリは/usr/local
配下にインストールされます。
手順では、/usr/local/go/bin
にPATHを通して、bin配下のgoコマンドを使えるようにしています。
上図の3列あるうち、一番左のツリーがGoのインストールディレクトリーを示しています。
GOROOTはGoが自動的に認識しているので、特に設定不要です。
GOPATHディレクトリー
さて、ググると見つかるgoの環境構築手順では、GOPATHも環境変数に設定しているのをよく見ます。
公式の手順では、GOPATHの説明はコードの書き方でちょろっと書かれているだけで、Go1.13現在は環境変数への設定は必須ではないようですね。
特に設定しない場合、$HOME/go
がGOPATHだと認識されるようです。
ただ、まぁ慣習というか、明示的に設定しておいても良いかもしれません。
(ネット上のどの記事もそんな感じの説明ですね)
上図の真ん中のツリーが、GOPATHで指定した$HOME/go
を示しています。
このGOPATH配下のbinも、PATHを通しておきます。
ワークスペースディレクトリー
最後に、ワークスペースディレクトリーです。
ワークスペースディレクトリーは、何かアプリケーションを開発しようとしたときに作成するディレクトリーです。
ワークスペースディレクトリーは任意の場所に作成して問題ありません。
上図の一番右のツリーがワークスペースディレクトリーを示しており、例として$HOME/workspace
というディレクトリーで用意しています。
環境変数への設定
私のPC環境では、Ubuntuデスクトップを使用しているので、環境変数の変更は$HOME/.profile
もしくは$HOME/.bash_profile
を更新します。
$HOME/.bash_profile
の方だと、GUIのアイコン起動のターミナルでは読み込まれないみたいなので、私は$HOME/.profile
を更新しました。
# .profile
# 〜省略〜
export GOPATH="$HOME/go"
export PATH="$PATH:$GOPATH/bin"
export PATH="$PATH:/usr/local/go/bin"
こんな感じに、ファイルの末尾に追加します。
Visual Studio Codeでの開発
ここから、上記で構築した環境を前提に、Visual Studio Code(vs code)で開発を始めた場合を想定して、Goの挙動を追っていきます。
開発支援ライブラリ
vs code上でGoの環境構築をする際に、いくつか開発支援ライブラリ(Go tools)をインストールすることになると思います。
下図は、Go toolsが一つもインストールされていない状態で、ワークスペースを開いたときのスクリーンショットです。
かなり見づらいですが、画面右下にAnalysis Tools Missing
と表示されています。
カーソルを合わせると、Not all Go tools are available on the GOPATH
とツールチップが表示されます。
クリックすると、インストールダイアログが表示されるので、ここからインストールできます。
インストールが成功すると、Go
という表示に変わります。
今回は説明しないですが、開発支援としてはLanguage Serverも導入すると快適なので、gopls
をインストールしておきましょう。
さて、ここでインストールされたGo toolsたちは、どこに格納されたでしょうか?
さきほどインストールする前のツールチップにGOPATHというワードが出ているのでお気づきかと思いますが、GOPATHディレクトリーに格納されています。
GOPATHディレクトリーにインストールされたGo toolsは、適宜、配下のbinやpkgに資材が格納されます。
$ ls -1 $GOPATH/bin
go-outline
gocode
gocode-gomod
godef
goimports
golint
gopkgs
gopls
vs codeで利用するGo toolsがGOPATHディレクトリー配下にインストールされたので、以後、別のワークスペースを作成して開発する際も、Go toolsは有効な状態となります。
依存モジュール
続いて、実際に開発を始めた際の、自分でコーディングしたソースコードが依存している、サードパーティのモジュールについてです。
その前に、この記事でサンプルとして使用するプロジェクトを先に紹介します。
ワークスペースの構成と、各種プログラムを以下に示します。
$ tree
.
├── cmd
│ ├── stdir_first
│ │ └── main.go
│ └── stdir_second
│ └── main.go
├── go.mod
├── go.sum
└── pkg
└── stdir
├── first
│ └── first.go
└── second
└── second.go
7 directories, 6 files
// cmd/stdir_first/main.go
package main
import (
"fmt"
"stdir/pkg/stdir/first"
)
func main() {
fmt.Println(first.Message())
}
// cmd/stdir_second/main.go
package main
import (
"fmt"
"stdir/pkg/stdir/second"
)
func main() {
fmt.Println(second.Message())
}
// pkg/stdir/first/first.go
package first
import "github.com/go-shadow/moment"
func Message() string {
return "First Message " + moment.New().Format("YYYY/MM/DD HH:mm:ss")
}
// pkg/stdir/second/second.go
package second
import "github.com/go-shadow/moment"
func Message() string {
return "Second Message " + moment.New().Format("YYYY/MM/DD HH:mm:ss")
}
// go.mod
module stdir
go 1.13
require (
github.com/go-shadow/moment v0.0.0-20140422073900-e837f27dad94
github.com/smartystreets/goconvey v1.6.4 // indirect
)
ただ、メッセージと日付が表示されるプログラムです。
cmd
やpkg
は、Goの標準的なディレクトリー構成に則っています。
GitHub - Standard Go Project Layout
サードパーティモジュールとして、日付ライブラリを使用しています。
もちろんパッケージ標準のtimeパッケージで機能は十分なのですが、今回は外部依存の例として使用しています。
さて、モジュール対応モードなのでgo.mod
が生成されていますが、vs codeでは、この状態ですでに依存モジュールが自動でインストールされます。
(Language Serverによるものか、vs codeによるものかは、ちょっと調べてません)
依存モジュールのインストール先がどこかというと、もちろん、GOPATHディレクトリーですね。
依存モジュールはワークスペースの中には、格納されません。
ワークスペースの中からの依存モジュールへの参照(青い矢印)は、$GOPATH
を参照して場所を特定します。
依存モジュールがGOPATHディレクトリー配下に格納されるので、異なるワークスペースでインストールされた依存モジュールを共有します。
JavaのMavenのような、リモートリポジトリーと、ローカルリポジトリーの考え方と似ていますね。
ビルド・インストール
開発したプロジェクトは、次に、ビルドして実行ファイルを生成すると思います。
Goでは、ビルド、インストールは、それぞれ、以下のように実行します。
ビルド
ビルドは以下のコマンドで実行できます。ワークスペースのルートで実行します。
$ go build ./...
ビルドコマンドは、基本的に最終成果物の実行ファイルは生成しません。
コンパイルを行い、生成された中間成果物をローカルビルドキャッシュというキャッシュに保存します。
よくネット上の記事で-o
オプションを付けて、ビルドコマンドで実行ファイルを生成しているのを見ますが、インストールコマンドが別に用意されているので、ビルドはあくまでコンパイルだけを行う目的で使ったほうが、シンプルだと個人的に思います。
クロスコンパイルの場合、話が別らしいですが、通常のコンパイルなら問題ないと思います
Just one side of me - go build vs go install
ビルド時にキャッシュを生成しているので、何か高速化・効率化とかを図っているんですかね。
コマンド引数の./...
は、ワークスペース配下のサブディレクトリーを再帰的にスキャンして、main関数
を探してビルドするという意味です。
今回のサンプルプロジェクトでビルドすると、cmd配下の2つのmain.go
がエントリーポイントとして認識されて、ビルドが行われます。
インストール
インストールは以下のコマンドで実行できます。ワークスペースのルートで実行します。
$ go install ./...
インストールコマンドは、最終成果物の実行ファイルを生成して、インストールを行います。
ビルドも同時に行われるので、このコマンドを一発叩くだけでも問題ありません。
生成された実行ファイルは、$GOPATH/bin
に格納されます。
ワークスペース配下ではないんですよね。これが、最初はよく理解できず、不思議に感じていました。
図からは省略してしまいましたが、ビルド時に生成したローカルキャッシュも、インストール時に使用しているので、ビルド時間が短縮されます
今回のサンプルプロジェクトでインストールすると、$GOPATH/bin
に以下のように実行ファイルが生成されます。
$ ls -1 $GOPATH/bin
go-outline
gocode
gocode-gomod
godef
goimports
golint
gopkgs
gopls
stdir_first
stdir_second
Go toolsのモジュールと並んで、ビルドされた実行ファイルが格納されています。
上図の赤い点線の枠は、PATHが通ったディレクトリーを意味しており、このディレクトリーに展開されると、絶対パス・相対パスによる指定をせずとも、実行できるようになります。
$ stdir_first
First Message 2020/04/24 23:14:56
$ stdir_second
Second Message 2020/04/24 23:15:04
これが、つまるところ、インストールされた ということになります。
どうしても、実行ファイルを生成することに意識が向かいがちですが、改めてGoの環境を鳥瞰してみると、然るべき場所に配置し、実行可能な状態にするところまで、Goは考えられているということですね。
今回は紹介しないですが、Dockerのgolangイメージでビルド、実行するときも、考え方は同じです。
そのため、任意の場所でビルド・インストールすると、$GOPATH/bin
として定義してある/go/bin
に実行ファイルが展開されます。
終わりに
個人的にGoを使い始めてから1年ちょっと経過してるので、ここらで改めて整理しておきたいということで、この記事を書きました。
図でGoの環境周りを説明されている記事が、意外とネット上になくて、目で見て動きが追えるような資料があればいいなと思っていたところだったので、このテックブログ開設の記念すべき1記事目に書いてみました。
Goを学習している誰かの、理解の助けになれば良いなと思います。
何か、間違い、疑問、コメント等がありましたが、お手数ですがTwitterまで連絡ください。
それでは、今回はこの辺で。