- 投稿日:2021-10-31T22:42:13+09:00
【Golang】ラズパイ+ go-sdl2 で /usr/bin/ld: -liconv が見つかりませんエラー【静的リンクのビルド時】
/usr/bin/ld: cannot find -liconv on go-sdl2 static build デスクトップ環境(X11)なしの RaspberryPi OS Lite 入りラズパイでスクリーン描画したい。 SDL という X11 の代替となるマルチメディア用のライブラリにスクリーンの描画機能があるらしい。ところが Go 言語(以下 Golang)で SDL を利用するために go-sdl2 を使うも、静的リンクでビルドすると「lib-iconv が見つかりません」とエラーが出るのです。 でも static タグなし(go build .)だと、ビルドできるし、ターミナルに描画はできるのです。どうしよう。 静的リンクでビルドするとエラーが出る $ go build -v -tags static -ldflags "-s -w" . github.com/veandco/go-sdl2/sdl # github.com/veandco/go-sdl2/sdl /usr/bin/ld: -liconv が見つかりません collect2: error: ld returned 1 exit status $ # 静的リンクしないなら大丈夫 $ go build -v . $ echo $? 0 TL; DR (今北産業) ビルド時に rpi タグを付けます。 - go build -v -tags static -ldflags "-s -w" . + go build -v -tags static -tags rpi -ldflags "-s -w" . RPi OS (buster) は libc6C の標準ライブラリ No.6 に lib-iconv をすでに持っています。しかし go-sdl2 は静的リンク時に Gentoo Linux を想定して .a ファイルが構成されているため、別途ラズパイ用のリンクファイルが用意されています。Golang のビルド時に rpi タグを指定すると gcc はラズパイ用のライブラリをリンクします。 関連 Issue Issue #394 static build in raspberry | go-sdl2 @ github.com TS; DR (Raspberry Pi Zero + Raspberry Pi OS Lite + Go + go-sdl2 を使う細かい備忘録) 1024x600@60Hz の 7 inch タッチパネル式 Portable Display にラズパイ Zero W をつなげて、Raspberry Pi OS Lite(GUIなし・デスクトップなし版)のターミナル画面(CUI 画面)上に画像を描画する際の備忘録です。 操作は mac から SSH 接続していますが、実行すると手元のターミナルでなく接続されているモニタのターミナルに描画されます。 SDL2 とは SDL (Simple DirectMedia Layer) は、C 言語で書かれたクロスプラットフォームのマルチメディアライブラリである。グラフィックの描画やサウンドの再生などのAPIを提供する。 オーディオ、キーボード、マウス、ジョイスティック、そして OpenGL および Direct3D を経由したグラフィックスハードウェアへのローレベルなアクセスを提供するよう設計されている。Windows、macOS、Linux、iOS、Android を公式にサポートしている。 (SDL @ Wikipedia より) つまり、デスクトップ含む X11 などの GUI 系ライブラリがない環境でも、画面に描画したりデバイスからの入力を取得することができるライブラリです。SDL2 を使うとターミナル Only の CUI 画面にも画像を出力できます。 Go-SDL2 とは go-sdl2 は、C 言語で書かれた SDL2 の Go 言語によるラッパー(バインディング)です。 例えば C 言語版 SDL2 の、とある関数を使いたい場合は、該当する Go-SDL2 のラッパー関数を使うと Go 言語でもモジュールとして使うことができる、という塩梅です。 go-sdl2 の使い方のポイント 「○○○ がしたいなぁ〜」と思ったら、SDL2 の Wiki で関数名を探し、該当する go-sdl2 の関数を go-sdl2 のドキュメントから探して使うことで Go に実装できます。 モニターの画面解像度を取得するまでの調べ方と実装例 ここでは「モニターの画面解像度を取得したいなぁ」という場合の具体例を紹介したいと思います。デスクトップなし(RPi OS Lite)の環境を前提とします。 本家 SDL2 の Wiki ドキュメントを開きます。 https://wiki.libsdl.org/APIByCategory カテゴリ別 @ 公式 Wiki 最初から go-dsl2 のドキュメントで探してもいいのですが、go-dsl2 はバージョン違いで仕様が結構変わります。そのため、本家の API の仕様を確認してから、go-dsl2 の自分が使っているバージョンのドキュメントを探す方が経験的に失敗が少ない気がします。 使えそうな関数を探します。 大カテゴリを見ると Video の Display and Window Management が一番近そうです。 Display and Window Management(ディスプレイやウィンドウの管理) Functions 項目(関数名一覧)を見ると SDL_Get* の Getter 関数がいくつかあります。今回は「取得」したいので Get 系にマトが絞れそうです。また、ウィンドウのサイズではなく「画面」サイズなので Display を含むものに絞ります。すると以下の 3 つの関数が使えそうです。 SDL_GetCurrentDisplayMode ... Get information about the current display mode. SDL_GetDesktopDisplayMode ... Get information about the desktop's display mode. SDL_GetDisplayMode ... Get information about a specific display mode. 各々の関数の説明をみると SDL_GetCurrentDisplayMode が近いと思われます。デスクトップなし環境でモニタは 1 つだからです。 SDL_GetCurrentDisplayMode ... 現在のモニタの状態に関する情報を取得 SDL_GetDesktopDisplayMode ... 現在のデスクトップのモニタの状態に関する情報を取得 SDL_GetDisplayMode ... 指定したモニタの状態に関する情報を取得 API の仕様(関数の引数と戻り値の構文)を確認します。 Syntax int SDL_GetCurrentDisplayMode( int displayIndex, SDL_DisplayMode * mode ); // 戻り値は int 型 // 第 1 引数は int 型。名は displayIndex。 // 第 2 引数は SDL_DisplayMode 型オブジェクトのポインタ。名は mode。 Return Returns 0 on success or a negative error code on failure; call SDL_GetError() for more information. // 成功すると 0 が返され、失敗すると -1 以下の数値が返えされる。 // SDL_GetError() を呼び出すと詳細なエラーが得られる。 FunctionParameters(引数の仕様) displayIndex ... the index of the display to query mode ... an SDL_DisplayMode structure filled in with the current display mode // displayIndex ... 問い合わせ先のモニタ番号 // mode ... 現在のモニタ情報を埋めるための SDL_DisplayMode 構造体(いわゆる参照渡しで情報を取得する) 第 2 引数の SDL_DisplayMode 型の構造を確認する。この構造を持ったオブジェクトに、各々の値が代入される。 DataFields Uint32 ... format ... one of the SDL_PixelFormatEnum values; see Remarks for details int ... w ... width, in screen coordinates int ... h ... height, in screen coordinates int ... refresh_rate ... refresh rate (in Hz), or 0 for unspecified void* ... driverdata ... driver-specific data, initialize to 0 // format フィールド ... Uint32 型の SDL_PixelFormatEnum で定義された enum 値 // w フィールド ... int 型の画面の横の座標 = 画面幅 // h フィールド ... int 型の画面の縦の座標 = 画面の高さ // refresh_rate フィールド ... int 型のリフレッシュレート(単位:Hz)、未定義の場合は 0 // driverdata フィールド ... void 型のポインタ。モニタドライバ特有のデータで、0 で初期化されていること。 いよいよ該当する go-sdl2 の関数を確認 GetCurrentDisplayMode | v0.4.10 | go-sdl2 @ pkg.go.dev go-dsl2 が v0.4.10 なので該当するバージョンのドキュメントを確認します。 GetCurrentDisplayMode func GetCurrentDisplayMode(displayIndex int) (mode DisplayMode, err error) // GetCurrentDisplayMode returns information about the current display mode. // (https://wiki.libsdl.org/SDL_GetCurrentDisplayMode) // GetCurrentDisplayMode は現在のモニタの状態の情報を返します。 本家と違い、参照渡しではなく DisplayMode 型のオブジェクトが返されるようです。 戻り値の DisplayMode 型の構造を確認する。 DisplayMode | v0.4.10 | go-sdl2 @ pkg.go.dev type DisplayMode struct { Format uint32 // one of the PixelFormatEnum values (https://wiki.libsdl.org/SDL_PixelFormatEnum) W int32 // width, in screen coordinates H int32 // height, in screen coordinates RefreshRate int32 // refresh rate (in Hz), or 0 for unspecified DriverData unsafe.Pointer // driver-specific data, initialize to 0 } 以上により、戻り値の w と H フィールドを参照すれば画面サイズが取得できそうです。 Go のサンプル // import "github.com/veandco/go-sdl2/sdl" func main(){ mode, err := sdl.GetCurrentDisplayMode(0) // 最初のモニタ情報を取得 if err != nil { log.Fatal(err) } fmt.Println("Width:", mode.W) fmt.Println("Height:", mode.H) } 検証環境 $ # モデル(Raspberry Pi Zero W H) $ more /proc/device-tree/model Raspberry Pi Zero W Rev 1.1 $ # OS 情報(Raspberry Pi OS Lite) $ cat /etc/os-release | grep PRETTY_NAME PRETTY_NAME="Raspbian GNU/Linux 10 (buster)" $ # カーネル情報 $ uname -a Linux rpi-keinos 5.10.76+ #1474 Thu Oct 28 13:37:16 BST 2021 armv6l GNU/Linux $ # Go 情報 $ go version go version go1.17.2 linux/arm 依存 apt パッケージのインストール sudo apt-get install --yes \ build-essential \ libfreeimage-dev \ libopenal-dev \ libpango1.0-dev \ libsndfile1-dev \ libudev-dev \ libasound2-dev \ libjpeg-dev \ libtiff5-dev \ libwebp-dev \ automake SDL2 の C ライブラリ・ビルド(X11 なし) apt-get install 経由の SDL2 パッケージは古く、ラズパイ Zero + X11 なしにも最適化されていないのでソースからビルドしました。 # SDL2 ライブラリ本体のビルド mkdir ~/SDL2 cd ~/SDL2 wget https://www.libsdl.org/release/SDL2-2.0.16.tar.gz tar xzf SDL2-2.0.16.tar.gz cd SDL2-2.0.16 mkdir build && cd build ../configure --host=arm-raspberry-linux-gnueabihf --disable-pulseaudio \ --disable-esd --disable-video-wayland \ --disable-video-x11 make install # IMAGE(SDL2 関連ライブラリのビルド) cd ~/SDL2 wget https://www.libsdl.org/projects/SDL_image/release/SDL2_image-2.0.5.tar.gz tar xzf SDL2_image-2.0.5.tar.gz cd SDL2_image-2.0.5 && mkdir build && cd build ../configure sudo make install # Mpeg(SDL2 関連ライブラリのビルド。Mixer のビルド前にビルドする必要がある) cd ~/SDL2 wget https://www.libsdl.org/projects/smpeg/release/smpeg2-2.0.0.tar.gz tar xzf smpeg2-2.0.0.tar.gz cd smpeg2-2.0.0 && mkdir build && cd build ../configure sudo make install # Mixer(SDL2 関連ライブラリのビルド) cd ~/SDL2 wget https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-2.0.4.tar.gz tar xzf SDL2_mixer-2.0.4.tar.gz cd SDL2_mixer-2.0.4 && mkdir build && cd build ../configure sudo make install # TTF(SDL2 関連ライブラリのビルド) cd ~/SDL2 wget https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-2.0.15.tar.gz tar xzf SDL2_ttf-2.0.15.tar.gz cd SDL2_ttf-2.0.15 && mkdir build && cd build sed -i -e 's/have_opengl=yes/have_opengl=no/' ../configure ../configure sudo make install Go で go-sdl2 使う際のポイント go get でモジュールを require する際は @master と指定する。 - go get github.com/veandco/go-sdl2/sdl + go get github.com/veandco/go-sdl2/sdl@master Golang サンプル・コード go.mod module github.com/KEINOS/sample go 1.17 require github.com/veandco/go-sdl2 v0.5.0-alpha.1.0.20211011035448-93229da6a705 main.go package main import ( "os" "github.com/veandco/go-sdl2/img" "github.com/veandco/go-sdl2/sdl" ) const ( bmpImagePath = "./sample.bmp" // 適当な ビットマップ画像 pngImagePath = "./sample.png" // 適当な PNG 画像 ) func run() (err error) { var window *sdl.Window var surface *sdl.Surface var bmpImage *sdl.Surface var pngImage *sdl.Surface if err = sdl.Init(sdl.INIT_VIDEO); err != nil { return } defer sdl.Quit() // Create a window for us to draw the images on if window, err = sdl.CreateWindow("Loading images", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, 800, 600, sdl.WINDOW_SHOWN); err != nil { return } defer window.Destroy() if surface, err = window.GetSurface(); err != nil { return } // Load a BMP image if bmpImage, err = sdl.LoadBMP(bmpImagePath); err != nil { return err } defer bmpImage.Free() // Load a PNG image if pngImage, err = img.Load(pngImagePath); err != nil { return err } defer pngImage.Free() // Hide Cursor _,_ = sdl.ShowCursor(sdl.DISABLE) // Draw the BMP image on the first half of the window bmpImage.BlitScaled(nil, surface, &sdl.Rect{X: 0, Y: 0, W: 400, H: 400}) // Draw the PNG image on the first half of the window pngImage.BlitScaled(nil, surface, &sdl.Rect{X: 400, Y: 0, W: 400, H: 400}) // Update the window surface with what we have drawn window.UpdateSurface() // Run infinite loop until user closes the window running := true for running { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { switch event.(type) { case *sdl.QuitEvent: running = false } } sdl.Delay(16) } return } func main() { if err := run(); err != nil { os.Exit(1) } } 静的ビルド $ ls sample.bmp sample.png go.mod main.go $ go mod tidy && go mod download ... $ go build -v -tags static -tags rpi -ldflags "-s -w" . ... $ ls sample.bmp sample.png go.mod main.go sample $ ./sample // ctrl+c するまで画面に表示される