Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/Cli/dotnet/Commands/Run/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ public class RunCommand
/// </summary>
public bool ListDevices { get; }

/// <summary>
/// Tracks whether restore was performed during device selection phase.
/// If true, we should skip restore in the build phase to avoid redundant work.
/// </summary>
private bool _restoreDoneForDeviceSelection;

/// <param name="applicationArgs">unparsed/arbitrary CLI tokens to be passed to the running application</param>
public RunCommand(
bool noBuild,
Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand All @@ -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<string, string>(properties);
Expand Down Expand Up @@ -474,7 +486,7 @@ private void EnsureProjectIsBuilt(out Func<ProjectCollection, ProjectInstance>?
virtualCommand = null;
buildResult = new RestoringCommand(
MSBuildArgs.CloneWithExplicitArgs([ProjectFileFullPath, .. MSBuildArgs.OtherMSBuildArgs]),
NoRestore,
NoRestore || _restoreDoneForDeviceSelection,
advertiseWorkloadUpdates: false
).Execute();
}
Expand Down
52 changes: 45 additions & 7 deletions src/Cli/dotnet/Commands/Run/RunCommandSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -26,6 +27,7 @@ internal sealed class RunCommandSelector : IDisposable
private readonly Dictionary<string, string> _globalProperties;
private readonly FacadeLogger? _binaryLogger;
private readonly bool _isInteractive;
private readonly MSBuildArgs _msbuildArgs;

private ProjectCollection? _collection;
private Microsoft.Build.Evaluation.Project? _project;
Expand All @@ -39,11 +41,13 @@ public RunCommandSelector(
string projectFilePath,
Dictionary<string, string> globalProperties,
bool isInteractive,
MSBuildArgs msbuildArgs,
FacadeLogger? binaryLogger = null)
{
_projectFilePath = projectFilePath;
_globalProperties = globalProperties;
_isInteractive = isInteractive;
_msbuildArgs = msbuildArgs;
_binaryLogger = binaryLogger;
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -216,11 +220,14 @@ public record DeviceItem(string Id, string? Description, string? Type, string? S
/// <summary>
/// Computes available devices by calling the ComputeAvailableDevices MSBuild target if it exists.
/// </summary>
/// <param name="noRestore">Whether restore should be skipped before computing devices</param>
/// <param name="devices">List of available devices if the target exists, null otherwise</param>
/// <param name="restoreWasPerformed">True if restore was performed, false otherwise</param>
/// <returns>True if the target was found and executed, false otherwise</returns>
public bool TryComputeAvailableDevices(out List<DeviceItem>? devices)
public bool TryComputeAvailableDevices(bool noRestore, out List<DeviceItem>? devices, out bool restoreWasPerformed)
{
devices = null;
restoreWasPerformed = false;

if (!OpenProjectIfNeeded(out var projectInstance))
{
Expand All @@ -234,10 +241,27 @@ public bool TryComputeAvailableDevices(out List<DeviceItem>? 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);

Expand Down Expand Up @@ -274,19 +298,24 @@ public bool TryComputeAvailableDevices(out List<DeviceItem>? devices)
/// or shows an error (non-interactive mode).
/// </summary>
/// <param name="listDevices">Whether to list devices and exit</param>
/// <param name="noRestore">Whether restore should be skipped</param>
/// <param name="selectedDevice">The selected device, or null if not needed</param>
/// <param name="runtimeIdentifier">The RuntimeIdentifier for the selected device, or null if not provided</param>
/// <param name="restoreWasPerformed">True if restore was performed, false otherwise</param>
/// <returns>True if we should continue, false if we should exit</returns>
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)
Expand Down Expand Up @@ -356,8 +385,6 @@ public bool TrySelectDevice(
return true;
}



if (_isInteractive)
{
var deviceItem = PromptForDevice(devices);
Expand Down Expand Up @@ -433,4 +460,15 @@ public bool TrySelectDevice(
return null;
}
}

/// <summary>
/// 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.
/// </summary>
private IEnumerable<ILogger> GetLoggers()
{
if (_binaryLogger is not null)
yield return _binaryLogger;
yield return CommonRunHelpers.GetConsoleLogger(_msbuildArgs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<TargetFrameworks>net9.0;$(CurrentTargetFramework)</TargetFrameworks>
</PropertyGroup>

<Target Name="ComputeAvailableDevices" Returns="@(Devices)">
<Target Name="ComputeAvailableDevices" Returns="@(Devices)" DependsOnTargets="ResolveFrameworkReferences">
<!-- If SingleDevice property is set, return only one device -->
<ItemGroup Condition="'$(NoDevices)' != 'true' and '$(SingleDevice)' == 'true'">
<Devices Include="single-device" Description="Single Device" Type="Emulator" Status="Online" RuntimeIdentifier="$(NETCoreSdkRuntimeIdentifier)" />
Expand Down