Organizing Build Infrastructure with Directory.Build Files
Intent control
- For "should we", "how should we organize", "recommend", "advise", or review requests, inspect the actual project files and return a proposed layout only. Do not create or edit files unless the user explicitly asks to apply, move, centralize, or clean up the configuration.
- Before summarizing, verify which project retains each project-specific property, item, or target. Name the actual file; do not infer it from sibling names.
- Recommend a verification command only after discovering a real
.sln,.slnx,.proj, or project file at that path. If no repo-root entry point exists, give commands for the discovered projects or state that the entry point was not found. - Every advice response must explain that
Microsoft.Common.propssearches upward from each project and automatically imports the nearestDirectory.Build.propsearly; project values can then override shared defaults. Explain the equivalent lateMicrosoft.Common.targetsimport when recommendingDirectory.Build.targets.
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:
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 [blocked] for the full explanation.
Directory.Build.props
Good candidates: language settings, assembly/package metadata, build warnings, code analysis, common analyzers.
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.
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:
Directory.Build.rsp
Contains default MSBuild CLI arguments applied to all builds under the directory tree.
Example Directory.Build.rsp:
- 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 [blocked] for full file examples.
Example layout:
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 [blocked] 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 restore/build against the discovered
.sln,.slnx,.proj, or project path, for exampledotnet restore <entrypoint> && dotnet build <entrypoint>. If no root entrypoint exists, validate each discovered project explicitly instead of claiming a repo-root build. Usedotnet msbuild <project> -pp:output.xmlto inspect the final merged view.
Troubleshooting
Diagnosis: Use the preprocessed project output to see all imports and final property values:
This expands all imports inline so you can see exactly where each property is set and what the final evaluated value is.


