Go言語を使い始めてちょっと困ったのが、ライブラリ的なものを別ファイルに置いて共用したいのだけれどどうしたら良いのかという点です。Goのマニュアルをつらつら見るにパッケージとかモジュールとかいろいろあるのですが、どう設定したら良いのかサッパリです。まあなんとか動くようになったのでメモ。
※「やっつけな日常」投稿順 indexはこちら
Goのモジュール/パッケージの管理なのですが、以下の理由から非常に分かりずらいのではないかと想像します(個人の感想です。)
-
- Goのバージョンアップによって管理方法が高度化してきた
- ネットワークリソースを含めた一元管理で高いところを狙っている
1のために、古い方法で説明されている例が自分が出元で使っている現バージョンでは動かない、ということもあり。自分としてはローカルなファイルシステムの上で自前のモジュールが呼び出せればOKなのだけれども、なぜか難しい方に連れていかれてしまう~ということもありです。
お陰で以下のようなエラーメッセージに度々遭遇いたしました。
-
- build command-line-arguments: cannot find module for path ちょめちょめ
- cannot find module for path ちょめちょめ
ちょめちょめのところにはパスなどが入りまする。まあ試行錯誤を重ねた結果なんとか以下の構成で自前のモジュールを呼び出すことに成功。今回はそのメモです。ただし、よく理解しているわけではないので暫定版。
-
- 自前のモジュールは $HOME/go/src/local/以下にディレクトリ掘って格納
- 呼び出し側のメインは $HOME/go/proj/以下にディレクトリ掘って格納
なお、使用しているGoのバージョンは、1.18 です。Androidスマホ上のTermux環境で動作させているのであしからず(基本他のLinux環境と変わらぬ筈。)
参照資料など
ご本家には以下の立派なマニュアルがあるのですが、立派過ぎてどこをどうみたら良いか、森に迷い込みました。
Go言語の Github にも資料があります。しかし、後になって(以下の試行錯誤をやった後で偶然)見つけた フラミナル様の以下のページが一番分かり易かったです(日本語だし。)先に読んでおけばよかった。。。
【Go】パッケージ/モジュールやgo modコマンドについてまとめ
ディレクトリとファイル構成
今回試してみたのは以下です。$HOME/go はとくに環境変数を指定などしなくても分かっているみたいです。src配下にモジュールのソースを置くことにし、当方自前のモジュールは local 配下にまとめて “local/チョメチョメ” などという感じでアクセスしたいという希望。今回は、以前やったPPM形式で画像ファイルを生成する機能をモジュールといたします。
import “local/ppm”
などという感じでppmモジュールを使えるようにしたいということです。
呼び出し側のメインプログラムは、$HOME/go 配下ではありますが各プロジェクト?用のディレクトリ proj配下にディレクトリを掘って配置すると。今回は、s2という名前にしたので proj/s2 に メインプログラム samplePPM2.go を配置してみました。ディレクトリとファイル構成は以下の通り。
$HOME/go/ | +-- src/ | +-- local/ | +-- go.mod | +-- ppm/ | +-- go.mod | +-- ppm.go +-- proj/ +-- s2/ +-- go.mod +-- samplePPM2.go
ここで勝手に確信いたしました原則が以下です。
go.mod ファイルは、呼び出し側にも呼び出される側にも配置
go.modファイルは、グローバルなURIでの参照(当然ネット経由でリポジトリからおろしてくる)から、ローカルな参照までコントロールしており、そのうえ不適合なバージョンでトラブらないようなバージョン管理の仕組みも組み込まれているようです。自前でチョイとモジュール書くにはメンドイです。しかし、当方手元のgoバージョンでは、このモジュール管理の仕組みをオフるか、go.modファイルの仕組みに乗るかしないとローカルファイルを参照できないようでした。しかたが無いので、
-
- 呼び出し側にも置く
- 呼び出され側にも置く、階層毎に置く
ことにいたしました。呼び出し側のファイルは実際の在り処を示すのに必須だと思いますが、呼び出され側にもgo.modがないとそこにモジュールがあるのだと認識されないみたいです。知らんけど。ホントか。
まずは呼び出し側
呼び出し側のメイン関数は以下です。
proj/s2/samplePPM2.go
package main import "local/ppm" func main() { var img ppm.PpmImage XMAX:= 256 YMAX:= 256 img.InitPpmImage(XMAX, YMAX, 256) for x:= 0; x < XMAX; x++ { for y:= 0; y < YMAX; y++ { img.DotXY(x, y, x, 0, y) } } img.WritePPM() }
以前、1本のファイルに全てを置いてあったときに比べると格段と見通しが良くなりましたな。モジュール化の効用大っと。import “local/ppm” しているところが今回の目玉かと。また、ご覧いただくと分かるとおり、別ファイルのモジュール化時に、Class風(GoにはClassという構文は無いみたいです)の定義にしてみました。
メイン関数の横においた go.modファイルの内容が以下に。メイン関数の中で package main と宣言しているので、module 名も main かなと思ったのですが、こちらは呼び出している側なので、何を書いても関係ないみたい(ホントか?)でした。
proj/s2/go.mod
module s2 go 1.18 require local/ppm v0.0.1 replace local/ppm v0.0.1 => ../../src/local/ppm
下の2行がキモで、requireという行で local/ppm というモジュールが必要なのだぞと宣言しているみたいです。本来、localのところにはそのモジュールが置かれているネットワーク上のURIを記すみたいです。そこにリポジトリが置かれているとGoのマネージャさんがダウンロードして使えるようにしてくれるということみたい。でも今回はローカルなファイルとして配置なのでその下の replace行で、ローカルファイルシステム上の実際の在り処へ導いています。
なお、バージョンの記載は必須のようです。本来はこれで不適合なモジュール・バージョンははじくのだと思います。今のところローカルモジュールにバージョン管理などしていないので、テキトーに書いたですが問題ないみたい。
呼び出される側
ライブラリモジュール側です。localという名のローカルなライブラリがまずあって、そのサブモジュールとして ppm があるという2段構成といたしました。ppmだけでなく、今後色々並べたいので。local直下にはサブディレクトリが並ぶだけですが、localは形式的にはモジュールであるので go.modおかないとダメみたいです。
src/local/go.mod
module local go 1.18
このディレクトリは local という名のモジュールであるぞよ、との宣言?
モジュール本体入れ物
モジュール本体、ppmディレクトリ内にも go.mod 必要でしたが、これもモジュール名のみ
src/local/ppm/go.mod
module ppm go 1.18
モジュール本体
モジュール本体のコードは以下です。現在は、ppm.goというファイル1本ですが、同名の package ppm を持たせて ppm2.go、ppm3.goなどと複数のファイルで1個のパッケージとすることができるみたいです。
src/local/ppm/ppm.go
package ppm import "fmt" type PpmImage struct { width int height int cmax int buf []int } func (this *PpmImage) InitPpmImage(w int, h int, cx int) bool { this.width = w this.height = h this.cmax = cx this.buf = make([]int, w*h*3) return true } func (this *PpmImage) DotXY(x int, y int, r int, g int, b int) bool { if x > (this.width - 1) { return false } else if y > (this.height - 1) { return false } else { this.buf[3*(y*this.width + x)] = r this.buf[3*(y*this.width + x) + 1] = g this.buf[3*(y*this.width + x) + 2] = b } return true } func (this *PpmImage) WritePPM() bool { fmt.Println("P3") fmt.Println(this.width, " ", this.height) fmt.Println(this.cmax) for y := 0; y < this.height; y++ { for x := 0; x < this.width; x++ { fmt.Print( this.buf[3*(y*this.width + x)], " ") fmt.Print( this.buf[3*(y*this.width + x) + 1], " ") fmt.Println(this.buf[3*(y*this.width + x) + 2]) } } return true }
Goの場合、Class構文は無いので、struct 使ってClass 相当を定義すれば良いみたいです。メソッド関数から自分を参照するのに 私は this 使ってしまいました。Pythonでは self だし。でも、goの中の人はそういう野暮な名前は付けないらしい。。。
しかし、大文字だったら Public 的な外部参照可能、小文字だったら private 的なというのは知らないとどうにもなりまっせん。トホホ。
まあ、ローカルモジュールとして動作したので結果オーライ。