SDK-style projectファイルのビルドイベント

.NET Coreに対応するために、C#の従来スタイルのプロジェクトファイルをSDKスタイルのプロジェクトファイルに変換したときに、ビルドイベントで失敗したことについて報告します。

.NET Frameworkだけに対応していたC#のプロジェクトを、.NET Frameworkと.NET Coreの両対応にしたいときは、プロジェクトファイルを従来スタイルからSDKスタイルに変更する必要があります。

従来スタイルプロジェクトでのビルドイベント

プロジェクトの設定にはビルド イベントの設定があります。この設定には、ビルド前にカスタムの処理を行う「ビルド前イベントのコマンドライン」とビルド後にカスタムの処理を行う「ビルド後イベントのコマンドライン」があります。

従来スタイルのプロジェクトで、「ビルド前イベントのコマンドライン」と「ビルド後イベントのコマンドライン」に以下の8行のコマンドを追加します。

echo Configuration: "$(Configuration)"
echo ConfigurationName: "$(ConfigurationName)"
echo SolutionPath: "$(SolutionPath)"
echo TargetPath:   "$(TargetPath)"
echo SolutionDir:  "$(SolutionDir)"
echo ProjectPath:  "$(ProjectPath)"
echo ProjectDir:   "$(ProjectDir)"
echo ProjectFileName: "$(ProjectFileName)"

すると、従来スタイルのプロジェクトファイルでは、以下のように設定されます。

  <PropertyGroup>
    <PreBuildEvent>echo Configuration: "$(Configuration)"
echo ConfigurationName: "$(ConfigurationName)"
echo SolutionPath: "$(SolutionPath)"
echo SolutionDir:  "$(SolutionDir)"
echo ProjectPath:  "$(ProjectPath)"
echo ProjectDir:   "$(ProjectDir)"
echo TargetPath:   "$(TargetPath)"
echo ProjectFileName: "$(ProjectFileName)"</PreBuildEvent>
  </PropertyGroup>
  <PropertyGroup>
    <PostBuildEvent>echo Configuration: "$(Configuration)"
echo ConfigurationName: "$(ConfigurationName)"
echo SolutionPath: "$(SolutionPath)"
echo SolutionDir:  "$(SolutionDir)"
echo ProjectPath:  "$(ProjectPath)"
echo ProjectDir:   "$(ProjectDir)"
echo TargetPath:   "$(TargetPath)"
echo ProjectFileName: "$(ProjectFileName)"</PostBuildEvent>
  </PropertyGroup>

ビルドを実行すると、ビルドの出力には

1>------ ビルド開始: プロジェクト: Test, 構成: Debug Any CPU ------
1>  Configuration: "Debug"
1>  ConfigurationName: "Debug"
1>  SolutionPath: "C:\src\Test2019\Test2019.sln"
1>  TargetPath:   "C:\src\Test2019\Test\bin\Debug\Test.exe"
1>  SolutionDir:  "C:\src\Test2019\"
1>  ProjectPath:  "C:\src\Test2019\Test\Test.csproj"
1>  ProjectDir:   "C:\src\Test2019\Test\"
1>  ProjectFileName: "Test.csproj"
1>  InstallerCleaner -> C:\src\Test2019\Test\bin\Debug\Test.exe
1>  Configuration: "Debug"
1>  ConfigurationName: "Debug"
1>  SolutionPath: "C:\src\Test2019\Test2019.sln"
1>  TargetPath:   "C:\src\Test2019\Test\bin\Debug\Test.exe"
1>  SolutionDir:  "C:\src\Test2019\"
1>  ProjectPath:  "C:\src\Test2019\Test\Test.csproj"
1>  ProjectDir:   "C:\src\Test2019\Test\"
1>  ProjectFileName: "Test.csproj"
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========

と表示されいます。

SDKスタイルプロジェクトでのビルドイベント

同様に、SDKスタイルのプロジェクトで、「ビルド前イベントのコマンドライン」と「ビルド後イベントのコマンドライン」に以下の8行のコマンドを追加します。

echo Configuration: "$(Configuration)"
echo ConfigurationName: "$(ConfigurationName)"
echo SolutionPath: "$(SolutionPath)"
echo TargetPath:   "$(TargetPath)"
echo SolutionDir:  "$(SolutionDir)"
echo ProjectPath:  "$(ProjectPath)"
echo ProjectDir:   "$(ProjectDir)"
echo ProjectFileName: "$(ProjectFileName)"

すると、SDKスタイルのプロジェクトファイルでは、以下のように設定されます。

  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec Command="echo Configuration: &quot;$(Configuration)&quot;&#xD;&#xA;echo ConfigurationName: &quot;$(ConfigurationName)&quot;&#xD;&#xA;echo SolutionPath: &quot;$(SolutionPath)&quot;&#xD;&#xA;echo TargetPath:   &quot;$(TargetPath)&quot;&#xD;&#xA;echo SolutionDir:  &quot;$(SolutionDir)&quot;&#xD;&#xA;echo ProjectPath:  &quot;$(ProjectPath)&quot;&#xD;&#xA;echo ProjectDir:   &quot;$(ProjectDir)&quot;&#xD;&#xA;echo ProjectFileName: &quot;$(ProjectFileName)&quot;" />
  </Target>

  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Command="echo Configuration: &quot;$(Configuration)&quot;&#xD;&#xA;echo ConfigurationName: &quot;$(ConfigurationName)&quot;&#xD;&#xA;echo SolutionPath: &quot;$(SolutionPath)&quot;&#xD;&#xA;echo TargetPath:   &quot;$(TargetPath)&quot;&#xD;&#xA;echo SolutionDir:  &quot;$(SolutionDir)&quot;&#xD;&#xA;echo ProjectPath:  &quot;$(ProjectPath)&quot;&#xD;&#xA;echo ProjectDir:   &quot;$(ProjectDir)&quot;&#xD;&#xA;echo ProjectFileName: &quot;$(ProjectFileName)&quot;" />
  </Target>

ビルドを実行すると、ビルドの出力には

1>------ ビルド開始: プロジェクト: Test, 構成: Debug Any CPU ------
1>Configuration: "Debug"
1>ConfigurationName: "Debug"
1>SolutionPath: "C:\src\Test2019\Test2019.sln"
1>TargetPath:   "C:\src\Test2019\Test\bin\Debug\netcoreapp3.0\Test.dll"
1>SolutionDir:  "C:\src\Test2019\"
1>ProjectPath:  "C:\src\Test2019\Test\Test.csproj"
1>ProjectDir:   "C:\src\Test2019\Test\"
1>ProjectFileName: "Test.csproj"
1>Test-> C:\src\Test2019\Test\bin\Debug\netcoreapp3.0\Test.dll
1>Configuration: "Debug"
1>ConfigurationName: "Debug"
1>SolutionPath: "C:\src\Test2019\Test2019.sln"
1>TargetPath:   "C:\src\Test2019\Test\bin\Debug\netcoreapp3.0\Test.dll"
1>SolutionDir:  "C:\src\Test2019\"
1>ProjectPath:  "C:\src\Test2019\Test\Test.csproj"
1>ProjectDir:   "C:\src\Test2019\Test\"
1>ProjectFileName: "Test.csproj"
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========

と表示されいます。

SDKスタイルプロジェクトに従来形式のタグで設定したビルドイベント

最後に、SDKスタイルのプロジェクトで、従来形式のタグで設定したビルドイベントを想定します。これは、従来スタイルのプロジェクトをSDKスタイルのプロジェクトに変更したときに、ビルドイベント設定系のタグをそのままにした場合を想定しています。

XMLエディターを使用して、SDKスタイルのプロジェクトファイルに、以下の設定を追加します。

  <PropertyGroup>
    <PreBuildEvent>echo Configuration: "$(Configuration)"
echo ConfigurationName: "$(ConfigurationName)"
echo SolutionPath: "$(SolutionPath)"
echo SolutionDir:  "$(SolutionDir)"
echo ProjectPath:  "$(ProjectPath)"
echo ProjectDir:   "$(ProjectDir)"
echo TargetPath:   "$(TargetPath)"
echo ProjectFileName: "$(ProjectFileName)"</PreBuildEvent>
  </PropertyGroup>
  <PropertyGroup>
    <PostBuildEvent>echo Configuration: "$(Configuration)"
echo ConfigurationName: "$(ConfigurationName)"
echo SolutionPath: "$(SolutionPath)"
echo SolutionDir:  "$(SolutionDir)"
echo ProjectPath:  "$(ProjectPath)"
echo ProjectDir:   "$(ProjectDir)"
echo TargetPath:   "$(TargetPath)"
echo ProjectFileName: "$(ProjectFileName)"</PostBuildEvent>
  </PropertyGroup>

もし、SDKスタイルのビルドイベント設定(<Target Name="PreBuild"...><Target Name="PostBuild"...>)が既に存在したときは、その設定を削除してから追加します。

設定後に、プロジェクトのプロパティを確認すると、ビルドイベントの設定が確認できます。

SDKスタイルのプロジェクトファイルに従来スタイルのビルドイベント設定したときのGUI

従来形式のタグでビルドイベントを設定したとしても、ビルドイベント設定のGUIには、問題なく表示します。Visual Studioは、SDKスタイルのプロジェクトファイルに対して、従来形式のタグもSDKスタイルのタグも正しく認識しています。

この状態で、ビルドを実行すると、ビルドの出力には

1>------ ビルド開始: プロジェクト: Test, 構成: Debug Any CPU ------
1>Configuration: "Debug"
1>ConfigurationName: ""
1>SolutionPath: "C:\src\Test2019\Test2019.sln"
1>SolutionDir:  "C:\src\Test2019\"
1>ProjectPath:  ""
1>ProjectDir:   ""
1>TargetPath:   ""
1>ProjectFileName: ""
1>Test-> C:\src\Test2019\Test\bin\Debug\netcoreapp3.0\Test.dll
1>Configuration: "Debug"
1>ConfigurationName: ""
1>SolutionPath: "C:\src\Test2019\Test2019.sln"
1>SolutionDir:  "C:\src\Test2019\"
1>ProjectPath:  ""
1>ProjectDir:   ""
1>TargetPath:   ""
1>ProjectFileName: ""
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========

と表示されいます。SDKスタイルのプロジェクトファイルに従来形式のタグでビルドイベントを設定しても、ビルドイベントは実行されます。

しかし、明らかに、状況が異なります。期待する動作になっていません。ソリューション系の変数は、ちゃんと展開できていますが、プロジェクト系の変数はすべて値が空です。

SDKスタイルへのコンバートではビルドイベントの修正が必須

上記のように、SDKスタイルのプロジェクトファイルに従来形式のタグを使ってビルドイベントを設定したら、

  • Visual StudioのGUIでは正しく表示される
  • ビルドではビルドイベントが実行される

のように問題なく動作するように見えます。しかし、実際のビルドでは、一部の変数が正しく設定されないことがわかりました。

そのため、従来形式のプロジェクトファイルをSDKスタイルのプロジェクトファイルに変更するときは、ビルドイベントの設定をSDKスタイルの新しいタグを使用することが必須となります。

このことは、マイクロソフトのサイトにも記載されています。ビルドイベント(csproj形式に追加されたもの)に書かれています。SDKスタイルのプロジェクトファイルで、従来形式のビルドイベント設定タグを使うと「$(ProjectDir) などのマクロが解決されない」と記載されています。ドキュメントには、$(ProjectDir)のマクロが例に上がっていますが、実験の結果からは、プロジェクト関連のマクロ全般が解決されないようです。

ビルドイベント設定のSDKスタイルへのコンバート方法

従来形式のタグでは、ビルドイベントコマンドは、タグ(Element)のコンテンツとして保存されています。しかし、SDKスタイルのタグでは、タグ(Element)の属性(Attribute)として保存されます。

ここでプロジェクトファイルの編集時には注意が必要です。それは、コンテンツとして設定するときと、属性(Attribute)と設定するときでは、使用できる文字種が異なります。属性の方が使用できる文字種が少ないのです。XMLファイル上では、使用できない文字はエスケープする必要があります。そのため、単純なコピー&ペーストでは変換できません。

では、簡単に変換する方法はあるのでしょうか?

はい、あります。幸い、Visual Studioは、SDKスタイルのプロジェクトファイルであっても、従来形式のタグのビルドイベント設定でもSDKスタイルのタグのビルドイベント設定も、GUIの設定画面では正しく表示されます。

これを利用して、簡単に変換できます。手順は以下の通りです。

  1. プロジェクトファイルを従来形式からSDKスタイルに変更する(ビルドイベント設定のタグは従来形式のままでよい)
  2. SDKスタイルのプロジェクトファイルをVisual Studioで開く
  3. プロジェクトのプロパティを開く
  4. ビルドイベント設定画面を開く
  5. ビルドイベントのコマンドラインの設定を一時的に退避する(コピー&ペーストでよい)
  6. ビルドイベントのコマンドラインの設定から設定内容を削除する
  7. プロジェクトファイルの変更を保存する(このタイミングでプロジェクトファイルから従来形式のビルドイベント設定は削除されます)
  8. 再度、ビルドイベント設定画面を開く
  9. 一時的に退避しておいたコマンドライン設定を戻す
  10. プロジェクトファイルの変更を保存する(このタイミングでプロジェクトファイルには、SDKスタイルのビルドイベント設定で保存されます)

上記の手順を踏むことにより、GUI上で設定している内容は全く同じものでですが、ビルドイベント設定はSDKスタイル形式で保存され、Visual Studio側で必要に応じてエスケープ処理をしてくれます。


今回の投稿は、私が、C#のプロジェクトファイルを従来形式からSDKスタイルに変更したときに迷った点とその対策方法でした。

コメントを残す