回転寿司をVisual Studioに降臨させる – Visual Studio Advent Calendar 2016

この記事は、Visual Studio Advent Calendar 2016の4日目の記事です。

sushi_rotation_on_mbp少し前に、新しいMacBook Proで回転寿司が話題になりましたね。こんな奴です:

centerclr-sushirotatorこれが転じて、最終的にVisual Studioの拡張機能として、こんなものを作りました:

Visual Studioの拡張機能… 私は度々dis… もとい、非常に難易度が高いという話をしているのですが、この拡張機能の実装はそれほど難しくありません。とは言え、罠が至る所に待ち構えています。今回は、拡張機能を作るうえで躓いてツマラナイ思いをしないように、導入部分のガイドを記録に残しておこうと思います。

なお、この拡張機能はGitHubで公開しています: CenterCLR.SushiRotator
すぐに試してみたい方は: Visual Studio Market place、または拡張機能から「sushi」で検索すれば出てきます。

# やじうまの杜でも取り上げられました: MacのTouch Barが回転寿司に! エディターでも続々カイテン


事前に考える必要のある事

Visual Studioの拡張機能は、俗に「VSIX」と呼ばれています。これは、拡張機能のパッケージに「CenterCLR.SushiRotator.vsix」のような拡張子が付いていることでもわかります。
ややこしいのは、拡張機能のすべてにvsix拡張子がついているわけではなく、一般的なセットアップインストーラーだったり、msi形式だったする事もあることです。

VSIX形式は、Visual Studio(のVSIXInstaller.exe)を使ってインストールします。また、VSIXでは、Visual Studio Market placeからの自動更新に対応していて、ユーザーレベルでのインストールにも対応しているため、ユーザーからすると扱いやすいものになっています。例えば、拡張機能がGACへのインストールを必要としているなどの複雑な処理を行う場合は、VSIX形式とすることはできないため、msiや独自のインストーラーを作る必要があります。

今回は、要するにエディタを拡張するだけであり、エディタの拡張ならVSIXだけで完結するため、標準的なVSIX形式で作ります。

また、対象のVisual Studioを何にするかも考える必要があります。Visual Studioの拡張機能は、以下のエディション(以上)でサポートされます:

  • Visual Studio 2005 Professional
  • Visual Studio 2008 Professional
  • Visual Studio 2010 Professional
  • Visual Studio 2012 Professional
  • Visual Studio 2013 Community
  • Visual Studio 2015 Community
  • Visual Studio 2017RC Community

より古いバージョンに対応させようとすると、それだけ拡張可能な機能が制限されます(機能拡張に対応するインターフェイスが定義されていないなど)。

これ以前のバージョン(2003など)でも、基本的なインターフェイスは実装されています(それどころか、古のVisual InterDevやJ++でも)。しかし、これらのバージョンでの拡張は、マイクロソフトと特別な契約を結んだデベロッパだけが開発することが出来たため、ほとんどの皆さんは対応させることが出来ません(まあ実際には書いたら動くのかもしれませんが。それから、既にサポート対象外なので、実質無視して良いと思います)。

また、以下の制限もあります:

  • 各バージョンのExpress Editionは、拡張機能に対応していません。
  • VSIXパッケージをサポートするのは、Visual Studio 2010以上です。2010未満は、カスタムインストーラーが必要です。当然、Market placeでの自動更新はできません。
  • 今回対象とする「テキストエディタの拡張」は、Visual Studio 2010以上でのみサポートされます。これは、2010以降はVisual StudioがWPFで書き直され、WPFコントロールを使う前提となっていることが遠因となっていると思われます。
  • Visual Studio 2012以降は、後述のvsixmanifestファイルの形式が変更されたため、2010をサポートする場合は古い形式で記述する必要があります。
  • それぞれの拡張機能を実装するには、対応するVisual Studio SDKが必要です。例えば、2010の場合は、VSSDK2010が必要となります。これらは下位互換性があるため、普通は目指すバージョン群の最も高いバージョンに対応するSDKをインストールすれば、問題ありません。
  • 使用する.NET Frameworkのバージョンにも注意して下さい。対応させる最も古いVisual Studioで開発可能な、最後のバージョンを選択します。(大丈夫だと思いますが、.NET Coreは使えません、念のため)。

vs2017rc_install_vsix上記の事を考え、今回はVisual Studio 2012 Professional以上、つい先日リリースされた2017RCまでを対象とします。SDKは2017RCのものが必要ですが、これは2017RCのインストーラーで選択すればインストール出来ます。.NET Frameworkは4.5を使用します。

ややこしいのは、拡張機能の開発自体は、対応させる最も新しいVisual Studioを使う必要がある、と言うことです。したがって、2017RCを使用します。


拡張機能のプロジェクトテンプレート

vsix1VSIXの拡張機能を作るには、プロジェクトの新規作成から「Extensibility」にある「VSIX Project」でプロジェクトを作成します:

vsix2次に、プロジェクトにエディタ拡張を追加します。これも、Extensibility配下から選択します。いくつか候補がありますが、ここでは「Editor Margin」を選択します。

vsix3このEditor Marginが、テキストエディタの一部の領域を占有する事が出来る拡張です。これを使って寿司を流します。

これらのテンプレートですが、以前は本当にくぁwせdrftgyふじこlp… いや、色々と問題があった… のですが、2017RCでは非常にすっきりしたテンプレートとなっていて、扱いやすく修正されました。基本となるVSIX Projectでプロジェクトを作っておいて、そこに必要なitemとして後から追加する感じです。但し、拡張機能で実現可能な機能に対して、圧倒的にテンプレートが足りてません。大半は一から手でクラスを書いたりする必要があります。

# 何故かテキストエディタに対する拡張のテンプレートだけが色々あります… もちろん、本当はもっと多彩な事が出来ます。


Editor Margin

Editor Marginは、テキストエディタの一部領域が、拡張機能のために予約され、そこに自由に何かを表示させることが出来るVSIXの拡張機能です。この一部領域とはつまり、WPFのCanvasであり、まあ、あとはWPFで好きなようにやってよ、という感じです。Editor Marginクラスを加えると、以下のようなテンプレートコードが生成されます:
(Disposeの面倒を見るコードもあるんですが、長いので省略)

namespace VSIXProject1
{
    /// <summary>
    /// Margin's canvas and visual definition including both size and content
    /// </summary>
    internal class EditorMargin1 : Canvas, IWpfTextViewMargin
    {
        /// <summary>
        /// Margin name.
        /// </summary>
        public const string MarginName = "EditorMargin1";

        /// <summary>
        /// Initializes a new instance of the <see cref="EditorMargin1"/> class for a given <paramref name="textView"/>.
        /// </summary>
        /// <param name="textView">The <see cref="IWpfTextView"/> to attach the margin to.</param>
        public EditorMargin1(IWpfTextView textView)
        {
            this.Height = 20; // Margin height sufficient to have the label
            this.ClipToBounds = true;
            this.Background = new SolidColorBrush(Colors.LightGreen);

            // Add a green colored label that says "Hello EditorMargin1"
            var label = new Label
            {
                Background = new SolidColorBrush(Colors.LightGreen),
                Content = "Hello EditorMargin1",
            };

            this.Children.Add(label);
        }

        #region IWpfTextViewMargin
        /// <summary>
        /// Gets the <see cref="Sytem.Windows.FrameworkElement"/> that implements the visual representation of the margin.
        /// </summary>
        /// <exception cref="ObjectDisposedException">The margin is disposed.</exception>
        public FrameworkElement VisualElement
        {
            // Since this margin implements Canvas, this is the object which renders
            // the margin.
            get
            {
                this.ThrowIfDisposed();
                return this;
            }
        }
        #endregion

        #region ITextViewMargin
        /// <summary>
        /// Gets the size of the margin.
        /// </summary>
        /// <remarks>
        /// For a horizontal margin this is the height of the margin,
        /// since the width will be determined by the <see cref="ITextView"/>.
        /// For a vertical margin this is the width of the margin,
        /// since the height will be determined by the <see cref="ITextView"/>.
        /// </remarks>
        /// <exception cref="ObjectDisposedException">The margin is disposed.</exception>
        public double MarginSize
        {
            get
            {
                this.ThrowIfDisposed();

                // Since this is a horizontal margin, its width will be bound to the width of the text view.
                // Therefore, its size is its height.
                return this.ActualHeight;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the margin is enabled.
        /// </summary>
        /// <exception cref="ObjectDisposedException">The margin is disposed.</exception>
        public bool Enabled
        {
            get
            {
                this.ThrowIfDisposed();

                // The margin should always be enabled
                return true;
            }
        }

        /// <summary>
        /// Gets the <see cref="ITextViewMargin"/> with the given <paramref name="marginName"/> or null if no match is found
        /// </summary>
        /// <param name="marginName">The name of the <see cref="ITextViewMargin"/></param>
        /// <returns>The <see cref="ITextViewMargin"/> named <paramref name="marginName"/>, or null if no match is found.</returns>
        /// <remarks>
        /// A margin returns itself if it is passed its own name. If the name does not match and it is a container margin, it
        /// forwards the call to its children. Margin name comparisons are case-insensitive.
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="marginName"/> is null.</exception>
        public ITextViewMargin GetTextViewMargin(string marginName)
        {
            return string.Equals(marginName, EditorMargin1.MarginName, StringComparison.OrdinalIgnoreCase) ? this : null;
        }
        #endregion
    }
}

コンストラクタで渡される引数IWpfTextViewと、IWpfTextViewMarginというインターフェイスの実装が、拡張機能をサポートする要となっています。また、このクラスはCanvasクラスを継承していますね。サンプルの実装は、このCanvasにLabelを手動生成して追加しています。やる気になれば普通にXAMLで書いても動きそうな気配です(試してません)。

コンストラクタの引数は依存注入されるのでしょう(Visual StudioはMEFを使用しているので、MEF経由で渡されるようです)。(間違えました、以下で示すファクトリクラスから生成しています)注意すべき点としては、このクラスにはstaticフィールドを含めることが出来ない、と言う事です。含めた場合、拡張機能のロード時に失敗します(結構悩んだ。Visual Studioのバグかもしれない)。staticフィールドが欲しい場合は、別のクラスに定義して、そこを参照するようにします(回転寿司でもやっています)。

サンプルのコードはコンストラクタ内で、Heightを20pxに指定しています。Canvasなので、このサイズが表示領域を規定することになります。また、WPFは標準でクリッピングを行いません。手動描画した場合に描画がはみ出ると、エディタ領域が酷いことになってしまうため、ClipToBoundsをtrueとしているようです。はみ出す可能性がなく、パフォーマンスに問題がある拡張機能を実装するのであれば、これをfalseとしても良いでしょう(どんな拡張機能なのかは良くわからない…)。

まんまWPFなので、もちろんアニメーションも使えます。回転寿司では、寿司を流すのにWPFのアニメーションを使っています。なので、非常に滑らかに流れることがわかると思います(上のGIFアニメーションはガタガタですが、ぜひ実際にインストールして見て下さい)。


Editor Margin Factory

Editor Marginクラスは、ファクトリクラスとの関連付けで実行されます。ファクトリクラスはEditor Marginテンプレートを追加した際に、自動的に追加されているはずです。以下に例を示します:

namespace VSIXProject1
{
    /// <summary>
    /// Export a <see cref="IWpfTextViewMarginProvider"/>, which returns an instance of the margin for the editor to use.
    /// </summary>
    [Export(typeof(IWpfTextViewMarginProvider))]
    [Name(EditorMargin1.MarginName)]
    [Order(After = PredefinedMarginNames.HorizontalScrollBar)]  // Ensure that the margin occurs below the horizontal scrollbar
    [MarginContainer(PredefinedMarginNames.Bottom)]             // Set the container to the bottom of the editor window
    [ContentType("text")]                                       // Show this margin for all text-based types
    [TextViewRole(PredefinedTextViewRoles.Interactive)]
    internal sealed class EditorMargin1Factory : IWpfTextViewMarginProvider
    {
        #region IWpfTextViewMarginProvider
        /// <summary>
        /// Creates an <see cref="IWpfTextViewMargin"/> for the given <see cref="IWpfTextViewHost"/>.
        /// </summary>
        /// <param name="wpfTextViewHost">The <see cref="IWpfTextViewHost"/> for which to create the <see cref="IWpfTextViewMargin"/>.</param>
        /// <param name="marginContainer">The margin that will contain the newly-created margin.</param>
        /// <returns>The <see cref="IWpfTextViewMargin"/>.
        /// The value may be null if this <see cref="IWpfTextViewMarginProvider"/> does not participate for this context.
        /// </returns>
        public IWpfTextViewMargin CreateMargin(IWpfTextViewHost wpfTextViewHost, IWpfTextViewMargin marginContainer)
        {
            return new EditorMargin1(wpfTextViewHost.TextView);
        }
        #endregion
    }
}

初見殺し的なひどいコードですが、このクラスの属性から、Visual StudioがEditor Marginのコードを認識して、実行可能となります。いじるのは、色々わかってからの方が良いでしょう。


vsixmanifestファイル

VSIX Projectプロジェクトを生成すると、*.vsixmanifestというファイルも追加されます。このファイルが、VSIXをパッケージングするのに使われる定義体で、中身はXMLです。NuGetで言う所のnupkgに対応するnuspecファイルです。

<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
  <Metadata>
    <Identity Id="VSIXProject1.Kouji Matsui.86820e26-2ec6-496e-ad20-38a2ba88bf46" Version="1.0" Language="en-US" Publisher="Kouji Matsui" />
    <DisplayName>VSIXProject1</DisplayName>
    <Description>Empty VSIX Project.</Description>
  </Metadata>
  <Installation>
    <InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[15.0]" />
  </Installation>
  <Dependencies>
    <Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
  </Dependencies>
  <Prerequisites>
    <Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[15.0,16.0)" DisplayName="Visual Studio core editor" />
  </Prerequisites>
  <Assets>
    <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" />
  </Assets>
</PackageManifest>

XMLはこのような形式です(上で述べたように、2010に対応させるには異なる形式で記述する必要があります)。但し、SDKがインストールされていれば、ソリューションエクスプローラーからダブルクリックで開くと、以下のようにビジュアル編集出来ます:

vsix4パッケージをMarket placeで公開する場合は、Identity・DisplayName・Descriptionなどをきちんと埋めておきましょう。他にもIcon・PreviewImage・License・Tagsなどが指定出来ます。

vsix5また、重要な項目として、Dependenciesの.NET Frameworkのバージョンと、Prerequisitesの「Visual Studio core editor」のバージョンを合わせておく必要があります。

vsix6Version Rangeの指定は、数学で使うような、以上・未満の記号による指定を行います。バージョン11(2012)以上~16(2018?)未満の場合、”[11.0, 16.0)”のように、以上を角括弧、未満を丸括弧で示します。このとき、”[11.0, 15.0]”としてはいけません。これでは、2017のマイナーバージョンアップに対応出来なくなってしまいます(15(2017)「以下」なので、マイナーバージョンが上がると外れてしまう)。

InstallationTargetは複数定義できます。最初に検討した、対応させるVisual Studioのバージョンとエディションの組み合わせが複雑になるので、以下のように定義するとよいと思います:

  <!-- これは回転寿司で使っている定義です。
       InstalledByMsiはmsiでインストールするかどうか、AllUsersは昇格が必要かどうかです。 -->
  <Installation InstalledByMsi="false" AllUsers="false">
    <!-- VS2012 -->
    <InstallationTarget Id="Microsoft.VisualStudio.Pro" Version="[11.0,12.0)" />
    <!-- VS2013- -->
    <InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[12.0,16.0)" />
    <InstallationTarget Id="Microsoft.VisualStudio.Pro" Version="[12.0,16.0)" />
  </Installation>

いつも通りと言うか、一旦vsixmanifestを生成したら、後は手動でXMLを弄った方が早い感じがひしひしと…


VSIXをデバッグする

このプロジェクトをビルドすると、bin\Debug(またはbin\Release)に、*.vsixが生成されます。デバッグ実行すると、検証用のVisual Studioが別途起動し、そこにVSIXが自動登録されてデバッグ可能になります。以下はデバッグ実行した例です。右側が開発中のVisual Studio、左側がデバッグ実行で新たに起動した「実験的なインスタンス」と命名されているVisual Studioで、その上で寿司が流れています。

vsix7

Visual Studio 2015以降で特徴的な、WPFライブビュー用のツールバーも表示されていることがわかります。

ところで、デバッグしていると途中で動作がおかしくなったりすることがあります。特に、変更したコードが反映されていないなどです。これは、VSIXのインストールフォルダが何らかの問題で破損した場合に発生します。インストールフォルダはユーザー毎に作られます。場所は、「C:\Users\[ユーザー名]\AppData\Local\Microsoft\VisualStudio」のような場所です。この配下に、Visual Studioのバージョン番号毎にフォルダが作られ、その中にインストールされます。

デバッグ実行した場合も、このフォルダ配下にコピーされます。2017RCの場合、サブフォルダは「15.0_31113400Exp」です。最後の「Exp」が「実験的なインスタンス」に対応します。何か動きがおかしいと思ったら、このフォルダを丸ごと削除して再度ビルドしてみて下さい(Visual Studioは一旦終了させる必要があるかもしれません)。削除しても、次回起動時には再生成されます。なお、経験的には「わりとよく壊れます」。アッハイとかつぶやきながら、削除すると良いでしょう。

それから、最新のVisual Studio対応バージョンを上げる場合(例えば、2013対応のものを2017RCに対応させる)、デバッグ実行時に起動するdevenv.exeへのパスも更新することをお忘れなく。「実験的なインスタンス」へのデプロイは最新のVisual Studioに行われる(これはSDKの機能で行われる)のに、起動するVisual Studioが古いバージョンのままだと、VSIXがロードされないように見えて混乱します。この問題も忘れたころにハマるパターンです。

C#のプロジェクトは、デバッグ時の起動パスを*.csproj.userに保存します。通常、Gitでソースコード管理していると、このファイルは管理対象外とする(.gitignoreで除外)のですが、ここにパスが書かれているため、git cloneしてデバッグしようとして失敗するのも良くハマります。*.csprojに手動で定義を移しておくとベターですが、そうすると今度は*.csproj.userでいつの間にかオーバーライドされていてハマるという、非常に苦しい展開が待っています…

# オーバーライドを無視するか警告する属性が欲しい…


VSIXのNuGetと低いバージョンに対応させる話

2017RCのVSIX Projectでプロジェクトを作ると、VSIXで必要なライブラリが、NuGet経由で参照されます。このパッケージは「Microsoft.VSSDK.BuildTools」「Microsoft.VisualStudio.CoreUtility」「Microsoft.VisualStudio.Text.UI」などで公開されています。これらのパッケージが公開されていることを知ったのは今回の回転寿司によるんですが、昔はNuGetパッケージ化されていなかったため、VSIXを含むプロジェクトのビルドは、非常に面倒な問題を含んでいました(当然、CIするにはVisual Studio本体が必要だったり)。

今はこのように、VSIXテンプレートとは別体となったおかげで、ずいぶんやりやすくなったと思います。NuGet経由での参照となったのは把握していませんが、ごく最近のバージョンからの筈です。

が、このパッケージの最低バージョンが2013以降のようで、2012やそれ以前のバージョンには対応していません。もし、古いバージョンも対応させる予定であるなら、別のパッケージ「VSSDK.CoreUtility」が公開されているので、これを使うと良いと思います(StyleCop Analyzersのauthorですね)。NuGetで「VSSDK」で検索すると、細分化されたパッケージのリストが表示されるので、必要なものを追加します。但し、VSIXのビルドシステムだけは最新のパッケージを使う必要があります(vsixmanifestの最新のスキーマを認識可能にするため)。つまり、「Microsoft.VSSDK.BuildTools」は、最初に挙げたもの(の最新版)を使うことをお勧めします。

昔のVSIXテンプレートから生成したプロジェクトを保守している皆様… ご愁傷さまです。手動でゴリゴリcsprojを書き換える未来が待っております… (泣


まとめ

全体的なコードは、回転寿司を参考にして見て下さい。VSIXのパッケージをローカルデバッグ出来ていよいよ公開できるようになったら、Visual Studio Market placeで公開できます。公開は無料で可能です。公開プロセスは変更中のようで、現在は Visual Studio Team Onlineで使う拡張機能と統合したいのか、変に中途半端になっている感じです。Market placeからのリンクで申請しようとすると VSTO 向けの公開として扱われて、認証プロセスがどうのとか面倒な事になります。

vsix8「Visual Studio Gallery」と呼ばれているこっちから行き、中ほどにある「アップロード」をクリックすると確実です。アップロードする前に、vsixmanifestの内容を見直してください。また、バージョンアップする場合は、vsixmanifestのバージョン番号を更新することをお忘れなく。

一応、アップロードした時点で、パッケージングが正しいかどうかの検証は行ってくれるようです。なお、更新されたパッケージをアップロードすると、使用中のユーザーにはおなじみのように「拡張機能の更新があります」と通知されます。

何か面白い拡張機能作って放流してください :)
それでは。

NuGetでビルドプロセスに介入するパッケージを作る

NuGetNuGetを使うと、サードパーティ(MSではない)アセンブリの導入が簡単に行えます。他にも、MSBuildのビルドプロセスをカスタマイズ出来るパッケージを作る事も出来ます。Visual Studioはビルド時にMSBuildを使っているので、Visual Studio上でも、MSBuildによるコマンドラインのビルドでも、シームレスにビルドプロセスをカスタマイズ出来ます。

例えば、C#やF#のソースコードをコンパイルする直前に、カスタムコードを自動生成させたりする事ができます。このようなスクリプトをNuGetパッケージ化すると、配布と導入が非常に簡単になり、アンインストールも簡単で、多くの人に気軽に使って貰えるようになります。

# MSBuildを介さない操作のカスタマイズは出来ません。例えばVisual Studio自体のカスタマイズは、VSIX拡張(VSSDK)を使いますがここでは触れません。

この記事では、どうやって最小限の労力でNuGetパッケージを作るのかのポイントを示します。NuGetのパッケージを作ったことがある方が、より理解しやすいでしょう。


MSBuildとtargetsスクリプト

MSBuildは、*.csprojのようなプロジェクトファイルを入力として、一種の宣言ベーススクリプト処理を行います。中身を見たことがある人が多いと思いますが、XMLで記述します。大まかには、以下のような構造になっています(一部のみ抜粋)。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <ProjectGuid>{3FCFCD0E-B40D-4A48-BF4C-85D552E46471}</ProjectGuid>
    <OutputType>Library</OutputType>
    <RootNamespace>CenterCLR.SampleCode</RootNamespace>
    <AssemblyName>CenterCLR.SampleCode</AssemblyName>
    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
    <!-- ... -->
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\Debug\</OutputPath>
    <!-- ... -->
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <!-- ... -->
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="Microsoft.CSharp" />
    <!-- ... -->
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <!-- ... -->
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

場合によってはもっと複雑なスクリプトになっている事もあります。MSBuildはこのXMLを読み取って、ビルド対象の情報を取得しています。例えば、PropertyGroupタグ内の定義は、ビルドに必要な各種定義(.NET Frameworkのターゲットバージョンや、アセンブリ名など)が含まれています。この定義は一種の変数のように振る舞い、スクリプト内からこれらの名前で参照する事ができます。

ざっと眺めると、Referenceタグでアセンブリ参照を定義し、Compileタグでコンパイル対象のファイルを定義しているように見えますね。

この記事で一番のポイントは、「Import」タグです。このタグのProject属性には、「*.targets」ファイルへのパスが示されています。上記の例ではImportタグは二か所ありますが、例えば最後のタグでは「$(MSBuildToolsPath)\Microsoft.CSharp.targets」を指定していますね。「$(MSBuildToolsPath)」がPropertyGroupなどで定義された値を参照する構文で、このフォルダ配下の「Microsoft.CSharp.targets」というファイルが指定されています。

このImportタグは、この場所にここで示されたファイルの定義をインポートするという定義です。つまり、「Microsoft.CSharp.targets」ファイルを読み取り、ここに挿入すると考えてください。丁度C言語のincludeのように機能します(XMLなので実際は違いますが)。

Microsoft.Common.propsやMicrosoft.CSharp.targetsファイルには、非常に多くの定義が含まれています。全てMSBuildのスクリプトです。Microsoft.Common.propsには、先ほどのMSBuildToolsPathのような共通のPropertyGroup定義が含まれているため、ほぼすべてのプロジェクトファイルはこのファイルをImportします。

Microsoft.CSharp.targetsはC#のビルドに必要な定義が含まれています。これには、*.csのコンパイルだけではなく、*.resxの変換やXAMLのコンパイル定義なども含まれます。F#のような他の言語では、「Microsoft.FSharp.Targets」のような感じで、その言語に合わせて変えています。

カスタムなスクリプトを直接プロジェクトファイルに書くことも出来るのですが、別のファイルにカスタムスクリプトを書いて、それをImportした方が、プロジェクトファイルを損傷させる可能性が減る(XMLなので容易に構造を壊してしまう可能性がある)ため、ベターです。基本的な方針は、この「カスタムtargetsスクリプト」を用意することと、これをどうやって既存のプロジェクトファイルに「自動的に挿入(Importタグを追加)」させるのかと言う事になります。

Visual Studio 2010未満の環境では、VSIX拡張を使って自動的に挿入させることができました(この手法は今でも使えます)。しかし、VSIX拡張は作ったり保守したりするのが非常に困難なため、お勧めしません。また、VSIX拡張を使ってしまうと、ビルドにdevenv.exe(つまりVisual Studio本体)が必要になってしまい、MSBuildによる継続インテグレーションが出来なかったり自由度が失われてしまいます。

# わかりやすい例として、AppVeyorのようなクラウドベースのビルドエージェントで継続インテグレーションが困難です(不可能ではありませんが、事前インストールと自動構成が必要)。

NuGetでビルドプロセスに介入する流れ

Visual Studio 2010からNuGetが使えるようになり、このtargetsスクリプトをNuGetで配布する事が出来るようになりました。どういう事かと言うと:

  1. NuGetパッケージに、targetsスクリプトとその他必要なファイル群を加える
  2. NuGetパッケージを配布する
  3. NuGetパッケージをプロジェクトに導入すると、プロジェクトファイルに自動的にImportタグが追加され、targetsの定義が利用可能になる

という流れです。但し、単にtargetsスクリプトを含めれば良い訳ではありません。以下の条件が存在します。

  • パッケージ内に「build」フォルダを追加し、その直下にtargetsスクリプトを配置する。
    パッケージを作ったことがある方なら分かると思いますが、通常ライブラリアセンブリを配布する場合は「lib」フォルダ配下に「net40」「net20」のようなプラットフォームターゲット名のフォルダを作り、その中にアセンブリファイルを配置します。しかし、targetsスクリプトファイルは必ずbuildフォルダ配下に配置しなければなりません。
  • targetsスクリプトファイル名は、必ず「[NuGetパッケージID].targets」でなければならない。
    例えばパッケージIDが「CenterCLR.RelaxVersioner」の場合は、「build/CenterCLR.RelaxVersioner.targets」に配置します。名前が違っていると、パッケージの生成には成功して配布も出来るのに、導入時に「ターゲットプラットフォームが存在しない」という不可解なエラーで失敗します。
  • targetsスクリプトで使用する追加のファイル群がある場合は、それらもbuildフォルダ内に全て配置する。
    targetsスクリプトからファイルを相対参照する場合、起点はbuildフォルダになります。どうしても別フォルダにしたい場合でも一旦buildフォルダに配置して、参照できることを確認してから移動した方が良いでしょう。

スクリプト実行レベルでの挿入や乗っ取りの方法

さて、これで物理的なパッケージングの枠組みは出来たのですが、実際カスタムスクリプトはどうやって書けば良いのでしょうか?

MSBuildのカスタムスクリプトは、これだけで膨大なテーマであるので、全部網羅的に調べるのは大変です。そこで以下のポイントを知っておけば、取り掛かりが楽になると思います。

  • PropertyGroupタグ内には、参照可能な定義を含める(これは前述で説明しました)。
    定義した値は、「$(Name)」という記法で参照出来ます。
  • ItemGroupタグ内には、操作対象のファイルなどを指定させる。
    「Compile」タグはコンパイル対象を指定させますが、これは「@(Compile)」という記法で参照できます。当然これらは複数定義されることがあるため、@(Compile)の評価結果はコレクション(複数)となります。
  • Condition属性に式を書くことで、そのタグがMSBuildの評価の対象になるかどうかが決定されます。
    csprojの例を見るとわかりますが、デバッグビルドやプラットフォームの識別を式で判定して、それぞれに定義されるPropertyGroupを変えたり、と言う事が出来ます。これはPropertyGroupだけではなく、ほぼすべてのタグで使用できます。
  • PropertyGroupやItemGroup内の定義は、串刺し的に評価される。
    例えば、PropertyGroupが何個定義されていても構わず、それらすべての定義が一つのPropertyGroupに定義されているのと同じように評価されます。この機能を利用して、カスタムスクリプト内で自由に定義を追加したり、Condition属性に応じて定義するかしないかを切り替えたりと言ったハックが実現出来ます。

余談ですが、「Debug|AnyCPU」という値の判定には、意味ありげに演算子っぽい「|」を使っていますが、これ、文字列としてただ連結しているだけです(ConfigurationとPlatformの値が結合して別の値と誤認しないようにするためと思われる)。

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\Debug\</OutputPath>
    <!-- ... -->
  </PropertyGroup>

and演算子が使えるにもかかわらず、このような「ダーティハック的な記述」が至る所にあるため、それらに惑わされれないように注意しましょう :)

このような前提で、まずは「Microsoft.Common.props」や「Microsoft.CSharp.targets」ファイルを眺めてみて下さい。これらのファイルは、「C:\Program Files (x86)\MSBuild\12.0」や、「C:\Windows\Microsoft.NET\Framework\v4.0.30319」配下にあります。

標準のMSBuildスクリプト群は非常に巧妙で複雑なのですが、ビルドを実現する戦略として以下のような手法を使っているようです。

Targetタグを論理的にネストさせて、ビルドシーケンスに階層構造を構築する

以下はMicrosoft.Common.targetsの一部です。

  <PropertyGroup>
    <BuildDependsOn>
      BeforeBuild;
      CoreBuild;
      AfterBuild
    </BuildDependsOn>
  </PropertyGroup>
  <Target
      Name="Build"
      Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
      DependsOnTargets="$(BuildDependsOn)"
      Returns="$(TargetPath)" />

この定義は、「Targetタグ」によって「Build」という名前の「ターゲット」を定義しています。この名前は、MSBuildのコマンドラインオプションで指定することができます。ビルドする場合は、ここからスクリプトの評価が開始されると思って下さい。ただし、このTargetタグは空タグなので、このターゲット自体は何も実行しません。その代わり、「DependsOnTargets属性」で参照されるサブターゲットを評価します。

DependsOnTargets属性は、「BuildDependsOn」の値が参照されています。前半のPropertyGroup内に定義されている、「BeforeBuild;CoreBuild;AfterBuild」が対象です。改行と空白は無視し、セミコロンで区切られます。

このTargetタグの後、更に別のTargetタグによって「BeforeBuild」「CoreBuild」「AfterBuild」それぞれのターゲットが定義されています。このリストは、まずBeforeBuildの定義が評価され、次にCoreBuildの定義が評価され、最後にAfterBuildの定義が評価される、というように、リストに書かれた順に従って、サブターゲットが評価されることになります。

CoreBuildの定義は後で示しますが、BeforeBuildとAfterBuildはそれぞれ以下のように定義されています。

<Target Name="BeforeBuild"/>
<Target Name="AfterBuild"/>

要するに空定義なので何も実行しませんが、このターゲット名、何か見覚えがありませんか? 実は、csprojのデフォルトのテンプレートには、BeforeBuildとAfterBuildのひな型が含まれています。再度抜粋します:

  <!-- ... -->

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
     // 必要ならここにカスタムの事前実行スクリプトを書く
  </Target>
  <Target Name="AfterBuild">
     // 必要ならここにカスタムの事後実行スクリプトを書く
  </Target>
  -->
</Project>

つまり、csprojでこのタグのコメントアウトを外してここに何か書くと、BeforeBuildとAfterBuildの内容をオーバーライドできると言う事です。こういうカラクリで、プロジェクトファイルにカスタムスクリプトを記述出来るようになっています。

さて、CoreBuildの定義を見ると:

  <PropertyGroup>
    <CoreBuildDependsOn>
      BuildOnlySettings;
      PrepareForBuild;
      PreBuildEvent;
      ResolveReferences;
      PrepareResources;
      ResolveKeySource;
      Compile;
      ExportWindowsMDFile;
      UnmanagedUnregistration;
      GenerateSerializationAssemblies;
      CreateSatelliteAssemblies;
      GenerateManifests;
      GetTargetPath;
      PrepareForRun;
      UnmanagedRegistration;
      IncrementalClean;
      PostBuildEvent
    </CoreBuildDependsOn>
  </PropertyGroup>
  <Target
      Name="CoreBuild"
      DependsOnTargets="$(CoreBuildDependsOn)">

    <OnError ExecuteTargets="_TimeStampAfterCompile;PostBuildEvent" Condition="'$(RunPostBuildEvent)'=='Always' or '$(RunPostBuildEvent)'=='OnOutputUpdated'"/>
    <OnError ExecuteTargets="_CleanRecordFileWrites"/>
  </Target>

Buildとおなじような構造の定義になっています(細かい相違は省略)。CoreBuildもまた空タグなので、このターゲット自体は何も実行しません。そしてDependsOnTargets値は「CoreBuildDependsOn」を参照し、今度は更に細かくサブターゲット群が定義されています。このリストそれぞれに対応するターゲット定義が、この後のスクリプトに全て含まれていて、この順にTargetタグが評価されます。このように、ネストしたサブターゲットを再帰的に探索してビルドを実現しているのです。

名前からして分かりやすそうな「Compile」というターゲットは、各処理系(C#やF#)のスクリプトに定義されているTarget定義に処理が移譲され、更に細かくターゲットがネストし、最終的にはC#やF#のコンパイラを起動してコンパイルを行っています。

さて、これでどうやってビルドプロセスに介入したら良いか分かってきましたね? 必要となるタイミングに対応するTargetタグを調べて(実はここが大変)、そのターゲットと同じ名前でオーバーライドしてしまうか、あるいは「*DependsOn」をオーバーライドして独自のターゲット名のリストに入れ替え、そこに自分のターゲット名を挿入しておいたり、標準のターゲットの代わりに自分のターゲットを実行させるように入れ替えたりすれば良い訳です。


ビルドプロセスに介入する例

CenterCLR.RelaxVersioner.128私が取り組んでいるプロジェクトに、RelaxVersionerというパッケージがあります。これは、Gitを前提として、バージョン番号の埋め込みなどを自動的に行うNuGetベースのパッケージです。

# バージョン番号を簡単に自動的に埋め込むには、何のツールが良いかとか、悩んでいる人多いですよね? ね??
# フィードバックやPR貰えるとやる気が出ます、よろしく :)

デザインの指針は、

  • とにかく簡単(ほぼパッケージをインストールするだけ)
  • ビルドの副作用による影響なし
  • 独自のUIを作らない。コントロールはすべてGitの標準機能(ブランチ・タグ・メッセージ)を使って実現する
  • 約束事を最小にする
  • 新たな学習コストを最小にする
  • Gitのリポジトリを汚さない(バージョン番号の変更を明示的にステージングから除外したりしなくても良い)
  • 継続インテグレーションにもそのまま使用可能
  • 必要であればカスタマイズ可能

というところを目指しています。現状で大体実現出来ています(mono対応が出来てない。コードは書いたけどうまく動かず… 誰か助けてー)。使い方はGitHubの方を見てもらうとして、とにかく導入が簡単で影響を最小化したかったので、この記事で説明したような、NuGetによる導入で即使用可能というところが重要でした。

やっていることは、各ソースファイルのコンパイル直前に、Gitのローカルリポジトリから吸い上げた情報を元に、AssemblyVersion属性を含んだソースファイルをテンポラリフォルダに自動生成して、コンパイルの対象に含める、という内容です。こんなコードが自動的に生成されてコンパイルされます:

[assembly: AssemblyVersion("0.5.30.0")]
[assembly: AssemblyFileVersion("2016.1.15.41306")]
[assembly: AssemblyInformationalVersion("a05ab9fc87b22234596f4ddd43136e9e526ebb90")]
[assembly: AssemblyMetadata("Build","Fri, 15 Jan 2016 13:56:53 GMT")]
[assembly: AssemblyMetadata("Branch","master")]
[assembly: AssemblyMetadata("Tags","0.5.30")]
[assembly: AssemblyMetadata("Author","Kouji Matsui <k@kekyo.net>")]
[assembly: AssemblyMetadata("Committer","Kouji Matsui <k@kekyo.net>")]
[assembly: AssemblyMetadata("Message","Fixed tab")]

細かいコードは除外して、ポイントだけに絞ったtargetsスクリプトはこのようなものです(オリジナルはココ):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <!-- Common -->
    <PropertyGroup>
        <RelaxVersionerOutputPath Condition="'$(RelaxVersionerOutputPath)' == ''">$([System.IO.Path]::Combine('$(ProjectDir)','$(IntermediateOutputPath)','RelaxVersioner$(DefaultLanguageSourceExtension)'))</RelaxVersionerOutputPath>
        <CoreBuildDependsOn>
            RelaxVersionerBuild;
            $(CoreBuildDependsOn);
        </CoreBuildDependsOn>
    </PropertyGroup>

    <!-- Build definition -->
    <Target Name="RelaxVersionerBuild" BeforeTargets="BeforeCompile" Outputs="$(RelaxVersionerOutputPath)">
        <Exec
            Command="CenterCLR.RelaxVersioner.exe &quot;$(SolutionDir.TrimEnd('\\'))&quot; &quot;$(ProjectDir.TrimEnd('\\'))&quot; &quot;$(RelaxVersionerOutputPath)&quot; &quot;$(TargetFrameworkVersion)&quot; &quot;$(TargetFrameworkProfile)&quot; &quot;$(Language)&quot;"
            Outputs="$(RelaxVersionerOutputPath)"
            WorkingDirectory="$(MSBuildThisFileDirectory)" />
        <ItemGroup>
            <Compile Include="$(RelaxVersionerOutputPath)" />
        </ItemGroup>
    </Target>
</Project>

RelaxVersionerOutputPathは、中間ファイルフォルダの配下にソースファイルを配置するためのパスを示します。Conditionはパスのカスタマイズの余地を残すためです。このパスは、ほとんどの場合、プロジェクトフォルダ配下の「obj\Debug\RelaxVersioner.cs」のようなパスとなります。

そして、CoreBuildDependsOnをオーバーライドし、この直前にRelaxVersionerBuildターゲットを実行するように再定義します。リストを丸ごと置き換えると、標準targetsスクリプトが更新された時におかしくなってしまう可能性があるので、元の定義を生かしたまま挿入するハックです。

# CoreBuildではなく、もっと深いコンパイルに近い位置でのターゲットでも良いかもしれません。どこの位置が適切なのかが難しいところですが、RelaxVersionerはマルチ言語対応の予定があり、言語毎に細分化されたターゲットに挿入すると意味がないので、CoreBuildを選択しています。

実際にソースコードを生成するのは、「CenterCLR.RelaxVersioner.exe」という実行ファイルで、これはNuGetパッケージのbuildフォルダに一緒に含めておきます。もちろん、ほかに必要なアセンブリも全て含める必要があります。RelaxVersionerはlibgit2sharpを使っているので、これらも配置してあります。

# MSBuild Taskではなく実行ファイルであるのは… MSBuild Taskはデバッグとかバージョンとかつらいんです。後でTask化にトライする予定。

コマンドラインにソースファイルのパスや、対象の言語名、.NET Frameworkのバージョン情報などを渡して、生成するソースコードの内容を変えています。最後に、生成したソースコードがコンパイルのターゲットとなるように、Compileタグで追加しておきます。


補足

.NET Core環境は、現在のところMSBuildを使用しないことになっているために、この記事の手法が使えません。ご存知の通り.NET Coreでは「project.json」がプロジェクト定義となっているのですが、これはdotnetコマンドが解釈を行います。

また、.NET Core toolsetをインストールしたVisual Studioのテンプレートから.NET Coreのプロジェクトを作った場合、project.jsonの他に*.xprojが生成されます。このxprojはMSBuildのプロジェクトスクリプトなのですが、NuGetの管理を行わない(NuGetの管理はproject.jsonで行われるため、結局Importタグが埋め込まれない)ので、やはり機能しません。

# ややこしいことになっていますが、VSでビルドした場合に、xproj (MSBuild) –> DNX Task (MSBuild) –> dotnet (project.json) –> NuGetting & Build というように処理が移譲されるようです(あまり細かく追っていないので、間違っているかもしれません)

.NET Coreはプロジェクト定義をproject.jsonベースに移行する事を前提として作業していたのですが、RTMのリリース直前になってこれがキャンセルされ、MSBuildをマルチプラットフォームに移植するという方針に変わりました。

しかし、結局RTMには移植が間に合わず、project.jsonベースでNuGetを管理する状態のままリリースされてしまいました。従って、まだしばらくは.NET Coreでのスムーズなビルドのカスタマイズは出来ないと言う事になりますが、MSBuildが移植される方針なので、将来的に同じ手法でビルドプロセスをコントロール出来そうです。

mono環境ではMSBuildのmono実装である「XBuild」が使えます。サブセットであるので一部互換性がありませんが、概ね同じような手法が使えると思います。モウコレデイインヂャナイノ?

余談

度々見るのと、私自身も思っているのですが、C#/F#/VB.netなど、複数の言語を一つのプロジェクト内でまとめて扱えるようにして欲しいという要望があります。しかし、現在のMSBuildのtargetsスクリプト群を見てわかる通り、これを実現するには「標準のtargets全部書き直し」しか無いですね。.NET Coreのxprojを見ていると、ひょっとしたらこれを進めてproject.jsonだけどうにかすれば出来るんじゃないか?と思ったんですが、.NET Coreチームはxprojもcsprojに戻すことを選択したようで、がっかりです。

# まあ、なんか、何でそこだけjsonなんだよ、分離したければどっちかに統一すれば良いのに、と思った。あと「project.json」っていう命名も誤解の元。
# それに、targetsがまともに機能しなくなるのが分かってた筈なのに、何で再考しなかったんだというのも… 互換性無くなるなら無くなるで、一部だけどうにかするんじゃなくて全体から(つまりMSBuild自体)見直して欲しかった。

MSBuild、複雑すぎます。何故こうなった感が大きいです。初期導入が古いとは言え、最初から不満でした。まず、階層構造をタグの論理的なネストで実現したのも、正直言ってXMLで処理系作ってその枠組みで無理やり実現しました感があるし、既存のスクリプトを壊さないように処理を追加したり交換したりといった事が簡単に出来ないし、targetsスクリプトを追って行くのがとにかく大変で、この記事のようにシンプルにまとめ上げれるようになるまでにかなり時間が掛かりました。こんな構造だから、拡張のためのエコシステムが発展しないんだと思う…

仕事でVSIX拡張を触っているのですが、VSIXの難解なインターフェイスと、MSBuildの複雑構造と、NuGetのどうしようもなく整理されていない感が組み合わさって、出自が違うので仕方ないとは言え本当につらい。この記事は備忘録も兼ねて書いたのですが、少しでもお役に立てれたら嬉しいです。

Regional Scrum Gathering Tokyo 2016に参加してきました

WP_20160118_19_04_21_Pro_LI「Regional Scrum Gathering Tokyo 2016」は、ソフトウェア開発手法として知られる「スクラム(Scrum)」に興味のある方、この手法を適用している組織の方、トレーナーの方などが集まる大きなイベントです(一般の方も参加できます)。

私は認定スクラムマスターを2年ほど前に取っていますが、1年前のRSGTには参加できず、今回の参加を非常に楽しみにしていました。セッションの概要や内容はリンク先を参照してもらうとして、参加者の視点で、内容をちょっと紹介したいと思います。

セッションスケジューラーはこちら。

どのセッションも興味深く面白い内容だったのですが、去年末にCSPOを取ったことで、全体を通してちょっと見方が変わったかなという、自分自身への印象がありました。とても全部紹介できないので、勝手視点で勘弁してください。本当にどのセッションも素晴らしかったです。


「生き延びよう!強い組織になろう! – 迷わず行けよ 行けばわかるさ」(Takahiro Kaiharaさん)

スクラムマスターの役割を一人で背負いすぎて折れてしまったという点が、本当に共感できました。これはつらい。つらい、と言うだけで、物事はどんどん悪い方向に転がっていきます。アジャイルソフトウェア開発やスクラムの導入を検討している方は、一人で抱え込まずに協力してくれる人を探して「みんなでやる」と言うのが重要だと思います。

WP_20160118_14_12_25_Pro_LIスクラムによって(いや、本当はスクラムによらず)目指すことは、スクラムなどのソフトウェア開発手法を適用することではなく(それは手段)、企業がどんな価値を提供できるのかだと思っています。

それはすごく大きな問題なのですが、それを「ひとり」で実現するのは、本当につらい事です。このRGSTや他のカンファレンス・勉強会、場合によっては外部コーチの助けを借りよう。ぼっちいかん (´Д`)

まだまだ課題はあるけれども、「学習する組織へ」「組織の文化へ」と、粛々と物事を進めていくのが大切だという話で結ばれていました。

カンファレンス終わった後で少しお話しさせて頂いたのですが、物腰柔らかい方で好印象でしたが、本人は超熱い方だと自負していました。分かる気がしますw


スクラムパタン入門 / Intro to Scrum Patterns(Kiro Haradaさん)

スクラムの共有知として、パタン(パターン / Pattern Language of Programs / PLoP)を適用しよう、それを今整理中ですという話です。パタンとは何か?という話から、何故スクラムの取り組みをパタン化する必要があったのかという経緯を分かりやすく解説していました。スクラムパタンはWikiで編集中で、パタンについてのアイデアがある方は、是非参加して欲しいとの事。最低レギュレーションは「そのパタンを3回適用して成功した」だそうです。

スクラム提唱者の一人「Jeff Sutherland」は、スクラムがこの世界に広まってからもう随分と時間が経っていて、新しい考えやアイデアをもっと取り入れて整理したいと考えています。スクラム自身のカイゼンですね。良い事だと思います。

一方、私はパタン(特にデザインパターン)について懐疑的だったのですが、その理由が「コンテキスト(文脈)」をはっきりさせた状況でないとパタンは有効に機能しない、という、ある意味当たり前の事に気が付いていなかった(無意識では知っていたので、何となく避けていて、いつもパタンに対してモヤモヤしていた)。それをサラッと明らかにして貰えただけで感動でした。

WP_20160118_16_24_10_Pro_LIパタンの使い方:

  • ある状況における問題を解決できる。(状況=文脈)
  • 結果として現れる状況が、新たな問題を生むかもしれない。
  • 新たに生まれた問題には、別のパタンを使う。

この前後関係(何かしかの想定される問題・パタン適用によって何が解決され、副作用としてどんな問題が想定されそうか)というのは、スクラムガイドにも殆ど載っていないため、主に書籍やセミナー・勉強会で経験知として積むしかないかなと思ってた(+パタン懐疑)のですが、パタン化によってデベロッパの事が直接分からなくても意思疎通ができるようになるのであれば、これは良い道具になるなと思いました。もっとも、このような言語化の難しい課題なので、作業は大変困難であるようです。ダメパタン案や置き換え案は、(書いてあるメモを)たき火にくべてサヨナラしたというエピソードが良かった。


Customer Expectations Management of Scrum スクラムにおける事前期待のマネジメント(Mitsunori Sekiさん)

Sekiさんは、同じMSMVP(カテゴリ違い)で、このセッションはプロダクトオーナーよりの話です。そもそも私は自分の会社はあるもののデベロッパー寄りなので、CSPO(認定スクラムプロダクトオーナー)を受けたら、今まで考えてたつもりになってたPOや企業の事業とはどんなものかの片鱗みたいなのを味わったという経緯があります。そんなわけで、このセッションも楽しみにしていたのです。

スライドはこちら。内容は、「サービス」やそれに対する「事前期待」とは何か?そしてそれをスクラムに当てはめた場合にどうなるのか?という話です。特にいきなり「サービスサイエンス」なる言葉も初耳で、そんな分野があるのかというのが第一印象でした。事前期待の対象・持ち方・持ち主や、顧客が求める対象を定義することで、その相関の中で何に対して注力すべきで、あるいはそうではないのかが明らかになります。

この方面、しょっぱなから苦手全開である事を自覚せずにいられなかった。これは非常にまずい事が可視化された! (;´Д`)

重要なことは、サービスの成果とサービスのプロセスの組み合わせで顧客の満足性を考えた場合に、成果側は「根付きにくく」プロセス側が重要度が高いという、サービスを施された側からは当たり前なのに実は見落とされがちだと言う事(あるいは、無意識に避けているか?)でした。「価格.com」に掲載して競争する場合の例が非常に分かりやすかった。

servicequalityで、ここまでは本当の意味での事業戦略みたいな話なんですが、この流れは、スクラムチームの運営にも当てはめることが出来ますよという感じで展開。

この図に身に覚えのある人は多いと思いますがw スクラムチームのアウトプットをサービスとして考えた場合、ステークホルダーが期待している「事前期待」とマッチしていない事はありませんか?

あるいは、このような分析手法を持ち込むことで、一般的に言われている「プロジェクトマネージメント」が、当たり前品質の一部しか担っておらず、そこで努力を続けてもあまり実りはなく、チーム内で差別化品質と言う事に注目できるようになりますよ、という内容でした。

… 素晴らしい、そして課題が増えました。まだまだ遠いなー


それにしても、裏番も超強力で、全部聞けなかったのが非常に悔しい。特に聞き逃した感あったのが「金融系IT企業におけるスクラムへの挑戦(Yasumi Nakanoさん / Hiroko Shimaseさん)」。一体、金融系というどうしようもない企業文化で、どうやってそこに挑戦していったのか、ビデオでいいから見たい… 後から概要聞いてみたら、やっぱりすごかったらしいです。

Publickeyで記事になっていました。注目度高い!

運営の関係者の皆さん、スピーカー・参加者の皆さんありがとうございました。来年も1月ごろに開催される予定です。募集は10月ぐらいかな?まだ参加されていない方、是非参加してみて下さい。またお会いしましょう。

SourceTreeで始めよう! Gitへの乗り換え指南 – Atlassian User Group NAGOYA 第3回 ユーザーミーティング

「SourceTreeで始めよう! Gitへの乗り換え指南」というタイトルで、AUG名古屋 第3回 ユーザーミーティングで登壇させて頂きました!

augn1Atlassianの製品では、JIRAを少しだけ、後はSourceTreeをどっぷりと使う、という経験値なのですが、以前登壇したGitの解説を元に、SourceTreeやBitbucketに絡めて話をさせて頂きました。

スライドの分量は多めなので、適当に端折って進行しました。
途中、解説が甘い部分にマサカリが (;´Д`) すいません、精進します。

あと、最終版がありましたので、ここで公開するスライドは最終版で公開しておきます。

ご静聴ありがとうございました。

オリジナルスライド:SourceTreeで始めよう! Gitへの乗り換え指南.pptx

augn2あと、登壇特典貰いました!Atlassianロゴ入りBluetoothスピーカーですよ!! やっほい!

会場は美味しい食べ物と酒が呑める(私は食べる専門ですが)場所で、あんなお店でプロジェクター完備だとは思いませんでした。Atlassian ExpertsのHSDさんの協賛で、会費もお値打ちでした。いつかうちでもやってみたいですね。

スライド中でも紹介しましたが、Windows系のSourceTree向けに、マージツールとして「TortoiseMerge」のポータブル版を作ったので、公開してあります。

GitHub: TortoiseMerge Portable

tortoisemergeportableSourceTreeは、サードパーティ製のマージツールをいくつかサポートしていて、TortoiseMergeも対応しています。しかし、公式パッケージはTortoiseSVN内に含まれた形で配布されており、TortoiseMergeだけを使いたい場合でも、TortoiseSVNをインストールしなければならず、インストールするとエクスプローラーがとてもアレになってしまうのが嫌だ… という向きに最適です。

ポータブルと銘打っていますが、純粋なポータブルとしてのバイナリファイルを固めただけの物と、MSIによるインストーラー版があります。インストーラー版は32ビット版しか用意していませんが、ぶっちゃけ、64ビット版が必要になるとも思えないので、64ビット版を作る気は多分起きないです(勝手にフォークして作ってください)。

注意点として、既にTortoiseSVNを使っている環境には入れないで下さい(64ビット版TortoiseSVN環境に、32ビットのインストーラー版を入れるとおかしくなるかも知れません)。

それではまた。

Visual Studioが何度も無関係のプロジェクトをビルドするのは、奴のせいかもしれない – NuGet編

NuGetまるで続きがあるかのようなラノベタイトルですが、一話完結です (;´Д`)

ものすごーく長い間、頭を悩ませてきた問題の一つが、先日ようやく判明したので共有しようと思います。
Visual Studio 2013(より前のバージョンも多分)で、比較的中規模~大規模なソリューション(沢山プロジェクトが含まれている)において、クリーンビルド後にも再びビルドが走って「イラっと」する問題です。

何度やっても発生する

GitなどのSCMからソースコードだけを取得して、完全なクリーンビルドを行っても起きます。これが起きると、以後のビルドは「リビルド」の如く、依存関係上ビルド不要なものを次々とビルドしてしまうので、開発効率が極めて悪くなります。

「お前はバッチファイルでビルドしてるだろ」

と言いたくなります。

何故か、自分一人で開発している場合には発生しません。また、使用しているPCによって、起きたり起きなかったりします。PCによって挙動が大体決まるものの、同じPCで試行しても現象が変わる事があります。規模の問題なのか、プロジェクトに関わっている誰かが何かをしたのか、PCが腐っているのか、Visual Studioの問題なのか、ずっと謎のままでした。

原因

謎な要因は、得てして複雑な背景があるものですが、今回もそうでした。

  • プロジェクトには「NuGet」でパッケージ導入しているものが含まれます。とあるパッケージ「ABC」のバージョンが「1.0.0」とします。
  • ビルドされたバイナリは、共通のフォルダに格納されます。例えば、「$(SolutionDir)$(PlatformName)\$(ConfigurationName)」です。今回のシステムでは、アセンブリを動的にロードする要件があるので、プロジェクトのサブフォルダに出力されるとデバッグがやりにくいのです(というか、デフォルトでこうして欲しいんだけどなぁ)。

チーム内の誰かが、新たにプロジェクトを追加する場合に、自分が関わったプロジェクトの構成をひな形に、新しいプロジェクトを追加します。この時、手動でアセンブリ参照を追加します。その中には「ABC.1.0.0.dll」が含まれます。手動で追加しているので、当然ファイルダイアログから「packages」フォルダ内を参照して追加するわけですが、これによって「NuGetとは何の関係もない事になっているアセンブリを追加した」事になります。

さて、コアメンバーが新しいNuGetパッケージ群に対応させるため、NuGetのパッケージを更新したとします。ここで、SafeUpdateをしないと、「ABC」パッケージには「1.0.0」と「1.0.3」のような、二つのバージョンが混在する可能性があります(SafeUpdateは、Update-Packageに-Safeを付ける)。packagesフォルダ内には、この二種類のパッケージが配置され、プロジェクトファイルも新しいバージョンを使うように修正されます、

プロジェクトがNuGetで構成されていれば。

手動でアセンブリ参照を追加したプロジェクトは、当然packages.configファイルを持っていません。そのため、NuGetのUpdateで自動的に参照が更新されません。しかし、SafeUpdateしなかったためにpackagesフォルダには「1.0.0」のパッケージが残っており、ビルド自体には成功します。ここに問題があります。

複数のプロジェクトがビルドされると、これらの混在したバージョンのアセンブリが、何度も「共通の」出力先フォルダにコピーされます。その結果、1.0.0と1.0.3のアセンブリが、まるでロシアンルーレットのように上書きコピーされ、ビルド完了時にはどちらのバージョンが配置されたのか分かりません。すると、次回ビルド時に、何の変更をしていなくても、Visual Studioが「あ、ビルドしなきゃ」と思っていそいそとビルドを始めます。

この問題は、マルチコアによる平行ビルドを実行すると顕著に表われます。環境によって発生したりしなかったりし、再現性が無いのでかなりイライラします orz

解決方法

  • NuGetで複数のバージョンを同時に使わない事。確認するには、ビルド後にpackagesフォルダ内を見て、複数のバージョンのパッケージが配置されていない事を確認する。もし、複数のバージョンが含まれていたら、どのプロジェクトが犯人かをチェックして修正する。csproj内のパスを修正し、packages.config内のバージョンも修正する。一旦、古いバージョンに合わせた後、SafeUpdateで一括更新すると楽かもしれない。
  • プロジェクトに、直接アセンブリ参照している構成が無い事を確認する。これはプロジェクトが多いと地味に苦痛かもしれない。
  • NuGet絡みで書きましたが、「共通の出力先フォルダ」を使い、異なるバージョンの同一のアセンブリ名のファイルを配置していると、同様の問題が発生する可能性があります。心当たりある場合は、確認してみましょう。

感想

久しぶりに一発ビルド完了した状態を見た。感慨深かった。
NuGet、ALMの一部としてみると脆弱で辛い。PSで構成とか、悪夢だ…

Visual Studio 2012/2013 の拡張機能を紹介 (2)

何だか反響があるようなので、急いで書かなければ (;´Д`)
のっけからゆるい紹介です。

Visual Studio拡張機能については、以下の記事もどうぞ:
Visual Studio 2012/2013 の拡張機能を紹介 (1)
Visual Studio 2012/2013 の拡張機能を紹介 (2)
OzCodeでかゆいところに手が届くデバッグを


「Claudia IDE」

ClaudiaIDEClaudiaIDE_Settingsクラウディア窓辺さん、とは、…. いや、もう説明不要でしょう。殺伐としたIT業界の戦士たちを奮い立たせるため、Visual Studioのエディタ背景に舞い降ります。

エディタ内で活躍してもらうために、いくつかの設定が可能です。

クラウディアさん以外にも、プロ生ちゃんこと「暮井 慧」に舞い降りてもらう拡張機能もあります。こっちはアニメーションもしますよ。


「DevArt T4 Editor」

DevArtT4EditorpngVisual StudioでT4テキストテンプレート(ビルド時にソースコードを生成できる、プリプロセッサベースのコード)を編集する場合の、コードハイライトやIntelliSenseっぽい補完機能が使えます。

T4を補助する拡張機能は他にもいろいろありますが、VSの濃色テーマ(スクリーンショットのような背景が暗いテーマ)選択時に、色が破綻していなかったので、これを使っています。もちろん、拡張機能が何もないと、とてもT4でコードを書く気になれません。


「Git Source Control Provider」

GitSourceControlProviderGitはソースコード管理システムの一種で、最近ではGitHubが知られるようになったので、ご存じの方も多いと思います。この拡張機能は、Visual Studio上でGitの操作が出来るようにするものです。

とは言っても、マイクロソフトもまたGitの拡張機能を公開していますし、ほかにも数種ありそうですが、謹製も含めてどれも今一つの出来です。ここで紹介した拡張機能は、その中でもまともな方かなと思います。

#この辺りは、こっちの記事でも紹介しましたので参考にどうぞ。私のお勧めは、残念ながら拡張機能ではなくSourceTreeです。


「Highlight all occurrences of selected word」

HighlightAllOccurrencesOfSelectedWordもう、これが無いと生きていけない系です。

エディタで選択した単語と同じ単語を見つけ出し、リアルタイムで色を付けます。上の例では「value_」をダブルクリックしたところですが、エディタ上のすべての「values_」に緑色が付きます。

「単語なの?構文解析して意味的に同一なものじゃないのか?」と思う人も居るかもしれません。そういう機能もあればうれしいのですが、とにかく文字列一致でハイライトしてくれると良い点として:

  • C#に限らず、VBやXAML、C++など、テキストエディタ全てで有効なので、いつでも使える安心感がある。
  • 純粋に文字列を検索したい場合も、ままある。コメント上の単語や、リテラル文字列の単語など。

ということがあります。何よりイージーで効果が高いので、これが使えないとかなり痛いです。


「Java Language Support」

JavaLanguageSupport基本、必要に迫られない限り、Javaでコードを書くことはあり得ないので、この拡張機能はスクリーンショットも取れなかったです。どうしてもソースコードを読みたい場合に備えて、ハイライト表示が出来るので入れてあります。

機能的にはデバッガも含まれているらしい(もちろん、Visual J++やJ#のようなモドキではなく、JVMで)ので、拡張機能としては気合が入っています。

今は、少なくともビルドやソースコード管理などを含めた、トータルでの開発環境として評価しなければ、真の価値は分からないと思うので、ここまでにしておきます。こう言う物もあるんだ的に捉えてもらえれば良いかなと。


「Productivity Power Tools」

ProductivityPowerToolsMS謹製のVS拡張機能です。VSの隙間を埋めます。この拡張機能、もうVSに取り込んでも良いんじゃないかと思うのですが…

かなり沢山の機能拡張が盛り込まれています。私のお気に入りとしては:

  • ソースコード保存時に、自動的にソースコード整形してくれる。また、C# usingの整理もやってくれる。まぁ、後者はやりすぎ感もあるので、オンにすべきかどうかは迷う所です。
  • ドキュメントタブを色分けして、ピン止め出来たりする。
  • スクロールバーに、編集箇所を色表示してくれる。
  • カラムタブ位置にガイドラインとなる線を引いてくれる。
  • 印刷時にソースコードの色付けをしてくれる。

などなどあります。勿論、人によって要不要は様々だと思うので、設定ダイアログから機能をオンにしたりオフにしたりして、自分なりにカスタマイズして下さい。

なお、リンクはVS2013用です。2012や2010用は同じ名前で別に存在するので、検索してください。


「RockMargin」

RockMarginテキストエディタの縦スクロールバーが巨大になり、スクロールバーにテキストのサムネイル(?)が表示されます。青っぽく強調されている部分が、ビューに表示されているテキストの範囲です。

このブロードなバーはドラッグできて、それに合わせてビューがスクロールします。編集時には何となくソースコードの位置を「イメージ」で覚えておいて、ささっとスクロールさせる事ができます。

テキストエディタで如何に目的の場所に移動するかという事に注目した拡張機能はいくつかありますが、私はこれが一番直感的で素早く移動出来て良いと思います。

この拡張機能、ギャラリーではまだまだマイナー順位だったので、見つけたときには「掘り出し物みっけ!」と喜んでいたのですが、de:codeに参加したらエバンジェリストの方々がデモで皆使っていて、別の意味でがっくりしましたw


「SQL Server Compact Toolbox」

SQLServerCompactToolBoxVisual Studio 2012までは、SQL Server Compact Editionのデータベースファイル(*.sdf)を直接オープン出来、SQL Serverと遜色のない編集が可能だったのですが、2013になってオミットされてしまいました。

この拡張機能は、VS内でSQL Server Compactのデータベースを編集可能にします。ただ、まだ発展途上で機能も網羅されていないため、どうしても2012の編集可能である事と比較して、個人的には不満があります。

とはいえ、作者は結構頻繁にバージョンアップを繰り返しているので、徐々に問題も減ると思います。

余談ですが、面白い事に、Visual Studio User Voiceでも結構なvote数でCompact Editionの続投が入っています。

Support Sql Server Compact in Visual Studio 2013
Sql Server Compact 5


「Unit Test Generator」

UnitTestGeneratorVisual Studio 2010までは、MSTestを使用するユニットテストコードの生成が簡単に行える機能が内蔵されていたのですが、これまたVSから切り離されてしまった機能です。

今は、TFS ALM Rangersというプロジェクトでこの拡張機能がリリースされているのですが、VSから分離したというより、同じようなものを一から書き直しているようで、かなり機能が不足しています。

UnitTestGenerator_SettingsInternalsVisibleTo属性の自動適用や、プライメートメソッドへのアクセサコードの自動生成などがなく、非常にシンプルなテストコードの生成だけが出来る状態です。

本来、TDDではテストコードから記述するため、こういった機能は馴染まないという事も背景にあるのかもしれません。ただ、私的にはフレームワークの設計をやるので構築&破棄が多く、採用したいコードが出来た時点でテストコード書きたいという欲求もあり… 難しいところです。


「VSColorOutput」

VSColorOutputこれも、無いと生きていけない系です。ビルド時のログなどが表示される「出力」ウインドウの行に色を自動的に適用します。

法則などはあまり把握していませんが、warningとかerrorとかが行内に含まれていると、黄色や赤色で強調してくれます。

VSColorOutput_PreSettingsVisual Studioのデフォルトでは、ビルド経過はプログレスバーでしか表示されず、エラーが発生した場合は「エラー一覧」に表示されます。しかし、このエラー一覧は、表示の順序が発生した順序となっておらず、見ても関連性が全く把握出来ないため、とても残念な機能になってしまっています。

結局、エラー一覧を自動的に表示するというオプションを外し、必ず「出力」ウインドウを表示させるのですが、今度はログからエラーが発生している箇所を探すのが困難。

そんな時にこの拡張機能があれば、スクリーンショットのように一目瞭然です。ログの上から見ていって、最初の赤い行に注目すれば良いので、とても楽です。

標準のルール設定では、「Error」のような単語が含まれていると赤く表示されてしまってアレなので、以下のような順序に変更しています。

vscoloroutput2


私的な定番としては、このぐらいです。

ReSharper_CodeAnalysis2近日ではとうとう「ReSharper」を使い始めました。これも解説はほとんど不要かと思います。というより、自分もまだ導入したてでレビュー出来ないと言う事もありますがww

上に含めなかったのは、この拡張機能が有償なためです。しかし、きちんとリファクタリングする文化があるのなら、きっと力になる拡張機能かなと期待しています。

Visual Studio 2012/2013 の拡張機能を紹介 (1)

Visual Studio 2010より、IDEの拡張機能を簡単に導入できるようになりました。

VSExtensions現在の拡張機能についておさらいしておきましょう。

Visual Studio拡張機能については、以下の記事もどうぞ:
Visual Studio 2012/2013 の拡張機能を紹介 (1)
Visual Studio 2012/2013 の拡張機能を紹介 (2)
OzCodeでかゆいところに手が届くデバッグを


Visual Studio拡張機能とは

VS2010以降では、「ツール」メニューの「拡張機能と更新プログラム」を選択することで、拡張機能を管理するダイアログを呼び出す事ができます。このダイアログから、オンラインで拡張機能を検索したり、インストール済の拡張機能の更新やアンインストールを行う事ができます。

VS2008までは自力でセットアップコード(WixやInstall Shieldを使って)を用意し、独自の配布形態をとる必要があったのですが、VS2010からは「*.vsix」という形式の専用セットアップパッケージがサポートされ、負担が軽減した事が大きいと思います。拡張機能がvsix形式となっていれば、上記ダイアログから、ダブルクリック→インストールで、簡単に機能を拡張することができます。

※余談ですが、ツールメニュー内には「アドインマネージャ」という項目もありますが、これは拡張機能とは関係ありません。というより、ここに項目が追加されていることを見た事がない… 私も詳細は分かりません。

VSGResharperまた、vsixはユーザー毎にインストールすることが前提となるため、インストール時に管理者権限を要求しないことも、インストールの負担軽減に良いようです。
とは言っても、拡張機能の内容によっては、システムレジストリ(HKLM)を変更する必要があったりするため、インストール方法がvsixに統一されたわけでもありません。この辺りは、非常に扱いやすくなったNuGetと異なり、まだまだ洗練の余地があるように思います。

オンラインでの検索は、Visual Studio Galleryに登録されているソフトウェアが対象です。このサイトには個人で作った拡張機能でも登録できます。現在、約5000タイトルが収録されており、有償無償に限らず、こんな拡張機能があるのかという発見があります。


NuGetとの違い

NuGet拡張機能やNuGetを殆ど使わない文化の所もあると思うので、その違いについても。

拡張機能はVSの機能を拡張します。たとえば、ソースコードエディタの機能を強化したり、デバッガの拡張を行ったり、Git/Subversionのようなソースコード管理ツールを統合したり、といった内容です。

NuGetはそうではなく、C#などのプロジェクトを開始後、そのプロジェクトのビルドに必要なサードパーティのライブラリを簡単に導入出来る仕組みです。

これまではこの部分も拡張機能が担っていたのですが、その場合は「参照設定」から、使用するライブラリを自分で探し出して、プロジェクトに追加する必要がありました。しかし、システムにインストールされた大量のライブラリから、目的のアセンブリ(DLL)を探し出すのは容易ではなく、また、大抵は組み合わせて使用するため、これを毎回行うのは頭痛の種です。

TooManyDependentLibraries2ため息が出る、端的な例。

更に、サードパーティ(特にオープンソースプロジェクト)のライブラリは更新頻度が高く、新しいバージョンがリリースされても、ライブラリの依存関係を把握するのが困難で、いつまでも古いバージョンのライブラリを使い続けるという「あるある」状態になってしまいます。

そこで、プロジェクトへのライブラリの導入を簡単にするために生み出されたのが「NuGet」拡張機能です。そう、NuGetもまた、VSの拡張機能の一つとなっています。

NuGetはプロジェクトで「これを使う」のように指定する・されることが多いため、少し探せば目的のパッケージが見つかりますが、拡張機能は使ってみないと分からないこともあるため、私の使っている拡張機能を紹介してみようと思いました。

Visual Studio 2012/2013 の拡張機能を紹介 (2) へ続きます。