Xamarin.Forms 用の fsproj をビルドしたときに遭遇したバグの話
はじめに
この記事で扱われるバグは Alpha preview で修正されています。記事を書いている間に修正されたことを記事を公開する直前に気づいて枕を濡らしました。
概要
F# で Xamarin.Forms 向けのプロジェクトを作成しようとしたらビルドエラーになったので調査した話。コードはほとんど出てきませんが無駄に長いです。
前書き
現在は F# の Xamarin.Android や Xamarin.iOS 向けのプロジェクトテンプレートが提供されていて、昔に比べると開発が容易になったと感じます。そんなプロジェクトテンプレートですが、残念なことに F# の Xamarin.Forms 向けは提供されていません。GitHub で見かける F# + Xamarin.Forms のサンプルは画面回りが C# で、共通部分が F# になっているものばかりです。F#er なら全部 F# にしたい。そこで今回は、Xamarin Forms のプロジェクトをすべて F# で作ってからアプリケーションを開発することに挑戦します。
手順
- 前述のプロジェクトテンプレートを使用して、それぞれのプロジェクトを作成します。次に PCL 側を追加し、Android / iOS プロジェクトから参照します。
- paket を使用して作成したプロジェクトに Xamarin.Forms パッケージを参照させます。 paket の使い方についてはこちらの素晴らしい記事 Paketコトハジメ #FsAdvent を参考にしてください。
- 各プラットフォームで Xamarin.Forms.Forms.Init を呼び出します。
// Android [<Activity (Label = "Sample.Droid", MainLauncher = true)>] type MainActivity () = inherit Xamarin.Forms.Platform.Android.FormsApplicationActivity() override this.OnCreate (bundle) = base.OnCreate (bundle) Xamarin.Forms.Forms.Init(this, bundle) this.LoadApplication(new App());
[<Register ("AppDelegate")>] type AppDelegate () = inherit Xamarin.Forms.Platform.iOS.FormsApplicationDelegate() // This method is invoked when the application is ready to run. override this.FinishedLaunching (app, options) = Xamarin.Forms.Forms.Init() this.LoadApplication(App()) base.FinishedLaunching(app, options)
あとは Visual Studio または Xamarin Studio を使用してビルドします。
Droid
いざビルドしてみても失敗してしまいます。ログを確認してみます。
エラー FS0010: 予期しない キーワード 'end' です メンバー定義内 エラー FS0010: この場所またはその前にある構造化コンストラクトが不完全です 実装ファイル内。このポイントまたはその前にある構造化コンストラクトが不完全です または他のトークンを指定してください。 警告 FS0046: 識別子 'fixed' は F# で将来利用するために予約されています
Resource.designer.fs
でエラーが発生していました。
// aapt resource value: 0x7f07001c static member end = 2131165212 // aapt resource value: 0x7f070098 static member end_padder = 2131165336
F# の予約語である end
がエスケープされていないためエラーになっています。このファイルは Xamarin.Android が自動生成するため、自分のプログラミングミスではありません。そもそもまだコードを書いていません。どうやら Xamarin.Forms が依存するパッケージに含まれるリソースを Resource.designer.fs
に出力する処理に問題があるようです。
Google it!
困ったのでぐぐると issue が見つかりました。しかしこのバグは修正済みだと Don Syme 氏が仰っていました。Bug: The end keyword is not escaped in generated codedom #10
RELEASE_NOTES によると、0.9.4.0
で修正されているとのことです。
バグの報告元では榎本さんが参照を更新したと仰っていました。24709 – F# reserved keyword 'end' being generated in member for resource.designer.fs
しかし私の環境ではエスケープされていません。再インストールしても、alpha チャンネルにしても同じ現象が発生しています。
DLL を確認する
FSharp.Compiler.CodeDom.dll
を確認します。Windows だと %ProgramFiles(x86)%MSBuild\Xamarin\Android
に、OS X だと /Library/Frameworks/Xamarin.Android.framework/Versions/{バージョン}/lib/xbuild/Xamarin/Android
に格納されています。
$ monodis --assembly /Library/Frameworks/Xamarin.Android.framework/Versions/7.0.2-37/lib/xbuild/Xamarin/Android/FSharp.Compiler.CodeDom.dll Assembly Table Name: FSharp.Compiler.CodeDom Hash Algoritm: 0x00008004 Version: 0.9.2.0 Flags: 0x00000000 PublicKey: BlobPtr (0x00000000) Zero sized public key Culture:
エスケープが Fix されたバージョンよりも古い DLL が格納されていました。
コードを確認する
Xamarin.Android はオープンソースソフトウェアですので、コードを参照できます。ビルド関連のコードを探すと、ResourceDesigner を生成するコードが見つかりました。FSharp.Compiler.CodeDom.FSharpCodeProvider も使用されています。
実験
FSharp.Compiler.CodeDom.dll
を入れ替えてみましょう。Xamarin.Android.Build.Tasks.dll
が参照している FSharp.Compiler.CodeDom.dll
は FSharp.Compiler.CodeDom, Version=0.9.2.0, Culture=neutral, PublicKeyToken=null
であるため、バージョンと PublicKeyToken を合わせる必要があります。
fsprojects/FSharp.Compiler.CodeDom から clone してビルドします。dll ができあがったら、先ほどのフォルダにある dll と入れ替えます。その後 Droid プロジェクトをビルドすると、なんと成功しました。再生成された Resource.designer.fs
内のキーワードもエスケープされています。ということは、この FSharp.Compiler.CodeDom.dll
をどうにかしてやればビルドが通るようになるはずです。
オープンソース版の xamarin-android をビルドする
であれば、オープンソース版を自分でビルドして参照してやればよさそうです。製品版の dll をいじるのはいろいろと問題がありますし。手始めに xamarin/xamarin-android からリポジトリを clone します。あとは git submodule update --init --recursive
make prepare
make
とコマンドを実行すればよいはずですが、ubuntu だとうまくいかなかったので今回は OS X でビルドしました。
targets の import 先を変更する
出来上がった生成物の中に Xamarin.Android.FSharp.targets
が含まれていますので、Droid プロジェクトの Import 先を変更します。
<Import Project="..\..\..\path\to\Xamarin.Android.FSharp.targets" />
Build
ここまで来たらビルド可能になっているはずなので、コマンドを実行してビルドを走らせます。
xbuild Sample.Droid.fsproj /p:AndroidSupportedAbis=armeabi-v7a,x86 /t:SignAndroidPackage ... Build succeeded
ビルドに成功しました。apk ファイルも生成されています。
Debug
出来上がったアプリを実行してもすぐに落ちるので IDE を使用してデバッグ実行してみたのですが、エラーが発生します。
Xamarin Studio だとこれといったエラーログは出力されないのですが、Visual Studio だと Xamarin.Android.Build.Debugging.Tasks.dll
が見つからなかったと出力されます。xamarin-android の生成物に Xamarin.Android.Build.Debugging.Tasks.dll
は含まれていないので、製品版の DLL を使用してみましたがうまくいかず。Stable に修正が反映されるのを待ちましょう。
iOS
iOS 向けプロジェクトは何もしなくてもビルドできてシミュレーターでも動作しています。テンプレートそのままなので当然なのですが何もエラーがなくて逆に怖いですね。
余談
xamarin-android のリポジトリを観察しているとこういうのも見つかりました。これは F# の TypeProvider という機能を使用してリソースを生成するライブラリのようです。 github.com
TypeProvider については恐らく F# level-9 ninja の方が記事を書いてくれると思います。
まとめ
Xamarin はオープンソースソフトウェアですので、情報を収集しやすく、自分で調査・実験することもできます。自分の使用するソフトウェアをビルドしてみるとわかることがたくさんあります。Xamarin は更新が早いのでアップデート内容は早めに確認しましょう。