Loqui 0.6.2 をリリースしました。
https://launchpad.net/loqui
内容は、共通バッファ(見ているチャンネル以外が全て流れてくる左下のペイン)のメッセージの行のどこかをダブルクリックすると、そのメッセージのチャンネルを選択する機能の追加。
実用的になったタイミングから、あったほうが便利だよなーと思いつつ、これ作るのいろいろ手加えなきゃで大変だよなーと思ってたんだけど、当初想定よりもっとざっくりした方法でだいたい動く作り方を思いついたからそれで作った。あればいいよなーと思ってから実装されるまで10年以上かかってる機能になるね...
ページ
▼
2015-10-26
2015-10-22
UbuntuからローカルのLinux KVM上のWindowsにリモートデスクトップする環境を作る
今回は、Ubuntu DesktopにLinux KVMを構成し、その上にWindowsをインストールし、Desktopの側からリモートデスクトップ(RDP)で接続する環境を作る方法について。絵にするとこういう環境。
「普通Windows上にLinuxじゃないの?」と思うかもしれないが、 Windowsのリモートデスクトップは優秀なので、Windows上にVirtualBoxでLinux desktopを上げるよりは快適に使うことができるので、どちらも使いたい環境では個人的には気に入っている使い方である。
前提として、Ubuntu Desktop 14.04がセットアップされているものとする。なお、今となっては同じことGUIでもできるじゃん?と思う手順もあるが、動くなら全然そっちで構わないと思う。断片的に作業してる部分があるので、ちょっと足りてない話があるかも。
ここからは、もう少し詳しい+やったら良いかもしれない手順。
bridge-utilsパッケージをインストール。
/etc/network/interfacesを例えば以下のように設定する。
ここでのパーティション構成はこういう形と想定する(本当は別ディスクにしたい)。
上記の前提のため、sda3をPV (Physical Volume)とし、それだけを含むvg0を作る。
なお、Webで管理したい場合は「KVMの仮想マシンをWebブラウザから管理する」の記事を参照。
virt-managerを起動するときは
入力の注意点は以下の通り。
・OSはWindows, Windows 7を選ぶ(7なのはそれ以降の項目がないので。できたら選ぶ。Vista以前は使わないですよね?)
・メモリ容量、ディスク容量ともに適切に増やす。
・LVM(略)を使った場合、ストレージで既存のストレージを選択するを選び、先ほど作ったLVのパスを入力する。
・ウィザードの最後の画面で、詳細オプションを表示し、必要なネットワーク、例えば「ホストデバイス br0」を選択する。
なお、Windows 10のときは、ウィザードの最後で「インストール前に設定をカスタマイズする」を選択し、CPUを適切に選ばないと(例えばCore2 Duo)エラーで立ち上がらないみたい。
参考:仮想環境(KVM)でのwindows10が、一度起動した後リブートすると起動しなくなる (症状は違うが、この対処と同様にCPUを変更すると動作が変わる)
確認
起動
停止
ただし「リモートデスクトップの設定」の「高度な設定」にある「セキュリティ」の項目がデフォルトの「ネゴシエーション」だと、最近のWindowsには接続できないので、他の項目、例えばTLSに変更する。
なお、RemoteFX を選ぶと、RDPの品質の設定でフォントスムージングが有効でも有効にならないので、それ以外、例えばTrue Color 32bitを選ぶこと。また、品質は最高(最低速)を選ぶこと。
Remminaを使うのはお手軽で良いのだが、アンチエイリアスが効かないのでガタガタで悲しい。しかし、xfreerdpを使うと有効にできる。
追記: と思っていたが、上述の通り品質でフォントスムージングを選んでおき、RemminaでもRemoteFXを選ばなければ有効になる模様。xfreerdpの手順も一応残しておくが、Remminaでいいと思う。
毎回コマンドを打つのはだるいので、以下のようなシェルスクリプトを置いて起動することにしている。パスワード入力をssh-askpassを使うのはちょっとチート気味。
---
これで設定方法と使い方はおしまい。両方使う方は試してみてはいかがか。
「普通Windows上にLinuxじゃないの?」と思うかもしれないが、 Windowsのリモートデスクトップは優秀なので、Windows上にVirtualBoxでLinux desktopを上げるよりは快適に使うことができるので、どちらも使いたい環境では個人的には気に入っている使い方である。
前提として、Ubuntu Desktop 14.04がセットアップされているものとする。なお、今となっては同じことGUIでもできるじゃん?と思う手順もあるが、動くなら全然そっちで構わないと思う。断片的に作業してる部分があるので、ちょっと足りてない話があるかも。
こだわらない手順ざっくり版
長くなってしまったので、なくてもいい部分を省いたざっくり手順を先に記載。ピンとこなければ、詳しくを参照。- Intel VT/AMD-VをBIOSで有効化 (詳しく)
- sudo apt-get install qemu-system qemu-kvm libvirt-bin virt-manager (詳しく)
- sudo virt-managerを実行し、localhostを右クリック→「新規」からVMをウィザードで追加 (詳しく:特にWindows10の場合)
- VMにWindowsをインストールし、リモートデスクトップを構成。振られるIPを確認 (詳しく)
- sudo apt-get install remmina-plugin-rdpし、remminaで「TLS」に設定してWindowsに接続 (詳しく)
ここからは、もう少し詳しい+やったら良いかもしれない手順。
初期設定
VMを作成する前に一度だけ必要になる作業。BIOSの設定
KVMを使うので、Intel VT (Intel Virtualization Technology) のようなCPUの仮想化支援機能をBIOS上で有効にしておく。ブリッジを構成する(必要であれば)
デフォルトのNATでは都合が悪い場合、VM用のネットワークを設定する。自分の環境ではホストNICを含むブリッジネットワークを作るのが便利なことが多いので、ホスト側にブリッジを構成してしまう。…と思っていたが、デフォルトで作成されるvirbr0 (192.168.122.0/24) のネットワークで十分かも。なお、後述の仮想マシンマネージャー(virt-manager)のGUIでも作れそうだが、やったことはない。bridge-utilsパッケージをインストール。
sudo apt-get install bridge-utils
/etc/network/interfacesを例えば以下のように設定する。
auto lo iface lo inet loopback auto eth0 iface eth0 inet manual auto br0 iface br0 inet static bridge_ports eth0 address 192.168.1.X netmask 255.255.255.0 network 192.168.1.0 broadcast 192.168.1.255 gateway 192.168.1.254 dns-nameservers 192.168.1.254
VMイメージ用のLVMの構成(お好みで)
VMのイメージはデフォルトでは/var/lib/libvirt/imagesに普通のファイルとして作られるので、ここの容量が十分にあれば別に使えるのだが、ファイルで取るよりLVMでブロックデバイスとして切り出してあげたほうが効率的と思うので、この構成を取るときはLVM用のパーティションを切ってLVM Volume Group vg0を作ることにしている。ここでのパーティション構成はこういう形と想定する(本当は別ディスクにしたい)。
上記の前提のため、sda3をPV (Physical Volume)とし、それだけを含むvg0を作る。
$ sudo apt-get install lvm2 $ sudo pvcreate /dev/sda3 $ sudo vgcreate vg0 /dev/sda3
KVM関連ソフトウェアのインストール
qemu-kvm単体で使うのはさすがにしんどいので、libvirtを使って管理する。その管理用GUIであるところの仮想マシンマネージャー(virt-manager)も入れる。$ sudo apt-get install qemu-system qemu-kvm libvirt-bin virt-manager
なお、Webで管理したい場合は「KVMの仮想マシンをWebブラウザから管理する」の記事を参照。
virt-managerを起動するときは
$ sudo virt-managerとする。
仮想マシンマネージャー(virt-manager)からのストレージ・ネットワークの設定(お好みで)
virt-managerを起動し「localhost」をダブルクリックすると、ホストの設定画面がでてくる。ここで「仮想ネットワーク」「ストレージ」「ネットワークインタフェース」タブを選ぶと、それぞれの設定ができる。私は使っていないが、うまく使えば便利かもしれない。VMの作成
VMを作成するごとに必要になる作業。基本的にはひとつだけだと思うが、複数使う場合はここを繰り返す。VMイメージの作成(LVMを使ったとき)
LVM(かつvirt-managerでストレージの構成を行っていないとき)は、事前にストレージを作っておく。$ sudo lvcreate -L 48G -n win1 vg0win1の部分はVM名あるいはVM名-rootなどを入れることが多いが、名前なのでわかればなんでもいい。
仮想マシンの追加
ISOイメージを準備した後、virt-managerを起動し、localhostを右クリックして「新規」を選択。ウィザード画面が表示されるので聞かれる情報を入れていくだけ。入力の注意点は以下の通り。
・OSはWindows, Windows 7を選ぶ(7なのはそれ以降の項目がないので。できたら選ぶ。Vista以前は使わないですよね?)
・メモリ容量、ディスク容量ともに適切に増やす。
・LVM(略)を使った場合、ストレージで既存のストレージを選択するを選び、先ほど作ったLVのパスを入力する。
・ウィザードの最後の画面で、詳細オプションを表示し、必要なネットワーク、例えば「ホストデバイス br0」を選択する。
なお、Windows 10のときは、ウィザードの最後で「インストール前に設定をカスタマイズする」を選択し、CPUを適切に選ばないと(例えばCore2 Duo)エラーで立ち上がらないみたい。
参考:仮想環境(KVM)でのwindows10が、一度起動した後リブートすると起動しなくなる (症状は違うが、この対処と同様にCPUを変更すると動作が変わる)
Windowsのインストール
virt-manager上から淡々とインストールする。リモートデスクトップを有効にする。必要であれば普通にIPアドレスを設定する(デフォルトのNATだと192.168.122.0/24のIPが振られていると思う)。このマシンにIPアドレスで接続しにいくので、設定しなくても確認をしておく。VMの起動・停止
もちろんGUIからもOn/Offできるのだが、普段リモートデスクトップからの接続となるので、ターミナルからの起動・停止も覚えておくと便利。確認
$ sudo virsh list --all
起動
$ sudo virsh start win1
停止
$ sudo virsh destroy win1
リモートデスクトップ接続
上記でVMができたら、ホスト側から接続する。Remminaを使う方法
普通にRemminaを使うのであれば、apt-get installして起動して設定画面で設定すればOK。$ sudo apt-get install remmina remmina-plugin-rdp
ただし「リモートデスクトップの設定」の「高度な設定」にある「セキュリティ」の項目がデフォルトの「ネゴシエーション」だと、最近のWindowsには接続できないので、他の項目、例えばTLSに変更する。
なお、RemoteFX を選ぶと、RDPの品質の設定でフォントスムージングが有効でも有効にならないので、それ以外、例えばTrue Color 32bitを選ぶこと。また、品質は最高(最低速)を選ぶこと。
xfreerdpを使う方法
追記: と思っていたが、上述の通り品質でフォントスムージングを選んでおき、RemminaでもRemoteFXを選ばなければ有効になる模様。xfreerdpの手順も一応残しておくが、Remminaでいいと思う。
$ sudo apt-get install xfreerdp-x11
毎回コマンドを打つのはだるいので、以下のようなシェルスクリプトを置いて起動することにしている。パスワード入力をssh-askpassを使うのはちょっとチート気味。
---
これで設定方法と使い方はおしまい。両方使う方は試してみてはいかがか。
2015-10-14
MSBuildでRAW画像ファイルを移動する
正直PowerShellでOK。普通こんな使い方してないけど、なんというかItemGroupの使い方の例ということで。
デジカメで写真を撮るときは、RAW画像も一緒に撮るのだけど、オンラインなところに一緒に置いておくと容量を食うので、オフラインなところに退避したい。しかし、こんな構造で保存しているので、そのままだと散らかっていて移動させにくい。
なので、ARWだけこんな感じの場所にムーブしたい(そしたらまるっと移動させればいいので)
もちろんスクリプトを書けば全く問題ないのだけど、MSBuildで書いたらこんな感じになったのでメモ。camera\に配置して動かす。
デジカメで写真を撮るときは、RAW画像も一緒に撮るのだけど、オンラインなところに一緒に置いておくと容量を食うので、オフラインなところに退避したい。しかし、こんな構造で保存しているので、そのままだと散らかっていて移動させにくい。
camera\20151014-自宅\DSC00274.JPG →これはとっておきたい camera\20151014-自宅\DSC00274.RAW →これは避けたい
なので、ARWだけこんな感じの場所にムーブしたい(そしたらまるっと移動させればいいので)
camera\ARW\20151014-自宅\DSC00274.RAW
もちろんスクリプトを書けば全く問題ないのだけど、MSBuildで書いたらこんな感じになったのでメモ。camera\に配置して動かす。
2015-10-10
Visual Studioソリューションの配置方針とMSBuildの活用
Visual Studioで開発をしていると、ひとつソリューションを作ってひとつのVisual Studioインスタンスでビルドしたり発行している間は良いが、そのうちそれ以上のことをしたくなってくる。例えば、複数のソリューションを複数のVisual Studioインスタンスで開いたり、CIにリリース用のzipファイルを作らせたりといったことである。
そのような状況を踏まえながら、自分がソリューション/プロジェクトを作るときに考える点について、文書としてまとめたことはなかったと思い、以下に記述する。
Visual Studio全般に通じる部分も多いと思うが、基本的にC# プロジェクト(.csproj)を想定する。
*1: プロジェクトは、アプリケーションをビルドするのに必要なものすべての論理的なコンテナーです。 - ソリューションとプロジェクトの作成
その「プロジェクト」を複数まとめたものが「ソリューション」となっており、プロジェクトの紐付けの情報が記録されているのが「ソリューションファイル」(*.sln)である。Visual Studioの1インスタンスは通常ひとつの「ソリューション」を開いている。
「プロジェクトファイル」(*.なんとかproj)/「ソリューションファイル」(*.sln)は、Visual StudioのGUIによって作成/編集される。
また、この「プロジェクトファイル」(*.なんとかproj) ファイルはMSBuildというビルドツールでビルドできる形式となっている。
MSBuildから実行できるプロジェクトファイルはVisual Studioに頼らずに一から作成することもでき、MSDNのドキュメント的にはそのファイルもプロジェクトファイルと呼ぶように見えるが、本記事では便宜上そのような1から書いたファイルは区別して「MSBuildファイル」と呼ぶことにする。
ざっくり図で表すと、以下の通りである。
詳しく説明していくと、以下の通り。
これをする理由の一つ目は、機械的に生成される部分と人が意図を持って記述している部分を分けることにより、人が記述している部分の書きやすさ/読みやすさを向上させるためである。ツール(Visual Studioやその拡張)が生成/編集する*.〜projファイルに記述するとなると、生成/編集するツールの都合に合わせて記述しなければならない。ただの一覧のような設定ファイルであればそれほど問題ではないが、もっと自由に記述できるMSBuildでは非常にストレスフルな作業になる。また、Visual StudioのGUIから生成したわけではない追加の記述があることがGUIから分かり辛く、気づきにくい。別に記述してあれば、これらの問題はない。
二つ目は、CIシステムからのエントリポイントとして便利なためである。.csprojは先の通りMSBuild.exeでビルド可能なのだが、複数のcsprojを順にビルドするとか、渡すパラメータがいくつもあったりすると、そのMSBuild.exeの呼び出し自体がスクリプトのようになってきて、CIシステムのジョブの見通しが悪くなったり、変更管理がしにくくなったりする。しかし、CIシステム側からはこのMSBuildファイルを呼ぶだけと決めておけば、CIシステムでのジョブの記述内容が容易に想像できるようになり、またCIシステムに設定する前のタスクの事前確認がしやすくなったりする。
なお、このプロジェクトをラップしたMSBuildファイルは、私は「{{Name}}.msbuild」というファイル名をつけることが多いが、「{{Name}}.proj」でも別にかまわないし、そっちのほうがプロジェクトファイルとしての本筋っぽくはある。.msbuildで慣れてしまったのもあるが、Visual Studioで開くファイルではないよーということがわかっていいかなと思う。(この.msbuild拡張子を使っているプロジェクトもgithubで見かけるので、それほど好き勝手ではないと思っている。例えば、xunit)
なお、ソリューションをビルドすることもできるが、余分なビルドを減らすためにも、できる限りプロジェクトファイルを指定するほうが良い。
MSBuildファイル上の記述としては、以下のようになる。これは、KiritoriMage.csprojを「Release」でoutput\KiritoriMageに出力する指示である。(OutputDirプロパティは事前に「output」と定義、MSBuildProjectDirectoryはMSBuildファイルが配置されているディレクトリ(自動定義))
これをすることで、出力用フォルダ以下は書き換え(削除)して良いことが明確になるため、様々な後処理が行いやすくなる。もちろん、bin\Debugの状態でもできなくはないのだが、「どこ?」となりにくいので処理しやすい。
後処理とは例えば、以下のようなことである。
引き続きKiritoriMageのBuildターゲットで説明するとこんな感じである。ffmpegは使わないので消している。
やりなおしたいときもこのフォルダを削除すればよいだけというのは、いろいろな意味で楽である(この削除をする「Clean」ターゲットは用意しておく)。 ただし、この出力先フォルダの差し替えをするのが面倒なプロジェクトタイプがあった覚えがあるので、労力との見合いで実施する。
理由は、ビルド構成はGUIでの設定となる都合上、どうしても設定内容をフラットに見せざるを得なくなるため、各構成間での差分に対する見通しが悪かったり(良く見せることが難しい、と言うべきか)、一部のみ違う構成の変更作業に手間がかかったりするためである。MSBuildファイル中にコードで指示していれば「このプロパティだけ違う」というのが意図として伝わりやすい(伝えるコードに書きやすい)。
プロパティで変更できなくても、XMLな設定ファイルがちょっと違うレベルなら、ビルド後にMSBuild Community TasksのXmlUpdateで更新してしまうという手もある。
この方針も、構成間の差分次第で考える。がっつり違うのであれば、別物として用意しておくのもひとつ。
正直これが便利だといえるのは、MSBuild LauncherのUIがこうだからというのも大きいが、この話だけ切り取って説明したのが「PowerShellにMSBuildLauncherで簡易GUIをつける」で、詳しい話はこちらを参照されたい。
Visual Studioは、基本的に一つのソリューションに対して同時に一つのデバッグ実行を行うことが想定されている(ように私には見える)。そのため、複数の実行が同時に走るようなものを一つのソリューションに押し込むと、扱いにくい(一応実行はできなくはない)。例えば、Webサーバーをデバッグ実行しながらクライアントアプリをデバッグ実行するといったケースでは、サーバー側、クライアント側でそれぞれソリューションを作成すると、二つのVisual Studioを同時に起動し、それぞれでデバッグ実行を行えて便利である。
そのため、同時に利用する複数のエントリポイントがある場合、複数のソリューションを使う。
他にも、ビルドの粒度のコントロールのために分割するという話もあるかもしれない。
なお、後の説明が通じやすいようにCommon, Server, Clientという名前をつけただけで、名前のつけかたはここでは触れない。Sharedといった他の汎用的な名前であったり、あるいはコードネームだったりとか、そういったものでも別に構わない。
子のMSBuildファイルには、各ソリューションごとの作業を記述する。親のMSBuildファイルには、MSBuildタスクによる子のターゲットの呼び出しと、組み合わせたときに必要になることを記述する。(とくになければ親側は単なる窓口になる。)
ただし、基本的にはCommonへのメソッドの追加などはCommonのソリューションを開いて実施する。read-onlyとは言わないが、あくまで使っている側は参照用というスタイルで使う。 というのも、Common側でビルドが完結することがはっきりしないといけないし、ユニットテストが実施できないからである(ServerやClientからCommonのユニットテスト プロジェクトへの参照があるのはおかしい)。
とはいえ名前変更のようなリファクタのときは入っているほうがよかったりするので、どちらで直していいかはケースバイケースではあるが。
(大規模な変更をするときは、merge-solutionsで全部入りのAll.slnというのを作ったりするがこれは完全にバッドノウハウ。)
標準のファイルシステムベースのプライベートリポジトリという方法(社内の開発環境の改善&効率化のためにNuGetを活用しよう - Build Insider)もあるが、ProGetのようにこのようなパッケージを管理できる製品もある。また、使ったことはないがMyGetのようなSaaSも利用できるだろう。
なお、チームが分割されている場合、例えば「共通基盤チーム」が存在する場合、NuGet配布にするとリリースサイクルなどがコントロールしやすくなる場合もある。配布元チームは利用側チームに適切なリリースとして見せられるし、利用側チームは適切なタイミングでバージョンアップできるからである。
しかし、決して処理順や参照変数などを追いやすい構造ではなく、これをやり始めるとそればっかりになってしまって、割に合わない。そして頑張って調べても、Visual Studioなどのバージョンによって変わってしまう可能性がある。
そもそも動かないといったトラブルシューティング的な部分であれば仕方ないが、~~というオプションがあれば後処理がいらなくて済むのになぁといった話は、時間を区切ってわかるレベルに留めるのが無難と思う。
MSBuildは、便利に使える範囲で使って、そこから出たらPowerShellなどのプログラムに任せる。
普通に考えるとMSBuild.exeで実行することになるが、そうなると「そうは言っても毎回コマンドを実行するのは辛い」という流れになり、GUIが欲しくなる。でもいいのがないね、という話で作ったのがMSBuild Launcherなので、そういう感じになったら使ってみてほしい。
例としてKiritoriMageのMSBuildファイルを開いた様子はこんな感じである。
なお、今よりもずっとノウハウがないときのプロジェクトなので全体のサンプルとしておすすめできるプロジェクトではないが、一例としてKiritoriMageのMSBuildファイルとしてはだいたいこんな感じである。
いろいろ作ってきていないもの(Portableライブラリ, ストアアプリなど)もあるので、配慮が欠けている部分もあるかもしれない。とはいえ、少しでも設計の参考になれば幸いである。
なお、最新にはついていっていない部分があるはずなので、変わっている部分があればぜひ教えて欲しい(DNXの影響はかなりありそう)。
そのような状況を踏まえながら、自分がソリューション/プロジェクトを作るときに考える点について、文書としてまとめたことはなかったと思い、以下に記述する。
Visual Studio全般に通じる部分も多いと思うが、基本的にC# プロジェクト(.csproj)を想定する。
前提: ソリューション/プロジェクトとは何か?
Visual Studioでは、アプリケーションをビルド(及び実行)するために必要なものをまとめたかたまりを「プロジェクト」と呼んでおり(*1)、「プロジェクトファイル」(*.なんとかproj)には、ファイル一覧や参照ライブラリ情報が記載されている。*1: プロジェクトは、アプリケーションをビルドするのに必要なものすべての論理的なコンテナーです。 - ソリューションとプロジェクトの作成
その「プロジェクト」を複数まとめたものが「ソリューション」となっており、プロジェクトの紐付けの情報が記録されているのが「ソリューションファイル」(*.sln)である。Visual Studioの1インスタンスは通常ひとつの「ソリューション」を開いている。
「プロジェクトファイル」(*.なんとかproj)/「ソリューションファイル」(*.sln)は、Visual StudioのGUIによって作成/編集される。
また、この「プロジェクトファイル」(*.なんとかproj) ファイルはMSBuildというビルドツールでビルドできる形式となっている。
MSBuildから実行できるプロジェクトファイルはVisual Studioに頼らずに一から作成することもでき、MSDNのドキュメント的にはそのファイルもプロジェクトファイルと呼ぶように見えるが、本記事では便宜上そのような1から書いたファイルは区別して「MSBuildファイル」と呼ぶことにする。
単一ソリューションの場合
まずはシンプルなひとつのソリューションに複数のプロジェクトという構成。Visual Studioからソリューションを作成することのほかに、やっていることがあるので、まずはその方針について記載する。ざっくり図で表すと、以下の通りである。
詳しく説明していくと、以下の通り。
コード記述時以外のビルドの窓口となるMSBuildファイルを用意する
コードを書いているときはVisual Studioでコードを書き、ビルドし、デバッグ実行する。それ以外の作業、例えばzipへのアーカイブであったり、サーバへのデプロイといった作業についてはVisual Studioで完結させるのではなく、Visual StudioでのプロジェクトをラップしたMSBuildファイルをMSBuild.exeなどでビルドし作業する。手続きを*.〜projに記述することもできるが、それは基本的にしない。これをする理由の一つ目は、機械的に生成される部分と人が意図を持って記述している部分を分けることにより、人が記述している部分の書きやすさ/読みやすさを向上させるためである。ツール(Visual Studioやその拡張)が生成/編集する*.〜projファイルに記述するとなると、生成/編集するツールの都合に合わせて記述しなければならない。ただの一覧のような設定ファイルであればそれほど問題ではないが、もっと自由に記述できるMSBuildでは非常にストレスフルな作業になる。また、Visual StudioのGUIから生成したわけではない追加の記述があることがGUIから分かり辛く、気づきにくい。別に記述してあれば、これらの問題はない。
二つ目は、CIシステムからのエントリポイントとして便利なためである。.csprojは先の通りMSBuild.exeでビルド可能なのだが、複数のcsprojを順にビルドするとか、渡すパラメータがいくつもあったりすると、そのMSBuild.exeの呼び出し自体がスクリプトのようになってきて、CIシステムのジョブの見通しが悪くなったり、変更管理がしにくくなったりする。しかし、CIシステム側からはこのMSBuildファイルを呼ぶだけと決めておけば、CIシステムでのジョブの記述内容が容易に想像できるようになり、またCIシステムに設定する前のタスクの事前確認がしやすくなったりする。
なお、このプロジェクトをラップしたMSBuildファイルは、私は「{{Name}}.msbuild」というファイル名をつけることが多いが、「{{Name}}.proj」でも別にかまわないし、そっちのほうがプロジェクトファイルとしての本筋っぽくはある。.msbuildで慣れてしまったのもあるが、Visual Studioで開くファイルではないよーということがわかっていいかなと思う。(この.msbuild拡張子を使っているプロジェクトもgithubで見かけるので、それほど好き勝手ではないと思っている。例えば、xunit)
MSBuildファイルは基本的にプロジェクトファイル(*.~proj)をビルドする
MSBuildは、MSBuildタスクで他のプロジェクトファイルをビルドすることができる。MSBuildファイルは、これを利用してVisual Studioで作ったプロジェクトをビルドする。なお、ソリューションをビルドすることもできるが、余分なビルドを減らすためにも、できる限りプロジェクトファイルを指定するほうが良い。
出力用フォルダを用意して後処理を可能にする
成果物出力フォルダはbin\Debug\のような各プロジェクトのデフォルトのパスではなく、「output\」のようなMSBuildファイル処理用の出力用フォルダを設定してそこに出力するようにする。MSBuildファイル上の記述としては、以下のようになる。これは、KiritoriMage.csprojを「Release」でoutput\KiritoriMageに出力する指示である。(OutputDirプロパティは事前に「output」と定義、MSBuildProjectDirectoryはMSBuildファイルが配置されているディレクトリ(自動定義))
これをすることで、出力用フォルダ以下は書き換え(削除)して良いことが明確になるため、様々な後処理が行いやすくなる。もちろん、bin\Debugの状態でもできなくはないのだが、「どこ?」となりにくいので処理しやすい。
後処理とは例えば、以下のようなことである。
- MSBuildで指定した変数に応じた環境情報を設定ファイルに設定する(*2)。
- NuGetで入れたパッケージが不要なファイルを成果物にしてくるので、削除する。
- 動的に生成するコンテンツを成果物に含める。
引き続きKiritoriMageのBuildターゲットで説明するとこんな感じである。ffmpegは使わないので消している。
やりなおしたいときもこのフォルダを削除すればよいだけというのは、いろいろな意味で楽である(この削除をする「Clean」ターゲットは用意しておく)。 ただし、この出力先フォルダの差し替えをするのが面倒なプロジェクトタイプがあった覚えがあるので、労力との見合いで実施する。
csprojには、可能な限りビルド構成は増やさない(Debug, Releaseだけで行けるならそうする)
ビルドのプロパティに関しては、MSBuildファイル側からできるだけ「Release」構成にプロパティで渡して変更することとして、可能な限りcsproj側のビルド構成(Configuration)は増やさない。理由は、ビルド構成はGUIでの設定となる都合上、どうしても設定内容をフラットに見せざるを得なくなるため、各構成間での差分に対する見通しが悪かったり(良く見せることが難しい、と言うべきか)、一部のみ違う構成の変更作業に手間がかかったりするためである。MSBuildファイル中にコードで指示していれば「このプロパティだけ違う」というのが意図として伝わりやすい(伝えるコードに書きやすい)。
プロパティで変更できなくても、XMLな設定ファイルがちょっと違うレベルなら、ビルド後にMSBuild Community TasksのXmlUpdateで更新してしまうという手もある。
この方針も、構成間の差分次第で考える。がっつり違うのであれば、別物として用意しておくのもひとつ。
ツール群は窓口MSBuildファイルから呼べるようにする
そのプロジェクトの便利ツールがある、といった場合、窓口となるMSBuildファイルにタスクを記述しておくと、そこに存在があること、及びどんなパラメータが必要なのか明確になって便利である。CIからも呼びやすい。正直これが便利だといえるのは、MSBuild LauncherのUIがこうだからというのも大きいが、この話だけ切り取って説明したのが「PowerShellにMSBuildLauncherで簡易GUIをつける」で、詳しい話はこちらを参照されたい。
複数のソリューションを使う場合とは
さて、複数のソリューションの方針を説明する前に、どんなときに複数になるのかという話。Visual Studioは、基本的に一つのソリューションに対して同時に一つのデバッグ実行を行うことが想定されている(ように私には見える)。そのため、複数の実行が同時に走るようなものを一つのソリューションに押し込むと、扱いにくい(一応実行はできなくはない)。例えば、Webサーバーをデバッグ実行しながらクライアントアプリをデバッグ実行するといったケースでは、サーバー側、クライアント側でそれぞれソリューションを作成すると、二つのVisual Studioを同時に起動し、それぞれでデバッグ実行を行えて便利である。
そのため、同時に利用する複数のエントリポイントがある場合、複数のソリューションを使う。
他にも、ビルドの粒度のコントロールのために分割するという話もあるかもしれない。
複数ソリューションの場合
上記の状況となる典型的なケース、ServerとClientが及びその共通部分であるCommonというソリューションを構成する場合を例に複数ソリューションの場合の方針について記載する。 まずは図で表すと、以下の通りとなる。なお、後の説明が通じやすいようにCommon, Server, Clientという名前をつけただけで、名前のつけかたはここでは触れない。Sharedといった他の汎用的な名前であったり、あるいはコードネームだったりとか、そういったものでも別に構わない。
各ソリューションごと、及びソリューションのまとまりごとにMSBuildファイルを用意する
先ほどのMSBuildファイルは、各ソリューションごと(子)、及びそのまとまりごと(親、通常トップにひとつ)に定義する。子のMSBuildファイルには、各ソリューションごとの作業を記述する。親のMSBuildファイルには、MSBuildタスクによる子のターゲットの呼び出しと、組み合わせたときに必要になることを記述する。(とくになければ親側は単なる窓口になる。)
別のソリューションのプロジェクトを参照してもよい
例えば、CommonのプロジェクトはServerとClientが参照してもOK。すべては追加せず、必要な分だけ参照する。ただし、基本的にはCommonへのメソッドの追加などはCommonのソリューションを開いて実施する。read-onlyとは言わないが、あくまで使っている側は参照用というスタイルで使う。 というのも、Common側でビルドが完結することがはっきりしないといけないし、ユニットテストが実施できないからである(ServerやClientからCommonのユニットテスト プロジェクトへの参照があるのはおかしい)。
とはいえ名前変更のようなリファクタのときは入っているほうがよかったりするので、どちらで直していいかはケースバイケースではあるが。
(大規模な変更をするときは、merge-solutionsで全部入りのAll.slnというのを作ったりするがこれは完全にバッドノウハウ。)
共通の定義を記述するMSBuildファイルを用意する
複数のプロジェクトにまたがって利用できる共通のターゲットがあれば、~.targetsという形で共有する形で追い出す。ディレクトリ階層は揃える
これはバッドノウハウではあるのだが、csproj内には相対パスで参照される項目があるので、csprojがあるフォルダ階層は可能な限り揃えるようにする。 例えば、こんな感じ。- XYZ.Server.Web\XYZ.Server.Web.csproj
- XYZ.Client.App\XYZ.Client.App.csproj
内部であってもNuGetパッケージを作成して利用する
上述のCommonは、共通に利用するアセンブリがあるということしか意味しないので、便利コードのようなものは、内部であってもNuGetパッケージにしてプライベートリポジトリで配布するとよい。標準のファイルシステムベースのプライベートリポジトリという方法(社内の開発環境の改善&効率化のためにNuGetを活用しよう - Build Insider)もあるが、ProGetのようにこのようなパッケージを管理できる製品もある。また、使ったことはないがMyGetのようなSaaSも利用できるだろう。
なお、チームが分割されている場合、例えば「共通基盤チーム」が存在する場合、NuGet配布にするとリリースサイクルなどがコントロールしやすくなる場合もある。配布元チームは利用側チームに適切なリリースとして見せられるし、利用側チームは適切なタイミングでバージョンアップできるからである。
その他のTips
MSBuild Community Tasksの活用
MSBuild Community Tasksというものがあり、Zipを作ったり、Version情報を生成したりするTaskなどがあるので、NuGetでインストールして活用する。csprojから参照されるtargetsファイルの構造に深入りしない
前述の通り、csprojファイルはMSBuildのプロジェクトファイルである。そのため、ビルド方法を記述したファイルをImportしており、追おうと思えばビルド作業を追うことができる。しかし、決して処理順や参照変数などを追いやすい構造ではなく、これをやり始めるとそればっかりになってしまって、割に合わない。そして頑張って調べても、Visual Studioなどのバージョンによって変わってしまう可能性がある。
そもそも動かないといったトラブルシューティング的な部分であれば仕方ないが、~~というオプションがあれば後処理がいらなくて済むのになぁといった話は、時間を区切ってわかるレベルに留めるのが無難と思う。
MSBuildは、便利に使える範囲で使って、そこから出たらPowerShellなどのプログラムに任せる。
MSBuild Launcherの活用
上述のMSBuildファイルはVisual Studioで開くためのファイルではないので、Visual StudioをGUIとしては使えないか、あるいは無理に使えるように作ってもVisual Studioがいろいろなターゲットを実行するために作られたUIではないので不便である。普通に考えるとMSBuild.exeで実行することになるが、そうなると「そうは言っても毎回コマンドを実行するのは辛い」という流れになり、GUIが欲しくなる。でもいいのがないね、という話で作ったのがMSBuild Launcherなので、そういう感じになったら使ってみてほしい。
例としてKiritoriMageのMSBuildファイルを開いた様子はこんな感じである。
なお、今よりもずっとノウハウがないときのプロジェクトなので全体のサンプルとしておすすめできるプロジェクトではないが、一例としてKiritoriMageのMSBuildファイルとしてはだいたいこんな感じである。
おわりに
自分でも、最初からこういうふうに作るわけではなく、ソリューションとプロジェクトファイルしかない場合や、ソリューション側のmsbuildがなかったりと、実際のところはもう少しいろいろ違っている。いろいろ作ってきていないもの(Portableライブラリ, ストアアプリなど)もあるので、配慮が欠けている部分もあるかもしれない。とはいえ、少しでも設計の参考になれば幸いである。
なお、最新にはついていっていない部分があるはずなので、変わっている部分があればぜひ教えて欲しい(DNXの影響はかなりありそう)。