target-authoring

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Custom Target Authoring Patterns

自定义Target编写模式

Canonical patterns from
Microsoft.Common.CurrentVersion.targets
in the MSBuild repository.
以下是MSBuild仓库中
Microsoft.Common.CurrentVersion.targets
里的标准模式。

The Three-Level Target Chain

三级Target链

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>
每个主要入口点(Build、Rebuild、Clean)都会委托给一个属性来列出其依赖项,该属性通过Before → Core → After的顺序形成链式结构:
xml
<PropertyGroup>
  <BuildDependsOn>
    BeforeBuild;
    CoreBuild;
    AfterBuild
  </BuildDependsOn>
</PropertyGroup>

<Target Name="Build"
    Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
    DependsOnTargets="$(BuildDependsOn)"
    Returns="@(TargetPathWithTargetPlatformMoniker)" />

<!-- 空的可扩展Target —— 用户可重写这些Target -->
<Target Name="BeforeBuild" />
<Target Name="AfterBuild" />
CoreBuild
会委托给
$(CoreBuildDependsOn)
,并包含错误处理程序:
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.
  • 委托给属性(
    DependsOnTargets="$(MyTargetDependsOn)"
    ),而非硬编码Target。
  • OnError
    应放在编排Target内部,确保即使构建失败也能执行清理操作。
  • 空的Before/After Target是扩展点。用户可重写它们,但SDK永远不会在其中添加逻辑。

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>
当向现有链添加自定义Target时,追加
DependsOn
属性:
xml
<!-- 正确:追加到现有链 -->
<PropertyGroup>
  <CompileDependsOn>$(CompileDependsOn);MyCodeGenTarget</CompileDependsOn>
</PropertyGroup>

<!-- 错误:覆盖整个链,丢弃SDK Target -->
<PropertyGroup>
  <CompileDependsOn>MyCodeGenTarget</CompileDependsOn>
</PropertyGroup>

DependsOnTargets vs BeforeTargets vs AfterTargets

DependsOnTargets vs BeforeTargets vs AfterTargets

MechanismDefined inBest for
DependsOnTargets
The target that needs depsTarget explicitly requires others
BeforeTargets
The injecting targetInsert before a target you don't own
AfterTargets
The 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.
机制定义位置最佳适用场景
DependsOnTargets
需要依赖项的TargetTarget明确需要其他Target作为前置条件
BeforeTargets
注入的Target在不属于你的Target之前插入
AfterTargets
注入的Target在不属于你的Target之后插入
验证Target使用
BeforeTargets
来拦截所有入口点:
xml
<Target Name="_CheckForInvalidConfigurationAndPlatform"
    BeforeTargets="$(BuildDependsOn);Build;$(RebuildDependsOn);Rebuild;$(CleanDependsOn);Clean">
</Target>
规则:
  • 当你的Target需要特定前置条件时,使用
    DependsOnTargets
  • 当注入到不属于你的流水线时,使用
    BeforeTargets
    /
    AfterTargets
  • 当你无法控制targets文件时,优先使用
    BeforeTargets="CoreCompile"
    而非修改
    $(CompileDependsOn)

Returns vs Outputs

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
    .
xml
<!-- Build返回供引用项目使用的项 -->
<Target Name="Build"
    DependsOnTargets="$(BuildDependsOn)"
    Returns="@(TargetPathWithTargetPlatformMoniker)" />

<!-- GetTargetPath是轻量级查询Target -->
<Target Name="GetTargetPath" Returns="@(TargetPathWithTargetPlatformMoniker)" />
  • Returns
    指定调用此项目时MSBuild任务接收的内容。用于项目间通信。
  • Outputs
    用于内部Target的增量性(时间戳检查)。用于最新状态检测。
  • 切勿混淆两者用途。查询Target(
    GetTargetPath
    GetTargetFrameworks
    )应使用
    Returns
    ,而非
    Outputs

Target Naming Conventions

Target命名约定

PatternMeaningExample
_PrefixedName
Internal/private target
_TimeStampBeforeCompile
CoreXxx
The actual implementation
CoreBuild
,
CoreCompile
BeforeXxx
/
AfterXxx
Empty extensibility hooks
BeforeBuild
,
AfterCompile
PrepareXxx
Setup/validation phase
PrepareForBuild
ResolveXxx
Discovery/resolution phase
ResolveReferences
GetXxx
Lightweight query (no side effects)
GetTargetPath
模式含义示例
_PrefixedName
内部/私有Target
_TimeStampBeforeCompile
CoreXxx
实际实现逻辑
CoreBuild
,
CoreCompile
BeforeXxx
/
AfterXxx
空的可扩展钩子
BeforeBuild
,
AfterCompile
PrepareXxx
设置/验证阶段
PrepareForBuild
ResolveXxx
发现/解析阶段
ResolveReferences
GetXxx
轻量级查询(无副作用)
GetTargetPath

Complete Custom Target Template

完整自定义Target模板

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>
xml
<!-- 1. 定义用于扩展的DependsOn链 -->
<PropertyGroup>
  <MyFeatureDependsOn>
    _ValidateMyFeatureInputs;
    BeforeMyFeature;
    CoreMyFeature;
    AfterMyFeature
  </MyFeatureDependsOn>
</PropertyGroup>

<!-- 2. 带有Returns的外部Target,用于项目间通信 -->
<Target Name="MyFeature"
    DependsOnTargets="$(MyFeatureDependsOn)"
    Returns="@(MyFeatureOutput)" />

<!-- 3. 空的扩展点 -->
<Target Name="BeforeMyFeature" />
<Target Name="AfterMyFeature" />

<!-- 4. 带有Inputs/Outputs的核心实现,用于增量构建 -->
<Target Name="CoreMyFeature"
    Inputs="$(MSBuildAllProjects);@(MyFeatureInput)"
    Outputs="$(IntermediateOutputPath)myfeature.generated.cs">
  <Exec Command="my-tool.exe -o $(IntermediateOutputPath)myfeature.generated.cs" />
  <!-- 5. 注册输出以进行清理跟踪 -->
  <ItemGroup>
    <Compile Include="$(IntermediateOutputPath)myfeature.generated.cs" />
    <FileWrites Include="$(IntermediateOutputPath)myfeature.generated.cs" />
  </ItemGroup>
</Target>

<!-- 6. 验证Target在依赖链中最先执行 -->
<Target Name="_ValidateMyFeatureInputs">
  <Error Text="MyFeatureInput项是必需的。"
         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.
  • 覆盖
    DependsOn
    属性
    会静默丢弃SDK Target。追加时务必包含
    $(ExistingProperty)
  • **在查询Target上使用
    Outputs
    **会导致MSBuild在“最新”状态下跳过它们,返回陈旧数据。请使用
    Returns
  • .props
    中定义Target
    意味着针对SDK Target的
    BeforeTargets
    没有可挂钩的对象。请将Target移至
    .targets
    文件。
  • **忘记
    OnError
    **会导致编排Target在构建错误时无法跟踪文件,破坏后续增量构建。