ソリューション レベルの --output オプションがビルド関連コマンドで無効に

7.0.200 SDK では、次のコマンドでソリューション ファイルを使用するときに、--output/-o オプションを指定できなくなるという変更がありました。

  • build
  • clean
  • pack
  • publish
  • store
  • test
  • vstest

その理由は、--output/-o オプションによって制御される OutputPath プロパティのセマンティクスが、ソリューションに対して明確に定義されていないためです。 この方法でビルドされたプロジェクトの出力は同じディレクトリに配置されますが、そこには一貫性がなく、ユーザーから多数の問題が報告されていました。

この変更は、7.0.201 SDK で警告レベルの重要度に下げられ、pack は影響を受けるコマンドの一覧から削除されました。

導入されたバージョン

.NET 7.0.200 SDK (7.0.201 SDK で初めて警告に下げられました)。

以前の動作

以前は、ソリューション ファイルを使用するときに --output/-o を指定すると、ビルドされた全プロジェクトの出力は、未定義の一貫性のない方法で指定したディレクトリに配置されました。

新しい動作

ソリューション ファイルと共に --output/-o オプションを使うと、dotnet CLI はエラーになります。 7.0.201 SDK 以降では、代わりに警告が生成され、dotnet pack の場合は警告もエラーも生成されません。

破壊的変更の種類

この破壊的変更により、ビルド スクリプトと継続的インテグレーション パイプラインの変更が必要になる場合があります。 結果として、バイナリとソースの両方の互換性に影響します。

変更理由

この変更が行われた理由は、--output/-o オプションによって制御される OutputPath プロパティのセマンティクスが、ソリューションに対して明確に定義されていないためです。 この方法でビルドされたプロジェクトの出力は同じディレクトリに配置されますが、そこには一貫性がなく、ユーザーから多数の問題が報告されていました。

ソリューションが --output オプションを指定してビルドされている場合、OutputPath プロパティはすべてのプロジェクトに対して同じ値に設定されます。つまり、すべてのプロジェクトの出力が同じディレクトリに配置されます。 ソリューション内のプロジェクトの複雑さに応じて、さまざまな一貫性のない結果が生じる可能性があります。 さまざまなソリューションの形の例と、それらが共有された OutputPath によってどのように影響を受けるかを見てみましょう。

1 つのプロジェクト、1 つの TargetFramework

1 つの TargetFramework (net7.0) を対象とする 1 つのプロジェクトを含むソリューションを想像してみてください。 この場合、--output オプションを指定することは、プロジェクト ファイルで OutputPath プロパティを設定することと同じです。 ビルド中 (他のコマンドにも当てはまりますますが、ここではビルドに焦点を当てます)、プロジェクトのすべての出力が指定したディレクトリに配置されます。

1 つのプロジェクト、複数の TargetFramework

次に、複数の TargetFrameworks (net6.0net7.0) を持つ 1 つのプロジェクトを含むソリューションを想像してみてください。 マルチターゲットのため、このプロジェクトは 2 回ビルドされます (TargetFramework ごとに 1 回)。 これらの "内部" ビルドごとに OutputPath は同じ値に設定されるため、各内部ビルドの出力は同じディレクトリに配置されます。 つまり、最後に完了するいずれかのビルドがもう一方のビルドの出力を上書きすることになり、MSBuild が既定で動作するような並列ビルド システムでは、'最後' は不確定です。

ライブラリ => コンソール => テスト、1 つの TargetFramework

次に、ライブラリ プロジェクト、そのライブラリ プロジェクトを参照するコンソール プロジェクト、そのコンソール プロジェクトを参照するテスト プロジェクトを含むソリューションを想像してみてください。 これらのプロジェクトはすべて、1 つの TargetFramework (net7.0) を対象としています。 この場合、ライブラリ プロジェクトが最初にビルドされ、次にコンソール プロジェクトがビルドされます。 テスト プロジェクトは最後にビルドされ、コンソール プロジェクトを参照します。 ビルドされたプロジェクトごとに、各ビルドの出力は OutputPath で指定されたディレクトリにコピーされます。そのため、最終的なディレクトリには 3 つすべてのプロジェクトからの資産が含まれます。 これはテストでは問題ありませんが、公開する場合は、テスト資産が運用環境に送信されることになるおそれがあります。

ライブラリ => コンソール => テスト、複数の TargetFramework

次に、同じプロジェクトのチェーンを考え、net6.0TargetFramework ビルドを net7.0 ビルドに加えて追加します。 1 プロジェクト、複数ターゲットのビルドと同じ問題が発生します。つまり、TFM 固有の資産が指定したディレクトリに一貫性のない方法でコピーされます。

複数のアプリ

ここまでは直線的な依存関係グラフのシナリオを見てきましたが、多くのソリューションでは、複数の関連アプリケーションが含まれている場合があります。 つまり、複数のアプリが同じ出力フォルダーに同時にビルドされる可能性があります。 複数のアプリに同じ名前の依存関係ファイルが含まれていた場合、複数のプロジェクトが出力パスのそのファイルに同時に書き込もうとしたときに、ビルドが断続的に失敗する可能性があります。

複数のアプリがあるファイルの異なるバージョンに依存している場合は、ビルドが成功しても、出力パスにコピーされるファイルのバージョンが非確定的になる場合があります。 これは、複数のプロジェクトが異なるバージョンの NuGet パッケージに (おそらく推移的に) 依存している場合に発生する可能性があります。 1 つのプロジェクト内では、NuGet はその各依存関係が同じバージョンに統合されるようにするのに役立ちます (NuGet パッケージやプロジェクト参照を介した推移的な依存関係を含む)。 その統合は、1 つのプロジェクトとその複数の依存プロジェクトというコンテキスト内で行われます。そのため、2 つの独立した最上位プロジェクトがビルドされると、あるパッケージの異なるバージョンが解決される可能性があります。 上位のバージョンに依存するプロジェクトが最後に依存関係をコピーしたときは、多くの場合、各アプリは正常に実行できます。 しかし、下位のバージョンが最後にコピーされた場合は、上位のバージョンに対してコンパイルされたアプリの実行時にアセンブリを読み込めなくなります。 コピーされるバージョンは非確定的になる可能性があるため、散発的で信頼性の低いビルドにつながるおそれがあり、その場合問題を診断することが非常に困難になります。

その他の例

このような内在的なエラーのさらなる実例については、dotnet/sdk#15607 のディスカッションを参照してください。

一般的な推奨事項は、以前に実行していたアクションを --output/-o オプション "なしで" 実行し、コマンドの完了後にその出力を目的の場所に移動することです。 特定のプロジェクトでアクションを実行し、--output/-o オプションを適用することもできます。その場合はセマンティクスがより明確に定義されるためです。

既存の動作を正確に維持したい場合は、--property フラグを使って、MSBuild プロパティを目的のディレクトリに設定できます。 使用するプロパティは、コマンドによって異なります。

コマンド プロパティ
build OutputPath dotnet build --property:OutputPath=DESIRED_PATH
clean OutputPath dotnet clean --property:OutputPath=DESIRED_PATH
pack PackageOutputPath dotnet pack --property:PackageOutputPath=DESIRED_PATH
publish PublishDir dotnet publish --property:PublishDir=DESIRED_PATH
store OutputPath dotnet store --property:OutputPath=DESIRED_PATH
test TestResultsDirectory dotnet test --property:OutputPath=DESIRED_PATH

最善の結果を得るには、DESIRED_PATH を絶対パスにする必要があります。 相対パスは予期しない方法で '固定される' (絶対パスにされる) ことがあり、すべてのコマンドで同じように動作しない場合があります。