关于 target-authoring
面向编写自定义 MSBuild 目标的开发者技能,介绍依赖链、扩展钩子、目标输出、命名和失败清理模式。
主要功能
- 01三级目标链模式
- 02安全扩展 DependsOn
- 03选择目标排序机制
- 04Returns 与 Outputs 指引
- 05目标命名约定
- 06使用 OnError 清理失败
适用场景
- 01向现有构建链添加目标
- 02在 SDK 目标前后插入操作
- 03在项目间返回查询结果
- 04避免增量构建数据过期
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dotnet/ skills --skill target-authoring安装后在 Claude Code 中输入/target-authoring确认已加载。
详细介绍
自定义目标编写模式
来自 MSBuild 仓库中 Microsoft.Common.CurrentVersion.targets 的规范模式。
三级目标链
每个主要入口点(Build、Rebuild、Clean)都会委托给一个列出其依赖项的属性,并沿 Before → Core → After 链接:
<PropertyGroup>
<BuildDependsOn>
BeforeBuild;
CoreBuild;
AfterBuild
</BuildDependsOn>
</PropertyGroup>
<Target Name="Build"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="$(BuildDependsOn)"
Returns="@(TargetPathWithTargetPlatformMoniker)" />
<Target Name="BeforeBuild" />
<Target Name="AfterBuild" />
CoreBuild 委托给 $(CoreBuildDependsOn),并包含错误处理程序:
<Target Name="CoreBuild" DependsOnTargets="$(CoreBuildDependsOn)">
<OnError ExecuteTargets="_TimeStampAfterCompile;PostBuildEvent"
Condition="'$(RunPostBuildEvent)' == 'Always'" />
<OnError ExecuteTargets="_CleanRecordFileWrites" />
</Target>
规则
- 委托给属性(
DependsOnTargets="$(MyTargetDependsOn)"),不要硬编码目标。 - 将
OnError放在协调目标内部,确保即使发生失败也会执行清理。 - 空的 Before/After 目标是扩展点。用户会重写这些目标;SDK 绝不会在其中放入逻辑。
扩展链 — 追加,绝不覆盖
向现有链添加自定义目标时,应将其追加到 DependsOn 属性中:
<PropertyGroup>
<CompileDependsOn>$(CompileDependsOn);MyCodeGenTarget</CompileDependsOn>
</PropertyGroup>
<PropertyGroup>
<CompileDependsOn>MyCodeGenTarget</CompileDependsOn>
</PropertyGroup>
DependsOnTargets 与 BeforeTargets 和 AfterTargets
| 机制 | 定义位置 | 最适用于 |
|---|---|---|
DependsOnTargets | 需要依赖项的目标 | 目标明确需要其他目标 |
BeforeTargets | 注入目标 | 在不属于自己的目标之前插入 |
AfterTargets | 注入目标 | 在不属于自己的目标之后插入 |
验证目标使用 BeforeTargets 拦截所有入口点:
<Target Name="_CheckForInvalidConfigurationAndPlatform"
BeforeTargets="$(BuildDependsOn);Build;$(RebuildDependsOn);Rebuild;$(CleanDependsOn);Clean">
</Target>
规则:
- 当目标需要特定前置条件时,使用
DependsOnTargets。 - 当向不属于自己的管道注入内容时,使用
BeforeTargets/AfterTargets。 - 当你无法控制目标文件时,优先使用
BeforeTargets="CoreCompile",而不是修改$(CompileDependsOn)。
Returns 与 Outputs
<Target Name="Build"
DependsOnTargets="$(BuildDependsOn)"
Returns="@(TargetPathWithTargetPlatformMoniker)" />
<Target Name="GetTargetPath" Returns="@(TargetPathWithTargetPlatformMoniker)" />
Returns指定调用此项目时 MSBuild 任务接收的内容。用于项目间通信。- 内部目标上的
Outputs用于增量构建(时间戳检查)。用于判断是否为最新状态。 - 绝不要混用这两种用途。查询目标(
GetTargetPath、GetTargetFrameworks)应使用Returns,而不是Outputs。
目标命名约定
| 模式 | 含义 | 示例 |
|---|---|---|
_PrefixedName | 内部/私有目标 | _TimeStampBeforeCompile |
CoreXxx | 实际实现 | CoreBuild、CoreCompile |
BeforeXxx / AfterXxx | 空的扩展钩子 | BeforeBuild、AfterCompile |
PrepareXxx | 设置/验证阶段 | PrepareForBuild |
ResolveXxx | 查找/解析阶段 | ResolveReferences |
GetXxx | 轻量级查询(无副作用) | GetTargetPath |
完整的自定义目标模板
<PropertyGroup>
<MyFeatureDependsOn>
_ValidateMyFeatureInputs;
BeforeMyFeature;
CoreMyFeature;
AfterMyFeature
</MyFeatureDependsOn>
</PropertyGroup>
<Target Name="MyFeature"
DependsOnTargets="$(MyFeatureDependsOn)"
Returns="@(MyFeatureOutput)" />
<Target Name="BeforeMyFeature" />
<Target Name="AfterMyFeature" />
<Target Name="CoreMyFeature"
Inputs="$(MSBuildAllProjects);@(MyFeatureInput)"
Outputs="$(IntermediateOutputPath)myfeature.generated.cs">
<Exec Command="my-tool.exe -o $(IntermediateOutputPath)myfeature.generated.cs" />
<ItemGroup>
<Compile Include="$(IntermediateOutputPath)myfeature.generated.cs" />
<FileWrites Include="$(IntermediateOutputPath)myfeature.generated.cs" />
</ItemGroup>
</Target>
<Target Name="_ValidateMyFeatureInputs">
<Error Text="MyFeatureInput items are required."
Condition="'@(MyFeatureInput)' == ''" />
</Target>
常见陷阱
- 覆盖
DependsOn属性会悄然丢弃 SDK 目标。追加时务必包含$(ExistingProperty)。 - **在查询目标上使用
Outputs**会导致 MSBuild 在目标“已是最新状态”时跳过这些目标,从而返回过期数据。请使用Returns。 - 在
.props中定义目标意味着 SDK 目标上的BeforeTargets尚无可挂接对象。请将目标移至.targets。 - **在协调目标中忘记
OnError**会导致文件跟踪在构建错误时失败,进而破坏后续增量构建。
评论
支持 Markdown