diff --git a/src/Cli/dotnet/Commands/Run/RunCommand.cs b/src/Cli/dotnet/Commands/Run/RunCommand.cs
index ba5801ff9b8c..fb5dffd622f5 100644
--- a/src/Cli/dotnet/Commands/Run/RunCommand.cs
+++ b/src/Cli/dotnet/Commands/Run/RunCommand.cs
@@ -96,6 +96,12 @@ public class RunCommand
///
public bool ListDevices { get; }
+ ///
+ /// Tracks whether restore was performed during device selection phase.
+ /// If true, we should skip restore in the build phase to avoid redundant work.
+ ///
+ private bool _restoreDoneForDeviceSelection;
+
/// unparsed/arbitrary CLI tokens to be passed to the running application
public RunCommand(
bool noBuild,
@@ -257,7 +263,7 @@ private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger)
}
// Create a single selector for both framework and device selection
- using var selector = new RunCommandSelector(ProjectFileFullPath, globalProperties, Interactive, logger);
+ using var selector = new RunCommandSelector(ProjectFileFullPath, globalProperties, Interactive, MSBuildArgs, logger);
// Step 1: Select target framework if needed
if (!selector.TrySelectTargetFramework(out string? selectedFramework))
@@ -284,8 +290,10 @@ private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger)
// Step 3: Select device if needed
if (selector.TrySelectDevice(
ListDevices,
+ NoRestore,
out string? selectedDevice,
- out string? runtimeIdentifier))
+ out string? runtimeIdentifier,
+ out _restoreDoneForDeviceSelection))
{
// If a device was selected (either by user or by prompt), apply it to MSBuildArgs
if (selectedDevice is not null)
@@ -296,6 +304,10 @@ private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger)
if (!string.IsNullOrEmpty(runtimeIdentifier))
{
properties["RuntimeIdentifier"] = runtimeIdentifier;
+
+ // If the device added a RuntimeIdentifier, we need to re-restore with that RID
+ // because the previous restore (if any) didn't include it
+ _restoreDoneForDeviceSelection = false;
}
var additionalProperties = new ReadOnlyDictionary(properties);
@@ -474,7 +486,7 @@ private void EnsureProjectIsBuilt(out Func?
virtualCommand = null;
buildResult = new RestoringCommand(
MSBuildArgs.CloneWithExplicitArgs([ProjectFileFullPath, .. MSBuildArgs.OtherMSBuildArgs]),
- NoRestore,
+ NoRestore || _restoreDoneForDeviceSelection,
advertiseWorkloadUpdates: false
).Execute();
}
diff --git a/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs b/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs
index 2faf4764a65d..f10239673125 100644
--- a/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs
+++ b/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs
@@ -6,6 +6,7 @@
using Microsoft.Build.Evaluation;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
using Microsoft.DotNet.Cli.Utils;
using Spectre.Console;
@@ -26,6 +27,7 @@ internal sealed class RunCommandSelector : IDisposable
private readonly Dictionary _globalProperties;
private readonly FacadeLogger? _binaryLogger;
private readonly bool _isInteractive;
+ private readonly MSBuildArgs _msbuildArgs;
private ProjectCollection? _collection;
private Microsoft.Build.Evaluation.Project? _project;
@@ -39,11 +41,13 @@ public RunCommandSelector(
string projectFilePath,
Dictionary globalProperties,
bool isInteractive,
+ MSBuildArgs msbuildArgs,
FacadeLogger? binaryLogger = null)
{
_projectFilePath = projectFilePath;
_globalProperties = globalProperties;
_isInteractive = isInteractive;
+ _msbuildArgs = msbuildArgs;
_binaryLogger = binaryLogger;
}
@@ -119,7 +123,7 @@ private bool OpenProjectIfNeeded([NotNullWhen(true)] out ProjectInstance? projec
{
_collection = new ProjectCollection(
globalProperties: _globalProperties,
- loggers: null,
+ loggers: GetLoggers(),
toolsetDefinitionLocations: ToolsetDefinitionLocations.Default);
_project = _collection.LoadProject(_projectFilePath);
_projectInstance = _project.CreateProjectInstance();
@@ -216,11 +220,14 @@ public record DeviceItem(string Id, string? Description, string? Type, string? S
///
/// Computes available devices by calling the ComputeAvailableDevices MSBuild target if it exists.
///
+ /// Whether restore should be skipped before computing devices
/// List of available devices if the target exists, null otherwise
+ /// True if restore was performed, false otherwise
/// True if the target was found and executed, false otherwise
- public bool TryComputeAvailableDevices(out List? devices)
+ public bool TryComputeAvailableDevices(bool noRestore, out List? devices, out bool restoreWasPerformed)
{
devices = null;
+ restoreWasPerformed = false;
if (!OpenProjectIfNeeded(out var projectInstance))
{
@@ -234,10 +241,27 @@ public bool TryComputeAvailableDevices(out List? devices)
return false;
}
+ // If restore is allowed, run restore first so device computation sees the restored assets
+ if (!noRestore)
+ {
+ // Run the Restore target
+ var restoreResult = projectInstance.Build(
+ targets: ["Restore"],
+ loggers: GetLoggers(),
+ remoteLoggers: null,
+ out _);
+ if (!restoreResult)
+ {
+ return false;
+ }
+
+ restoreWasPerformed = true;
+ }
+
// Build the target
var buildResult = projectInstance.Build(
targets: [Constants.ComputeAvailableDevices],
- loggers: _binaryLogger is null ? null : [_binaryLogger],
+ loggers: GetLoggers(),
remoteLoggers: null,
out var targetOutputs);
@@ -274,19 +298,24 @@ public bool TryComputeAvailableDevices(out List? devices)
/// or shows an error (non-interactive mode).
///
/// Whether to list devices and exit
+ /// Whether restore should be skipped
/// The selected device, or null if not needed
/// The RuntimeIdentifier for the selected device, or null if not provided
+ /// True if restore was performed, false otherwise
/// True if we should continue, false if we should exit
public bool TrySelectDevice(
bool listDevices,
+ bool noRestore,
out string? selectedDevice,
- out string? runtimeIdentifier)
+ out string? runtimeIdentifier,
+ out bool restoreWasPerformed)
{
selectedDevice = null;
runtimeIdentifier = null;
+ restoreWasPerformed = false;
// Try to get available devices from the project
- bool targetExists = TryComputeAvailableDevices(out var devices);
+ bool targetExists = TryComputeAvailableDevices(noRestore, out var devices, out restoreWasPerformed);
// If the target doesn't exist, continue without device selection
if (!targetExists)
@@ -356,8 +385,6 @@ public bool TrySelectDevice(
return true;
}
-
-
if (_isInteractive)
{
var deviceItem = PromptForDevice(devices);
@@ -433,4 +460,15 @@ public bool TrySelectDevice(
return null;
}
}
+
+ ///
+ /// Gets the list of loggers to use for MSBuild operations.
+ /// Creates a fresh console logger each time to avoid disposal issues when calling Build() multiple times.
+ ///
+ private IEnumerable GetLoggers()
+ {
+ if (_binaryLogger is not null)
+ yield return _binaryLogger;
+ yield return CommonRunHelpers.GetConsoleLogger(_msbuildArgs);
+ }
}
diff --git a/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj b/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj
index c0f6c2109c0f..1214d95cdf76 100644
--- a/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj
+++ b/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj
@@ -5,7 +5,7 @@
net9.0;$(CurrentTargetFramework)
-
+