きよくらの備忘録

「三日坊主と呼ばせない!日記」改め。主にソフトウェア開発関連の話題。

CLIツールを使わずにコード中からプロジェクトをビルドしたりdacpacをデプロイしたりする(その1)

先日、こんなエントリを書いた。 kiyokura.hateblo.jp

要約すると『コード中からMSBuild.exeを使ってプロジェクトファイルからdacpacをビルドして、それをSqlPackage.exeを使ってデプロイする』という内容である。 上記エントリではMSBuildやSqlPackageを利用するために割と無理やり泥臭くそれらのパスを取得しているところがなんだかアレであったり、そもそもローカルの環境に事前にインストールされているものに強く依存しているあたりもアレだったりするので、そのあたりで若干悶々としていた。

そんな折にこの上記内容をチームのメンバに展開したところ、優秀なチームメンバから

  • SqlPackage.exeのところDacFxで置き換えれるのではないか?
  • MsBuild.exeのところはMicrosoft.Build.Evaluation 名前空間のライブラリで置き換えれるのではないか?

という素晴らしい指摘をいただいた。 ので、さっそく試してみることにした。

Microsoft.Build.Evaluation でプロジェクトをビルドする(その1:csproj編)

Microsoft.Build.Evaluation名前空間の機能は主に NuGetパッケージ の Microsoft.Build で提供されている模様。 www.nuget.org

早速、新規にコンソールアプリを作成して上記パッケージを取り込み、以下のようなコードで別のシンプルなクラスライブラリのビルドを試みた。

var p1 = ProjectCollection.GlobalProjectCollection.LoadProject(@"C:\MyClassLib\MyClassLib.csproj");
p1.Build();

だが、上記コードでコンパイルは通るのだがBuild()メソッド実行箇所で以下の例外が発生した。

Microsoft.Build.Shared.InternalErrorException
  HResult=0x80131500
  Message=MSB0001: Internal MSBuild Error: Type information for Microsoft.Build.Utilities.ToolLocationHelper was present in the whitelist cache as Microsoft.Build.Utilities.ToolLocationHelper, Microsoft.Build.Utilities.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a but the type could not be loaded. unexpectedly null
  Source=Microsoft.Build

調べてみるとビルド実行には Microsoft.Build.Tasks.Core パッケージも必要になる模様。

www.nuget.org

これのパッケージを追加すると無事に上記コードでクラスライブラリが生成された。

Microsoft.Build.Evaluation でプロジェクトをビルドする(その2:sqlproj編)

次は同様にSQL Server Databaseプロジェクトのビルドを試みるため、以下のようなコードを実行してみた。

var p2 = ProjectCollection.GlobalProjectCollection.LoadProject(@"C:\MyDataBase\MyDataBase.sqlproj");
p2.Build();

しかし実行で例外などは発生しないものの、成果物が生成されていなかった。 少し調べてみると以下のことが分かった。

  • 実行されたビルドが失敗に終わっても例外は発生しない(Buildメソッドの実行自体は成功してるのでそれはそうだろう)
  • ビルドの成否はBuildメソッドの戻り値としてbool値で返される
  • ビルドの実行ログはBuildメソッドにロガーを渡すことで取得できる
    • ロガーは Microsoft.Build.Framework.ILogger の実装である必要がある
    • 既定でFileLoggerやConsoleLoggerが実装されている

という事で、以下のようにコードを書き換えて再度実行してみた。

var p2 = ProjectCollection.GlobalProjectCollection.LoadProject(@"C:\MyDataBase\MyDataBase.sqlproj");
var p2Result = p2.Build(new[] { new ConsoleLogger() });
Console.WriteLine($"p2 result : {p2Result}");

すると以下のようなビルドエラーが出力された。つまるところSQL Server Databaseプロジェクトをビルドするためのtarget、Microsoft.Data.Tools.Schema.SqlTasks.targets が参照しているアセンブリが足らない(見れていない)模様だ。

C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VisualStudio\v16.0\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets(491,5): error MSB4062: The "SqlModelResolutionTask" task could not be loaded from the assembly C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\Dac\150\Microsoft.Data.Tools.Schema.Tasks.Sql.dll. アセンブリ 'Microsoft.Data.Tools.Schema.Tasks.Sql, Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' からの型 'Microsoft.Data.Tools.Schema.Tasks.Sql.SqlBuildTask' にあるメソッド 'get_BuildEngine' に実装が含まれていません。 Confirm that the declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask.

しばらく悩んだのだが、ふと「Microsoft.Data.Tools.Schema.Tasks.Sql.dllとかってDacFxのパッケージに含まれてるちゃうか??」と思い、Microsoft.SqlServer.DACFx をインストールして再度実行してみた。 www.nuget.org

すると、以下の通りビルドが成功し、dacpacファイルも生成されていた。 f:id:kiyokura:20200916225548p:plain

さらに、ビルドスクリプトのプロパティを設定してdacpacを出力先を任意に任意の名前で出力することもできた。

var p2 = ProjectCollection.GlobalProjectCollection.LoadProject(@"C:\MyDataBase\MyDataBase.sqlproj");
p2.SetProperty("OutputPath", @"C:\hoge\");
p2.SetProperty("SqlTargetName", "output");
var p2Result = p2.Build(new[] { new ConsoleLogger() });
Console.WriteLine($"p2 result : {p2Result}");

f:id:kiyokura:20200916230246p:plain

続く

ちょっと長くなったので今回のエントリはいったんここまでとする。 次回はMicrosoft.SqlServer.DACFxを利用したdacpacのデプロイについて書く予定だ。