Building C# iphone application using a Visual studio project file
はじめに
Visual stuid(以下,VS)oでMonotouchのライブラリを参照して作成したC# プロジェクトから,iPhoneアプリケーションをビルド,シミュレータで実行するまでの手順を紹介します.
小規模なチームでiPhoneアプリケーションを開発するときには,メンバーが最も慣れ親しんだソースコード開発環境をそのまま利用することは大きな価値があります.そこでVisual stuidoをソースコードおよびプロジェクト編集に用い,それらを元にiPhone アプリケーションをビルド,テストする環境を構築しました.
今の環境はシミュレータで実行することができます.ただしXIBファイルを使わない前提で作成しています.実際のデバイスでの実行は,私がまだその環境を持たないために,動作確認ができていません.
概要
前回のエントリで,VSのソースコード編集補完機能をMonotouchのdllを参照して有効にする方法を示しました.ここではVisual StudioのソリューションファイルからのMakefileの構築,およびMakeの実行を目標とします.
初期検討
開発環境をどうするかを検討しました.補完機能の便利さからVSもしくはMonoDevelop(以下,MD)いずれかのIDEを使います.Windowsではないプラットホームで開発することも考えると,いずれのIDEも使えることが望ましいです.これらの条件で,どちらのIDEを主に利用するかを検討しました.結果VSを主としました.
その検討過程は:
- MDはiPhoneのプロジェクトファイルをサポートしていて,ビルド,テスト,インストールがIDEから実行できる.
- MDで作成したiPhoneのプロジェクトファイルは,VSでは開けない (サポートされていないプロジェクトだとエラーが出る)
- VSで"空のプロジェクト"で作成したプロジェクトファイルは,MDでも開いて編集できる.
これらからVSの"空のプロジェクト"で作成したソリューションを使うことにしました.
ビルドツールの検討
VSのソリューションからのビルド,テストの自動化は必須です.VSのソリューションファイルはビルドツール MSBuild の設定ファイルでもあります.そこでmonoのMSBuild互換ツール xbuild で,ビルドを自動化できるか検討しました.
結果は出来ません.理由はxbuild (というよりxbuildが読み込む microsoft.build.task.dll)が,ソースコードでmonoのコンパイラを設定するからです.MicorsoftのMSBuildのドキュメントには,プロパティ CscToolPath でコンパイラを指定できると書かれているのですが,xbuildはこのプロパティを見ておらずmonoのコンパイラをデフォルトで指定します.
Monotouchのコンパイルにはgmcsではなく/Developer/Monotouch/usr/bin/smcs (あるいはmoonlightのコンパイラそのものでもいいのかもしれませんが) を使わねばなりません.このパスが設定できないのでxbuildが利用できません.
そのために,ソースファイルおよびリソースファイルのリスト抜き出し処理をするコマンドライン プログラムを作り,それらのリストからのビルド,テストは make で行うことにしました.
処理フロー
Makefileのビルド,実行のフローは以下のとおりです:
csprojファイルからファイルリストを作るプログラムのコードは以下のとおりです:
$ コマンド csprojファイル名 > config.make
のように使います.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; namespace ConfigBuilder { class Program { const string msbuild_ns = "http://schemas.microsoft.com/developer/msbuild/2003"; static void Main(string[] args) { var xdoc = XDocument.Load(args[0]); var asmn = xdoc.Descendants(XName.Get("AssemblyName", msbuild_ns)).First<XElement>().Value; var dlls = from el in xdoc.Descendants(XName.Get("HintPath", msbuild_ns)) select el.Value; var srcs = from el in xdoc.Descendants(XName.Get("Compile", msbuild_ns)) select el.Attribute("Include"); var ress = from el in xdoc.Descendants(XName.Get("Content", msbuild_ns)) select el.Attribute("Include"); Console.WriteLine("#Configuration for the Makefile, generated from {0}.", args[0]); Console.WriteLine("CONFIG = DebugiPhoneSimulator"); Console.WriteLine("ASSEMBLY_NAME = {0}", asmn); Console.Write("FILES = "); foreach (String s in srcs) Console.Write("\\\n\t{0}", s); Console.WriteLine(); Console.Write("EXTRAS = "); foreach (String s in ress) Console.Write("\\\n\t{0}", s); Console.WriteLine(); Console.Write("REFERENCES = "); foreach (String s in dlls) Console.Write("\\\n\t{0}", s.Substring(0, s.Length - 4)); Console.WriteLine(); //Console.ReadKey(); } } }
これらのために2つのファイル,ファイルリスト config.make および Makefile を使います.
まずconfig.makeというファイルを作り,ファイルリストを書いていきます.最初のCONFIGだけは決めうち,あとの項目はそれぞれソリューションファイルの登録内容にあわせて記入します.
CONFIG = DebugiPhoneSimulator ASSEMBLY_NAME = VSSample_ScrollView FILES = Main.cs CustomScrollViewController.cs EXTRAS = thu_nanoha.jpg thu_riinzwei.jpg REFERENCES = monotouch
Makefile本体は以下のものになります.all でビルド,runでシミュレータで実行します.なぜかは分かりませんが,iPhone simulator が起動している状態ではrunしてもアプリが起動しません.このため必ずiPhone simulatorが終了した状態で,make runします.
# MonoTouch make file # rev 001 2009/10/23 A. Uehara include config.make srcdir=. top_srcdir=.. # definitions MT_TOOL_DIR = /Developer/MonoTouch/usr/bin MT_LIB_DIR = /Developer/MonoTouch/usr/lib/mono/2.1 CSC = $(MT_TOOL_DIR)/smcs MTOUCH = $(MT_TOOL_DIR)/mtouch MIBTOOL = $(MT_TOOL_DIR)/mibtool ARM_MONO = $(MT_TOOL_DIR)/arm-darwin-mono MONO = $(MT_TOOL_DIR)/mono MTOUCH_PACK = $(MT_TOOL_DIR)/mtouchpack TOOL_DIR = /usr/bin RESGEN = $(TOOL_DIR)/resgen AL = $(TOOL_DIR)/al COMPILE_TARGET = exe _pwd = $(shell pwd) _info_plist = $(APP_DIR)/Info.plist # -- debug bild for an iphone simulator -- ifeq ($(CONFIG), DebugiPhoneSimulator) BUILD_DIR = $(_pwd)/bin/iPhoneSimulator/Debug CSC_FLAGS = -noconfig -codepage:utf8 -warn:4 -optimize- -debug "-define:DEBUG" ASSEMBLY_MDB = $(ASSEMBLY).mdb MTOUCH_FLAGS = -v --nomanifest --nosign -sim "$(APP_DIR)" endif # -- release build for an iphone simulator ifeq ($(CONFIG), ReleaseiPhoneSimulator) BUILD_DIR = $(_pwd)/bin/iPhoneSimulator/Release CSC_FLAGS = -noconfig -codepage:utf8 -warn:4 -optimize- ASSEMBLY_MDB = MTOUCH_FLAGS = -v --nomanifest --nosign -sim "$(APP_DIR)" endif # -- debug bild for an iphone ifeq ($(CONFIG), DebugiPhone) BUILD_DIR = $(_pwd)/bin/iPhone/Debug CSC_FLAGS = -noconfig -codepage:utf8 -warn:4 -optimize- -debug "-define:DEBUG" ASSEMBLY = bin/iPhoneStimulator/Debug/Sample_ScrollView.exe ASSEMBLY_MDB = $(ASSEMBLY).mdb MTOUCH_FLAGS = -v --nomanifest --dev "$(APP_DIR) --certificate=$(CERTIFICATION) endif # -- release build for an iphone ifeq ($(CONFIG), ReleaseiPhone) BUILD_DIR = $(_pwd)/bin/iPhone/Release CSC_FLAGS = -noconfig -codepage:utf8 -warn:4 -optimize- ASSEMBLY_MDB = MTOUCH_FLAGS = -v --nomanifest --dev "$(APP_DIR)" --certificate=$(CERTIFICATION) endif ASSEMBLY = $(BUILD_DIR)/$(ASSEMBLY_NAME).$(COMPILE_TARGET) APP_DIR = $(BUILD_DIR)/$(ASSEMBLY_NAME).app APP_ASSEMBLY = $(APP_DIR)/$(ASSEMBLY_NAME).$(COMPILE_TARGET) CLEANFILES = $(APP_DIR) $(ASSEMBLY) build_references := $(foreach item, $(REFERENCES), "/r:$(item).dll") mtouch_references := $(foreach item, $(REFERENCES), -r="$(MT_LIB_DIR)/$(item).dll") copy_contents := $(foreach item, $(EXTRAS), $(APP_DIR)/$(item) ) define _do_copy_contents $1: $2 @echo "Copying '$$<' to '$$@'" @mkdir -p '$$(shell dirname '$$@')' cp '$$<' '$$@' # @test -z '$$<' || cp '$$<' '$$@' endef # rules all: $(copy_contents) $(APP_ASSEMBLY) $(_info_plist) $(APP_ASSEMBLY): $(ASSEMBLY) mkdir -p $(APP_DIR) $(MTOUCH) $(MTOUCH_FLAGS) $(mtouch_references) "$(ASSEMBLY)" $(ASSEMBLY): $(FILES) mkdir -p $(shell dirname $(ASSEMBLY)) $(CSC) $(CSC_FLAGS) -out:$(ASSEMBLY) -target:$(COMPILE_TARGET) $(build_references) $(FILES) $(eval $(foreach item, $(EXTRAS), $(call _do_copy_contents, $(APP_DIR)/$(item), $(item) ))) clean: -rm -Rf $(CLEANFILES) run: all $(MTOUCH) -launchsim=$(APP_DIR) $(_info_plist): config.make mkdir -p $(shell dirname $(_info_plist)) $(shell echo "<?xml version="1.0" encoding=\"utf-8\"?>" > $(_info_plist)) $(shell echo "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" >> $(_info_plist)) $(shell echo "<plist version=\"1.0\">" >> $(_info_plist)) $(shell echo " <dict>" >> $(_info_plist)) $(shell echo "<key>CFBundleDevelopmentRegion</key><string>English</string>" >> $(_info_plist)) $(shell echo "<key>CFBundleDisplayName</key><string>$(ASSEMBLY_NAME)</string>" >> $(_info_plist)) $(shell echo "<key>CFBundleExecutable</key><string>$(ASSEMBLY_NAME)</string>" >> $(_info_plist)) $(shell echo "<key>CFBundleIdentifier</key><string>com.yourcompany.$(ASSEMBLY_NAME)</string>" >> $(_info_plist)) $(shell echo "<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>" >> $(_info_plist)) $(shell echo "<key>CFBundleName</key><string>$(ASSEMBLY_NAME)</string>" >> $(_info_plist)) $(shell echo "<key>CFBundlePackageType</key><string>APPL</string>" >> $(_info_plist)) $(shell echo "<key>CFBundleSignature</key><string>????</string>" >> $(_info_plist)) $(shell echo "<key>CFBundleSupportedPlatforms</key><array><string>iphonesimulator</string></array>" >> $(_info_plist)) $(shell echo "<key>CFBundleVersion</key><string>1.0</string>" >> $(_info_plist)) $(shell echo "<key>DTPlatformName</key><string>iphonesimulator</string>" >> $(_info_plist)) $(shell echo "<key>DTSDKName</key><string>iphonesimulator3.0</string>" >> $(_info_plist)) $(shell echo "<key>LSRequiresIPhoneOS</key><true />" >> $(_info_plist)) $(shell echo " </dict>" >> $(_info_plist)) $(shell echo "</plist>" >> $(_info_plist))
サンプルコード
csprojファイルからリストを抜き出すのが
OneDrive
です.
上記のMakefileは雛形を兼ねてサンプルコードを作成しました.
OneDrive
これには,VSでコード補完するにはmonotouch.dllが必要ですが,2次配布はできないために,それだけは含んでいません.このフォルダにmonotouch.dllを含んでいなくてもビルドはできます.
ボタンを押すと画像が上下にスクロールして切り替わるだけのサンプルです.mini convertibleの写真を2次利用しています.比率がすごいことになっていますが,気にしないでください.
つまづいたところ
monoの環境で閉じたビルド環境にこだわり,xbuildのみで環境構築をしようとしてかなり時間を使いました.結局のところコンパイラのツールパス設定ができなことがソースコードを見て初めて分かりました.
それならCscタスクをパス設定できるものに変更してみたのですが,dllを作成してそれをGACに登録して,さらにxbuildのターゲットファイルにdll変更を反映させて,さらにxbuildの構文で上記Makefileの記述をするという,かなり手間のかかることになってしまいました.
GACのインストールという1点だけでも,方法として相当に一般性を失ってしまいました.もう1つの手としてxbuildから上記Makefile相当のシェルスクリプトを生成して実行するのも手だったのですが,それならばファイルリストだけxbuildで出して残りの作業はMakefileに記述したほうがよほどスマートと考えました.
そんなわけで,xbuildにこだわるこの方法は破棄し,上記Makefileに移った次第です.