2
1

【Golang】ラズパイ+ go-sdl2 で /usr/bin/ld: -liconv が見つかりませんエラー【静的リンクのビルド時】

Last updated at Posted at 2021-10-31

/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 (今北産業)

  1. ビルド時に rpi タグ(-tags rpi を付けます)

    - go build -v -tags static -ldflags "-s -w" .
    + go build -v -tags static -tags rpi -ldflags "-s -w" .
    
  2. RPi OS (buster) は libc6C の標準ライブラリ No.6lib-iconv をすでに持っています。しかし go-sdl2 は静的リンク時に Gentoo Linux を想定して .a ファイルが構成されているため、別途ラズパイ用のリンクファイルが用意されています。Golang のビルド時に rpi タグを指定すると gcc はラズパイ用のライブラリをリンクします。

  3. 関連 Issue


TS; DR (Raspberry Pi Zero + Raspberry Pi OS Lite + Go + go-sdl2 を使う細かい備忘録)

  • 1024x600@60Hz の 7 inch タッチパネル式 Portable Display
    • CX070PI_70R_50P_CTP
    • V5.0 D190824
    • Amazon で 9 千円弱で購入した Swehoo という怪しい俺様ブランドのメーカー不明品ですが、どうやら Waveshare 社の商品のコピー品のようです。

上記 7 インチのタッチパネル式ディスプレイに、ラズパイ 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 で C 言語の関数名を探し、該当する go-sdl2 の Go 関数を go-sdl2 のドキュメントから探して使うことで Go に実装できます。

モニターの画面解像度を取得するまでの調べ方と実装例
ここでは「モニターの画面解像度を取得したいなぁ」という場合の具体例を紹介したいと思います。デスクトップなし(RPi OS Lite)の環境を前提とします。
  1. 本家 SDL2 の Wiki ドキュメントを開きます。

    最初から go-dsl2 のドキュメントで探してもいいのですが、go-dsl2 はバージョン違いで仕様が結構変わります。そのため、本家 SDL2 の API の仕様を確認してから、go-dsl2 の自分が使っているバージョンのドキュメントを探す方が経験的に失敗が少ない気がします。

  2. 使えそうな関数を探します。

    1. 大カテゴリを見ると VideoDisplay and Window Management が一番近そうです。

    2. Functions 項目(関数名一覧)を見ると SDL_Get*Getter 関数がいくつかあります。今回は「取得」したいので Get 系にマトが絞れそうです。また、ウィンドウのサイズではなく「画面」サイズなので Display を含むものに絞ります。すると以下の 3 つの関数が使えそうです。

    3. 各々の関数の説明をみると SDL_GetCurrentDisplayMode が近いと思われます。デスクトップなし環境でモニタは 1 つだからです。

      • SDL_GetCurrentDisplayMode ... 現在のモニタの状態に関する情報を取得
      • SDL_GetDesktopDisplayMode ... 現在のデスクトップのモニタの状態に関する情報を取得
      • SDL_GetDisplayMode ... 指定したモニタの状態に関する情報を取得
    4. 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 構造体(いわゆる参照渡しで情報を取得する)
      
    5. 第 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 で初期化されていること。
      
    6. いよいよ該当する 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 型のオブジェクトが返されるようです。

    7. 戻り値の DisplayMode 型の構造を確認する。

      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
      }
      

      以上により、戻り値の wH フィールドを参照すれば画面サイズが取得できそうです。

    8. 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 使う際のポイント

  1. 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 するまで画面に表示される
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1