Visual Studio 2015 Update 1で、.csproj + project.json 構成のプロジェクトを、.xproj プロジェクトから参照しようとしたら素直にはできなったという話と、自力で何とかしたという話。

大雑把に言って

面倒ごとが起きる原因はおおむね、ASP.NET 系の作業が来年初頭に伸びてるのと、UWP 系の作業が今年7月に前倒したのとの合わせ技のせい。

たぶん、ASP.NET 5系/.NET CoreのRTM(来年の第1四半期内という予定)までには治るかなぁ。

ASP.NET 系

オープンソース化とかクロスプラットフォーム路線を一番推し進めてるのはここ。.NET Coreのランタイム(CoreCLR)を一番欲してるのもここ。

xproj 形式

ASP.NET 5や、.NET Core向けライブラリ、.NET Core向けコンソール アプリを書く場合、プロジェクトが.xprojという新しい形式になっています。

ASP.NET 系ではプロジェクト中のソースコードとかパッケージ管理を簡素化したいみたい。既存の.csproj (もしくはVBの場合は.vbproj)との違いは以下の表のような感じ。

これまで(.csproj形式) 新方式(.xproj形式)
ファイル管理 `.csproj`ファイルの中に、ビルドに含めたいファイルを全部1行1行書く 特定フォルダー以下のC#ファイル全部がビルド対象。除外したいものだけ`exclude`設定を書く
ライブラリ参照 プロジェクト参照、DLLの直接参照、NuGetパッケージ参照が分かれてる。特に、NuGetパッケージは`packages.config`という別ファイルに記述 この3つを一元化
ビルド処理 XAMLとか、ビルド時処理が必要なものを含めたり、汎用的な使い方ができる 基本的にWeb用で、HTMLやJavaScriptみたいにそのままデプロイしたいファイルか、C#ファイルのコンパイルくらい

project.json

実際のところ、.xprojファイルの中にはVisual Studioに関する設定しか入っていなくて、実際プロジェクトのビルドに必要な情報は全部project.jsonという名前のJSONファイルに入っています。コマンドライン ビルドするなら.xprojファイルは不要。

project.jsonには、以下のような情報が入っています。

  • (プロジェクトをNuGetパッケージ化するとき用の)名前、バージョン、作者など
  • ターゲット
    • フレームワークのバージョン(.NET 4.5とか)
    • 実行環境(Windows とか)
  • 依存関係
    • NuGetパッケージ
    • 他のプロジェクト
    • DLLを直接参照

NuGet 3

.NET Coreは、Windowsデスクトップ向けの.NET Frameworkと違って、ライブラリの参照を、たとえ標準ライブラリでも、必要な分だけNuGetパッケージとして参照する仕組みになっています。一枚岩リリース/巨大インストーラーの撤廃。パッケージ リリース化。

ということでNuGetの利用が加速すると思われるわけですが、そこで、NuGetの利便性向上が必須。 これまでのNuget(2系、packages.config)には以下のような問題がありました。

  • 1つのプロジェクトを複数のソリューションから参照するとビルドできなくなる
    • ダウンロードしてきたパッケージをソリューション単位でキャッシュする
    • .csproj側にキャッシュしたDLLへの相対パスが入る
    • ソリューションの場所が違うと相対パスが狂う
  • 依存関係を芋ずる式に含む
    • 依存先のさらに依存先も、packages.config.csprojに情報が追加される
    • 「依存先の依存先」とかも含めたバージョン管理がむちゃくちゃ大変に

例えば、Rx-Mainを参照したとします。まず、packages.configっていうXMLファイルができて、以下のような状態に。

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Rx-Core" version="2.2.5" targetFramework="net46" />
  <package id="Rx-Interfaces" version="2.2.5" targetFramework="net46" />
  <package id="Rx-Linq" version="2.2.5" targetFramework="net46" />
  <package id="Rx-Main" version="2.2.5" targetFramework="net46" />
  <package id="Rx-PlatformServices" version="2.2.5" targetFramework="net46" />
</packages>

Rx-InterfacesとかRx-Linqは、Rx-Mainの依存先です。芋ずる式に全部。

そして、このファイルだけじゃなくて、.csprojファイルにも以下のような行が追加されます。

    <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
      <HintPath>..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll</HintPath>
      <Private>True</Private>
    </Reference>
    <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
      <HintPath>..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll</HintPath>
      <Private>True</Private>
    </Reference>
    <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
      <HintPath>..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll</HintPath>
      <Private>True</Private>
    </Reference>
    <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
      <HintPath>..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
      <Private>True</Private>
    </Reference>

そこで、NuGet 3系(7月のVS 2015リリースの頃に3.0、今は3.3)では、パッケージの管理方式を変えました。

  • ダウンロードしてきたパッケージはユーザー単位でキャッシュ(ユーザー フォルダー直下に.nugetフォルダーを作ってそこで一元管理)
  • .csproj.xprojはノータッチ、project.jsonにだけ情報を記録
  • 直接の依存先だけを記録

例えば、同じくRx-Mainを参照すると、project.jsonファイルが以下のような状態になります。

{
  "frameworks": {
    "net46": {}
  },
  "runtimes": {
    "win": {},
    "win-anycpu": {}
  },
  "dependencies": {
    "Rx-Main": "2.2.5"
  }
}

だいぶすっきりして、管理が楽になりました。

そして、NuGet 3系は、これまでのpackages.configを止めて、ASP.NET 5に合わせてproject.jsonを使うようになりました。

UWP 系

Universal Windows Platformアプリ。要するにWindows 10向け。

元々は.NET Coreと同じスケジュール感で動いていた雰囲気があるんですが、Windows 10の前倒しに合わせて、UWP系ツールのリリースも今年7月に前倒しになりました。

ライブラリに関しては、UWPと.NET Coreは共通のものを使います(.NET Core向けフレームワーク、略してCoreFX)。 その結果、.NET CoreはRC(7月時点ではベータ)なのに、UWPはUpdate 1(7月時点ではRTM)。Update 1なUWPが、RCなCoreFXに依存しているという状態になっていたりします。

ちなみに、CoreFXの祖先はWindows 8のストア アプリ(かつてメトロと呼ばれていた何か)向け.NETです。Windows 8の時点で、パッケージ リリース化・クロスプラットフォーム化を前提としたライブラリ整理が完了しています。 なので、.NET Core全体はRCといっても、CoreFXの部分は十分こなれてはいます。

.csproj + project.json

ここで、UWP系ツールの苦悩が。

  • 割かしWeb前提なところがある.xprojは使えない
  • でも、NuGet 3系(project.jsonに移行)は使いたい

ということで、UWP系ツールは.csproj + project.jsonというハイブリッド構成になっています。

ここでおさらい。

  • .xprojにはVisual Studioが必要とする少ない情報しか入っていなくて、プロジェクト関連の設定は全部project.jsonに入ってる
  • .csprojにはプロジェクト関連の設定が全部入ってる
    • NuGet 2系では、NuGetパッケージ参照情報がpackages.config.csprojで2重管理になっていた

ということで、UWP系ツールでは、

  • .csprojからはNuGetパッケージ参照情報を消した
    • NuGet 3系リリースと同時にVisual Studio側にも手が入って、project.json側だけ見ればよくなった
  • project.jsonにはNuGetパッケージ参照情報だけが入ってる(.xprojで使う場合のサブセット)

という状態になっています。

任意のプロジェクトでNuGet 3

NuGet 3系はproject.jsonを前提にしています。ASP.NET 5系は最初からproject.jsonで、NuGet 3系です。UWP系は.csproj + project.jsonというハイブリッド構成でNuGet 3系に対応しました。さて、じゃあ、既存の.csprojで作っていたライブラリやコンソール アプリ、WPFアプリなどはどうか。

.csproj + project.jsonは、公式ドキュメントによれば以下のような状態です。

  • 公式にプロジェクト テンプレートがあって、「マイクロソフトがサポートします」と言っているのはUWPだけ
  • ただし、手動でpackages.configproject.jsonに置き換えれば、実は任意の.csprojでNuGet 3系を使える

ということで、packages.configからproject.jsonへの移行プログラムを自分で書いたのが以下のものになります。

.xprojから.csprojを参照

なにぶん.xprojは新しい試みなので、まだまだ足りてないところがあります。ASP.NET 5や.NET Core本体がだいぶ安定してきても、Visual Studio上の.xprojの挙動が安定するまではRTMにできないんだろうなぁという雰囲気。

その1つが.xprojから既存の.csprojで作ったライブラリをプロジェクト参照する方法。以下のようなラッパーとなるproject.jsonを書いてやる必要があります。

{
  "version": "1.0.0-*",
  "frameworks": {
    "net35": {
      "wrappedProject": "../../ClassLibraryNet35/ClassLibraryNet35.csproj",
      "bin": {
        "assembly": "../../ClassLibraryNet35/obj/{configuration}/ClassLibraryNet35.dll",
        "pdb": "../../ClassLibraryNet35/obj/{configuration}/ClassLibraryNet35.pdb"
      }
    }
  },
  "dependencies": { }
}

.xprojが出た当初はこいつの手書きが必須でした。Visual Studio 2015がRTMしたころには、一応Visual Studioがこいつを生成してくれるようにはなっています。

ただ、ここでまた問題があって、.csproj + project.json構成にしてしまった.csprojプロジェクトを参照すると挙動が狂います。 上記のようなラッパーproject.jsonがまともに作られなくなったり、 .csprojで参照している側のproject.jsonが変に書き換わってビルドできなくなったり。

公式には、UWP系以外の.csproj + project.json構成は「できるけどサポート対象外」なので、この問題は現状では「自己責任」扱いではあります。

ということで、結局、ラッパーproject.jsonの生成も自前でプログラム書きました。

まとめ

ASP.NET 5系は来年第1四半期にRTM予定、UWP系は今年7月にRTM済み。そんな中、ASP.NET 5系とUWP系で共通で使っている部分が結構カオス。

それ以外のタイプのプロジェクトでも、自力で.csproj + project.json構成にすればNuGet 3系の恩恵にあずかれます。ただし、現状は「できるけどマイクロソフトのサポート対象外、自己責任」。ということで自力で何とかするプログラム書きました。

おそらく、ASP.NET 5系RTMの来年第1四半期に合わせて、この辺りの挙動をやっと仕上げるんじゃないかなぁという気はしています。それまでの数か月間のつなぎに。