Target Authoring
.NET (Microsoft)OfficialPublished by .NET (Microsoft)
- Skill
- Guidelines
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides the standard architectural patterns for creating custom MSBuild targets. It focuses on maintaining the integrity of the build pipeline by using proper dependency chaining, correct extensibility hooks, and avoiding common pitfalls that lead to broken SDK integrations or stale build results. It covers the three-level target chain, proper use of DependsOn properties, and the distinction between Returns and Outputs.
What it does
- Defines the three-level Build-CoreBuild-AfterBuild pattern for target orchestration.
- Provides rules for extending existing build chains without overwriting SDK targets.
- Explains the correct usage of DependsOnTargets, BeforeTargets, and AfterTargets.
- Clarifies the functional difference between Returns for inter-project queries and Outputs for incremental builds.
- Establishes standard naming conventions for internal, core, and extensibility targets.
- Offers a complete template for implementing robust custom features in MSBuild.
When to use it
- When creating custom build logic that needs to integrate with standard MSBuild entry points.
- When diagnosing issues where custom targets are silently breaking SDK build chains.
- When fixing incremental build issues caused by incorrect use of Outputs versus Returns.
- When standardizing target naming and structure across a complex build system.
- When you need to inject logic before or after existing targets you do not own.
Advantages
- Prevents silent breakage of SDK-provided build targets.
- Ensures build extensibility through standard hooks.
- Improves build reliability by enforcing correct incrementality and error handling.
- Promotes maintainable and predictable build system architecture.
Usage examples
- “How do I add my custom code generation target to the build process without breaking the existing Compile target?”
- The skill instructs you to append your target to the CompileDependsOn property using $(CompileDependsOn);MyCodeGenTarget, rather than overwriting the property.
- “My query target is returning stale data during the build, what am I doing wrong?”
- The skill explains that you are likely using Outputs for a query target; you should switch to using Returns to ensure the target is always evaluated.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/dotnet/target-authoring/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip target-authoring.zip -d .agents/skills
Documentation by .NET (Microsoft)
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:
<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:
<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.OnErrorgoes 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:
<!-- 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
| Mechanism | Defined in | Best for |
|---|---|---|
DependsOnTargets | The target that needs deps | Target explicitly requires others |
BeforeTargets | The injecting target | Insert before a target you don't own |
AfterTargets | The injecting target | Insert after a target you don't own |
Validation targets use BeforeTargets to intercept all entry points:
<Target Name="_CheckForInvalidConfigurationAndPlatform"
BeforeTargets="$(BuildDependsOn);Build;$(RebuildDependsOn);Rebuild;$(CleanDependsOn);Clean">
</Target>Rules:
Use
DependsOnTargetswhen your target needs specific prerequisites.Use
BeforeTargets/AfterTargetswhen 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
<!-- 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)" />Returnsspecifies what the MSBuild task receives when calling this project. Use for inter-project communication.Outputson inner targets is for incrementality (timestamp checks). Use for up-to-date detection.Never mix the two purposes. Query targets (
GetTargetPath,GetTargetFrameworks) should useReturns, notOutputs.
Target Naming Conventions
| Pattern | Meaning | Example |
|---|---|---|
_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 |
Complete Custom Target Template
<!-- 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
DependsOnproperties drops SDK targets silently. Always include$(ExistingProperty)when appending.Using
Outputson query targets causes MSBuild to skip them when "up to date," returning stale data. UseReturns.Defining targets in
.propsmeansBeforeTargetson SDK targets have nothing to hook into yet. Move targets to.targets.Forgetting
OnErrorin orchestrating targets means file tracking fails on build errors, breaking subsequent incremental builds.
Files
- ATTRIBUTION.mdAdded by AutoGineer846 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md6.5 KiB
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- .NET (Microsoft)
- Publisher
- .NET (Microsoft) (Official)
- Source
- dotnet/skills · plugins/dotnet-msbuild/skills/target-authoring · commit 96c3d68
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by .NET (Microsoft).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download target-authoring.zip