Visual Studioで、ソリューションフィルタ(.slnf)を使う

# はじめに

Visual Studioには、ソリューションの中にある任意のプロジェクトをフィルタリングすることができる。
今回は、このプロジェクトのフィルタリング機能について書く。

公式ドキュメントは https://docs.microsoft.com/en-us/visualstudio/msbuild/solution-filters?view=vs-2019 に存在する。

# プロジェクトのフィルタリングとは何か

Visual Studioのプロジェクトは、一つのソリューション(.sln)と、その配下に所属する複数のプロジェクト(.*proj)で構成される。
配下に所属する任意のプロジェクトをアンロードすることにより、そのプロジェクトを完全に無視することが可能になる。

アンロード機能自体は(多分)結構前から存在していたが、slnfファイルが使えるようになったのは**msbuildは16.7以降、VSは2019以降**と思われる。

## なぜフィルタリング機能を使うのか

小規模プロジェクトならば、配下に所属するプロジェクトは5つにも満たない数になるだろうが、
そこそこ大きなプロジェクトになると、簡単に二桁を超える数となる(場合によっては三桁?)。
この時、潤沢なリソースを持つマシンでも、ソリューションを開くたび、あるいはビルドするたびに時間がかかることになり、非常にストレスを抱えることになる。
また、大規模なプロジェクトになればなるほど、全てのプロジェクトを自分が担当するということは少なくなり、少数のプロジェクトのみ開くことができれば開発業務が可能になるということもあるだろう。
更に、CI等でビルドするとき、例えばプラットフォームごとにビルドするプロジェクトを限定したいときもあるかもしれない。

そのようなとき、プロジェクトのフィルタリング機能を駆使すれば、特定の範囲以外はプロジェクトを無視することが可能になるため、より快適な開発ができるようになるだろうと思う。

## フィルタリング機能で制御できないもの

* ソリューション構成マネージャのPlatformとConfigurationの組み合わせ
* ソリューションアイテム(プロジェクトに属さないファイル等)

# フィルタリングファイル(.slnf)とは

プロジェクトのロード/アンロード自体はVSから行えるが、通常フィルタリング設定をローカルを超えて共有することはできない。
また、Visual Studioとしては、記憶できる設定は一つのみで、複数のフィルタリング設定(プラットフォーム別等)を保存しておくことは、通常できない。
ソリューションの構成マネージャでも、ビルドする・しない程度は変更可能だが、数が増えてくるとsln内部が複雑になり、読み込むプロジェクトは結局変わらないため、ソリューションの読み込みでロスになるという所はあまり変わらない。
そこで、フィルタリング設定を複数持ちたい、あるいはローカルの範囲を超えて共有したい場合のために、ファイルとして保存できるようになった。そこで使われているのがslnfファイルとなる。
注意点として、**ソリューションアイテム(プロジェクトに属さないファイル等)はアンロードができない**ので、slnfファイルを用いてもソリューションアイテムを無視することはできない。

## slnfのフォーマット

実態は下記のようなオブジェクトを持つjsonファイル。
JSONPathの構文は、 https://github.com/json-path/JsonPath を参照

|JSONPath|型 |意味|
|--------|---|---|
| `$.solution` |object|ルートオブジェクト|
| `$.solution.path` |string|ソリューションファイルへのパス(slnfからの相対パスでも可)|
| `$.solution.projects[0:]` |string|読み込み対象のプロジェクト|

例は以下

```json
{
  "solution": {
    "path": "path/to/sln",
    "projects": [
      "app1\\app1.csproj",
      "app2\\app2.csproj"
    ]
  }
}
```

なお、フォーマットに対する公式ドキュメントは今のところ見つかってないため、予告なく変更される場合もあるので注意が必要。

## `$.solution.path`

ソリューションファイルへのパス。slnfから見た相対パスでも可。
パス区切り文字については、`/`と`\`どちらも受け付けるようだ。

## `$.solution.projects`

ロードしたいプロジェクトのリスト。
**slnからの相対パスを指定すること(もう少し厳密にいうと、sln内部にあるプロジェクトファイルへのパスと同じ値にすること)。**
また、文字種の違いとパスセパレータの違いについて、VSの場合は大文字小文字の違いOK、パスセパレータが異なっていてもOKという比較的緩い基準だが、
dotnet-sdkの`dotnet build`コマンドについては、**正確にslnファイル内部のプロジェクトファイルパスと同じでなければエラーを出す**ので注意

## Visual Studioでの作り方

書式としてはそれほど複雑ではないので、書こうと思えば書くのは容易だが、まとめて作成する場合や、実際の動作確認をしながら行う場合は、Visual Studioで作ることも可能(というよりVSで作るのが正統かも)。

作り方としては、

1. 普通にソリューションを開く
2. 読み込みたくないプロジェクトを右クリック
3. "プロジェクトのアンロード"を選択
4. 読み込まないプロジェクトを全てアンロードするまで繰り返す
5. ソリューションエクスプローラのソリューション部分を右クリックして、"ソリューションフィルタとして保存"を選択
6. 任意のパスに拡張子`.slnf`として保存する

となる。

### 少数のプロジェクトだけロードしたいとき

上記はアンロードするプロジェクトが少ない場合の方法で、逆にロードするプロジェクトが、全体のプロジェクトに比べて圧倒的に少ない場合は、

```
devenv(Visual Studioのコマンド) /DoNotLoadProjects [開きたいソリューションファイル]
```

とすれば、全てのプロジェクトがアンロードされた状態で開かれるので、ロードしたいプロジェクトを右クリックしてロードして、slnfを保存する。
`/DoNotLoadProjects`については https://docs.microsoft.com/en-us/visualstudio/ide/reference/donotloadprojects-devenv-exe?view=vs-2019 を参照

# 使い方

slnfを作成したら、今度はそれを使う段階になる。
使い方としては、slnと全く同様にVisual Studioでソリューションファイルとして開くことができる。
ソリューションを開くと、slnfで指定されたプロジェクトのみがロードされた状態で起動される。
また、`dotnet build`等、コマンドラインからビルドを行いたい場合は、`dotnet build path/to/slnf`とすれば、ロード対象としたプロジェクトをビルドしていく。
ちなみに [vscodeのC#拡張](https://github.com/OmniSharp/omnisharp-vscode) でも使用は可能。

## 編集したい場合

VisualStudioは、slnfを最初に読み込んだ時点でslnfを使うのを止めるため、作成したslnfファイルを編集したい場合は、Visual Studio上でファイルを編集することが可能。
ソリューションアイテムとしてslnに追加しておくのが良いかもしれない。
編集後、slnfを開きなおせば設定が反映される。

**VS上でロード/アンロードを行っても、slnfの方には変更が入らないことに注意すること**

## 依存関係

プロジェクト間に依存関係がある場合は以下のような動作になる

* slnに依存関係の記述がある場合(ソリューションを右クリックして"プロジェクトの依存関係"で設定するやり方)は、アンロードされたプロジェクトは無視される
* csprojでProjectReferenceを指定していた場合
    * VSは無視する
    * dotnet-sdkは無視せずビルドを行う

VSとdotnet-sdkで若干挙動が異なることに注意

# おわりに

実際複数人が関わる程度の規模になると、このフィルタリング機能というのは実際有効になると思うので、積極的に導入していきたい。
また、CI等ではビルド時間の節約につながるため、場合によってはかなり有用な場合もあるかもしれない。
これの存在を知ったのが https://twitter.com/neuecc/status/1423424343611043843 のツイートで、まだ時間が浅いので漏れや間違い等あるかもしれないので、そのようなことがあれば、随時修正していきたい。