2015年10月18日日曜日

チョコレート ヌガー バージョン VI ~ Prig v2 リリースに添えて - Chocolatey + NuGet + VSIX -

個人でやってます、Microsoft Fakes のオープン ソース代替実装プロジェクト、Prig ですが、先週末ようやく v2.0.0 を正式版にできました。

配布に使っている Chocolatey でモデレーション中のため、インストール スクリプト周りでこまごまとした修正が入る可能性はありますが、上記の通り、メインの機能は実装し終わっているので、お試しは可能です。使うには、Chocolatey をインストールした上で、Prig.2.0.0.nupkg をダウンロードしたディレクトリに移動し、管理者権限の PowerShell から、

して下さい。

さて、v2 系を正式版にできたところで、ちょっと落ち着きましたので、設計的な覚書をつらつらと。今回は、タイトルにもある、一見して複雑なパッケージングになってしまった経緯です。私自身、これがベストな解だとは思っていませんので、もし「もっといい方法あるよ!」などありましたら、是非 @urasandesu までリプいただければと。では、行ってみましょう!


以下の記事、ライブラリを使用/参考させていただきました。この記事も、どなたかの役に立てば光栄です!(`・ω・ )ゝ
Dynamically Add/Edit Environment Variables of Remote Process - CodeProject
nunit/nunit-vs-adapter - GitHub
jamdagni86-OpenCover.UI - GitHub
edsykes/VisualStudioBuildEvents - GitHub
How to: Add a Dependency to a VSIX Package -MSDN
Packages In Visual Studio Templates
NuGetのインストール時に拡張SDKを参照させる方法 #win8dev_jp #wpdev_jp - KatsuYuzuのブログ
visual studio 2013 - NuGet Package which installs a VSIX - Stack Overflow
neue cc - Open on GitHub - Visual StudioからGitHubのページを開くVS拡張
c# - Automatically update Visual Studio Extension - Stack Overflow
Config Hosting multiple WCF services in one NT service
テキトーに使うWCF | d.sunnyone.org
PowerShell AST Modification | Adam Driscoll's Blog
Vsix Installation Utility 1.1.2
PowerShell Grammar - Windows PowerShell Blog - Site Home - MSDN Blogs
拡張機能を利用してVisual Studioをより便利にしよう:CodeZine
registry - Run script during/after VSIX install? - Stack Overflow
PowerShellでOneGet & Chocolateyパッケージを作るときに気を付ける10のこと
One Getとchocolateyのパッケージを作ってみよう by @oota_ken #chocolatey #packaging
Installing commands into the NuGet PowerShell profile - http://xavierdecoster.com
NuGet - Execute commands from c# code
Invoking NuGet Services from inside Visual Studio
Visual Studio SDK - emu雑記
Visual Studio 拡張機能 メニューコマンド編 その2 - emu雑記
Windows Customize VS status bar in VSPackage (CSVSPackageStatusBar) sample in C#, XML for Visual Studio 2008
runceel/ReactiveProperty - GitHub
.net - How to get type of COM object - Stack Overflow
Git Workflow for Vendor Branching - uPortal - Apereo Wiki
ノンデザイナーにオススメ、「ココナラ」でアイコンを書いてもらいました。
ランサーズでiPhoneアプリのアイコン作成を依頼してみた。 | Simple gadget life
キャラクター iPhoneアプリアイコンのデザインをプロに依頼してみる(その4) - むらかみの雑記帳
Differences Between Logos and Icons
How to create a .MSI Installer with dependencies of other MSI Files
Visual Studio Extension Deployment
NuGet does not execute scripts when restoring packages - Stack Overflow





目次

きっかけ
2014 年の年末に v1.1.0 をリリースし、その裏では Swathe の大規模改修に入っていたにも関わらず、2015/01/10(土)の朝イチから私こんなことを言ってます:
たぶん誰かに「こうするともっと便利になりそう」的なことを言われたのだと思いますが、記憶が定かではありません。もしかすると、初夢で何かあったんでしょうか・・・?
真相はともかく、この日の内に Visual Studio のテスト エクスプローラー上でとりあえず動かせる、ところまでは行けました。ポイントは、「Visual Studio のテスト エクスプローラーに対して、プロファイラをアタッチするための環境変数を ON にする」です。.NET のプロファイラをアタッチするための・・・な話の詳細は、こちらで解説していますので、ここでは詳しくは解説しません。





NuGet だけ
v1 時代は、NuGet というか PowerShell の仕組みを借りて Prig Assembly の追加や、Stub Settings File の編集を行っていました。自分が PowerShell に慣れていたのもありますが、インタラクティブに .NET のメタ データを走査でき、かつフリーのツールでは、これ以上のものは無いだろう、という理由です。

Visual Studio のテスト エクスプローラーに対して環境変数を有効にするにも、はじめは既存の枠組みでできないか、というところから検証を始めました・・・が、残念ながらそれは不可能でした。Visual Studio は、ビルドの度に自身の環境変数を初期化するらしく、テストを実行する際に依存関係に手が入っていた場合に走る自動ビルドで、設定した環境変数が無かったことになってしまうのです。

Package Console Manager で環境変数を設定した直後は存在する環境変数が・・・


プロジェクトをビルドすると消えてる!?

プロファイラをアタッチするための環境変数を ON に保ち続けるには、Visual Studio のビルド イベントをフックし、ビルドの度にその環境変数を元に戻すようにする必要があります。VSPackage でやっているサンプルを参考に、NuGet 側で無理やり DTE オブジェクトを引っ張り出し、OnBuildDone イベントをフックするようなことも検証したのですが、上手く行きません。結局、NuGet だけで行くことは諦め、何らかの Visual Studio 拡張を書くことにしました。





前提の再確認
NuGet から別の何らかのパッケージ マネージャに移行するということで、前提として置いたのは以下のような要件です:
  1. サイレント インストール
  2. 参照関係の注入
  3. マシン毎での有効化
  4. ローカル ソースからのインストール
  5. 依存関係の解決
1. は、単にバッチ実行できる、というだけではなく、バージョン管理システムに登録した何らかの情報から、自動的に環境を復元できることも重視しています。NuGet で言うレストア機能ですね。

2. は、既存のプロジェクトに Assembly の参照やファイルを追加できる仕組みがあることを指しています。NuGet で言う、*.nuspec に書く files タグ周りの振る舞いをイメージしています。ライブラリを使うのに、いちいち手動で参照設定を追加していた時代に戻るのは、NuGet を知ってしまった今からは戻れません。

3. は、マシン全体に対して有効になるようインストール/アンインストールできること、です。Prig の v1 系は、NuGet の install.ps1 でごにょごにょすることでこの辺りを実現していたのですが、NuGet は基本的に、Visual Studio のソリューション内のプロジェクト単位、広くてもソリューション単位にパッケージをインストールするものですので、元々無理のあるやり方だったと思います。未だに閉じられない、環境依存なインストール時問題があることを考えると、ここは要改善ポイントでしょう。

4. は、ローカルのディレクトリや共有ディレクトリにパッケージを並べておけば、ネットワークにつないでいなくても 1.5. の仕組みが有効になることを指しています。NuGet ですと、-Source パラメータを指定した際の振る舞いです。

5. は、NuGet で言う、*.nuspec に書く dependencies タグ周りの振る舞いをイメージしています。ライブラリアン(自分含む)が、ヘルパー/ユーティリティなどを作った時に、依存先として指定できる仕組みがあること、ですね。今後 Fakes で言うヘルパー ライブラリ SPEmulatorsFakes.ContribVerification Fakes みたいなものが Prig 向けにも欲しくなった時、Prig を依存先として指定できるようにしたいです。

ちなみに、3. 以外は、基本的に「NuGet でできていたことができること」です。きっと簡単ですよね!(フラグ)





VSIX だけ
何らかの Visual Studio 拡張を書くにあたっては、こちらのページにある通り、Microsoft からはパッケージ方式が 5 つ提示されています。
  • VSIX
  • MSI
  • VSI
  • Platform Registration
  • Platform Registration in MSI
VSPackage でやっているサンプルの仕組みを使うとすると、この内 VSPackage が使えるのは、VSIX と MSI です。MSI は、前提にある依存関係の解決ができないとのことなので、VSIX 一択になりました。じゃあ、VSIX で行けるね?となれば良かったのですが、残念ながら、前提にある参照関係の注入ができません。もちろん新規プロジェクトであれば、プロジェクト テンプレートを使えば良いのですが、レガシー コードを含むプロジェクト向けに、自動化されたユニット テストを可能にすることを謳っているライブラリが、新規プロジェクトへの導入しかサポートしないのは、さすがにあんまりです。

単一のパッケージ マネージャの仕組みで行くのは、早くも無理と悟りました。





NuGet + VSIX
じゃあ合わせ技で、と初めに考えたのは「VSIX のパッケージを NuGet で包む」です。VSIX であれば、マシン全体に対してインストールが可能ですので、NuGet を使っていても、前提にあるマシン毎での有効化はもう少しうまいことできそうな。それにググると、同じようなことを考えている方もいらっしゃり、これは行ける!とやり始めるのですが・・・はい、ダメでした・・・orz。

VSIX 自身に、レジストリとかを触る仕組みはないので、NuGet 側にそういった実装を入れる必要があるのですが、NuGet は、パッケージ レストア時にスクリプトを実行できないということを忘れていました。例えば、開発環境の構築手順を考えると、
  1. Visual Studio 等、マシン全体で有効にする開発ツールのインストール。
  2. プロジェクトを clone。
  3. パッケージ レストア。(※ここでやっと NuGet パッケージが復元)
  4. 追加スクリプトを手動で実行。(※VSIX のインストールチェックや、レジストリ等を変更するため)
  5. Visual Studio 再起動。(※VSIX 初回インストール時)
のように、NuGet だけならなんとかなっていたサイレント インストールの機能が、NuGet + VSIX だと、普段はやらない手作業(4, 5)を増やさないと役に立ちません。これでは、インストール時にもし問題が発生した場合、さらに問題切り分けの難易度が上がることは必至でしょう。

ちなみに、実はこの組み合わせ、かなりの確度で行けると考えていましたので、NuGet 側から VSIX を色々する専用のライブラリを書いたりもしていました。名前を Gall と言います:


Gall の意味は「虫こぶ」。パッケージの中のパッケージという、異質さの組み合わせを表しています。まあ、タイトルの通り、この組み合わせはボツになりましたので、今後日の目を見ることは無いと思いますが、もし興味があればご覧ください。PowerShell の AST から式木を導出したい方などには、ちょうど良いサイズのサンプルになっているかと思います(小声)





Chocolatey + VSIX
前提にあるマシン毎での有効化をまず考えたほうが良いんじゃないか?と考えて検証を始めたのが、NuGet と同じ基盤を持ち、対象をマシン全体に広げた Chocolatey と VSIX の組み合わせ。Chocolatey は、標準で VSIX をサポートしてくれていますので、割と鉄板な感じ。なお、この Install-ChocolateyVsixPackage のヘルプを見ると、-vsixUrl パラメータに指定できるのは URL だけで、前提にあるローカル ソースからのインストールができないんじゃないか?と思われるかもしれませんが、file:// も有効な URL として解釈されていますので、([uri]$vsixPath).AbsoluteUri とかすれば、問題無く扱ってくれます・・・まあ、結局一歩足りなかったのですががが。

今度は、前提にある参照関係の注入ができないのですね。Chocolatey が PowerShell スクリプト関係でサポートするのは、ネストしたプロンプトで実行する ps1 スクリプトをインストールする Install-ChocolateyPowershellCommand と、zip を決まった場所に展開する Install-ChocolateyZipPackage($env:PSModulePath に展開すれば PowerShell モジュールが使えるようにできる)のみ。対して、NuGet の Package Manager Console は、ネストしたプロンプトをサポートせず、$env:PSModulePath は無視する、という振る舞いをします。辛うじて、Profile.ps1 は読み込んでくれますので、ゴリゴリにインストールスクリプトを書けば、Package Manager Console でも Prig のサポート コマンドを使えるようにはできるのですが、イケてない・・・。

この頃には、前提にある依存関係の解決を諦め、古き良き MSI で行くしかないか・・・とも考え始めました。





Chocolatey + NuGet + VSIX
現在の構成になった閃きは、@aetos382 さんとやりとりしている時に思いついたものです。このアイディアは実際上手く行きました。インストール シーケンスは以下のようになります。


ポイントは、Chocolatey があることで、NuGet と VSIX の関係を、NuGet + VSIX で考えていた構成と逆にできたところです(VSIX が NuGet のインストール状況を確認する)。これにより、NuGet 部分は常に最終依存先となりますので、パッケージ レストア時にスクリプトを実行できなくても問題ありません(NuGet 部分だけ復元できれば良い)。開発環境の構築手順を考えると、、
  1. Visual Studio 等、マシン全体で有効にする開発ツールのインストール。(※ここで Chocolatey とともに VSIX のインストール、レジストリ等の変更が入る)
  2. プロジェクトを clone。
  3. パッケージ レストア。(※NuGet パッケージ復元)
  4. 追加スクリプトを手動で実行。(※不要)
  5. Visual Studio 再起動。(※不要)
と、自然な流れで構築ができるようになっています。

Chocolatey のインストール スクリプトである ChocolateyInstall.ps1 と、VSIX から NuGet を叩いている箇所も確認してみましょうか:
ChocolateyInstall.ps1#L34-L55
ここでは NuGet パッケージを動的に作成しています。Chocolatey パッケージと NuGet パッケージに含まれる dll に重複があるため、インストール時にパッケージを作ることで、ダウンロード サイズを小さくする作戦・・・だったのですが、そういえばもう必要ないかも。迂回に使うデリゲートとそのヘルパーを、全て T4 で生成していた時代の名残です(dll 1 つ 1 つが、今の 10 倍ぐらいのサイズだった)。
*.nuspec ファイルは、そのままだと Chocolatey パッケージを作成する際に除外されてしまうため、拡張子を変えて同梱してあります。それを元に戻し、パッケージを作成した後、その親ディレクトリを、NuGet の Source に加えておきます。こうすることで、VSIX から NuGet のインストールを行った際、この親ディレクトリも検索パスに含まれるようになります。

ChocolateyInstall.ps1#L58-L61
マシンでグローバルに必要な環境変数をインストールします。現状は Chocolatey パッケージのインストール ディレクトリのみ。プロファイラから、必要な設定を読み込む際に参照します。

ChocolateyInstall.ps1#L64-L69
プロファイラをレジストリに登録します。

ChocolateyInstall.ps1#L72-L77
Visual Studio のテスト エクスプローラを、Prig で使う dll が読み込めるよう拡張します。この辺りは、v1 系と結構仕組みが変わっているため、また別の機会に解説したいですね。

ChocolateyInstall.ps1#L80-L84
最後に VSIX をインストールします。Chocolatey 標準の Install-ChocolateyVsixPackage を呼ぶだけですね。


PrigPackageController.cs#L147-L152
VSIX 側、Prig Assembly 追加時は、はじめに NuGet がインストールされているかどうかをチェックします。現バージョンと異なるパッケージが入っていれば一旦アンインストールし、インストール。NuGet パッケージの仕事は、参照関係の注入に特化させることができましたので、ここでも基本的に、用意された API を叩くだけになります(というか、NuGet に対してはそれしかできなかったりします・・・)。

PrigPackageController.cs#L155-L160
NuGet パッケージのインストール有無を問わず、Prig Setup Session を通じて、Prig AssemblyStub Settings File の追加を行います。v1 時代からあった、手動で呼んでいたコマンドを、プログラムから呼んでいるだけですね。





おまけ
Chocolatey のモデレーションでは、アイコンについてもチェックされる、ということでしたので、良い機会だと思い、Prig のアイコンをクラウド ソーシングしました:
最終的には、hana_5010 さんの提案を採用させていただきました:
Prig
2 番目のアイコンをベースに、採用後のやりとりで、hana_5010 さんから提示いただいたパターンから選ばせていただいたものです。

イメージを伝えるのに相当苦労した覚えがあります。何しろ、イメージが無いのですから(えー)。依頼詳細には「アイコンは『入れ替わる』『分離する』などがわかりやすく表現されていることがポイントとなります。」「アイコンには何か暗いイメージというか、後ろめたさを感じるものが入っていると評価がしやすいです。」などとそれらしいことを書いていますが、書いている当時は、何をもってそれらが表現されているとするかを全くイメージできていなかった気がします(スミマセン・・・)。そんな状態にも関わらず、10 名以上のデザイナーの方から提案いただけたのは、本当にありがたかったです。

ところでこのアイコン、蛸がモチーフということですが、
  • 『入れ替わる』『分離する』⇒蛸が持つ海の忍者的な振る舞い、アイコンに表現された外殻を引き剥がしている動き。
  • 暗いイメージというか、後ろめたさ⇒海外における蛸の悪魔的な扱い。
という感じで、まさに依頼に沿ったものとなっていると思います。
ちなみに、球体の中身をグリーンにしたのは、私からさせていただいた唯一のリクエストですが、邪魔なものを無理やり引き剥がして、テストをグリーンにするという Prig の機能が、上手く織り込めたのではないでしょうか。

・・・え?肝心の日本じゃ、蛸は別に悪魔的じゃない、ですって?
プログラミング言語がなぜか美少女として擬人化されることが多い日本ですので、それらの言語が、こういう生き物と遭遇してどうなるかを考えると・・・暗いイメージというか、後ろめたさは十分に表現できているんじゃないでしょうか?(小声)


0 件のコメント:

コメントを投稿