其他
编写或修复 MSBuild 自定义 Target 时…
在 MSBuild 项目中编写或诊断自定义 Target 时,按 Canonical 模式处理目标链、扩展点、Returns/Outputs 和清理注册。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 38/100 ×0.15
适用场景
编写或修复 MSBuild 自定义 Ta特定
解决思路
- 为每个主要入口点(Build、Rebuild、Clean)建立三级目标链:入口目标委托给一个属性,该属性列出依赖,并链式经过 Before → Core → After。
- 在 PropertyGroup 中定义链属性,例如 <BuildDependsOn>BeforeBuild;CoreBuild;AfterBuild</BuildDependsOn>。
- 入口目标按示例设置:Condition=" '$(_InvalidConfigurationWarning)' != 'true' "、DependsOnTargets="$(BuildDependsOn)"、Returns="@(TargetPathWithTargetPlatformMoniker)"。
- 定义空的 BeforeBuild 和 AfterBuild 目标作为可扩展点;用户覆盖它们,SDK 不把逻辑放入其中。
- CoreBuild 委托给 $(CoreBuildDependsOn),并包含 OnError 处理器:OnError ExecuteTargets="_TimeStampAfterCompile;PostBuildEvent" Condition="'$(RunPostBuildEvent)' == 'Always'";OnError ExecuteTargets="_CleanRecordFileWrites"。
- 始终委托给属性(DependsOnTargets="$(MyTargetDependsOn)"),不要硬编码目标列表。
- 将 OnError 放在编排目标内部,确保即使失败也运行清理。
- 扩展已有链时追加而不是覆盖:<CompileDependsOn>$(CompileDependsOn);MyCodeGenTarget</CompileDependsOn>;不要写成 <CompileDependsOn>MyCodeGenTarget</CompileDependsOn>,否则会丢弃 SDK 目标。
- 选择机制:目标需要特定前置条件时用 DependsOnTargets;注入到你并不拥有的管道时用 BeforeTargets/AfterTargets。
- 验证目标用 BeforeTargets 拦截所有入口点,例如 BeforeTargets="$(BuildDependsOn);Build;$(RebuildDependsOn);Rebuild;$(CleanDependsOn);Clean"。
- 当你控制不了目标文件时,优先使用 BeforeTargets="CoreCompile",而不是修改 $(CompileDependsOn)。
- 用 Returns 指定 MSBuild 任务调用该项目时接收的内容,用于项目间通信,例如 Build 返回 @(TargetPathWithTargetPlatformMoniker)。
- 用 Returns 实现轻量查询目标,例如 GetTargetPath Returns="@(TargetPathWithTargetPlatformMoniker)"。
- 用 Outputs 仅用于内部目标的增量/时间戳检查,以进行最新检测。
- 不要混用 Returns 和 Outputs 的用途;查询目标(GetTargetPath、GetTargetFrameworks)应使用 Returns,而不是 Outputs。
- 遵循命名约定:_PrefixedName 内部/私有目标;CoreXxx 实际实现;BeforeXxx/AfterXxx 空扩展钩子;PrepareXxx 设置/验证阶段;ResolveXxx 发现/解析阶段;GetXxx 轻量查询且无副作用。
- 完整模板第 1 步:定义 MyFeatureDependsOn 链,如 _ValidateMyFeatureInputs;BeforeMyFeature;CoreMyFeature;AfterMyFeature。
- 完整模板第 2 步:定义外部目标 MyFeature,使用 DependsOnTargets="$(MyFeatureDependsOn)" 和 Returns="@(MyFeatureOutput)"。
- 完整模板第 3 步:定义空的 BeforeMyFeature 和 AfterMyFeature 目标。
- 完整模板第 4 步:CoreMyFeature 使用 Inputs="$(MSBuildAllProjects);@(MyFeatureInput)" 和 Outputs="$(IntermediateOutputPath)myfeature.generated.cs" 实现增量;执行 Exec Command="my-tool.exe -o $(IntermediateOutputPath)myfeature.generated.cs"。
- 完整模板第 5 步:在 CoreMyFeature 中用 ItemGroup 添加 Compile Include 和 FileWrites Include 注册输出,以便 clean 跟踪。
- 完整模板第 6 步:添加 _ValidateMyFeatureInputs 目标,在依赖链最先运行,并用 Error Text="MyFeatureInput items are required." Condition="'@(MyFeatureInput)' == ''" 验证输入。
- 避免覆盖 DependsOn 属性导致 SDK 目标静默丢失;追加时始终包含 $(ExistingProperty)。
- 不要在查询目标上使用 Outputs,否则 MSBuild 可能因最新而跳过并返回陈旧数据;改用 Returns。
- 不要在 .props 中定义目标;在 .props 中 BeforeTargets 对 SDK 目标无可挂钩对象,应把目标移到 .targets。
- 不要忘记在编排目标中使用 OnError,否则构建错误时文件跟踪失败,破坏后续增量构建。
适用边界 · 注意事项
- 不适用于非 MSBuild 构建系统。
- 不用于增量构建调优(应使用 incremental-build)或并行化(应使用 build-parallelism)。
- 不用于一般 MSBuild 反模式诊断(应使用 msbuild-antipatterns)。