Restore, Build and Clean

This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its dotnet CLI) and NuGet 7.9, as published in the MSBuild, .NET and NuGet documentation on learn.microsoft.com, which is the reference these pages are written and verified against. Material about .NET 11 / NuGet 8.0 is still in preview and is always flagged as such — never presented as baseline.

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.

dotnet build is the command run most often, and most of its options decide which build you get: the configuration, the target framework, the runtime, the output folder, whether it restores and whether it builds references. This page runs each option on Ledgerline with .NET SDK 10.0.402 and shows the result. The engine underneath is described on How a .NET Build Works.

Restore, then build

dotnet build restores first (it is dotnet msbuild -restore), so a separate dotnet restore is optional on a developer machine. In CI, separating them makes the restore a cacheable, separately-timed step and lets the build run offline:

dotnet restore --locked-mode
dotnet build -c Release --no-restore
  Ledgerline.Build -> ~/ledgerline/src/Ledgerline.Build/bin/Release/netstandard2.0/Ledgerline.Build.dll
  Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Release/netstandard2.0/Ledgerline.dll
  Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Release/net10.0/Ledgerline.dll
  Ledgerline: 4 journal lines balance.
  Ledgerline.Cli -> ~/ledgerline/src/Ledgerline.Cli/bin/Release/net10.0/Ledgerline.Cli.dll
  Ledgerline.Report -> ~/ledgerline/samples/Ledgerline.Report/bin/Release/net10.0/Ledgerline.Report.dll
  Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Release/net8.0/Ledgerline.dll
  Ledgerline.Tests -> ~/ledgerline/tests/Ledgerline.Tests/bin/Release/net10.0/Ledgerline.Tests.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:02.63

The Ledgerline: 4 journal lines balance. line is the ValidateLedger task from the Ledgerline.Build package (Writing Custom Tasks).

With --no-restore, a project that was never restored fails with NETSDK1004:

~/.dotnet/sdk/10.0.402/Sdks/Microsoft.NET.Sdk/targets/Microsoft.PackageDependencyResolution.targets(266,5): error NETSDK1004: Assets file '~/ledgerline/samples/Ledgerline.Report/obj/project.assets.json' not found. Run a NuGet package restore to generate this file. [~/ledgerline/samples/Ledgerline.Report/Ledgerline.Report.csproj]

The restore must also have been run with the same -r (and, for some options, the same properties) as the build; dotnet restore -r linux-x64 before dotnet build -r linux-x64 --no-restore. The restore options — -s/--source, --configfile, --packages, --locked-mode, --force-evaluate, --no-http-cache, --ignore-failed-sources — are covered on Package Restore.

The options

dotnet build -h lists the CLI’s own options; MSBuild switches (-p, -bl, -tl, -m, -warnaserror, …) are not in the list but are passed through unchanged.

Option Effect

-c, --configuration

Debug (default for build) or Release (the default for pack and publish since .NET 8).

-f, --framework

Build one target framework of a multi-targeted project. The framework must be listed in TargetFrameworks.

-r, --runtime; -a, --arch; --os

Build for a runtime identifier: the output gains a RID folder and an apphost for that platform.

--ucr, --use-current-runtime

-r with the RID of the current machine.

--sc, --no-self-contained

Include the runtime (relevant for publish; see Publish and Deployment Models).

--no-restore

Skip the implicit restore.

--no-dependencies

Build only the named project; its project references must already be built.

--no-incremental

Rebuild: ignore the up-to-date checks (equivalent to -t:Rebuild).

-o, --output

Put one project’s output in a folder (not supported for solutions).

--artifacts-path

Put all output of every project under one folder, one subfolder per project.

--version-suffix

Set $(VersionSuffix).

-v, --verbosity

q, m (default), n, d, diag.

--interactive

Allow restore to prompt for credentials (device-flow login of a credential provider).

--disable-build-servers

Do not use or start MSBuild nodes, the compiler server or MSBuild Server for this command.

-p:Name=Value

Set a global property (MSBuild).

-bl[:file]

Write a binary log (MSBuild).

--tl[:auto|on|off]

Terminal Logger (MSBuild); on by default in an interactive terminal since .NET 9.

Framework, references and rebuilds

dotnet build src/Ledgerline -f net8.0 --no-restore
  Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/net8.0/Ledgerline.dll

--no-dependencies trusts that references are already built. After a Release-only build, building the Debug CLI that way fails, because the referenced Debug Ledgerline.dll does not exist:

dotnet build src/Ledgerline.Cli --no-restore --no-dependencies
~/.dotnet/sdk/10.0.402/Microsoft.Common.CurrentVersion.targets(5096,5): error MSB3030: Could not copy the file "~/ledgerline/src/Ledgerline/bin/Debug/net10.0/Ledgerline.dll" because it was not found. [~/ledgerline/src/Ledgerline.Cli/Ledgerline.Cli.csproj]

It is meant for the inner loop, after a full build; never use it in CI.

Global properties reach every project

-p sets a global property for the whole build, so it reaches project references too:

dotnet build src/Ledgerline.Cli -bl:logs/cli.binlog -p:Version=1.2.3 -p:InformationalVersion=1.2.3-local
  Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/net10.0/Ledgerline.dll
  Ledgerline.Cli -> ~/ledgerline/src/Ledgerline.Cli/bin/Debug/net10.0/Ledgerline.Cli.dll
BinaryLogger wrote to: ~/ledgerline/logs/cli.binlog

Both Ledgerline.Cli.dll and Ledgerline.dll now carry version 1.2.3.0 and the informational version 1.2.3-local+e33226ff… (Source Link appended the commit). To version one project only, set the property in that project or its Directory.Build.props. The last line is MSBuild 18.8’s end-of-build summary of log files written (Logging and Binary Logs).

The runtime options behave differently. -r linux-x64 gave Ledgerline.Cli a RID folder but built the referenced library without one; --ucr (which sets the UseCurrentRuntimeIdentifier property) built both with the RID:

dotnet build src/Ledgerline.Cli -r linux-x64
  Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/net10.0/Ledgerline.dll
  Ledgerline.Cli -> ~/ledgerline/src/Ledgerline.Cli/bin/Debug/net10.0/linux-x64/Ledgerline.Cli.dll
dotnet build src/Ledgerline.Cli --ucr
  Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/net10.0/linux-x64/Ledgerline.dll
  Ledgerline.Cli -> ~/ledgerline/src/Ledgerline.Cli/bin/Debug/net10.0/linux-x64/Ledgerline.Cli.dll

The SDK removes RuntimeIdentifier from the global properties it passes to referenced libraries, so they are built once and shared; a property that only sets the RID indirectly is not removed.

Library versus executable output

src/Ledgerline/bin/Debug/net10.0/:
Ledgerline.deps.json  Ledgerline.dll  Ledgerline.pdb  Ledgerline.xml

src/Ledgerline.Cli/bin/Debug/net10.0/:
Ledgerline.Cli  Ledgerline.Cli.deps.json  Ledgerline.Cli.dll  Ledgerline.Cli.pdb  Ledgerline.Cli.runtimeconfig.json
Ledgerline.dll  Ledgerline.pdb  Ledgerline.xml

An executable adds the apphost (Ledgerline.Cli, Ledgerline.Cli.exe on Windows), the runtimeconfig.json that names the runtime to load, and copies of its references. A library’s output is not runnable; its dependencies come from the consuming application. For a framework-dependent app, dotnet build output is runnable on the build machine but is not a deployment — use dotnet publish.

Output folders

-o is for one project. On a solution it warns, because every project would copy into one folder:

~/.dotnet/sdk/10.0.402/Current/SolutionFile/ImportAfter/Microsoft.NET.Sdk.Solution.targets(36,5): warning NETSDK1194: The "--output" option isn't supported when building a solution. Specifying a solution-level output path results in all projects copying outputs to the same directory, which can lead to inconsistent builds. [~/ledgerline/Ledgerline.slnx]

The artifacts layout (.NET 8 and later) is the supported way to collect output in one place. --artifacts-path enables it for one command; <UseArtifactsOutput>true</UseArtifactsOutput> in Directory.Build.props enables it for the repository (in an artifacts/ folder next to the props file):

dotnet build src/Ledgerline.Cli --artifacts-path artifacts/out
find artifacts/out -maxdepth 3 -type d | sort
  Ledgerline -> ~/ledgerline/artifacts/out/bin/Ledgerline/debug_net10.0/Ledgerline.dll
  Ledgerline.Cli -> ~/ledgerline/artifacts/out/bin/Ledgerline.Cli/debug/Ledgerline.Cli.dll
artifacts/out
artifacts/out/bin
artifacts/out/bin/Ledgerline
artifacts/out/bin/Ledgerline.Cli
artifacts/out/bin/Ledgerline.Cli/debug
artifacts/out/bin/Ledgerline/debug_net10.0
artifacts/out/obj
artifacts/out/obj/Ledgerline
artifacts/out/obj/Ledgerline.Cli
artifacts/out/obj/Ledgerline.Cli/debug
artifacts/out/obj/Ledgerline/debug_net10.0

The layout is <artifacts>/<type>/<project>/<pivot>: the type is bin, obj, publish or package; the pivot combines the configuration, the target framework (only for multi-targeted projects, hence debug_net10.0 for Ledgerline and debug for the CLI) and the RID. Ledgerline keeps its packages in artifacts/feed, which does not collide with these folders.

dotnet clean

dotnet clean runs the Clean target: it deletes the files the previous build recorded writing (obj/<config>/<tfm>/*.FileListAbsolute.txt) for one configuration, framework and RID — not the bin and obj folders. After Debug, Release and linux-x64 builds of the CLI:

find src/Ledgerline.Cli/bin src/Ledgerline.Cli/obj -type f | wc -l
dotnet clean src/Ledgerline.Cli
find src/Ledgerline.Cli/bin src/Ledgerline.Cli/obj -type f | wc -l
77
58

The Release and linux-x64 outputs and the restore files in obj survived. Pass the same -c, -f and -r as the build, or delete bin/obj (or the artifacts folder) when a truly clean state is needed, for example after changing TargetFrameworks.

Building with the wrong SDK

A project that targets a framework newer than the SDK fails in evaluation with NETSDK1045 — here, net11.0 with SDK 10.0.402:

<sdk>/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.TargetFrameworkInference.targets(184,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 11.0.  Either target .NET 10.0 or lower, or use a version of the .NET SDK that supports .NET 11.0. Download the .NET SDK from https://aka.ms/dotnet/download [~/demo/App.csproj]

Install the newer SDK and select it in global.json (Installing and Selecting SDKs).

Source: NETSDK1045.

Common mistakes

  • dotnet build -o out on a solution — NETSDK1194; use --artifacts-path.

  • --no-restore without a matching restore — NETSDK1004, or stale assets after changing -r.

  • --no-dependencies in CI — MSB3030 or a build against stale references.

  • Expecting -p:Version to touch one project — global properties reach every reference.

  • Trusting dotnet clean to reset everything — it cleans one configuration/framework/RID.

  • Shipping dotnet build output — use dotnet publish.