やっつけな日常(9) スマホでGo! なんとかローカルなモジュールを利用。go 1.18

Joseph Halfmoon

Go言語を使い始めてちょっと困ったのが、ライブラリ的なものを別ファイルに置いて共用したいのだけれどどうしたら良いのかという点です。Goのマニュアルをつらつら見るにパッケージとかモジュールとかいろいろあるのですが、どう設定したら良いのかサッパリです。まあなんとか動くようになったのでメモ。

※「やっつけな日常」投稿順 indexはこちら

Goのモジュール/パッケージの管理なのですが、以下の理由から非常に分かりずらいのではないかと想像します(個人の感想です。)

    1. Goのバージョンアップによって管理方法が高度化してきた
    2. ネットワークリソースを含めた一元管理で高いところを狙っている

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 Modules Reference

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 的なというのは知らないとどうにもなりまっせん。トホホ。

まあ、ローカルモジュールとして動作したので結果オーライ。

やっつけな日常(8) でこでもマンデルブロ? へ戻る

やっつけな日常(10) スマホでGo!Line描画をppmパッケージに追加 へ進む