Imports and Project SDKs
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
Real builds are made of many MSBuild files: the project, the SDK’s hundreds of .props and .targets, the repository’s
Directory.Build.*, and the build logic of packages. Two mechanisms combine them: Import, which pastes a file into
the current one, and project SDKs, which package a pair of imports (Sdk.props, Sdk.targets) under a name that
MSBuild resolves — from the .NET SDK, from NuGet, or from MSBuild’s own folder. This page shows both with
MSBuild 18.9 (.NET SDK 10.0.402) and builds Ledgerline with two SDKs from the
microsoft/MSBuildSdks repository (a Microsoft GitHub repository; these SDKs
have no page on MS Learn).
Import
<Import Project="path" /> inlines the file at that point of the evaluation. Paths are relative to the importing file;
$(MSBuildThisFileDirectory) makes paths in imported files independent of the project’s location. Two variations make
imports extensible:
<Project>
<!-- Optional, per-developer file: imported only when present. -->
<Import Project="local.props" Condition="Exists('local.props')" />
<!-- Extension point: every .props file in the folder, in sorted order. -->
<Import Project="extensions/*.props" />
<Target Name="Show">
<Message Importance="high" Text="Loaded: $(Loaded.TrimStart(';'))" />
</Target>
</Project>
with extensions/10-currency.props and extensions/20-audit.props each appending its name to Loaded:
dotnet msbuild imports.proj -t:Show -nologo -v:m
# after creating local.props, which sets Loaded to "local"
dotnet msbuild imports.proj -t:Show -nologo -v:m
Loaded: 10-currency;20-audit
Loaded: local;10-currency;20-audit
Wildcard matches are imported in sorted order (hence the numeric prefixes) and a wildcard that matches nothing is not an error. A plain path that does not exist is:
~/demo/App.csproj(2,3): error MSB4019: The imported project "~/demo/build/ledger.targets" was not found. Confirm that the expression in the Import declaration "build/ledger.targets", which evaluated to "build/ledger.targets", is correct, and that the file exists on disk.
ImportGroup groups imports under one condition. The SDK’s own extension points are imports of this kind:
Microsoft.Common.targets imports $(MSBuildExtensionsPath)/$(MSBuildToolsVersion)/Microsoft.Common.targets/ImportBefore/*
and ImportAfter/* when the corresponding ImportByWildcard… properties are true.
Three ways to use a project SDK
| Form | When to use |
|---|---|
|
The normal form: implicit |
|
Add a second SDK on top of the first; the version is an attribute. |
|
Full control of placement — the only way to put project content after |
The explicit form is shown on SDK-Style Project Files.
Resolving SDKs
MSBuild asks its SDK resolvers in turn: the .NET SDK resolver (SDKs shipped with the selected .NET SDK, such as
Microsoft.NET.Sdk.Web), the workload resolver (SDKs installed by workloads), the NuGet resolver (an SDK
published as a package, downloaded like any package — only when a version is known) and the default resolver (SDKs in
MSBuild’s Sdks folder). The version for the NuGet resolver comes from the project (Sdk="Name/1.2.3") or, better, once
for the repository from msbuild-sdks in global.json:
{
"sdk": {
"version": "10.0.402",
"rollForward": "latestPatch",
"allowPrerelease": false
},
"test": {
"runner": "Microsoft.Testing.Platform"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "4.1.82",
"Microsoft.Build.NoTargets": "3.7.134"
}
}
Without a version anywhere, every resolver declines and the build stops before evaluation:
~/demo/Docs.proj : error : Could not resolve SDK "Microsoft.Build.NoTargets". Exactly one of the probing messages below indicates why we could not resolve the SDK. Investigate and resolve that message to correctly specify the SDK.
~/demo/Docs.proj : error : SDK resolver "Microsoft.DotNet.MSBuildWorkloadSdkResolver" returned null.
~/demo/Docs.proj : error : The NuGetSdkResolver did not resolve this SDK because there was no version specified in the project or global.json.
~/demo/Docs.proj : error MSB4236: The SDK 'Microsoft.Build.NoTargets' specified could not be found.
Build FAILED.
~/demo/Docs.proj : error : Could not resolve SDK "Microsoft.Build.NoTargets". Exactly one of the probing messages below indicates why we could not resolve the SDK. Investigate and resolve that message to correctly specify the SDK.
~/demo/Docs.proj : error : SDK resolver "Microsoft.DotNet.MSBuildWorkloadSdkResolver" returned null.
~/demo/Docs.proj : error : The NuGetSdkResolver did not resolve this SDK because there was no version specified in the project or global.json.
~/demo/Docs.proj : error MSB4236: The SDK 'Microsoft.Build.NoTargets' specified could not be found.
0 Warning(s)
2 Error(s)
Time Elapsed 00:00:00.15
Only one version of a given SDK can be used in one build; MSBuild warns when projects ask for different ones, which is
another reason to keep the versions in global.json.
Microsoft.Build.Traversal: building a tree without a solution
A traversal project lists other projects with ProjectReference items and forwards Build, Restore, Test, Pack,
Publish and Clean to them. It replaces a solution file on the command line and in CI, and can use globs, so new
projects are picked up without editing anything. The Ledgerline tree as a dirs.proj:
<Project Sdk="Microsoft.Build.Traversal">
<ItemGroup>
<ProjectReference Include="src/**/*.csproj" />
<ProjectReference Include="tests/**/*.csproj" />
<ProjectReference Include="docs/*.proj" />
<!-- Template content is not a project of this repository, and the sample consumes the local feed. -->
<ProjectReference Remove="src/Ledgerline.Templates/templates/**" />
<ProjectReference Remove="samples/**" />
</ItemGroup>
</Project>
The first version had only the two Include globs and samples/** removed. src/**/*.csproj also matched the
template’s content project (src/Ledgerline.Templates/templates/ledgerline-app/LedgerlineApp.csproj), which is not
meant to be built in the repository and failed under Central Package Management:
~/ledgerline/src/Ledgerline.Templates/templates/ledgerline-app/LedgerlineApp.csproj : error NU1008: The following PackageReference items cannot define a value for Version: Ledgerline. Projects using Central Package Management must define a Version value on a PackageVersion item. For more information, visit https://aka.ms/nuget/cpm/gettingstarted [~/ledgerline/dirs.proj]
With the template folder removed:
dotnet build dirs.proj -tl:off -v:m
ls docs/bin/Debug/net10.0/
Ledgerline.Build -> ~/ledgerline/src/Ledgerline.Build/bin/Debug/netstandard2.0/Ledgerline.Build.dll
Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/net8.0/Ledgerline.dll
Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/netstandard2.0/Ledgerline.dll
Ledgerline.Templates -> ~/ledgerline/src/Ledgerline.Templates/bin/Debug/net10.0/Ledgerline.Templates.dll
Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/net10.0/Ledgerline.dll
Ledgerline.Cli -> ~/ledgerline/src/Ledgerline.Cli/bin/Debug/net10.0/Ledgerline.Cli.dll
Ledgerline.Tests -> ~/ledgerline/tests/Ledgerline.Tests/bin/Debug/net10.0/Ledgerline.Tests.dll
Build succeeded.
CHANGELOG.md
README.md
dotnet pack dirs.proj packs the four packable projects the same way. Testing through a traversal project depends on
the test mode: Ledgerline's global.json selects Microsoft.Testing.Platform, and in that mode dotnet test accepts
only a project, solution or directory, so dotnet test dirs.proj stops with "Specify which project or solution file to
use…" and dotnet test --project dirs.proj reports No test projects were found. — run the tests through the solution
or the test project instead
(Test, VSTest and Microsoft.Testing.Platform).
Traversal projects can also reference other traversal projects (src/dirs.proj), which keeps very large trees
manageable.
Microsoft.Build.NoTargets: a project that compiles nothing
The last two lines above come from docs/Ledgerline.Docs.proj, a NoTargets project. It takes part in the build — it can
copy files, run targets, have ProjectReference items and be packed — but produces no assembly:
<Project Sdk="Microsoft.Build.NoTargets">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Include="../README.md;../CHANGELOG.md" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
Typical uses are documentation folders, packaging projects that only bundle files, and projects that exist to run a tool in the right order.
Microsoft.Build.CentralPackageVersions (deprecated)
Before NuGet had Central Package Management, this SDK provided a Packages.props file with central versions. Its README
now begins with "Deprecated": the feature is built into NuGet and MSBuild (Directory.Packages.props,
ManagePackageVersionsCentrally) and a migration guide is provided. The last version, 2.1.3, dates from 2021; nuget.org
does not flag the package itself as deprecated. Use
Central Package Management.
Common mistakes
-
A NuGet-delivered SDK without a version —
MSB4236. -
Different SDK versions in different projects — warnings and surprises; use
global.json. -
Globs in traversal projects that match too much — template content,
samples/, test fixtures; useRemove. -
An
Importof a file that only exists after restore, without a condition —MSB4019on a clean clone. -
Relying on import order of unsorted names — prefix wildcard-imported files with numbers.