target-authoring
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCustom Target Authoring Patterns
自定义Target编写模式
Canonical patterns from in the MSBuild repository.
Microsoft.Common.CurrentVersion.targets以下是MSBuild仓库中里的标准模式。
Microsoft.Common.CurrentVersion.targetsThe 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$(CoreBuildDependsOn)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 (), not hardcoded targets.
DependsOnTargets="$(MyTargetDependsOn)" - goes inside the orchestrating target to ensure cleanup runs even on failure.
OnError - Empty Before/After targets are extensibility points. Users override them; SDKs never put logic in them.
- 委托给属性(),而非硬编码Target。
DependsOnTargets="$(MyTargetDependsOn)" - 应放在编排Target内部,确保即使构建失败也能执行清理操作。
OnError - 空的Before/After Target是扩展点。用户可重写它们,但SDK永远不会在其中添加逻辑。
Chain Extension — Append, Never Overwrite
链扩展 —— 追加,绝不覆盖
When adding a custom target to an existing chain, append to the property:
DependsOnxml
<!-- 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时,追加到属性:
DependsOnxml
<!-- 正确:追加到现有链 -->
<PropertyGroup>
<CompileDependsOn>$(CompileDependsOn);MyCodeGenTarget</CompileDependsOn>
</PropertyGroup>
<!-- 错误:覆盖整个链,丢弃SDK Target -->
<PropertyGroup>
<CompileDependsOn>MyCodeGenTarget</CompileDependsOn>
</PropertyGroup>DependsOnTargets vs BeforeTargets vs AfterTargets
DependsOnTargets vs BeforeTargets vs AfterTargets
| Mechanism | Defined in | Best for |
|---|---|---|
| The target that needs deps | Target explicitly requires others |
| The injecting target | Insert before a target you don't own |
| The injecting target | Insert after a target you don't own |
Validation targets use to intercept all entry points:
BeforeTargetsxml
<Target Name="_CheckForInvalidConfigurationAndPlatform"
BeforeTargets="$(BuildDependsOn);Build;$(RebuildDependsOn);Rebuild;$(CleanDependsOn);Clean">
</Target>Rules:
- Use when your target needs specific prerequisites.
DependsOnTargets - Use /
BeforeTargetswhen injecting into a pipeline you don't own.AfterTargets - Prefer over modifying
BeforeTargets="CoreCompile"when you don't control the targets file.$(CompileDependsOn)
| 机制 | 定义位置 | 最佳适用场景 |
|---|---|---|
| 需要依赖项的Target | Target明确需要其他Target作为前置条件 |
| 注入的Target | 在不属于你的Target之前插入 |
| 注入的Target | 在不属于你的Target之后插入 |
验证Target使用来拦截所有入口点:
BeforeTargetsxml
<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)" />- specifies what the MSBuild task receives when calling this project. Use for inter-project communication.
Returns - on inner targets is for incrementality (timestamp checks). Use for up-to-date detection.
Outputs - Never mix the two purposes. Query targets (,
GetTargetPath) should useGetTargetFrameworks, notReturns.Outputs
xml
<!-- Build返回供引用项目使用的项 -->
<Target Name="Build"
DependsOnTargets="$(BuildDependsOn)"
Returns="@(TargetPathWithTargetPlatformMoniker)" />
<!-- GetTargetPath是轻量级查询Target -->
<Target Name="GetTargetPath" Returns="@(TargetPathWithTargetPlatformMoniker)" />- 指定调用此项目时MSBuild任务接收的内容。用于项目间通信。
Returns - 用于内部Target的增量性(时间戳检查)。用于最新状态检测。
Outputs - 切勿混淆两者用途。查询Target(、
GetTargetPath)应使用GetTargetFrameworks,而非Returns。Outputs
Target Naming Conventions
Target命名约定
| Pattern | Meaning | Example |
|---|---|---|
| Internal/private target | |
| The actual implementation | |
| Empty extensibility hooks | |
| Setup/validation phase | |
| Discovery/resolution phase | |
| Lightweight query (no side effects) | |
| 模式 | 含义 | 示例 |
|---|---|---|
| 内部/私有Target | |
| 实际实现逻辑 | |
| 空的可扩展钩子 | |
| 设置/验证阶段 | |
| 发现/解析阶段 | |
| 轻量级查询(无副作用) | |
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 properties drops SDK targets silently. Always include
DependsOnwhen appending.$(ExistingProperty) - Using on query targets causes MSBuild to skip them when "up to date," returning stale data. Use
Outputs.Returns - Defining targets in means
.propson SDK targets have nothing to hook into yet. Move targets toBeforeTargets..targets - Forgetting in orchestrating targets means file tracking fails on build errors, breaking subsequent incremental builds.
OnError
- 覆盖属性会静默丢弃SDK Target。追加时务必包含
DependsOn。$(ExistingProperty) - **在查询Target上使用**会导致MSBuild在“最新”状态下跳过它们,返回陈旧数据。请使用
Outputs。Returns - 在中定义Target意味着针对SDK Target的
.props没有可挂钩的对象。请将Target移至BeforeTargets文件。.targets - **忘记**会导致编排Target在构建错误时无法跟踪文件,破坏后续增量构建。
OnError