Intel MacでSDLを使ったプログラムをDMDでコンパイルしapp形式にする

Mac OS X 上でSDLを使ったプログラムをDMDコンパイルしてもそのままでは起動できなくて苦労しました。
試しに以前作成したソフトウェアMac OS X 10.4 Intel上で使えるようにしてみたのでその記録を残しておきたいと思います。

1. はじめに

ビルドに使用した環境は次の通りです。

DMDと後述のsdlbootがPowerPCに対応していないため、IntelMac OS X向けのみの説明です。

2. SDL.frameworkを使ったビルド

Mac OS XSDLを使用するには大きく分けて

の三つの方法があります。

ここでは作成したプログラムを配布する時のことを考えまして、三番目の「フレームワークを使う」方法をとることにします。

dmdでリンクするときには、リンカオプションを"-Lリンカオプション"のようにする必要があります。たとえばSDL.frameworkを使ってリンクするときには次のようになります。

$ dmd -L-framework -LSDL obj1.o obj2.o -ofprogram

otoolを使って使用しているライブラリを調べると、私のプログラムではSDL, SDL_image, SDL_mixerを使っていることが確認できます。

$ otool -L program
program:
        @executable_path/../Frameworks/SDL.framework/Versions/A/SDL (compatibility version 1.0.0, current version 1.0.0)
        @executable_path/../Frameworks/SDL_mixer.framework/Versions/A/SDL_mixer (compatibility version 1.0.0, current version 1.0.0)
        @executable_path/../Frameworks/SDL_image.framework/Versions/A/SDL_image (compatibility version 1.0.0, current version 1.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.3.11)

フレームワークはコンピュータ全体にインストールしてもいいですし、自分だけが使えるようにインストールしてもかまいません。
前者は"/Library/Frameworks/", 後者は"$(HOME)/Library/Frameworks/"にSDL.frameworkを配置すれば(自分の環境では)使用することができます。

3. sdlbootを使って起動する

さてビルドに成功したプログラムを実行してみると、次のようなエラーが出て起動できません。

$ ./program
****-**-** **:**:**.***  program[593] *** _NSAutoreleaseNoPool(): Object 0x******** of class NSLock autoreleased with no pool in place - just leaking
(中略)
****-**-** **:**:**.*** program[***] *** Uncaught exception: <NSInternalInconsistencyException> Error (1002) creating CGSWindow
Trace/BPT trap

SDLを使ったプログラムをMac OS X用にビルドするとCocoaの初期化フェーズを飛ばしてしまうのが原因のようです。詳しくはsdlbootのページを見てください。

main_hook.tgzを上記リンク先からダウンロードし展開します。
main_hook/sdlboot内のビルド済みファイルはFinkSDLパッケージを使っているようなので、フレームワークを使ってビルドし直します。
私が修正したMakefileの内容を記述しておきます。

all: sdlboot.dylib

sdlboot.dylib: sdlboot.o
#	$(CC) -dynamiclib -fPIC -o $@ $< `sdl-config --libs | sed s/-lSDLmain//`
#	$(CC) -flat_namespace -dynamiclib -init _sdlboot_init_ -single_module -fPIC -o $@ $< `sdl-config --libs | sed s/-lSDLmain//`
	$(CC) -dynamiclib -fPIC -o $@ $< -framework SDL -framework Cocoa

sdlboot.o: sdlboot.m ../main_hook.c
#	$(CC) -c -fPIC $< `sdl-config --cflags`
	$(CC) -c -fPIC $< -framework SDL -I/Library/Frameworks/SDL.framework/Headers -framework Cocoa

clean:
	rm -f *.o *.dylib

ビルドしたプログラムと同じ場所にsdlboot, sdlboot.dylibをコピーし、

$ ./sdlboot ./program

のように使うとやっと起動することができるようになりました。

(おまけ) gdcを使ってビルドする場合

Mac OS X用のgdcを使っている場合はSDLのDevelopment用のアーカイブの中にあるSDLMain.mを使うことで起動できない問題を回避できます。
D言語プログラムをgdcでビルドするときには、

// via http://gamehell2000.googlecode.com/svn/trunk/omega/koke/boot.d
version (darwin) {
  extern (C) int _d_run_Dmain(int argc, char* argv[]);
  extern (C) int SDL_main(int argc, char* argv[]) {
    return _d_run_Dmain(argc, argv);
  }
}

というコードを追加しておき、SDL-devel-1.2.X-extras.dmgの中のSDLMain.mと一緒にコンパイルすることで正しい順番でCocoaアプリケーションの初期化作業が行われるようになります。
この方法ですとsdlbootを使わずに起動できるようになるのでdmdと比べると簡単です。

4. .app形式の作成

最後にせっかくですのでパッケージ化してみます。MacでSDLアプリケーションの作り方を見ると、Info.plistを作らなくても実行できることがわかりました。簡単なのでその方法にすることにします。

4.1. 必要なファイル構造を作る
$ mkdir -p test.app/Contents/MacOS
$ cp program sdlboot sdlboot.dylib test.app/Contents/MacOS

この状態でtest.appを開くとtest.app/Contents/MacOS/testを実行してくれるようです。

4.2. sdlbootでprogramを起動するスクリプトを作る

上述のようにtest.appを開くとtest.app/Contents/MacOS/testを実行してくれますが、今回は引数を渡す必要があるのでシェルスクリプトを使います。WindowsLinuxなどで動くプログラムと同じソースコードが使えるようにするため、カレントディレクトリをプログラムと同じ場所に変更するスクリプトを用意します。

$ echo -e '#!/bin/sh\ncd (dirname $0)\n./sdlboot ./program' > test.app/Contents/MacOS/test
$ chmod +x test.app/Contents/MacOS/test
4.3. アイコン画

イコン画像は、Finderからアプリケーションの「情報を見る」→アイコンのペースト、で貼り付けることができます。

パッケージの作成に必要な作業はこれだけですが、

  • PROGRAM.app/Contents/MacOS/ にPROGRAMを置けばそのファイルが実行される
  • sdlbootを使った場合は、メニューバーに表示されるプログラム名は実際に実行しているファイル名になる
  • アプリケーションの情報を見ても著作権情報やバージョンが表示されない
  • アイコンを付けるとPROGRAM.app/ContentsにIconという名前の不可視ファイルが作成される

という制限があります。ちゃんとInfo.plistを書いてPROGRAM.app/Contents/に置いた方がユーザフレンドリーなプログラムになるでしょう。

5. 配布するとき

開発環境のみで起動するにはこのままでいいですが、使用しているフレームワーク(今回はSDLフレームワーク)を

PROGRAM.app/Contents/Frameworks

におくことで、フレームワークがインストールされてない環境でもSDLを使うことができます。
またアイコンをFinderから貼り付けた場合にはアイコンファイルがパッケージ内に保存されているので、dmg(ディスクイメージ)やFinderからzip書庫にまとめた方がいいでしょう。

6. 参考にしたページ

MacOSX版Ruby/SDLバイナリの作成方法
Info.plistの書き方にも触れられています。
MacOSX+SDLでの配布物作成法
SDL.frameworkを使ってビルドする方法など。