窓を作っては壊していた人のブログ

この謎のブログタイトルの由来を知るものはもういないだろう

Xamarin SDKのPinningを行う ~MSBuildと仲良くなる~

本日Twitter

Xamarin SDK複数バージョン入れて、指定したバージョンビルドって可能なんだっけ?

みたいな書き込みがあり,そういえば前Currentを書き換えてiOSSDKの変更をしたことがあったなと思い,他にやり方がないか試してみました. 以下に挙げるテクニックなどを使用したSDKのPinningを行うことで出来ることとしては,

  • OSSになったXamarin SDKを自前ビルドして最新版を試してみること
  • あるバージョンから入ったバグ探しをすること
  • macのバージョンが低く,Xcodeのバージョンなどが上げられない時の対処(たぶん)

などが挙げられるかと思います. どんな仕組みでビルドが走っているのかの一端を見ることも出来るので,興味のある人は読んでみてください.

今回提案する方法は3つあります.

実験環境

  • macOS 10.12.6
  • mono 5.8.0.108
  • Visual Studio Community 2017 for Mac (Preview) Version 7.4 Preview (7.4 build 884)
  • Xamarin.Android Version: {8.1.0.25,8.2.0.1,8.2.0.6}
  • Xamarin.Mac Version: 4.2.0.8
  • Xamarin.iOS Version: 11.8.0.8

その1,Currentの書き換え

デフォルトではmacOSの場合 /Library/Frameworks/Xamarin.{Android,iOS,Mac}.framework/Versions/Current/のバージョンを使用します. あなたがもしsudoを使える立場なのであれば,シンボリックリンクを張り直すことでバージョンのPinningが行えます.

メリット:

  • 以降のビルドが基本的にはそのバージョンで行われ続ける

デメリット:

  • sudoを行う権限が必要
  • 新しいバージョンのSDKがインストールされるとCurrentが新しい方に移り,再度修正が必要
  • 全プロジェクトでそのバージョンが使われてしまい,使い勝手が悪かったりする

その2,csprojを書き換える

Android, iOS, macOSのプロジェクトのcsprojを見てみると,いくつか.targetファイルをImportしているのが確認できるかと思います. macOSだと

<Import Project="$(MSBuildExtensionsPath)\Xamarin\Mac\Xamarin.Mac.CSharp.targets" />

こんな感じのものです.

それをどんどん遡っていくと,

macの場合は /Library/Frameworks/Mono.framework/External/xbuild/Xamarin/Mac/Xamarin.Mac.ObjCBinding.CSharp.propsXamarinMacFrameworkRoot

Androidの場合は /Library/Frameworks/Mono.framework/External/xbuild/Xamarin/Android/Xamarin.Android.Common.propsXamarinAndroidVersion (未検証です...)

iOSの場合は /Library/Frameworks/Mono.framework/External/xbuild/Xamarin/iOS/Xamarin.iOS.ObjCBinding.CSharp.propsMonoTouchSdkRoot

というプロパティにたどり着きます. これらのプロパティが設定されていない場合,Currentを指すような仕組みになっているようです.

決して面倒だからと言って上記のpropsファイルを書き換えるようなことはしないでください

ということで,このプロパティを指定することでSDKのPinningが行えそうです. macOSのプロジェクトの場合,プロジェクトのcsprojを開いて

<PropertyGroup>
  <XamarinMacFrameworkRoot>/Library/Frameworks/Xamarin.Mac.framework/Versions/4.2.0.8/</XamarinMacFrameworkRoot>
</PropertyGroup>

こんな感じでパスを指定してあげます. こうすることでsudoなどを使わずとも良くなりました. PropertyGroupのConditionをいい感じに変えてあげればCurrentを使うときや,Pinningしたバージョンでビルドするといったことが切り替えることが出来るので,そちらも試してみてください.

その際のビルドログを載せておきます.(179行目ぐらいから指定したバージョンを使っていることが見える)

macOSでSDKを指定した場合とそうでない場合のビルドログ(VS4Mに出力されてるやつのコピペなので見づらい) · GitHub

メリット:

  • csprojを見ればどのバージョンでビルドしたいのかがはっきりわかる
  • 他のプロジェクトに影響を与えない

デメリット:

  • csprojファイルを自分で書き換えるのが面倒

その3,コマンドラインでビルドする

この条件は少し特殊です. 自前でCI環境などを作っていて,cscだったりmsbuildを自分で叩く人のみに関係してきます. この場合も その2 と変わらずプロパティを指定してあげるだけです.

csc /p:XamarinMacFrameworkRoot=/Library/Frameworks/Xamarin.Mac.framework/Versions/4.2.0.8/ ... some_project.csproj

みたいな感じでしょうか. これはcsprojに書いてなくてもプロパティを外部から与えてあげる際に使用されるような場合に用いられます.

詳しくはこちら

MSBuild コマンド ライン リファレンス | Microsoft Docs

まとめ

3つの方法でSDKのPinningが可能であることがわかりました. 一番オススメなのが私としてはcsprojを書き換え,SDKのPathを指定してあげることです.

今回はmacOSをターゲットとする場合でしたが,iOSでも同様の手順で可能のはずです.

また今回はAndroidの検証が行えていません. Android

<XamarinAndroidVersion>8.2.0-6</XamarinAndroidVersion>

の様に指定されているので,このプロパティに対象のバージョンを指定することで可能になりそうです. どなたか試していただければと...

またこれ以外に簡単な手段があればぜひともコメントやTwitterなどで教えていただければと思います(普通にシェルとかの環境変数で出来たら楽そう...)


追記(2018/02/07 13:40)

このようなリプをいただいたので

XamarinMacFrameworkRoot=/Library/Frameworks/Xamarin.Mac.framework/Versions/4.2.0.8/ /Applications/Visual\ Studio.app/Contents/MacOS/VisualStudio

みたいな方法でVS4Mを起動してみた所,SDKに関する変数を反映した状態でVS4Mが起動でき,またビルドを走らせることが出来ました. 情報ありがとうございました.

とすると,コマンドラインでビルドを走らせる場合はある程度プロファイルを作ってシェルを叩いてしまってもいいし,MSBuildにパラメータとして与えても問題ないって感じですね. CI環境では同時に環境変数を与えることが出来るものも多いので(Visual Studio App Centerとかもそうだし),そういう感じで条件変えながらビルドのテストが出来そうです.