Target Authoring

作者 dotnet0608d8924cd3MIT5.5K 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫今天更新

Canonical patterns for writing custom MSBuild targets. USE FOR: diagnosing and fixing custom target authoring anti-patterns; broken SDK target chains across files (e.g., Directory.Build.targets silently redefining SDK targets); targets that replace CompileDependsOn instead of extending it with $(CompileDependsOn); query targets returning stale results from Outputs vs Returns misuse; missing Inputs/Outputs causing unnecessary rebuilds; missing FileWrites registration. Covers DependsOnTargets vs BeforeTargets vs AfterTargets, the Build→CoreBuild three-level pattern, and the $(XxxDependsOn) chain-extension pattern. DO NOT USE FOR: incremental build tuning (use incremental-build), parallelization (use build-parallelism), general anti-patterns (use msbuild-antipatterns), non-MSBuild build systems.

AI 產生的概覽

撰寫自訂 MSBuild 目標的標準模式,涵蓋相依性鏈、擴充與增量建置。

功能
此技能記錄撰寫自訂 MSBuild 目標的標準模式,內容取自 Microsoft.Common.CurrentVersion.targets。它說明 Build/CoreBuild 三層目標鏈、$(XxxDependsOn) 鏈結擴充模式,以及 DependsOnTargets、BeforeTargets 與 AfterTargets 之間的選擇。它也涵蓋 Returns 與 Outputs 的差異、目標命名慣例、完整的自訂目標範本,以及覆寫 DependsOn 屬性或漏掉 FileWrites 註冊等常見陷阱。
適用情境
適用於診斷與修正自訂 MSBuild 目標撰寫問題,例如 SDK 目標鏈中斷、目標取代而非擴充 CompileDependsOn、查詢目標傳回過期結果,或因缺少 Inputs/Outputs 而造成不必要的重建。不適用於增量建置調校、平行化、一般 MSBuild 反模式或非 MSBuild 建置系統。
執行需求
不需要指令碼或工具,僅為指示性內容。假定使用者熟悉 MSBuild 專案檔與目標撰寫。

Custom Target Authoring Patterns

Canonical patterns from Microsoft.Common.CurrentVersion.targets in the MSBuild repository.

The Three-Level Target Chain

Every major entry point (Build, Rebuild, Clean) delegates to a property listing its dependencies, which chains through Before → Core → After:

xml
<PropertyGroup>  <BuildDependsOn>    BeforeBuild;    CoreBuild;    AfterBuild  </BuildDependsOn></PropertyGroup>
<Target Name="Build"    Condition=" '$(_InvalidConfigurationWarning)' != 'true' "    DependsOnTargets="$(BuildDependsOn)"    Returns="@(TargetPathWithTargetPlatformMoniker)" />
<!-- Empty extensibility targets — users override these --><Target Name="BeforeBuild" /><Target Name="AfterBuild" />

CoreBuild delegates to $(CoreBuildDependsOn) and includes error handlers:

xml
<Target Name="CoreBuild" DependsOnTargets="$(CoreBuildDependsOn)">  <OnError ExecuteTargets="_TimeStampAfterCompile;PostBuildEvent"      Condition="'$(RunPostBuildEvent)' == 'Always'" />  <OnError ExecuteTargets="_CleanRecordFileWrites" /></Target>

Rules

  • Delegate to a property (DependsOnTargets="$(MyTargetDependsOn)"), not hardcoded targets.
  • OnError goes inside the orchestrating target to ensure cleanup runs even on failure.
  • Empty Before/After targets are extensibility points. Users override them; SDKs never put logic in them.

Chain Extension — Append, Never Overwrite

When adding a custom target to an existing chain, append to the DependsOn property:

xml
<!-- GOOD: Append to existing chain --><PropertyGroup>  <CompileDependsOn>$(CompileDependsOn);MyCodeGenTarget</CompileDependsOn></PropertyGroup>
<!-- BAD: Overwrites the entire chain, dropping SDK targets --><PropertyGroup>  <CompileDependsOn>MyCodeGenTarget</CompileDependsOn></PropertyGroup>

DependsOnTargets vs BeforeTargets vs AfterTargets

MechanismDefined inBest for
DependsOnTargetsThe target that needs depsTarget explicitly requires others
BeforeTargetsThe injecting targetInsert before a target you don't own
AfterTargetsThe injecting targetInsert after a target you don't own

Validation targets use BeforeTargets to intercept all entry points:

xml
<Target Name="_CheckForInvalidConfigurationAndPlatform"    BeforeTargets="$(BuildDependsOn);Build;$(RebuildDependsOn);Rebuild;$(CleanDependsOn);Clean"></Target>

Rules:

  • Use DependsOnTargets when your target needs specific prerequisites.
  • Use BeforeTargets/AfterTargets when injecting into a pipeline you don't own.
  • Prefer BeforeTargets="CoreCompile" over modifying $(CompileDependsOn) when you don't control the targets file.

Returns vs Outputs

xml
<!-- Build returns items for consumption by referencing projects --><Target Name="Build"    DependsOnTargets="$(BuildDependsOn)"    Returns="@(TargetPathWithTargetPlatformMoniker)" />
<!-- GetTargetPath is a lightweight query target --><Target Name="GetTargetPath" Returns="@(TargetPathWithTargetPlatformMoniker)" />
  • Returns specifies what the MSBuild task receives when calling this project. Use for inter-project communication.
  • Outputs on inner targets is for incrementality (timestamp checks). Use for up-to-date detection.
  • Never mix the two purposes. Query targets (GetTargetPath, GetTargetFrameworks) should use Returns, not Outputs.

Target Naming Conventions

PatternMeaningExample
_PrefixedNameInternal/private target_TimeStampBeforeCompile
CoreXxxThe actual implementationCoreBuild, CoreCompile
BeforeXxx / AfterXxxEmpty extensibility hooksBeforeBuild, AfterCompile
PrepareXxxSetup/validation phasePrepareForBuild
ResolveXxxDiscovery/resolution phaseResolveReferences
GetXxxLightweight query (no side effects)GetTargetPath

Complete Custom Target Template

xml
<!-- 1. Define the DependsOn chain for extensibility --><PropertyGroup>  <MyFeatureDependsOn>    _ValidateMyFeatureInputs;    BeforeMyFeature;    CoreMyFeature;    AfterMyFeature  </MyFeatureDependsOn></PropertyGroup>
<!-- 2. Outer target with Returns for inter-project communication --><Target Name="MyFeature"    DependsOnTargets="$(MyFeatureDependsOn)"    Returns="@(MyFeatureOutput)" />
<!-- 3. Empty extensibility points --><Target Name="BeforeMyFeature" /><Target Name="AfterMyFeature" />
<!-- 4. Core implementation with Inputs/Outputs for incrementality --><Target Name="CoreMyFeature"    Inputs="$(MSBuildAllProjects);@(MyFeatureInput)"    Outputs="$(IntermediateOutputPath)myfeature.generated.cs">  <Exec Command="my-tool.exe -o $(IntermediateOutputPath)myfeature.generated.cs" />  <!-- 5. Register outputs for clean tracking -->  <ItemGroup>    <Compile Include="$(IntermediateOutputPath)myfeature.generated.cs" />    <FileWrites Include="$(IntermediateOutputPath)myfeature.generated.cs" />  </ItemGroup></Target>
<!-- 6. Validation target runs first in the dependency chain --><Target Name="_ValidateMyFeatureInputs">  <Error Text="MyFeatureInput items are required."         Condition="'@(MyFeatureInput)' == ''" /></Target>

Common Pitfalls

  • Overwriting DependsOn properties drops SDK targets silently. Always include $(ExistingProperty) when appending.
  • Using Outputs on query targets causes MSBuild to skip them when "up to date," returning stale data. Use Returns.
  • Defining targets in .props means BeforeTargets on SDK targets have nothing to hook into yet. Move targets to .targets.
  • Forgetting OnError in orchestrating targets means file tracking fails on build errors, breaking subsequent incremental builds.

來源與署名

來源:dotnet/skills位於plugins/dotnet-msbuild/skills/target-authoring提交0608d89

授權條款: MIT

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架