dotnet-msbuild-tasks について
カスタム MSBuild タスクを作成する .NET 開発者向けのガイドです。タスク実装、CLI のラッパー、増分実行、デバッグ、NuGet 配布を扱い、.NET 8 SDK と MSBuild 17.8 以降を前提としています。
主な機能
- 01ITask タスクの実装
- 02ToolTask による CLI のラップ
- 03変更項目だけを増分処理
- 04インラインタスクとパラメーターの定義
- 05タスクのデバッグとパッケージ化
活用シーン
- 01ビルドワークフローに独自処理を追加
- 02外部 CLI を MSBuild に組み込む
- 03未変更のファイル群の再処理を避ける
- 04NuGet でビルドタスクを配布
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-msbuild-tasksインストール後、Claude Code で/dotnet-msbuild-tasksを入力して読み込まれたか確認します。
詳細
dotnet-msbuild-tasks
カスタム MSBuild タスクを作成するためのガイダンスです。ITask インターフェイスの実装、CLI ラッパー向けの ToolTask の拡張、インクリメンタル実行のための IIncrementalTask(MSBuild 17.8 以降)の使用、CodeTaskFactory によるインラインタスクの定義、UsingTask によるタスクの登録、タスクパラメーターの宣言、タスクのデバッグ、タスクの NuGet パッケージ化を扱います。
バージョンの前提条件: .NET 8.0 以降の SDK(MSBuild 17.8 以降)。IIncrementalTask には MSBuild 17.8 以降(VS 2022 17.8 以降、.NET 8 SDK)が必要です。すべての例で SDK スタイルのプロジェクトを使用します。明示的に示されている場合を除き、すべての C# の例では using Microsoft.Build.Framework; と using Microsoft.Build.Utilities; がスコープ内にあることを前提とします。
対象範囲: このスキルでは、カスタム MSBuild タスクの作成(ITask、ToolTask、IIncrementalTask、インラインタスク、UsingTask、パラメーター、デバッグ、NuGet パッケージ化)を扱います。MSBuild プロジェクトシステムの作成(ターゲット、props、項目、条件)は [skill:dotnet-msbuild-authoring] が担当します。
相互参照:カスタムターゲット、インポート順序、項目、条件、プロパティ関数については [skill:dotnet-msbuild-authoring] を参照してください。
ITask インターフェイス
すべての MSBuild タスクは Microsoft.Build.Framework.ITask を実装します。最も簡単な方法は、BuildEngine と HostObject の既定の実装を提供する Microsoft.Build.Utilities.Task を継承することです。
最小限のカスタムタスク
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class GenerateFileHash : Task
{
[Required]
public string InputFile { get; set; } = string.Empty;
[Output]
public string Hash { get; set; } = string.Empty;
public override bool Execute()
{
if (!File.Exists(InputFile))
{
Log.LogError("Input file not found: {0}", InputFile);
return false;
}
using var stream = File.OpenRead(InputFile);
var bytes = System.Security.Cryptography.SHA256.HashData(stream);
Hash = Convert.ToHexString(bytes).ToLowerInvariant();
Log.LogMessage(MessageImportance.Normal,
"SHA-256 hash for {0}: {1}", InputFile, Hash);
return true;
}
}
ITask のコントラクト
| メンバー | 目的 |
|---|---|
BuildEngine | ログ記録、エラー報告、ビルドコンテキストを提供します |
HostObject | ホスト固有のデータです(使用されることはまれです) |
Execute() | タスクを実行します。成功の場合は true、失敗の場合は false を返します |
Task 基底クラスは、便利なメソッドを備えた Log プロパティ(TaskLoggingHelper)を公開します。
| メソッド | 使用する場面 |
|---|---|
Log.LogMessage(importance, msg) | 情報出力(Normal、High、Low) |
Log.LogWarning(msg) | 致命的ではない問題 |
Log.LogError(msg) | 致命的なエラー(ビルドが失敗します) |
Log.LogWarningFromException(ex) | 捕捉した例外に基づく警告 |
Log.LogErrorFromException(ex) | 捕捉した例外に基づくエラー |
ToolTask 基底クラス
ToolTask は、外部コマンドラインツールをラップするための Task の派生クラスです。プロセスの起動、出力の取得、終了コードの解釈を処理します。
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class RunLintTool : ToolTask
{
[Required]
public string SourceDirectory { get; set; } = string.Empty;
public string Severity { get; set; } = "warning";
// Required: name of the executable
protected override string ToolName => "dotnet-lint";
// Required: full path or tool name (OS resolves via PATH)
protected override string GenerateFullPathToTool()
{
// Return tool name; the OS resolves it via PATH at process start
return ToolName;
}
// Required: build the command-line arguments
protected override string GenerateCommandLineCommands()
{
var builder = new CommandLineBuilder();
builder.AppendSwitch("--check");
builder.AppendSwitchIfNotNull("--severity ", Severity);
builder.AppendFileNameIfNotNull(SourceDirectory);
return builder.ToString();
}
// Optional: interpret non-zero exit codes
protected override bool HandleTaskExecutionErrors()
{
Log.LogError("{0} found lint violations in {1}",
ToolName, SourceDirectory);
return false;
}
}
主な ToolTask のオーバーライド
| オーバーライド | 目的 |
|---|---|
ToolName | 実行可能ファイル名(例:dotnet-lint) |
GenerateFullPathToTool() | 実行可能ファイルの完全パスを指定します。または ToolName を返して、OS に PATH 経由で解決させます |
GenerateCommandLineCommands() | ツールの引数文字列を作成します |
GenerateResponseFileCommands() | (長いコマンドラインの場合)応答ファイルに書き込む引数を指定します |
HandleTaskExecutionErrors() | 0 以外の終了コードをカスタム処理します |
StandardOutputLoggingImportance | 標準出力のログレベル(既定値:Low) |
StandardErrorLoggingImportance | 標準エラー出力のログレベル(既定値:Normal) |
長いコマンドライン向けの応答ファイル
引数リストが OS のコマンドラインの上限を超える場合(多数のソースファイルを扱うときによくあります)、GenerateResponseFileCommands() を使用して、一時応答ファイルに引数を書き込みます。
protected override string GenerateResponseFileCommands()
{
var builder = new CommandLineBuilder();
// These arguments go into a @response.rsp file
foreach (var source in SourceFiles)
{
builder.AppendFileNameIfNotNull(source.ItemSpec);
}
return builder.ToString();
}
protected override string GenerateCommandLineCommands()
{
// These arguments stay on the command line (before the @file ref)
var builder = new CommandLineBuilder();
builder.AppendSwitchIfNotNull("--config ", ConfigFile);
return builder.ToString();
}
MSBuild は応答ファイルを作成し、@responsefile.rsp をツールに渡した後、クリーンアップします。ツールは @file 構文をサポートしている必要があります(ほとんどの .NET ツールはサポートしています)。
ToolTask と Task の使い分け: 外部 CLI ツールをラップする場合は ToolTask を使用します。ロジックが純粋な .NET コードで、外部プロセスを使用しない場合は Task(ITask)を使用します。
IIncrementalTask
Microsoft.Build.Framework.IIncrementalTask(MSBuild 17.8 以降、VS 2022 17.8 以降、.NET 8 SDK)は、タスクが事前にフィルターされた入力を受け取れることを MSBuild エンジンに通知します。ターゲットが Inputs/Outputs を宣言し、エンジンが変更された入力を判定すると、エンジンはすべての項目リストではなく、変更された項目のみを IIncrementalTask を実装するタスクに渡します。
バージョン要件
IIncrementalTask には以下が必要です。
- MSBuild 17.8 以降(VS 2022 17.8 以降に付属)
- .NET 8.0 SDK 以降
古いバージョンの MSBuild を対象とするタスクでは、このインターフェイスを参照しないでください。古いバージョンでインクリメンタル処理を行うには、ターゲットレベルの Inputs/Outputs を使用してください。ターゲットレベルのインクリメンタル処理パターンについては、[skill:dotnet-msbuild-authoring] を参照してください。
動作の仕組み
- ターゲットで
InputsとOutputsを宣言します(必須です。エンジンはこれらを使って変更を検出します)。 - MSBuild がタイムスタンプを比較し、最新でない入力を特定します。
- タスクが
IIncrementalTaskを実装している場合、MSBuild は全項目ではなく、変更された項目のみをタスクのITaskItem[]パラメーターに渡します。 - タスクはそれらの項目のみを処理します。手動でタイムスタンプを確認する必要はありません。
FailIfIncrementalBuildIsNotPossible プロパティで、フォールバック動作を制御します。
false(既定値):エンジンが変更された入力を特定できない場合(たとえばOutputsがない場合)、すべての入力を渡すようにフォールバックします。タスクは完全リビルドモードで実行されます。true:エンジンがインクリメンタル入力を提供できない場合、タスクはエラーを記録して失敗します。完全リビルドが許容できないほど遅い場合に使用してください。
実装
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class TransformTemplates : Task, IIncrementalTask
{
[Required]
public ITaskItem[] Templates { get; set; } = [];
[Output]
public ITaskItem[] GeneratedFiles { get; set; } = [];
// IIncrementalTask: if true, the task errors when the engine
// cannot provide filtered inputs (falls back to full set if false)
public bool FailIfIncrementalBuildIsNotPossible { get; set; }
public override bool Execute()
{
// Templates contains ONLY changed items (filtered by engine)
// when the target has Inputs/Outputs and incremental build is possible
var outputs = new List<ITaskItem>();
foreach (var template in Templates)
{
var inputPath = template.GetMetadata("FullPath");
var outputPath = Path.ChangeExtension(inputPath, ".g.cs");
var content = ProcessTemplate(File.ReadAllText(inputPath));
File.WriteAllText(outputPath, content);
Log.LogMessage(MessageImportance.Normal,
"Transformed: {0} -> {1}", inputPath, outputPath);
outputs.Add(new TaskItem(outputPath));
}
GeneratedFiles = outputs.ToArray();
return true;
}
private static string ProcessTemplate(string input)
{
// Template transformation logic
return $"// Auto-generated\n{input}";
}
}
<Target Name="TransformAllTemplates"
BeforeTargets="CoreCompile"
Inputs="@(Template)"
Outputs="@(Template->'%(RootDir)%(Directory)%(Filename).g.cs')">
<TransformTemplates Templates="@(Template)">
<Output TaskParameter="GeneratedFiles" ItemName="Compile" />
</TransformTemplates>
</Target>
IIncrementalTask とターゲットレベルの Inputs/Outputs のみを使う場合の違い: IIncrementalTask がない場合、ターゲットレベルのインクリメンタル処理は全か無かです。入力がひとつでも変更されると、すべての項目を使ってターゲット全体が再実行されます。IIncrementalTask を使うと、エンジンが項目リストを事前にフィルタリングし、変更された項目のみをタスクに渡すため、大量のファイルを処理するターゲットを高速化できます。
タスクのパラメーター
タスクのパラメーターは、タスククラスのパブリックプロパティです。MSBuild は XML 属性をこれらのプロパティに自動的にマッピングします。
パラメーター属性
| 属性 | 効果 |
|---|---|
[Required] | パラメーターが指定されていない場合、ビルドは失敗します |
[Output] | 値は %(TaskName.PropertyName) を介して後続のタスク/ターゲットで使用できます |
| 属性なし | 既定値を持つ省略可能なパラメーター |
パラメーターの型
| .NET 型 | MSBuild XML | 例 |
|---|---|---|
string | スカラー値 | InputFile="src/app.cs" |
bool | true/false | Verbose="true" |
int | 数値 | MaxRetries="3" |
string[] | セミコロン区切り | Assemblies="a.dll;b.dll" |
ITaskItem | 単一項目 | SourceFile="@(MainSource)" |
ITaskItem[] | 項目コレクション | SourceFiles="@(Compile)" |
ITaskItem メタデータへのアクセス
ITaskItem には、ファイルパス以外にも豊富なメタデータが含まれています。
public class ProcessAssets : Task
{
[Required]
public ITaskItem[] Assets { get; set; } = [];
[Output]
public ITaskItem[] ProcessedAssets { get; set; } = [];
public override bool Execute()
{
var results = new List<ITaskItem>();
foreach (var asset in Assets)
{
// ItemSpec = the Include value (relative path)
var relativePath = asset.ItemSpec;
// Built-in metadata
var fullPath = asset.GetMetadata("FullPath");
var filename = asset.GetMetadata("Filename");
var extension = asset.GetMetadata("Extension");
// Custom metadata set in MSBuild XML
var category = asset.GetMetadata("Category");
Log.LogMessage(MessageImportance.Normal,
"Processing {0} (category: {1})", filename, category);
var output = new TaskItem(
Path.ChangeExtension(fullPath, ".processed" + extension));
// Copy all metadata from input to output
asset.CopyMetadataTo(output);
// Add new metadata
output.SetMetadata("ProcessedAt",
DateTime.UtcNow.ToString("o"));
results.Add(output);
}
ProcessedAssets = results.ToArray();
return true;
}
}
<ItemGroup>
<GameAsset Include="textures/*.png">
<Category>texture</Category>
</GameAsset>
<GameAsset Include="models/*.fbx">
<Category>model</Category>
</GameAsset>
</ItemGroup>
<Target Name="ProcessGameAssets" BeforeTargets="Build">
<ProcessAssets Assets="@(GameAsset)">
<Output TaskParameter="ProcessedAssets" ItemName="ProcessedGameAsset" />
</ProcessAssets>
</Target>
インラインタスク(CodeTaskFactory)
個別のアセンブリを用意する必要がない単純なタスクでは、CodeTaskFactory を使用して、MSBuild XML 内にタスクロジックをインラインで定義できます。コードはビルド時にコンパイルされます。
<UsingTask TaskName="GetTimestamp"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Format ParameterType="System.String" Required="false" />
<Timestamp ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
var format = string.IsNullOrEmpty(Format) ? "yyyyMMdd-HHmmss" : Format;
Timestamp = DateTime.UtcNow.ToString(format);
]]>
</Code>
</Task>
</UsingTask>
<Target Name="StampBuild" BeforeTargets="CoreCompile">
<GetTimestamp Format="yyyy.MMdd.HHmm">
<Output TaskParameter="Timestamp" PropertyName="BuildTimestamp" />
</GetTimestamp>
<Message Importance="high" Text="Build timestamp: $(BuildTimestamp)" />
</Target>
CodeTaskFactory のコードタイプ
Type | 説明 |
|---|---|
Fragment | コードは Execute() メソッドの本体内で実行されます。パラメーターにはローカル変数としてアクセスします。 |
Method | コードはメソッド本体全体です。return true; または return false; を含める必要があります。 |
Class | コードはクラス全体です。ITask を実装するか、Task を継承する必要があります。 |
アセンブリ参照の追加
<UsingTask TaskName="ValidateJson"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<JsonFile ParameterType="System.String" Required="true" />
<IsValid ParameterType="System.Boolean" Output="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Text.Json" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try
{
var content = System.IO.File.ReadAllText(JsonFile);
System.Text.Json.JsonDocument.Parse(content);
IsValid = true;
}
catch (System.Text.Json.JsonException)
{
IsValid = false;
Log.LogWarning("Invalid JSON: {0}", JsonFile);
}
]]>
</Code>
</Task>
</UsingTask>
インラインタスクとコンパイル済みタスクの使い分け: インラインタスクは、単純で自己完結したロジック(タイムスタンプ、ファイルの確認、文字列操作)に最適です。複雑なロジックや複数の依存関係がある場合、または複数のプロジェクトで再利用する場合は、タスクアセンブリをコンパイルし、NuGet 経由で配布してください。
UsingTask の登録
UsingTask は、カスタムタスクの実装を MSBuild がどこから読み込むかを指定します。タスクを使用するターゲットより前に記述する必要があります。
コンパイル済みアセンブリから読み込む場合
<UsingTask TaskName="MyCompany.Build.GenerateFileHash"
AssemblyFile="$(MSBuildThisFileDirectory)..\tools\MyCompany.Build.Tasks.dll" />
<UsingTask TaskName="MyCompany.Build.GenerateFileHash"
AssemblyName="MyCompany.Build.Tasks, Version=1.0.0.0, Culture=neutral" />
タスク名の解決
| 属性 | 値 | 効果 |
|---|---|---|
TaskName | GenerateFileHash | 短縮名。一致した最初のタスクが使用されます |
TaskName | MyCompany.Build.GenerateFileHash | 完全修飾名。完全一致が必要です |
AssemblyFile | 相対パスまたは絶対パス | ファイルパスから読み込みます |
AssemblyName | 厳密名または単純名 | アセンブリ ID で読み込みます |
NuGet パッケージ経由で配布するタスクには、$(MSBuildThisFileDirectory) とともに AssemblyFile を使用してください。パスは、利用側のプロジェクトではなく .targets ファイルを基準に解決されます。
条件付き登録
<UsingTask TaskName="MyCompany.Build.CodeGen"
AssemblyFile="$(MSBuildThisFileDirectory)..\tools\MyCompany.Build.Tasks.dll"
Condition="Exists('$(MSBuildThisFileDirectory)..\tools\MyCompany.Build.Tasks.dll')" />
タスクのデバッグ
カスタム MSBuild タスクをデバッグするには、MSBuild プロセスにデバッガーをアタッチする必要があります。
MSBUILDDEBUGONSTART
ビルドを実行する前に、MSBUILDDEBUGONSTART 環境変数を設定します。
| 値 | 動作 |
|---|---|
1 | MSBuild は起動時に Debugger.Launch() を呼び出します -- JIT デバッガーのアタッチダイアログが表示されます |
2 | MSBuild はデバッガーがアタッチされるまで待機し(PID をコンソールに出力)、その後続行します |
# Option 1: Launch debugger dialog (Windows)
set MSBUILDDEBUGONSTART=1
dotnet build
# Option 2: Wait for debugger attach (cross-platform)
export MSBUILDDEBUGONSTART=2
dotnet build
# MSBuild prints: "Waiting for debugger to attach (PID: 12345)..."
# Attach from VS or VS Code, then execution continues
デバッグのワークフロー
- ターミナルで
MSBUILDDEBUGONSTART=2を設定します。 - カスタムタスクを使用するプロジェクトで
dotnet buildを実行します。 - MSBuild が一時停止し、プロセス ID を出力します。
- デバッガーをアタッチします(Visual Studio:[Debug]>[Attach to Process]、VS Code:[.NET Attach])。
- タスクの
Execute()メソッドにブレークポイントを設定します。 - 実行を続行すると、デバッガーがブレークポイントで停止します。
プログラムからデバッガーを起動する
開発ビルドでは、タスク内に条件付きでデバッガーを起動するコードを追加します。
public override bool Execute()
{
#if DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Launch();
}
#endif
// Task logic ...
return true;
}
公開前にデバッガーの起動コードを削除するか、条件を付けてください。 タスクアセンブリは Release ビルドのみを出荷してください。#if DEBUG ガードにより、本番環境でデバッガーのダイアログが表示されなくなります。
タスクの NuGet パッケージ化
カスタム MSBuild タスクは、通常 NuGet パッケージとして配布されます。パッケージでは、.props/.targets ファイルとタスクアセンブリを正しいフォルダーに配置する必要があります。
パッケージのレイアウト
MyCompany.Build.Tasks.nupkg
build/
MyCompany.Build.Tasks.props (optional: set defaults)
MyCompany.Build.Tasks.targets (UsingTask + target definitions)
buildTransitive/
MyCompany.Build.Tasks.props (optional: set defaults)
MyCompany.Build.Tasks.targets (UsingTask + target definitions)
tools/
net8.0/ (matches csproj TargetFramework)
MyCompany.Build.Tasks.dll (task assembly)
(other dependencies)
build と buildTransitive
| フォルダー | 適用範囲 |
|---|---|
build/ | ターゲット/プロパティは直接の利用側のみに適用されます |
buildTransitive/ | ターゲット/プロパティは直接の利用側と、それを間接的に参照するすべてのプロジェクトに適用されます |
依存関係グラフ内のすべてのプロジェクトで実行する必要があるタスク(コードアナライザー、ソースジェネレーターなど)には、buildTransitive/ を使用します。利用側のプロジェクトに固有のタスクには、build/ を使用します。
NuGet パッケージ用の .targets ファイル
<Project>
<UsingTask TaskName="MyCompany.Build.GenerateFileHash"
AssemblyFile="$(MSBuildThisFileDirectory)..\tools\net8.0\MyCompany.Build.Tasks.dll" />
<Target Name="_MyCompanyHashOutputs"
AfterTargets="Build"
Condition="'$(GenerateOutputHashes)' == 'true'">
<GenerateFileHash InputFile="$(TargetPath)">
<Output TaskParameter="Hash" PropertyName="_OutputHash" />
</GenerateFileHash>
<Message Importance="high"
Text="Output hash: $(_OutputHash)" />
</Target>
</Project>
NuGet パッケージ用の .props ファイル
<Project>
<PropertyGroup>
<GenerateOutputHashes Condition="'$(GenerateOutputHashes)' == ''">false</GenerateOutputHashes>
</PropertyGroup>
</Project>
タスクパッケージ用のプロジェクトファイル
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageId>MyCompany.Build.Tasks</PackageId>
<PackageVersion>1.0.0</PackageVersion>
<Description>Custom MSBuild tasks for MyCompany build pipeline</Description>
<IncludeBuildOutput>false</IncludeBuildOutput>
<NoWarn>$(NoWarn);NU5100</NoWarn>
<DevelopmentDependency>true</DevelopmentDependency>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="17.8.3"
PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.8.3"
PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)/**/*.dll" Pack="true"
PackagePath="tools/$(TargetFramework)/" />
</ItemGroup>
<ItemGroup>
<None Include="build/**" Pack="true" PackagePath="build/" />
<None Include="buildTransitive/**" Pack="true" PackagePath="buildTransitive/" />
</ItemGroup>
</Project>
主な csproj 設定:
IncludeBuildOutput=falseにより、タスク DLL がlib/フォルダーに配置されるのを防ぎます。配置されると、利用側へのコンパイル参照として追加されます。DevelopmentDependency=trueは、パッケージをビルド時のみ使用するものとして指定し、利用側のランタイム依存関係に引き継がれないようにします。- MSBuild フレームワーク参照に
PrivateAssets="all"を指定すると、それらが推移的な依存関係になるのを防げます。
エージェントでの注意点
-
エラーをログに記録せずに
falseを返す。Execute()がfalseを返しても、Log.LogErrorが呼び出されていない場合、MSBuild は対処可能なメッセージを表示せず、汎用的な「タスクが失敗しました」というエラーを報告します。falseを返す前に、必ずエラーをログに記録してください。 -
Log.LogMessageの代わりにConsole.WriteLineを使用する。 コンソール出力は MSBuild のログ記録インフラストラクチャを迂回するため、ビルドログ、バイナリログ、IDE のエラー一覧に表示されないことがあります。必ずLog.LogMessage、Log.LogWarning、またはLog.LogErrorを使用してください。 -
バージョンを確認せずに
IIncrementalTaskを参照する。 このインターフェイスには MSBuild 17.8 以降(.NET 8 SDK)が必要です。これを参照するタスクは、古い MSBuild バージョンではTypeLoadExceptionが発生して読み込みに失敗します。古い SDK もサポートする場合は、代わりにターゲットレベルのInputs/Outputsを使用してください。古い MSBuild と新しい MSBuild の両方をタスクでサポートする必要がある場合は、MSBuild のバージョン範囲ごとに別のタスクアセンブリを配布するか、バージョン定数を使った#if条件付きコンパイルを使用してください。 -
タスク DLL を NuGet の
lib/フォルダーに配置する。 これにより、アセンブリが使用側プロジェクトのコンパイル参照として追加され、型の名前空間が汚染されます。IncludeBuildOutput=falseを設定し、代わりにtools/にパッケージ化してください。 -
MSBuild フレームワークパッケージ参照に
PrivateAssets="all"を指定し忘れる。 指定しないと、Microsoft.Build.FrameworkとMicrosoft.Build.Utilities.Coreが使用側プロジェクトの推移的な依存関係となり、バージョンの競合が発生します。 -
プロジェクトからの相対パスを指定して
AssemblyFileを使用する。 NuGet パッケージでは、.targetsファイルは使用側プロジェクトとは異なる場所にあります。.targetsファイル自体を基準にパスを構築するには、$(MSBuildThisFileDirectory)を使用してください。 -
リリースビルドに
Debugger.Launch()を残す。 無条件のDebugger.Launch()を含むタスクを配布すると、CI/CD サーバーでビルドが停止します。#if DEBUGで囲むか、パッケージ化する前に削除してください。 -
複雑な依存関係を持つインラインタスク。
CodeTaskFactoryは、限られたアセンブリ参照を使ってビルド時にコードをコンパイルします。NuGet パッケージや複雑な型階層を必要とするタスクには、代わりにスタンドアロンのタスクアセンブリをコンパイルしてください。