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 从公开仓库中收录这些内容。

举报或申请下架