Skip to content

Commit ca5a917

Browse files
[dotnet-run] fix logging and Restore for device selection
c164a9b is mostly working with two issues I've discovered while testing: 1. If `ComputeAvailableDevices` requires `Restore` to have run (which is actually the case for iOS and Android), `dotnet run` would fail unless you did an explicit `dotnet restore` first: D:\src\helloandroid> D:\src\dotnet\sdk\artifacts\bin\redist\Debug\dotnet\dotnet.exe run -bl failed with 1 error(s) (0.1s) D:\src\dotnet\sdk\artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets(266,5): error NETSDK1004: Assets file 'D:\src\helloandroid\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. We can test this by changing `DotnetRunDevices.csproj`: <Target Name="ComputeAvailableDevices" DependsOnTargets="ResolveFrameworkReferences"> Doing an explicit `Restore` step before `ComputeAvailableDevices` resolves this. 2. We were not passing loggers `ProjectInstance.Build()` calls in `RunCommandSelector`, so no logging output was shown during device computation. This is now fixed by creating a fresh console logger each time we call `Build()`. I tested this manually with a console app with a `ComputeAvailableDevices` target and a lengthy sleep: helloconsole ComputeAvailableDevices (9.1s)
1 parent adbbd10 commit ca5a917

File tree

3 files changed

+60
-11
lines changed

3 files changed

+60
-11
lines changed

src/Cli/dotnet/Commands/Run/RunCommand.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ public class RunCommand
9696
/// </summary>
9797
public bool ListDevices { get; }
9898

99+
/// <summary>
100+
/// Tracks whether restore was performed during device selection phase.
101+
/// If true, we should skip restore in the build phase to avoid redundant work.
102+
/// </summary>
103+
private bool _restoreDoneForDeviceSelection;
104+
99105
/// <param name="applicationArgs">unparsed/arbitrary CLI tokens to be passed to the running application</param>
100106
public RunCommand(
101107
bool noBuild,
@@ -257,7 +263,7 @@ private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger)
257263
}
258264

259265
// Create a single selector for both framework and device selection
260-
using var selector = new RunCommandSelector(ProjectFileFullPath, globalProperties, Interactive, logger);
266+
using var selector = new RunCommandSelector(ProjectFileFullPath, globalProperties, Interactive, MSBuildArgs, logger);
261267

262268
// Step 1: Select target framework if needed
263269
if (!selector.TrySelectTargetFramework(out string? selectedFramework))
@@ -284,8 +290,10 @@ private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger)
284290
// Step 3: Select device if needed
285291
if (selector.TrySelectDevice(
286292
ListDevices,
293+
NoRestore,
287294
out string? selectedDevice,
288-
out string? runtimeIdentifier))
295+
out string? runtimeIdentifier,
296+
out _restoreDoneForDeviceSelection))
289297
{
290298
// If a device was selected (either by user or by prompt), apply it to MSBuildArgs
291299
if (selectedDevice is not null)
@@ -296,6 +304,9 @@ private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger)
296304
if (!string.IsNullOrEmpty(runtimeIdentifier))
297305
{
298306
properties["RuntimeIdentifier"] = runtimeIdentifier;
307+
// If the device added a RuntimeIdentifier, we need to re-restore with that RID
308+
// because the previous restore (if any) didn't include it
309+
_restoreDoneForDeviceSelection = false;
299310
}
300311

301312
var additionalProperties = new ReadOnlyDictionary<string, string>(properties);
@@ -474,7 +485,7 @@ private void EnsureProjectIsBuilt(out Func<ProjectCollection, ProjectInstance>?
474485
virtualCommand = null;
475486
buildResult = new RestoringCommand(
476487
MSBuildArgs.CloneWithExplicitArgs([ProjectFileFullPath, .. MSBuildArgs.OtherMSBuildArgs]),
477-
NoRestore,
488+
NoRestore || _restoreDoneForDeviceSelection,
478489
advertiseWorkloadUpdates: false
479490
).Execute();
480491
}

src/Cli/dotnet/Commands/Run/RunCommandSelector.cs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.Build.Evaluation;
77
using Microsoft.Build.Exceptions;
88
using Microsoft.Build.Execution;
9+
using Microsoft.Build.Framework;
910
using Microsoft.DotNet.Cli.Utils;
1011
using Spectre.Console;
1112

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

3032
private ProjectCollection? _collection;
3133
private Microsoft.Build.Evaluation.Project? _project;
@@ -39,11 +41,13 @@ public RunCommandSelector(
3941
string projectFilePath,
4042
Dictionary<string, string> globalProperties,
4143
bool isInteractive,
44+
MSBuildArgs msbuildArgs,
4245
FacadeLogger? binaryLogger = null)
4346
{
4447
_projectFilePath = projectFilePath;
4548
_globalProperties = globalProperties;
4649
_isInteractive = isInteractive;
50+
_msbuildArgs = msbuildArgs;
4751
_binaryLogger = binaryLogger;
4852
}
4953

@@ -119,7 +123,7 @@ private bool OpenProjectIfNeeded([NotNullWhen(true)] out ProjectInstance? projec
119123
{
120124
_collection = new ProjectCollection(
121125
globalProperties: _globalProperties,
122-
loggers: null,
126+
loggers: GetLoggers(),
123127
toolsetDefinitionLocations: ToolsetDefinitionLocations.Default);
124128
_project = _collection.LoadProject(_projectFilePath);
125129
_projectInstance = _project.CreateProjectInstance();
@@ -216,11 +220,14 @@ public record DeviceItem(string Id, string? Description, string? Type, string? S
216220
/// <summary>
217221
/// Computes available devices by calling the ComputeAvailableDevices MSBuild target if it exists.
218222
/// </summary>
223+
/// <param name="noRestore">Whether restore should be skipped before computing devices</param>
219224
/// <param name="devices">List of available devices if the target exists, null otherwise</param>
225+
/// <param name="restoreWasPerformed">True if restore was performed, false otherwise</param>
220226
/// <returns>True if the target was found and executed, false otherwise</returns>
221-
public bool TryComputeAvailableDevices(out List<DeviceItem>? devices)
227+
public bool TryComputeAvailableDevices(bool noRestore, out List<DeviceItem>? devices, out bool restoreWasPerformed)
222228
{
223229
devices = null;
230+
restoreWasPerformed = false;
224231

225232
if (!OpenProjectIfNeeded(out var projectInstance))
226233
{
@@ -234,10 +241,27 @@ public bool TryComputeAvailableDevices(out List<DeviceItem>? devices)
234241
return false;
235242
}
236243

244+
// If restore is allowed, run restore first so device computation sees the restored assets
245+
if (!noRestore)
246+
{
247+
// Run the Restore target
248+
var restoreResult = projectInstance.Build(
249+
targets: ["Restore"],
250+
loggers: GetLoggers(),
251+
remoteLoggers: null,
252+
out _);
253+
if (!restoreResult)
254+
{
255+
return false;
256+
}
257+
258+
restoreWasPerformed = true;
259+
}
260+
237261
// Build the target
238262
var buildResult = projectInstance.Build(
239263
targets: [Constants.ComputeAvailableDevices],
240-
loggers: _binaryLogger is null ? null : [_binaryLogger],
264+
loggers: GetLoggers(),
241265
remoteLoggers: null,
242266
out var targetOutputs);
243267

@@ -274,19 +298,24 @@ public bool TryComputeAvailableDevices(out List<DeviceItem>? devices)
274298
/// or shows an error (non-interactive mode).
275299
/// </summary>
276300
/// <param name="listDevices">Whether to list devices and exit</param>
301+
/// <param name="noRestore">Whether restore should be skipped</param>
277302
/// <param name="selectedDevice">The selected device, or null if not needed</param>
278303
/// <param name="runtimeIdentifier">The RuntimeIdentifier for the selected device, or null if not provided</param>
304+
/// <param name="restoreWasPerformed">True if restore was performed, false otherwise</param>
279305
/// <returns>True if we should continue, false if we should exit</returns>
280306
public bool TrySelectDevice(
281307
bool listDevices,
308+
bool noRestore,
282309
out string? selectedDevice,
283-
out string? runtimeIdentifier)
310+
out string? runtimeIdentifier,
311+
out bool restoreWasPerformed)
284312
{
285313
selectedDevice = null;
286314
runtimeIdentifier = null;
315+
restoreWasPerformed = false;
287316

288317
// Try to get available devices from the project
289-
bool targetExists = TryComputeAvailableDevices(out var devices);
318+
bool targetExists = TryComputeAvailableDevices(noRestore, out var devices, out bool restorePerformed);
290319

291320
// If the target doesn't exist, continue without device selection
292321
if (!targetExists)
@@ -356,8 +385,6 @@ public bool TrySelectDevice(
356385
return true;
357386
}
358387

359-
360-
361388
if (_isInteractive)
362389
{
363390
var deviceItem = PromptForDevice(devices);
@@ -433,4 +460,15 @@ public bool TrySelectDevice(
433460
return null;
434461
}
435462
}
463+
464+
/// <summary>
465+
/// Gets the list of loggers to use for MSBuild operations.
466+
/// Creates a fresh console logger each time to avoid disposal issues when calling Build() multiple times.
467+
/// </summary>
468+
private IEnumerable<ILogger> GetLoggers()
469+
{
470+
if (_binaryLogger is not null)
471+
yield return _binaryLogger;
472+
yield return CommonRunHelpers.GetConsoleLogger(_msbuildArgs);
473+
}
436474
}

test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<TargetFrameworks>net9.0;$(CurrentTargetFramework)</TargetFrameworks>
66
</PropertyGroup>
77

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

0 commit comments

Comments
 (0)