Skip to content

Commit c164a9b

Browse files
[dotnet-run] implement "device" selection logic (#51914)
Context: https://github.com/dotnet/sdk/blob/5398e10de90dc9a27e0290ad55c2ae67360ea8be/documentation/specs/dotnet-run-for-maui.md ## Spec Changes ## **Added RuntimeIdentifier Support** - Examples to include `%(RuntimeIdentifier)` metadata (e.g., `android-arm64`, `ios-arm64`, `iossimulator-arm64`) - When a device provides a `%(RuntimeIdentifier)`, it will be passed as `-p:RuntimeIdentifier` to subsequent MSBuild steps (build, deploy, ComputeRunArguments, run) - `%(RuntimeIdentifier)` is optional but recommended **Added Binary Logs Documentation** - Added new section "Binary Logs for Device Selection" explaining: - When binlog files are created (when using `-bl:` with `dotnet run`) ## Implementation ## **Renamed `TargetFrameworkSelector` to `RunCommandSelector`** - Expanded scope from just framework selection to handle both target framework and device selection - Made it a non-static class implementing `IDisposable` to: - Cache the MSBuild project instance across operations - Avoid loading/evaluating the project multiple times, except when global properties change - Properly manage MSBuild resources (ProjectCollection, Project, ProjectInstance) with `IDisposable` - Added `InvalidateGlobalProperties()` method to re-evaluate the project when needed with a `$(TargetFramework)` global property change. - Binary logger is owned by the `selector` instance and properly disposed. **Added Tests** - Mock test project (`DotnetRunDevices.csproj`) implements `ComputeAvailableDevices` target that returns hardcoded device items based on the target framework - Test project includes `GenerateDeviceInfo` target that runs during build when a device is selected: - Generates `DeviceInfo.cs` with constants for `$(Device)` and `$(RuntimeIdentifier)` properties - Writes to intermediate output directory before compilation - Test application prints these generated constants, allowing tests to verify that: - The correct device ID was passed to MSBuild - `$(RuntimeIdentifier)` was propagated correctly (when provided by device) - Multi-targeted apps can have different devices per framework
1 parent 0d31ba8 commit c164a9b

28 files changed

+1545
-211
lines changed

documentation/specs/dotnet-run-for-maui.md

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,24 @@ to make extensible for .NET MAUI (and future) scenarios.
5656
```xml
5757
<ItemGroup>
5858
<!-- Android examples -->
59-
<Devices Include="emulator-5554" Description="Pixel 7 - API 35" Type="Emulator" Status="Offline" />
60-
<Devices Include="emulator-5555" Description="Pixel 7 - API 36" Type="Emulator" Status="Online" />
61-
<Devices Include="0A041FDD400327" Description="Pixel 7 Pro" Type="Device" Status="Online" />
59+
<Devices Include="emulator-5554" Description="Pixel 7 - API 35" Type="Emulator" Status="Offline" RuntimeIdentifier="android-x64" />
60+
<Devices Include="emulator-5555" Description="Pixel 7 - API 36" Type="Emulator" Status="Online" RuntimeIdentifier="android-x64" />
61+
<Devices Include="0A041FDD400327" Description="Pixel 7 Pro" Type="Device" Status="Online" RuntimeIdentifier="android-arm64" />
6262
<!-- iOS examples -->
63-
<Devices Include="94E71AE5-8040-4DB2-8A9C-6CD24EF4E7DE" Description="iPhone 11 - iOS 18.6" Type="Simulator" Status="Shutdown" />
64-
<Devices Include="FBF5DCE8-EE2B-4215-8118-3A2190DE1AD7" Description="iPhone 14 - iOS 26.0" Type="Simulator" Status="Booted" />
65-
<Devices Include="23261B78-1E31-469C-A46E-1776D386EFD8" Description="My iPhone 13" Type="Device" Status="Unavailable" />
66-
<Devices Include="AF40CC64-2CDB-5F16-9651-86BCDF380881" Description="My iPhone 15" Type="Device" Status="Paired" />
63+
<Devices Include="94E71AE5-8040-4DB2-8A9C-6CD24EF4E7DE" Description="iPhone 11 - iOS 18.6" Type="Simulator" Status="Shutdown" RuntimeIdentifier="iossimulator-arm64" />
64+
<Devices Include="FBF5DCE8-EE2B-4215-8118-3A2190DE1AD7" Description="iPhone 14 - iOS 26.0" Type="Simulator" Status="Booted" RuntimeIdentifier="iossimulator-arm64" />
65+
<Devices Include="23261B78-1E31-469C-A46E-1776D386EFD8" Description="My iPhone 13" Type="Device" Status="Unavailable" RuntimeIdentifier="ios-arm64" />
66+
<Devices Include="AF40CC64-2CDB-5F16-9651-86BCDF380881" Description="My iPhone 15" Type="Device" Status="Paired" RuntimeIdentifier="ios-arm64" />
6767
</ItemGroup>
6868
```
6969

70-
_NOTE: each workload can decide which metadata values for `%(Type)`
71-
and `%(Status)` are useful, filtering offline devices, etc. The output
72-
above would be analogous to running `adb devices`, `xcrun simctl list
73-
devices`, or `xcrun devicectl list devices`._
70+
_NOTE: each workload can decide which metadata values for `%(Type)`,
71+
`%(Status)`, and `%(RuntimeIdentifier)` are useful, filtering offline
72+
devices, etc. The output above would be analogous to running `adb
73+
devices`, `xcrun simctl list devices`, or `xcrun devicectl list
74+
devices`. The `%(RuntimeIdentifier)` metadata is optional but
75+
recommended, as it allows the build system to pass the appropriate RID
76+
to subsequent build, deploy, and run steps._
7477

7578
* Continuing on...
7679

@@ -81,24 +84,28 @@ devices`, or `xcrun devicectl list devices`._
8184
`--device` switch. Listing the options returned by the
8285
`ComputeAvailableDevices` MSBuild target.
8386

84-
* `build`: unchanged, but is passed `-p:Device`.
87+
* `build`: unchanged, but is passed `-p:Device` and optionally `-p:RuntimeIdentifier`
88+
if the selected device provided a `%(RuntimeIdentifier)` metadata value.
8589

8690
* `deploy`
8791

8892
* If a `DeployToDevice` MSBuild target is available, provided by the
8993
iOS or Android workload, etc.
9094

9195
* Call the MSBuild target, passing in the identifier for the selected
92-
`-p:Device` global MSBuild property.
96+
`-p:Device` global MSBuild property, and optionally `-p:RuntimeIdentifier`
97+
if the selected device provided a `%(RuntimeIdentifier)` metadata value.
9398

9499
* This step needs to run, even with `--no-build`, as you may have
95100
selected a different device.
96101

97-
* `ComputeRunArguments`: unchanged, but is passed `-p:Device`.
102+
* `ComputeRunArguments`: unchanged, but is passed `-p:Device` and optionally
103+
`-p:RuntimeIdentifier` if the selected device provided a `%(RuntimeIdentifier)`
104+
metadata value.
98105

99106
* `run`: unchanged. `ComputeRunArguments` should have set a valid
100107
`$(RunCommand)` and `$(RunArguments)` using the value supplied by
101-
`-p:Device`.
108+
`-p:Device` and optionally `-p:RuntimeIdentifier`.
102109

103110
## New `dotnet run` Command-line Switches
104111

@@ -139,6 +146,20 @@ A new `--device` switch will:
139146
* The iOS and Android workloads will know how to interpret `$(Device)`
140147
to select an appropriate device, emulator, or simulator.
141148

149+
## Binary Logs for Device Selection
150+
151+
When using `-bl` with `dotnet run`, all MSBuild operations are logged to a single
152+
binlog file: device selection, build, deploy, and run argument computation.
153+
154+
File naming for `dotnet run` binlogs:
155+
156+
* `-bl:filename.binlog` creates `filename-dotnet-run.binlog`
157+
* `-bl` creates `msbuild-dotnet-run.binlog`
158+
159+
Note: The build step may also create `msbuild.binlog` separately. Use
160+
`--no-build` with `-bl` to only capture run-specific MSBuild
161+
operations.
162+
142163
## What about Launch Profiles?
143164

144165
The iOS and Android workloads ignore all

src/Cli/Microsoft.DotNet.Cli.Utils/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public static class Constants
2929
// MSBuild targets
3030
public const string Build = nameof(Build);
3131
public const string ComputeRunArguments = nameof(ComputeRunArguments);
32+
public const string ComputeAvailableDevices = nameof(ComputeAvailableDevices);
3233
public const string CoreCompile = nameof(CoreCompile);
3334

3435
// MSBuild item metadata

src/Cli/dotnet/Commands/CliCommandStrings.resx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1786,6 +1786,30 @@ Your project targets multiple frameworks. Specify which framework to run using '
17861786
<data name="RunRuntimeOptionDescription" xml:space="preserve">
17871787
<value>The target runtime to run for.</value>
17881788
</data>
1789+
<data name="CommandOptionDeviceDescription" xml:space="preserve">
1790+
<value>The device identifier to use for running the application.</value>
1791+
</data>
1792+
<data name="CommandOptionDeviceHelpName" xml:space="preserve">
1793+
<value>DEVICE</value>
1794+
</data>
1795+
<data name="CommandOptionListDevicesDescription" xml:space="preserve">
1796+
<value>List available devices for running the application.</value>
1797+
</data>
1798+
<data name="RunCommandAvailableDevices" xml:space="preserve">
1799+
<value>Available devices:</value>
1800+
</data>
1801+
<data name="RunCommandNoDevicesAvailable" xml:space="preserve">
1802+
<value>No devices are available for this project.</value>
1803+
</data>
1804+
<data name="RunCommandSelectDevicePrompt" xml:space="preserve">
1805+
<value>Select a device to run on:</value>
1806+
</data>
1807+
<data name="RunCommandMoreDevicesText" xml:space="preserve">
1808+
<value>Move up and down to reveal more devices</value>
1809+
</data>
1810+
<data name="RunCommandExceptionUnableToRunSpecifyDevice" xml:space="preserve">
1811+
<value>Unable to run this project because multiple devices are available. Please specify which device to use by passing the {0} argument with one of the following values:</value>
1812+
</data>
17891813
<data name="RuntimeConfigDefinition" xml:space="preserve">
17901814
<value>Path to &lt;application&gt;.runtimeconfig.json file.</value>
17911815
</data>

src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ public override RunApiOutput Execute()
102102
launchProfile: null,
103103
noLaunchProfile: false,
104104
noLaunchProfileArguments: false,
105+
device: null,
106+
listDevices: false,
105107
noRestore: false,
106108
noCache: false,
107109
interactive: false,
@@ -112,7 +114,7 @@ public override RunApiOutput Execute()
112114
msbuildRestoreProperties: ReadOnlyDictionary<string, string>.Empty);
113115

114116
runCommand.TryGetLaunchProfileSettingsIfNeeded(out var launchSettings);
115-
var targetCommand = (Utils.Command)runCommand.GetTargetCommand(buildCommand.CreateProjectInstance, cachedRunProperties: null);
117+
var targetCommand = (Utils.Command)runCommand.GetTargetCommand(buildCommand.CreateProjectInstance, cachedRunProperties: null, logger: null);
116118
runCommand.ApplyLaunchSettingsProfileToCommand(targetCommand, launchSettings);
117119

118120
return new RunApiOutput.RunCommand

0 commit comments

Comments
 (0)