directory-build-organization
Skill4 starsChanged 3 months agoMITView on GitHub
Guide for organizing MSBuild infrastructure with Directory.Build.props, Directory.Build.targets, Directory.Packages.props, and Directory.Build.rsp. USE FOR: structuring multi-project repos, centralizing build settings, implementing NuGet Central Package Management (CPM) with ManagePackageVersionsCentrally, consolidating duplicated properties across .csproj files, setting up multi-level Directory.Build hierarchy with GetPathOfFileAbove, understanding evaluation order (Directory.Build.props → SDK .props → .csproj → SDK .targets → Directory.Build.targets). Critical pitfall: $(TargetFramework) conditions in .props silently fail for single-targeting projects — must use .targets. DO NOT USE FOR: non-MSBuild build systems, migrating legacy projects to SDK-style (use msbuild-modernization), single-project solutions with no shared settings.
1 other project uses a version of this, 1 of them edited.
Adopted by
Most recently taken up by
- microsoft/testfxedited, first seen Oct 9, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 1
- microsoft/testfx★ 1.1k76% same
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository7 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .github/copilot-instructions.mdCopilot instructions
- coverage-analysisSkill
- msbuild-antipatternsSkill
- nuget-release-governanceSkill
- roslyn-analyzer-developmentSkill
- scaffold-dotnet-test-projectSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 169
Diagnosis: Use the preprocessed project output to see all imports and final property values:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Organizing Build Infrastructure with Directory.Build Files
Directory.Build.props vs Directory.Build.targets
Understanding which file to use is critical. They differ in when they are imported during evaluation:
Evaluation order:
Directory.Build.props → SDK .props → YourProject.csproj → SDK .targets → Directory.Build.targetsUse .props for | Use .targets for |
| Setting property defaults | Custom build targets |
| Common item definitions | Late-bound property overrides |
| Properties projects can override | Post-build steps |
| Assembly/package metadata | Conditional logic on final values |
| Analyzer PackageReferences | Targets that depend on SDK-defined properties |
Rule of thumb: Properties and items go in .props. Custom targets and late-bound logic go in .targets.
Because .props is imported before the project file, the project can override any value set there. Because .targets is imported after everything, it gets the final say—but projects cannot override .targets values.
⚠️ Critical: TargetFramework Availability in .props vs .targets
Property conditions on $(TargetFramework) in .props files silently fail for single-targeting projects — the property is empty during .props evaluation. Move TFM-conditional properties to .targets instead. ItemGroup and Target conditions are not affected.
See targetframework-props-pitfall.md for the full explanation.
Directory.Build.props
Good candidates: language settings, assembly/package metadata, build warnings, code analysis, common analyzers.
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<Company>Contoso</Company>
<Authors>Contoso Engineering</Authors>
</PropertyGroup>
</Project>Do NOT put here: project-specific TFMs, project-specific PackageReferences, targets/build logic, or properties depending on SDK-defined values (not available during .props evaluation).
Directory.Build.targets
Good candidates: custom build targets, late-bound property overrides (values depending on SDK properties), post-build validation.
<Project>
<Target Name="ValidateProjectSettings" BeforeTargets="Build">
<Error Text="All libraries must target netstandard2.0 or higher"
Condition="'$(OutputType)' == 'Library' AND '$(TargetFramework)' == 'net472'" />
</Target>
<PropertyGroup>
<!-- DocumentationFile depends on OutputPath, which is set by the SDK -->
<DocumentationFile Condition="'$(IsPackable)' == 'true'">$(OutputPath)$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
</Project>Directory.Packages.props (Central Package Management)
Central Package Management (CPM) provides a single source of truth for all NuGet package versions. See https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management for details.
Enable CPM in Directory.Packages.props at the repo root:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="xunit" Version="2.9.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<!-- GlobalPackageReference applies to ALL projects — great for analyzers -->
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<GlobalPackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" />
</ItemGroup>
</Project>Directory.Build.rsp
Contains default MSBuild CLI arguments applied to all builds under the directory tree.
Example Directory.Build.rsp:
/maxcpucount
/nodeReuse:false
/consoleLoggerParameters:Summary;ForceNoAlign
/warnAsMessage:MSB3277- Works with both
msbuildanddotnetCLI in modern .NET versions - Great for enforcing consistent CI and local build flags
- Each argument goes on its own line
Multi-level Directory.Build Files
MSBuild only auto-imports the first Directory.Build.props (or .targets) it finds walking up from the project directory. To chain multiple levels, explicitly import the parent at the top of the inner file. See multi-level-examples for full file examples.
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))"
Condition="Exists('$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))')" />
<!-- Inner-level overrides go here -->
</Project>Example layout:
repo/
Directory.Build.props ← repo-wide (lang version, company info, analyzers)
Directory.Build.targets ← repo-wide targets
Directory.Packages.props ← central package versions
src/
Directory.Build.props ← src-specific (imports repo-level, sets IsPackable=true)
test/
Directory.Build.props ← test-specific (imports repo-level, sets IsPackable=false, adds test packages)Artifact Output Layout (.NET 8+)
Set <ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath> in Directory.Build.props to automatically produce project-name-separated bin/, obj/, and publish/ directories under a single artifacts/ folder, avoiding bin/obj clashes by default. See common-patterns for the directory layout and additional patterns (conditional settings by project type, post-pack validation).
Workflow: Organizing Build Infrastructure
- Audit all
.csprojfiles — Catalog every<PropertyGroup>,<ItemGroup>, and custom<Target>across the solution. Note which settings repeat and which are project-specific. - Create root
Directory.Build.props— Move shared property defaults (LangVersion, Nullable, TreatWarningsAsErrors, metadata) here. These are imported before the project file so projects can override them. - Create root
Directory.Build.targets— Move custom build targets, post-build validation, and any properties that depend on SDK-defined values (e.g.,OutputPath,TargetFrameworkfor single-targeting projects) here. These are imported after the SDK so all properties are available. - Create
Directory.Packages.props— Enable Central Package Management (ManagePackageVersionsCentrally), list allPackageVersionentries, and removeVersion=fromPackageReferenceitems in.csprojfiles. - Set up multi-level hierarchy — Create inner
Directory.Build.propsfiles forsrc/andtest/folders with distinct settings. UseGetPathOfFileAboveto chain to the parent. - Simplify
.csprojfiles — Remove all centralized properties, version attributes, and duplicated targets. Each project should only contain what is unique to it. - Validate — Run
dotnet restore && dotnet buildand verify no regressions. Usedotnet msbuild -pp:output.xmlto inspect the final merged view if needed.
Troubleshooting
| Problem | Cause | Fix |
Directory.Build.props isn't picked up | File name casing wrong (exact match required on Linux/macOS) | Verify exact casing: Directory.Build.props (capital D, B) |
Properties from .props are ignored by projects | Project sets the same property after the import | Move the property to Directory.Build.targets to set it after the project |
| Multi-level import doesn't work | Missing GetPathOfFileAbove import in inner file | Add the <Import> element at the top of the inner file (see Multi-level section) |
Properties using SDK values are empty in .props | SDK properties aren't defined yet during .props evaluation | Move to .targets which is imported after the SDK |
Directory.Packages.props not found | File not at repo root or not named exactly | Must be named Directory.Packages.props and at or above the project directory |
Property condition on $(TargetFramework) doesn't match in .props | TargetFramework isn't set yet for single-targeting projects during .props evaluation | Move property to .targets, or use ItemGroup/Target conditions instead (which evaluate late) |
Diagnosis: Use the preprocessed project output to see all imports and final property values:
dotnet msbuild -pp:output.xml MyProject.csprojThis expands all imports inline so you can see exactly where each property is set and what the final evaluated value is.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

