mcpField
D
dotnet-msbuild-tasks

dotnet-msbuild-tasks

github.com/wshaddix開発8674 回インストール35 週間前に更新
リポジトリ

dotnet-msbuild-tasks について

カスタム MSBuild タスクを作成する .NET 開発者向けのガイドです。タスク実装、CLI のラッパー、増分実行、デバッグ、NuGet 配布を扱い、.NET 8 SDK と MSBuild 17.8 以降を前提としています。

主な機能

  1. 01ITask タスクの実装
  2. 02ToolTask による CLI のラップ
  3. 03変更項目だけを増分処理
  4. 04インラインタスクとパラメーターの定義
  5. 05タスクのデバッグとパッケージ化

活用シーン

  1. 01ビルドワークフローに独自処理を追加
  2. 02外部 CLI を MSBuild に組み込む
  3. 03未変更のファイル群の再処理を避ける
  4. 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] を参照してください。

動作の仕組み
  1. ターゲットで Inputs と Outputs を宣言します(必須です。エンジンはこれらを使って変更を検出します)。
  2. MSBuild がタイムスタンプを比較し、最新でない入力を特定します。
  3. タスクが IIncrementalTask を実装している場合、MSBuild は全項目ではなく、変更された項目のみをタスクの ITaskItem[] パラメーターに渡します。
  4. タスクはそれらの項目のみを処理します。手動でタイムスタンプを確認する必要はありません。

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"
booltrue/falseVerbose="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" />
タスク名の解決
属性値効果
TaskNameGenerateFileHash短縮名。一致した最初のタスクが使用されます
TaskNameMyCompany.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 環境変数を設定します。

値動作
1MSBuild は起動時に Debugger.Launch() を呼び出します -- JIT デバッガーのアタッチダイアログが表示されます
2MSBuild はデバッガーがアタッチされるまで待機し(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
デバッグのワークフロー
  1. ターミナルで MSBUILDDEBUGONSTART=2 を設定します。
  2. カスタムタスクを使用するプロジェクトで dotnet build を実行します。
  3. MSBuild が一時停止し、プロセス ID を出力します。
  4. デバッガーをアタッチします(Visual Studio:[Debug]>[Attach to Process]、VS Code:[.NET Attach])。
  5. タスクの Execute() メソッドにブレークポイントを設定します。
  6. 実行を続行すると、デバッガーがブレークポイントで停止します。
プログラムからデバッガーを起動する

開発ビルドでは、タスク内に条件付きでデバッガーを起動するコードを追加します。

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" を指定すると、それらが推移的な依存関係になるのを防げます。

エージェントでの注意点

  1. エラーをログに記録せずに false を返す。 Execute() が false を返しても、Log.LogError が呼び出されていない場合、MSBuild は対処可能なメッセージを表示せず、汎用的な「タスクが失敗しました」というエラーを報告します。false を返す前に、必ずエラーをログに記録してください。

  2. Log.LogMessage の代わりに Console.WriteLine を使用する。 コンソール出力は MSBuild のログ記録インフラストラクチャを迂回するため、ビルドログ、バイナリログ、IDE のエラー一覧に表示されないことがあります。必ず Log.LogMessage、Log.LogWarning、または Log.LogError を使用してください。

  3. バージョンを確認せずに IIncrementalTask を参照する。 このインターフェイスには MSBuild 17.8 以降(.NET 8 SDK)が必要です。これを参照するタスクは、古い MSBuild バージョンでは TypeLoadException が発生して読み込みに失敗します。古い SDK もサポートする場合は、代わりにターゲットレベルの Inputs/Outputs を使用してください。古い MSBuild と新しい MSBuild の両方をタスクでサポートする必要がある場合は、MSBuild のバージョン範囲ごとに別のタスクアセンブリを配布するか、バージョン定数を使った #if 条件付きコンパイルを使用してください。

  4. タスク DLL を NuGet の lib/ フォルダーに配置する。 これにより、アセンブリが使用側プロジェクトのコンパイル参照として追加され、型の名前空間が汚染されます。IncludeBuildOutput=false を設定し、代わりに tools/ にパッケージ化してください。

  5. MSBuild フレームワークパッケージ参照に PrivateAssets="all" を指定し忘れる。 指定しないと、Microsoft.Build.Framework と Microsoft.Build.Utilities.Core が使用側プロジェクトの推移的な依存関係となり、バージョンの競合が発生します。

  6. プロジェクトからの相対パスを指定して AssemblyFile を使用する。 NuGet パッケージでは、.targets ファイルは使用側プロジェクトとは異なる場所にあります。.targets ファイル自体を基準にパスを構築するには、$(MSBuildThisFileDirectory) を使用してください。

  7. リリースビルドに Debugger.Launch() を残す。 無条件の Debugger.Launch() を含むタスクを配布すると、CI/CD サーバーでビルドが停止します。#if DEBUG で囲むか、パッケージ化する前に削除してください。

  8. 複雑な依存関係を持つインラインタスク。 CodeTaskFactory は、限られたアセンブリ参照を使ってビルド時にコードをコンパイルします。NuGet パッケージや複雑な型階層を必要とするタスクには、代わりにスタンドアロンのタスクアセンブリをコンパイルしてください。


参考資料

コメント