SDD(Sleep-Driven Development)

睡眠の重要性!!睡眠の重要性!!

Visual Studio for Mac Extensionで独自Padを作る

Visual Studio for Macでの独自Padの実装方法です。 ちなみにPadは以下のようなやつです。間違ってたらごめんなさい。

f:id:crocus7724:20170520015028p:plain

今回はサンプルとしてビルドした回数とデバッグした回数を記録しておくPadを作ります。
(本当はRunした回数を取りたかったけど取得の仕方がわからなかった・・・)

PadContentの作成

まず最初にPadContentを継承したクラスを作成します。これはiOS/AndroidでいうUIViewController/Activityみたいな役割で、表示するViewを組み立てたりします。たぶん

実際にコードを見てみましょう。

using System;
using Gtk;
using MonoDevelop.Components;
using MonoDevelop.Debugger;
using MonoDevelop.Ide;
using MonoDevelop.Ide.Gui;
using MonoDevelop.Projects;

namespace SamplePadExtension
{
    /// <summary>
    /// ビルドした回数とデバッグ実行した回数を表示するPad
    /// </summary>
    public class SamplePadExtensionPad : PadContent
    {
        // デバッグした回数
        private int debugCount;

        // ビルドした回数
        private int buildCount;

        // デバッグした回数を表示するラベル
        private Label debugCountLabel;

        // ビルドした回数を表示するラベル
        private Label buildCountLabel;

        // 実際に表示するものを指定するバッキングフィールド
        // overrideのControlプロパティがget onlyなので
        private Control control;

        /// <summary>
        /// 実際に表示するControl(View)を格納するプロパティ
        /// </summary>
        public override Control Control => control;

        /// <summary>
        /// 実際に表示されるときに呼ばれる?
        /// </summary>
        /// <param name="window">Padのウィンドウ. Toolbarとかとれる</param>
        protected override void Initialize(IPadWindow window)
        {
            buildCountLabel = new Label {Text = $"Build count: {buildCount}"};
            debugCountLabel = new Label {Text = $"Debug count: {debugCount}"};

            // 縦積みするレイアウト
            var vBox = new VBox {buildCountLabel, debugCountLabel};
            // 表示
            vBox.ShowAll();
            control = vBox;

            IdeApp.ProjectOperations.StartBuild += OnStartBuild;
            DebuggingService.DebugSessionStarted += OnDebugSessionStarted;
        }

        // ビルドが始まったときに呼ばれる
        private void OnStartBuild(object sender, BuildEventArgs args)
            => buildCountLabel.Text = $"Build count: {++buildCount}";

        // デバッグ実行が始まったときに呼ばれる
        private void OnDebugSessionStarted(object sender, EventArgs eventArgs)
            => debugCountLabel.Text = $"Debug count: {++debugCount}";

        public override void Dispose()
        {
            IdeApp.ProjectOperations.StartBuild -= OnStartBuild;
            DebuggingService.DebugSessionStarted -= OnDebugSessionStarted;

            base.Dispose();
        }
    }
}

注意点なのですが、デバッグの開始を取得するためにMonodevelop.Debuggerを使っています。
デフォルトだと参照できないのでVisual Studio for Mac ExtensionプロジェクトのAddin Reference(下の画像の赤枠のやつ)をダブルクリックして表示されるポップアップからMonodevelop.Debuggerを選択してください。

f:id:crocus7724:20170520020303p:plain

Manifest.addin.xmlの編集

次に上で書いたPadContentを継承したクラスをVisual Studio for Mac Extensionが認識できるようにします。 Propertiesフォルダの中にあるManifest.addin.xmlファイルを以下のように編集します。

<?xml version="1.0" encoding="UTF-8"?>
<ExtensionModel>
    <!-- MainMenu→View→PadsにPadを追加する -->
    <Extension path="/MonoDevelop/Ide/Pads">
        <!-- 実際に追加するPad -->
        <!-- idは固有識別子 -->
        <!-- defaultPlacementは表示させたときのデフォルトの位置 -->
        <!-- classはPadContentを継承したクラスの完全修飾名 -->
        <!-- _labelはPadsに追加されたときに表示される文字 -->
        <Pad id="SamplePadExtension.SamplePadExtensionPad"
             defaultPlacement="Bottom"
             class="SamplePadExtension.SamplePadExtensionPad"
             _label="Sample Extension Pad"/>
    </Extension>
</ExtensionModel>

これで準備は完了です!

実行

それでは実際に実行してみましょう。

f:id:crocus7724:20170520021013p:plain

実行すると上のようなのが追加されていて、ビルドしたりデバッグしたりすると下のようにカウントが増えていくはずです。

f:id:crocus7724:20170520021057p:plain

Xamarin StudioまではファイルテンプレートにGtkがあったのですが、VS for MacからGtkテンプレートが消失してしまい、Xamarin StudioはVS for Macに置き換わる予定ですからGtkファイル手作業作成しか残されてないかなーと思うのですが辛すぎるので手抜きViewです。
もし凝ったレイアウトを作成したい場合は覚悟してください私はやりたくない

もうちょっと詳しく

もう少し凝ったレイアウトやよくあるリスト形式のPadを作成したい場合はおそらくBreakpointのPad実装である

monodevelop/BreakpointPad.cs at master · mono/monodevelop · GitHub

が参考になると思います。

まとめ

Visual Studio for Mac ExtensionならPadの作成も見た目を気にしなくていいならとても簡単にできます。
Padが作成できるとやれることの幅が広がるのでぜひ作ってみてください。

Visual Studio for Mac Extensionの作り方

Visual Studio for Macが正式リリースされたっぽい?のでVisual Studio for Macの拡張の作り方を調べてみました。(Xamarin Studio Addinと変わってなかったけど)

Addin Makerインストー

最初にVS for Mac ExtensionのテンプレートやデバッグサポートをしてくれるAddin Makerを入れます。

VS for Macを開き、Main MenuのVisual Studio→Extensions…を選択します。

f:id:crocus7724:20170513161535p:plain

そして開いたウィンドウのGallery→Addin Development→Addin Makerを選択しインストールします。

f:id:crocus7724:20170513161923p:plain

いつものようにFile→New Solution…を開き、Other→Miscellaneous→Generalに以下のようなものが追加されていたらインストール成功です。

f:id:crocus7724:20170513162226p:plain

_人人人人人人人人人人人人人人_
> Xamarin Studio Addin <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

Extension作成

ここではサンプルとしてメニューから選択するとConsole.WriteLine("Hello World");を挿入するExtensionを作りたいと思います。(誰得だけど)

まずNew SolutionでXamarin Studio Addinを選択し、プロジェクトを作成します。
なおAddin Makerをインストールして最初の数回はよくアセンブリが見つからなかったりするエラーが発生しますが、VS for Macを再起動したりビルドしたりするとそのうちなおります。

次にメニュー等から選択されたときに実行されるCommandHandlerを作成し以下のように記述します。

using MonoDevelop.Components.Commands;
using MonoDevelop.Ide;

namespace SampleVSExtension
{
    /// <summary>
    /// コマンドが選択されると現在のキャレット位置に
    /// <code>Console.WriteLine("Hello World");</code>が挿入される
    /// コマンドハンドラー
    /// </summary>
    public class HelloWorldCommandHandler : CommandHandler
    {
        /// <summary>
        /// メインメニューなどが開かれるときなどに実行される
        /// ここで可視性や非活性、動的な文字の変更などができる
        /// </summary>
        /// <param name="info">コマンドのステータス</param>
        protected override void Update(CommandInfo info)
        {
            // ActiveDocument(現在開いているファイル)がnullではない(なにかしらのファイルを開いている)
            // ときにコマンドを表示
            info.Visible = IdeApp.Workbench.ActiveDocument != null;
        }

        /// <summary>
        /// コマンドが実際に選択されたときに実行される
        /// </summary>
        protected override void Run()
        {
            // 現在開いているエディタの情報を取得
            var editor = IdeApp.Workbench.ActiveDocument.Editor;
            // 現在のキャレット位置にConsole.WriteLine("Hello World");
            // を表示
            editor.InsertAtCaret("Console.WriteLine(\"Hello World\");");
        }
    }
}

もしusing MonoDevelopとかでエラーが表示されたらVS for Macを一回閉じてもう一度起動してみてください。

細かい説明などはソースコードに書いたので省略して次にCommandの種別を決めるSampleVSExtensionCommandsを作成します。

namespace SampleVSExtension
{
    public enum SampleVSExtensionCommands
    {
        InsertHelloWorld,
    }
}

これはただ単に種別を表すだけなのでとくに説明はありません。

最後に実際にどのような拡張をするかをVS for Macに知らせるManifest.addin.xmlを編集しましょう。
Manifest.addin.xmlAssemblyInfo.csなどがあるPropertiesフォルダの中にあります。

以下のように定義してみましょう。

<?xml version="1.0" encoding="UTF-8"?>
<ExtensionModel>
    <!-- Editという種類のコマンドを定義する -->
    <Extension path="/MonoDevelop/Ide/Commands/Edit">
        <!-- idは用意した種別を表すenumを完全修飾名({namespace}.{enum}.{value})で指定 -->
        <!--_labelはコマンドが表示される文字 -->
        <!-- defaultHandlerはCommandHandlerを拡張したクラスを完全修飾名で指定 -->
        <Command id="SampleVSExtension.SampleVSExtensionCommands.InsertHelloWorld"
                 _label="Insert Hello World"
                 defaultHandler="SampleVSExtension.HelloWorldCommandHandler"/>
    </Extension>
    <!-- MainMenuのEditに表示するコマンドを定義する -->
    <Extension path="/MonoDevelop/Ide/MainMenu/Edit">
        <!-- 上で定義したコマンドのidを指定 -->
        <CommandItem id="SampleVSExtension.SampleVSExtensionCommands.InsertHelloWorld"/>
    </Extension>
</ExtensionModel>

これで準備は完了です。実際に実行してみましょう!

MainMenuのEditにInsert Hello Worldが追加されていて

f:id:crocus7724:20170513171210p:plain

選択したら現在の行に挿入されたら成功です!

f:id:crocus7724:20170513171321p:plain

何故かキャレット位置ではなく行の頭に挿入されているのが謎というかバグですが気にしたら負けです。これで無事やりたいことはできました!!

他の機能の作り方

Visual Studio for Mac Extensionは参考になるものがたくさん公開されています。

私の場合はEditなどのソースコードを編集する機能などは以下を参考にしましたし、

monodevelop/main/src/addins/CSharpBinding at master · mono/monodevelop · GitHub

プロジェクトテンプレートなどの作り方は以下が参考になりました。

monodevelop/main/src/addins/AspNet at master · mono/monodevelop · GitHub

そしてこれらは以下にまとまっています。

monodevelop/main/src/addins at master · mono/monodevelop · GitHub

素晴らしいですね(((

まとめ

VS for MacのPreviewが外れ、.NET CoreやC#7対応を含めてXamarin Studioから徐々に移行してくると思います。
VS for MacのExtensionもXamarin Studio addinとあまり変わってない(たぶん)と思うのでぜひ作ってみてください!!

RiderでXamarin.Androidがサポートされました!!

RiderのPublic EAP20でXamarin.Androidがサポートされました 🎉🎉🎉🎉🎉
(ついでにT4テンプレートとNode.js)

さっそく試してみましょう。

Androidプロジェクト作成

Riderが起動したらNew solution or projectを選択します。

f:id:crocus7724:20170415001848p:plain

Bindings Libraryプロジェクトまである・・・!!
ネイティブバインディングまでRiderだけでできるかはあとで調べるとして、とりあえずBlank Appを選択してみます。

f:id:crocus7724:20170415002343p:plain

初期状態はこんな感じ。

さっそくデバッグしてみます。

f:id:crocus7724:20170415002535p:plain

シミュレータを適当に選んで・・・

f:id:crocus7724:20170415002638p:plain

画面真っ黒(´・ω・`)
無事に実行できました!!

f:id:crocus7724:20170415003555p:plain

ちなみにブレークポイントでもちゃんと止まります。

f:id:crocus7724:20170415014234p:plain また、Xamarin.FormsのAndroidプロジェクトも実行できました!!
(Xamarin.iOSがloadしてある状態だとビルドエラーになったのでわざとunloadしてます)

これは捗りますね!!

注意点

ただRider自体がまだPublic EAPな上にXamarin.Androidに対応したばっかりなので結構怪しいところがあります。

Riderで作成したXamarin.AndroidプロジェクトがXamarin Studioで開けない

開けません。

f:id:crocus7724:20170415005225p:plain

ただエラー内容はただ単にXamarin StudioはそんなProject Type GUIDなんて知らないよって言っているだけなので書き換えればOKです。

Xamarin.Androidの.csprojの

<ProjectTypeGuids>{02CD1F06-00EA-420C-944B-AD8BE8AD9848}</ProjectTypeGuids>

<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>

にすれば

f:id:crocus7724:20170415005627p:plain

Xamarin Studioでも開けました!!

ただ個人的にはまだXamarin Studioで作成してからRiderで開く運用のほうがいいと思います。(書き換えよろしくないと思いますし)

C#7使えない?

RiderはC#7に対応しているのですが、Xamarin Studioで作成したプロジェクトだと残念ながらコンパイル時にエラーになってしまいます。

ただ、Visual Studio for Macで作成したプロジェクトはRiderでもC#7で実行できるので、ヒトバシラーにはおすすめです() f:id:crocus7724:20170415022758p:plain




今回のバージョンアップでRiderでもXamarin.Androidプロジェクトが実行できるようになりました!
今回は適当なサンプルプロジェクトを実行しただけでこれが実用に耐えうるかは未知数ですが、これは大きな前進だと思います。また、Xamarin.iOSに関しても

ということで期待ですね・・・!!

Xamarin.Forms上でHello World

これは[学生さん・初心者さん大歓迎!]Xamarin Advent Calendar 2016 - Qiitaの23日目の記事です。

前日は

普段Swift書いてますがXamarinに入門してみました | Developers.IO

でした。

今回、本当はXamarin Studio Addinについて書こうとしたのですが、Xamarin Studioにブチ切れた諸々の諸事情により書く時間が足りなかったので思いっきりネタに走りました。本当に誰得レベル。

最近Xamarin.Forms書いてないし(専らNativeのほう)、そういえば私も1本もアプリ作れてないので初心者だと思います。なので、入門に向けてXamarin.Forms上でHello Worldをやってみました。
目指すのは以下のような感じ。

f:id:crocus7724:20161223225732p:plain

大事なことなのでもう一度いいます。ネタです。

環境

  • macOS Sierra
  • Xamarin Studio 6.1.2
  • Xamarin Forms 2.3.1.114

Hello World!!

とりあえずXamarin StudioでちゃちゃっとXamarin FormsのPortableでプロジェクトを作成します。(今考えるとSharedのほうが良かった)

作成したらぱぱっとHello Worldを実行したいのですが、PortableライブラリではNuGetを追加できませんでした。なので実行するためのインターフェースとして以下をPortableライブラリに追加します。

using System.Threading.Tasks;

namespace HelloForms
{
    public interface IScripting
    {
        Task<T> RunAsync<T>(string code);
    }
}

またNative側のNuGetパッケージにMicrosoft.CodeAnalytics.CSharp.Scriptingを追加します。 そして先程のインターフェースを実装したクラスを作成します。

using System.Threading.Tasks;
using HelloForms.iOS;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Xamarin.Forms;

[assembly: Dependency(typeof(Scripting))]

namespace HelloForms.iOS
{
    public class Scripting : IScripting
    {
        public async Task<T> RunAsync<T>(string code)
        {
            var result = await CSharpScript.RunAsync<T>(code);

            return result.ReturnValue;
        }
    }
}

上の[assembly:Dependency(typeof(Scripting))]はPortableライブラリ側でこのクラスを使うために必要です。 Tで戻り値の型を指定できるのですが今回全く使ってませんでした。(なくてもよかった)

これで準備は整いました。あとはPortableライブラリ側でViewとなんちゃってViewModelを作り・・・

  • HelloFormsPage.xaml
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:HelloForms"
             x:Class="HelloForms.HelloFormsPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="0,20,0,0"
                    Android="0"
                    WinPhone="0" />
    </ContentPage.Padding>
    <ContentPage.BindingContext>
        <local:HelloFormsPageViewModel />
    </ContentPage.BindingContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="5*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="5*" />
        </Grid.RowDefinitions>

        <Editor Text="{Binding Code,Mode=OneWayToSource}" />
        <Button Grid.Row="1" Text="Run Script"
                Command="{Binding ExecuteCommand}" />
        <Editor Grid.Row="2" Text="{Binding Result}" />
    </Grid>
</ContentPage>
  • HelloFormsPageViewModel
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;

namespace HelloForms
{
    public class HelloFormsPageViewModel : INotifyPropertyChanged
    {
        private string _code;
        private string _result;

        public string Code
        {
            get { return _code; }
            set
            {
                _code = value;
                OnPropertyChanged();
            }
        }

        public Command ExecuteCommand { get; }

        public string Result
        {
            get { return _result; }
            set
            {
                _result = value;
                OnPropertyChanged();
            }
        }

        public HelloFormsPageViewModel()
        {
            ExecuteCommand = new Command(async () =>
            {
                try
                {
                    var result = await DependencyService.Get<IScripting>().RunAsync<object>(Code);
                    Result = result?.ToString() ?? "";
                }
                catch (Exception e)
                {
                    Result = e.Message;
                }
            });
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

実行すれば・・・

f:id:crocus7724:20161220000511g:plain

Hello Worldができました!!さらにIDE側のApplication OutputにもHello Worldできます。
これで無事私も入門できました!

なにか違う

思えば確かにHello Worldと出力されたけどあれは真のHello Worldでは無い気がします。私が、いや私達が目指す真のHello Worldはまっさらな画面の真ん中にシステム標準の大きさで表示されているHello Worldなはずです。

というわけで改造。各プラットフォーム毎のScripting.RunAsync<T>の中身を以下のように変更

public async Task<T> RunAsync<T>(string code)
{
    var result = await CSharpScript.RunAsync<T>(code,ScriptOptions.Default
        .AddReferences(Assembly.Load("Xamarin.Forms.Core"),
            Assembly.Load("Xamarin.Forms.Platform"),
            Assembly.Load("Xamarin.Forms.Platform.iOS"),
            Assembly.Load("Xamarin.Forms.Xaml"))
        .AddImports("Xamarin.Forms"));
    return result.ReturnValue;
}

Xamarin.Forms関連のアセンブリを片っ端から読み込ませます。ここいらへんヤケクソ これで実行時にXamarin.Forms関連のアセンブリが参照と、using Xamarin.Formsの省略ができます。

これで完成。

いちいち入力するの怠いので以下のコードをクリップボードにコピーしといて・・・

Application.Current.MainPage.Navigation.PushAsync(new ContentPage
{
    Content = new StackLayout
    {
        VerticalOptions = LayoutOptions.Center,
        HorizontalOptions = LayoutOptions.Center,
        Children = { new Label
        {
            Text = "Hello World!"
        }}
    }
})

実行時貼り付けてやれば・・・

f:id:crocus7724:20161221222342g:plain

やりました!!真のHello Worldができました!!

違う

よくよく考えなくてもXamarin.FormsのViewはXAMLで組み立てるのが可読性的な意味でベストです。
なので正真正銘のHello WorldXAMLを使って表示させるべきなのではないでしょうか??

というわけでまたまた改造していきます。
まず、XAMLで書かれた文字列をインスタンスに変えなければならないのですが、自力で実装するのは骨が折れます。
幸いなことに、文字列からインスタンスに変換してくれるメソッドはXamarin.Formsにはあります。

https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Xaml/ViewExtensions.cs

このExtensionsクラスのLoadFromXamlメソッドの第2引数がstringのものがまさに求めていたものなのですが、非常に残念なことにアクセシビリティinternalです。

なので通常の方法で呼び出すことができません。。。非常に残念なので

using System;
using System.Linq.Expressions;

namespace HelloForms
{
    public static class Extensions
    {
        private static readonly Func<object, string, object> loadFromXamlDelegate;

        static Extensions()
        {
            // object view;
            var view = Expression.Parameter(typeof(object), "view");
            // string xaml;
            var xaml = Expression.Parameter(typeof(string), "xaml");

            // (view, xaml) => Xamarin.Forms.Xaml.Extensions.LoadFromXaml<view.Type>(view, xaml)
            var lambda = Expression.Lambda<Func<object, string, object>>(
                Expression.Call(typeof(Xamarin.Forms.Xaml.Extensions),
                    nameof(Xamarin.Forms.Xaml.Extensions.LoadFromXaml),
                    new[] {view.Type},view, xaml),
                view, xaml);

            loadFromXamlDelegate = lambda.Compile();
        }

        public static TXaml LoadFromXaml<TXaml>(this TXaml view, string xaml)
            => (TXaml) loadXamlDelegate.Invoke(view, xaml);
    }
}

リフレクションで呼び出すことにしました。
毎回リフレクション叩くのは芸がないなーということと通常のリフレクションだとうまくいかなかったので式木を使ってデリゲートを生成しています。 すごい便利ですね。式木。なおこれ式木でやる意味がない気がしますが、気がするだけで気のせいです。

あとはこいつを呼び出してあげればいいわけです。

RunAsyncHelloFormsの参照を追加してあげて

public async Task<T> RunAsync<T>(string code)
{
    var result = await CSharpScript.RunAsync<T>(code, ScriptOptions.Default
            .AddReferences(Assembly.Load("Xamarin.Forms.Core"),
                Assembly.Load("Xamarin.Forms.Platform"),
                Assembly.Load("Xamarin.Forms.Platform.iOS"),
                Assembly.Load("Xamarin.Forms.Xaml"),
                Assembly.Load("HelloForms"))
            .AddImports("Xamarin.Forms","HelloForms"));
    return result.ReturnValue;
}

実行時にいちいち書くのは面倒なので以下のコードをクリップボードにあらかじめコピーしといて

var page = new ContentPage().LoadFromXaml(@"<?xml version=""1.0"" encoding=""utf-8""?>
<ContentPage xmlns=""http://xamarin.com/schemas/2014/forms""
             xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
             xmlns:local=""clr-namespace:HelloForms""
             x:Class=""HelloForms.HelloFormsPage"">
    <StackLayout VerticalOptions=""Center"" HorizontalOptions=""Center"">
        <Label Text=""Hello World!!"" />
    </StackLayout>
</ContentPage>");

実行してあげれば

f:id:crocus7724:20161223191825g:plain

やりました!XAMLでHelloWorldに成功しました!!

今回は時間がなかったのでnew ContentPage().LoadFromXaml("{XAML}")インスタンス化しましたが、ちゃんとXAML用のエディタを用意してあげればいつもと同じような感じでアプリケーション上でXamarin.Formsを使ってMVVMが試せますね。凄い。補完がないので地獄ですが。

ようするに

Roslynです。 詳しくは以下が参考になります。

C#スクリプト実行 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

Roslyn for Scriptingで、あなたのアプリケーションにもC#スクリプトを!! – kekyoの丼

アプリケーションのマクロ的な言語にC#が使えます。
これでどこでもC#が書ける!ということで詳しく説明する記事を書こうと思ったのですが...断念。

理由の1つ目がNuGet。追加する前はほとんどない(実際Xamarin.Formsだけ)NuGetパッケージが

f:id:crocus7724:20161220003553p:plain

Microsoft.CodeAnalytics.CSharp.Scripting追加後・・・

f:id:crocus7724:20161220003626p:plain

こうなりました。すごい(小並感)

また、ScriptingはiOSでは使えません。シミュレータ動いてんじゃんと思うかもしれませんが、

f:id:crocus7724:20161221065702p:plain

ハイ。JITコンパイルじゃないと使えないらしいです(シミュレータはJITコンパイル、実機はAOTコンパイル)。なのでJITコンパイルが使えないiOSと、最近はAndroidもAOTですかね??まあモバイル勢はいろいろ制約がキツそうです。無念。

じゃあXamarin.Macは?と思うかもしれませんが、Target FrameworkがMobileだとSystem.Runtime.LoaderSystem.NotImplementedExceptionが出てScriptが実行できず、.NET4.5だとそもそもインストールできませんでした。

そもそもSystem.Runtime.Loaderが謎い。iPhoneシミュレータだとSystem.Runtime.Loaderがなくても実行できる(むしろあるとNotImplemented)のですが、iPhone実機で実行しようとするとコンパイルエラー。うーん、コンパイルの仕方で必要なアセンブリが変わるんですかね。。ここいらへんの仕組みがよくわかってないです。

というわけでXamarinだと今のところUWP?とAndroid(JIT)?とiPhoneシミュレータ上だけです。おしい、惜しい。
ただ前までは式木もできなかったらしいので今後Scriptingもできるようになることを期待してます。

以上、ネタでした。

明日の担当はnobukuma - Qiitaさんになります。よろしくお願いします!

MacでもRiderを使って快適にXamarinで開発しよう!!

これは

[学生さん・初心者さん大歓迎!]Xamarin Advent Calendar 2016 - Qiita

の7日目の記事です。

前日は

qiita.com

でした。
大学の講義でJavaやって2年目でServletその後Xamarinの流れとか完全に「自分かな?」と思いましたがこの話はまたいずれ。

今回はモバイルどころかIDEでほぼMac向けのお話ですがタイトルに(無理やり)Xamarinが入ったのでセーフ

祝!!Rider Public EAP Release!!

2016/11/22、RiderがPublic EAPになりました!!
長かった・・・

今回はそんなRiderの紹介です!

Riderとは

RiderはJetBrainsが絶賛開発中のクロスプラットフォームC# IDEです。詳しくは以下をお読み下さい。

Project Rider – 新しい C# IDE #jetbrainsrider | JetBrains ブログ

簡単に言うとJavaなどの開発環境でお馴染みのIntelliJ Platformで動作し、バックエンドではC#で書かれたReSharperが動作しています。

つまりは最強。

Riderの対応状況

最初にXamarinプロジェクトで使うに当たってのRiderの対応状況を見ていきたいと思います。現在のRiderの対応状況は以下のとおりです。

機能 対応状況 備考
プロジェクト読み込み UWPとかは無理(遠い未来対応するかも?)
ビルド 私はなぜかできるけどできない人もいる?
実行
デバッグ
コーディングスタイル 近いうちにきそう
XAML ハイライトが残念だけどReSharperと遜色ない補完
F# こんなに投票されてるのだから対応されないはずがない!
NuGet Xamarin Studioより高機能だけど若干怪しいところがある
ReSharperの機能
(補完やpostfixなど)
だいぶ対応したけど細かいところがまだ
IntelliJ Platformの機能
(Gitや各種プラグインなど)
普段使いには全く問題なく感じる

あくまで主観なので間違いがあったらコメントをぜひ。
コーディングスタイルは裏道がありそうですがそれは後述。

XamarinでRiderを使うにあたり、実行・デバッグはできません。なのでRider単体での開発は不可能です。
よってRiderでコーディング→Xamarin Studioでビルド・実行になります。

面倒くさいし切り替え忘れてXamarin Studioで編集してイライラする事もありますが、それでもRiderでコーディングすると圧倒的に書きやすいです。

始め方

そんな超有望なRiderをさっそくインストールしていきましょう!
インストール方法は2パターンあって www.jetbrains.com

からダウンロードしていつも通りインストールする方法とJetBrains Toolboxを使ってインストールする方法があります。Toolboxのダウンロードは以下からできます。

www.jetbrains.com

個人的には他のJetBrains製品を使っているならToolboxを利用したほうが楽だと思います。

初期設定

前回とほぼ変わりませんが初期設定について。

Riderを初めてインストールして起動するとおそらく以下の画面が表示されます。

f:id:crocus7724:20161127101303p:plain

前のバージョンから設定をインポートする?って聞かれますが初めてなのでデフォルトのままOK。
次はテーマを聞かれます。

f:id:crocus7724:20161127101441p:plain

個人的にダークテーマが好きなのでDarcula一択。
お次はエディターのカラーテーマです。左から独自、Visual Studio風(あくまで風)、IntelliJ風(というかそのまま)になります。お好きなものをどうぞ。

f:id:crocus7724:20161127104435p:plain

次にキーマップを選択できます。Visual Studio風とReSharperIntelliJ(macOS)から選択できます。

f:id:crocus7724:20161127104447p:plain

なおキーマップはあとで設定から下記も選べるようになります。

キーマップが選択し終わったらお次はDefault Pluginsです。私はいつも全部Enableのままにするのですが、もし「絶対使わねーよ!」という機能があれば無効にすれば起動が少し早くなるかも?

f:id:crocus7724:20161127104523p:plain

そして次におすすめっぽいPluginsがインストールできます。

f:id:crocus7724:20161127104513p:plain

IdeaVimは私は使ったことないのでなんとも言えません。
ReSharper UnityではデバッグまでできるっぽいのでそのXamarin版マダーと常々思っていますがくる気配はまだありません。
Heap Allocations Viewerは前にも書きましたが、あると便利だと思います。ただnewとかまで表示するのは正直邪魔なので表示・非表示の設定ができればなーと思います。
最後のPython Community Editionは・・・なんでしょう?Pythonスクリプトが書けるらしいのですが果たしてC# IDEであるRiderで使う機会あるの?

欲しいと思ったPluginをインストールしたら最後に

f:id:crocus7724:20161127104059p:plain

で初期設定は終了です。
それではStart using Riderを押しましょう!

f:id:crocus7724:20161127104217p:plain

これで今日からあなたもRiderを使い始めることができます。お疲れ様でした!!

機能

本当は機能をいろいろ紹介していきたいのですが確実に長文駄文になるので泣く泣く諦めます。。。が、せっかく調べたので2点紹介致します。

Postfix

よくコーディングをしていると途中まで式を書いて「これ変数にしたいな」とか「これをリターンしたいな」と思うことがあるのですが、そのときに役立つのが「Postfix」です。変数名やクラス名のあとで.を入力して対象の文字を入力してTabを押すか補完で出てきたものを選択すると展開します。

現在Riderで使えるPostfixは以下のとおりです。

入力 展開後 備考
Foo.var var foo = new Foo();
Foo.new new Foo()
foo.return return foo; 戻り値と型が一致してるとき
foo.yield yield return foo;
foo.throw throw foo;
Foo.typeof typeof(Foo) クラス
foo.null if(foo==null) { }
foo.notnull if(foo!=null) {}
foo.switch switch(foo) {}
foo.not !foo bool型
foo.if if(foo) {} bool型
foo.else if(!foo) {} bool型
foo.while while(foo) {} bool型
foo.lock lock(foo) {} 参照型
foo.using using(foo) {} IDispose型
task.await await task Task型
list.for for (var i = 0; i < list.Count; i++) {} コレクションや配列など
list.forr for (var i = list.Count - 1; i >= 0; i--) コレクションや配列など
list.foreach foreach (var item in list){ } コレクションや配列など
foo.parse int.Parse(foo) string型
foo.tryparse int.TryParse(foo, ) string型
foo.arg Method(foo) *
foo.cast ((object)foo) *
foo.par (foo) *
foo.prop Foo = foo; *
プロパティを生成してくれる
foo.field _foo = foo; *
フィールドを生成してくれる
foo.sel foo *
fooが選択状態になる
foo.to target = foo; *
まずToString()が出てくるのでescで消してからTab

結構あります。幾つかは私も初めて知りました。個人的にfoo.nameofがほしいのですがnameofはC#でも特別扱いっぽいので難しいのかも。

基本的に型が一致してないと補完に出てきませんが、いくつか(たとえばifとか)は補完に出てこなくてもTabを押せば展開されます。
また、*がついているやつは補完には出てきません。たまに展開に失敗するのでおそらく正式対応していないと思います。

自作Live Templates

ReSharperのLive Templateはマクロが使えてものすごく便利です。ただ、現時点では自分でLive TemplateをRiderで作成できません。(Live Templateの項目はありますが、言語にC#がなくXamarin StudioのTemplateに毛が生えた程度)

これについて先日行われた?中の人へのQ&Aにてサポートされることは確定なのですが、回答の中で

Current workaround: create a template in Visual Studio + ReSharper, save it to solution-wide .dotSettings file, and Rider will pick it up.

とあります。
なるほど、.dotSettingsファイルがあればLive Template自体は動くらしいです。
私はVisual Studioには詳しくはないのですが、とりあえずVisual Studio + ReSharperで下記のようなLive Templateを作成しました。

f:id:crocus7724:20161206021451p:plain

あとはReSharperのTemplateウィンドウにあるExportボタンをクリックするとUser.DotSettingsファイルが吐き出されます。

それをMacに持ってきたら適当なソリューションと同じ階層にUser.DotSettingsファイルを置いて{ソリューション名}.sln.DotSettingsにリネームすれば・・・

f:id:crocus7724:20161206121450g:plain

展開できました!!(若干バグってるけど)
これでBindable Propertyが爆速で書けます!

なお.DotSettingsファイルの置き場所がわからなくて右往左往してましたが、mdfindで.DotSettingsを検索したところクローン後放置して久しいXamarin.Formsリポジトリに.DotSettingsファイルがあり、それを参考にしました。ありがとうございます。

ちなみに.csprojも.slnも.storyboardも手で編集する昨今、「GUIで編集できないなら手動で編集すればいいじゃない」なノリで.DotSettingsファイルを開いてみたら

<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/@KeyIndexDefined">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Shortcut/@EntryValue">bindable</s:String>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Description/@EntryValue">Generate Bindable Property</s:String>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Text/@EntryValue">#region $name$ Bindable Property&#xD;
&#xD;
public static readonly BindableProperty $name$Property&#xD;
            = BindableProperty.Create(nameof($name$),typeof($type$),typeof($declearType$));&#xD;
&#xD;
public $type$ $name$&#xD;
{&#xD;
    get { return (($type$)GetValue($name$Property)); }&#xD;
    set { SetValue($name$Property, value); }&#xD;
}&#xD;
&#xD;
#endregion</s:String>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Reformat/@EntryValue">True</s:Boolean>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Field/=type/@KeyIndexDefined">True</s:Boolean>
    <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Field/=type/InitialRange/@EntryValue">1</s:Int64>
    <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Field/=type/Order/@EntryValue">0</s:Int64>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Field/=name/@KeyIndexDefined">True</s:Boolean>
    <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Field/=name/InitialRange/@EntryValue">2</s:Int64>
    <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Field/=name/Order/@EntryValue">1</s:Int64>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Field/=declearType/@KeyIndexDefined">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Field/=declearType/Expression/@EntryValue">typeName()</s:String>
    <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Field/=declearType/InitialRange/@EntryValue">-1</s:Int64>
    <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=02646611FBC11F46BCFB19BD6AE7495C/Field/=declearType/Order/@EntryValue">2</s:Int64></wpf:ResourceDictionary>

諦めました。

また、この.DotSettingsファイルはコーディング規則的なのを決められるみたいですが、こちらで簡単にやってみたところ変更できませんでした。ただ、私自身.DotSettingsを理解してないのでもうちょっと調べてみます。

→Xamarin.Formsリポジトリから.DotSettingsファイルをパクってきたところ、private修飾子をつけたら「いらねえよ」と言われるようになりましたなんでや!

f:id:crocus7724:20161206131042p:plain

Xamarin.Formsで開発するときは本家に合わせる意味でXamarin.Forms.sln.DotSettingsファイルをコピペしてもいいのではないでしょうか?

なお、Riderのyoutrackによると、RiderでのCodeStyle作成 のプロトタイプは既にできていて、次のEAPで利用できるようになるらしいです。(Live Templateは?)

次のEAPが待ち遠しいですね。

終わりに

以上、Riderの紹介でした。現在RiderはPublic EAPであり、正式リリースはまだまだ先です(2017年第2四半期になるらしい?)。また細かなところでバグいですが、現時点でもReSharperの恩恵を多大に受けることができます。ぜひ使ってみて下さい!!

なお学生の皆さんは今回出てきたReSharperIntelliJなどのJetBrains製品が在学期間中ずっと無料で使えます!
詳細は以下

www.jetbrains.com

人気の言語は一通り網羅しているので大学の講義などでプログラミングをやるときに使ってみてはいかがでしょうか?

明日の担当はmmmmmiya - Qiitaさんです。よろしくお願いします(((o(゚▽゚)o)))♡

RiderのHeap Allocations Viewerについて

コーディング規則をみていると「フィールドにアンスコつけるな!」「privateは冗長だ!」とReSharperにコーディング規則を支配された私には哀しみしかないのですが、ふと「全員がReSharperを使えば問題ないんじゃね」と思ったのでRiderを使ってもらうべく記事書きたいと思います。

というわけで次回のビルドからPublic EAP(になったらいいな)らしいですが、その前にRiderに新しいPluginができました。

f:id:crocus7724:20161113111108j:plain

ほとんどの人は真ん中のReSharper Unityに目が行くと思いますが、残念ながら私はUnityやってないので今回は一番右の紹介です。(まあHeap Allocations ViewerもどちらかというとUnity向け)

なお私はメモリ等については詳しくないので解説部分とかは軽く読み流してください

Heap Allocations Viewerとは

直訳すると「ヒープ割り当てビューア」(訳:Google先生)

ようするにヒープに割り当てられる処理をわかりやすく表示してくれる拡張機能です。

ヒープに割り当てられるとGCゴミが発生して場合によってはパフォーマンスに大きく影響するのでなるべく表示されなくしたいですね。

まあHeap Allocations Viewerを入れるとすぐわかるのですが、表示される例を出していきたいと思います。

new

一番わかりやすいヒープ確保する処理はnewです。つまり参照型のインスタンス作成。

f:id:crocus7724:20161113112014p:plain

なおintやdoubleなどの構造体はnewしても表示されません。まあ構造体はスタックなので当たり前ですが。

f:id:crocus7724:20161113112524p:plain

LINQ

次はみんな大好きLINQです。LINQはメソッドチェインで連続して処理を書けるのでとても見やすく書きやすいですが、実行性能は・・・どうなんでしょう? WhereやSelectなどよく使われるメソッドは最適化がかけられていると風のうわさで耳にしましたがよくわかりません。

f:id:crocus7724:20161113112856p:plain

Heap Allocations ViewerですとLINQメソッドというだけで表示されます。(ちなみにToList()やFirstOrDefault()などのよく終端で使われるメソッドは表示されません。)

まあLINQは繋げた分だけEnumeratorを撒き散らかしてこれがGCゴミになるとどこかで読んだ気がしますが出典不明なので「LINQはなるべく繋げないほうがいいよー」とだけ覚えておけばいいと思います。

デリゲート

デリゲートというかメソッドグループというかなんというか。ようするにメソッドの引数にメソッドを渡すやつです。 具体的に見てみましょう。

private int InvokeSampleMethod(Func<int,int> func)=>func.Invoke(10);

private int OnSampleMethod(int i) => ++i;

というメソッド(例なので全く意味のないメソッドです)に対して、

InvokeSampleMethod(x=>++i);
InvokeSampleMethod(OnSampleMethod);
InvokeSampleMethod(i=>OnSampleMethod(i));

という3つの書き方をします。メソッドの実行結果としてはほぼ同じですが、この中に1つだけHeap Allocations Viewerで表示されないものがあります。

それは・・・

f:id:crocus7724:20161113115508p:plain

です。どうやら正解は自己完結しているラムダ式の模様。詳しくは

neue cc - Unityでのボクシングの殺し方、或いはラムダ式における見えないnewの見極め方

をお読みください。

なおメソッドを直接引き渡すやりかたは他にイベントがあります。ということはイベントの購読も自己完結しているラムダ式で書くことで無駄なGCゴミを生成しないと思いきやラムダ式だとイベントの購読を解除できません!残念無念。

クロージャ

デリゲートやったのでついでによくあるパターンとしてこんなのがあると思います。

int i=0;

InvokeSampleMethod(x=>x*i);

これはHeap Allocations Viewerだとこうなります。

f:id:crocus7724:20161113131105p:plain

f:id:crocus7724:20161113131115p:plain

上の大先生のブログにも書いてありましたが、外の変数をキャプチャする場合、クラスが新しく作られるのでGCゴミが生まれる模様。ただこのパターンはよく見かけるというか、このパターンが便利すぎるので大人しく諦めましょう。しょせんクラス1個分(でもクラス1個分・・・)

見た感じ外の変数を取り込むほどクラスにフィールドが生成されるのでなるべく外の変数を取り込まないようにすると幸せになれるかもしれません。

可変長引数

他にHeap Allocations Viewerが表示される例として可変長引数(params)があります。 例としてDebug.WriteLine(string format, params object[] args)を書いてみると、

f:id:crocus7724:20161113122829p:plain

こうなります。

省略可能ですしnew object[]{"hoge","bar"}などと書かなくてよいので個人的には好きですが、結局配列を生成しちゃうので引数が少ない想定のときはオーバーロードで対応するのがスピードアップへの近道ですかね。(でも配列1個分なら別にゴニョゴニョ)

ボックス化

ところでさっきのスクショを見ていると、引数のところに赤いアンダーラインが引かれてると思います。

わざと赤いラインが引かれるように書いたのですが、objectの引数に構造体を渡すと表示されます。 ようするに構造体(Value Type)からobject(Reference Type)に変更されてせっかくスタックのはずがヒープになってしまってGCゴミガーということです。

ボックス化もパフォーマンスに大きく影響するので(なんとボックス化するのとしないのでは1億回ループを回したときに0.2秒の差がつく!らしい?)できるだけ避けていきたいですね。

他のボックス化の例として、構造体でGetType()を使ったり、自分で定義した構造体でToString()をオーバーライドしないで使ったり、””+1と書くとボックス化が発生します。

まとめ

以上、Riderの新しいプラグインの解説(?)でした。このプラグインを使ったからといって別にコーディングが楽になったりしないし、表示されるのを気にしてコードを複雑にするのは個人的にアンチパターンだと思います。ただ、チームでやっている場合、誰かがこれを使っていれば、もしかしたら無駄な処理に気付けるかもしれません。なのでMacC#やっている人は積極的に使ってみてほしいですね!Riderも一緒に!!

Xamarin SutdioでNuGetパッケージを簡単に作る

NuGetパッケージは依存関係を自動(?)で解消してくれたりリポジトリを検索してインストールしたりで利用する分には簡単(??)なのですが、いざ作成しようとすると.nuspecを編集したりコマンド叩かなきゃいけなかったりで結構めんどくさいです。

なので簡単にNuGetパッケージを作成するXamarin Studio Addinを作成しました。

なお簡単っていっても個人運用向けで、ちゃんと公開する場合はしっかり.nuspecを編集しないといけないので結局面倒くさいです。

使い方

一番簡単な使い方。

1
からdllをダウンロードして

/Applications/Xamarin Studio.app/Contents/Resources/lib/monodevelop/AddIns

とかに入れる。

2 Xamarin Studio起動してプロジェクト開く。

3 ソリューションダブルクリックしてDescriptionに適当な文字を入れる。(ビルドしてなかったらビルドする)

f:id:crocus7724:20161018193706p:plain

4 MainMenu→Edit→Make Nuspecで.nuspecファイル作成

f:id:crocus7724:20161018193908p:plain

5 MainMenu→Edit→Make PackageでNuGetパッケージ作成

f:id:crocus7724:20161018193948p:plain

もっと詳しく

一応、上記のやり方でパッケージは作れるのですが大抵残念仕様です。なのでしっかり.nuspecを編集します。

ソリューションに追加された.nuspecファイルを見てみましょう。

<?xml version="1.0" encoding="utf-8"?>
<package>
  <metadata>
    <id>$id$</id>
    <version>$version$</version>
    <authors>$author$</authors>
    <licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl>
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>$description$</description>
    <releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
    <copyright>$copyright$</copyright>
    <tags>Tag1 Tag2</tags>
    <dependencies>
      <dependency id="Xamarin.Forms" version="2.3.1.114" />
      <dependency id="Xamarin.Android.Support.Animated.Vector.Drawable" version="23.3.0" />
      <dependency id="Xamarin.Android.Support.Design" version="23.3.0" />
      <dependency id="Xamarin.Android.Support.v4" version="23.3.0" />
      <dependency id="Xamarin.Android.Support.v7.AppCompat" version="23.3.0" />
      <dependency id="Xamarin.Android.Support.v7.CardView" version="23.3.0" />
      <dependency id="Xamarin.Android.Support.v7.MediaRouter" version="23.3.0" />
      <dependency id="Xamarin.Android.Support.v7.RecyclerView" version="23.3.0" />
      <dependency id="Xamarin.Android.Support.Vector.Drawable" version="23.3.0" />
    </dependencies>
  </metadata>
  <files>
    <file src="../Sample/bin/$Configuration$/Sample.dll" target="lib/" />
    <file src="../Sample.iOS/bin/$Configuration$/Sample.iOS.dll" target="lib/" />
    <file src="../Sample.Android/bin/$Configuration$/Sample.Android.dll" target="lib/" />
  </files>
</package>

Xamarin Studioで開くとシンタックスハイライトが全く効かないのでつらいですが見てみると$id$とか$$で囲われたのがあると思います。 これはMake Packageを実行すると対応した値に変換されます。

以下がその対応表になります。

From To Description
$id$ solution.Name ソリューションの名前
$title$ solution.Name ソリューションの名前
$version$ solution.Version ソリューションの名前
$author$ solution.AuthorInformation.Name ソリューションの製作者情報の名前
$description$ solution.Description ソリューションの詳細
$copyright$ solution.AuthorInformation.Copyright ソリューションの製作者情報のコピーライト
$Configuration$ IdeApp.Workspace.ActiveConfigurationId 現在のコンフィグ(DebugとかReleaseとか)

ただし、$hoge$はdependenciesでは使えません。そこで使うことを想定してないので書くととても残念な事になります。

また、dependencyがいっぱいありますがこれは各プロジェクトのpackages.configの中身をそのまま持ってきた感じになります。

こんな感じ↓

private static IEnumerable<XElement> GetDependencies()
            => ProjectService.CurrentSolution.GetAllProjects()
                .Where(x => File.Exists(x.BaseDirectory.Combine("packages.config")))
                .SelectMany(x => XElement.Load(x.BaseDirectory.Combine("packages.config")).Elements("package"))
                .Select(x => new XElement("dependency",
                    new XAttribute("id", x.Attribute("id").Value),
                    new XAttribute("version", x.Attribute("version").Value)))
                .Distinct(x => x.Attribute("id")?.Value);

また、filesも同じくプロジェクトをごそっと持ってきた感じになります。 こんな感じ↓

private static IEnumerable<XElement> GetFiles()
            => ProjectService.CurrentSolution.GetAllProjects()
                .Select(x => new XElement("file",
                    new XAttribute("src",
                        Path.Combine("..",
                            x.GetOutputFileName(IdeApp.Workspace.ActiveConfiguration)
                                .ToRelative(ProjectService.CurrentSolution.BaseDirectory)).Replace(
                            IdeApp.Workspace.ActiveConfigurationId, "$Configuration$")),
                    new XAttribute("target", "lib/")));

テストプロジェクトがあった場合はfilesに含まれてしまったり、全部lib/なので全プラットフォーム対象になってしまったりなのでfileは要編集です。

そしてそれぞれよしなに編集した.nuspecファイルがこちらになります。

<?xml version="1.0" encoding="utf-8"?>
<package>
  <metadata>
    <id>$id$</id>
    <version>$version$</version>
    <authors>$author$</authors>
    <licenseUrl>https://opensource.org/licenses/MIT</licenseUrl>
    <projectUrl>https://github.com/crocus7724</projectUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>$description$</description>
    <releaseNotes>First Release.</releaseNotes>
    <copyright>$copyright$</copyright>
    <tags>Xamarin, Xamarin.Forms</tags>
    <dependencies>
      <dependency id="Xamarin.Forms" version="2.3.1.114" />
    </dependencies>
  </metadata>
  <files>
    <file src="../Sample/bin/$Configuration$/Sample.dll" target="lib/portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10" />
    <file src="../Sample.iOS/bin/$Configuration$/Sample.iOS.dll" target="lib/Xamarin.iOS10" />
    <file src="../Sample.Android/bin/$Configuration$/Sample.Android.dll" target="lib/MonoAndroid10" />
  </files>
</package>

.nuspecファイルを編集した場合は⌘+Sで保存してください。残念ながらオートセーブは未実装です。

また、一応申し訳程度の設定があります。

f:id:crocus7724:20161020020910p:plain

適当英語で申し訳ないのですが、

  • 出力先ディレクトリ(Defaultだと.slnと同じ階層とか)
  • Releaseビルドを使うか($Configuration$が常時ReleaseになりますDebugって直接書いてたら意味ないオプション)
  • Make Package前にビルドするか
  • 作ったパッケージをnuget pushするか

を設定できます。

ただ、Auto Publishをする場合は事前にTerminalでnuget setapikey <key> -source <url>をする必要があります。 今回はローカルのサーバーにDockerでNuGet ServerをApiKey=nugetPublish URL=http://192.168.1.11:5001で立ててそれを使ってみました。

この場合のAPIキーの設定はこんな感じ。

nuget setapikey nuget -source http://192.168.1.11:5001

それではMake Packageをしてみましょう。

f:id:crocus7724:20161020025746p:plain

上記のようにエラーっぽいメッセージがなければ成功です。 あとはNuGetのSourcesにURLを足して

f:id:crocus7724:20161020024151p:plain

NuGetパッケージマネージャーを見てみると

f:id:crocus7724:20161020025924p:plain

追加するとちゃんとクラスも表示されているので大成功です!(もっと特徴的な名前にすればよかった)

f:id:crocus7724:20161020023311p:plain

最後に

実はこれソースコードを見ればわかるのですが裏で結構がちゃがちゃやってます(だいたいnugetのせい)。

なので結構バギーです。もしよろしければ使ってみて動かなかった場合は@crocus7724までお知らせください。