Skip to content

Conversation

@jonathanpeppers
Copy link
Member

@jonathanpeppers jonathanpeppers commented Dec 5, 2025

Context: https://github.com/dotnet/sdk/blob/49c0d73c0bfa9eb2870827828354d5c96ed786f0/documentation/specs/dotnet-run-for-maui.md
Context: dotnet/android#10631
Context: dotnet/android#10640

Add support for calling the DeployToDevice MSBuild target during dotnet run. The target is invoked after the build step (or with --no-build) to enable deployment to physical devices or emulators.

The main change here is to create the RunCommandSelector earlier in the RunCommand execution, so that it can be used both for selecting the target framework and device before build, and for invoking the DeployToDevice target after build.

I tested this by making DotnetRunDevices.csproj include a target:

<Target Name="DeployToDevice" DependsOnTargets="ResolveFrameworkReferences">
  <Message Text="DeployToDevice: Deployed to device $(Device) with RuntimeIdentifier $(RuntimeIdentifier)" Importance="high" />
</Target>

Where DependsOnTargets="ResolveFrameworkReferences" mimics what can happen in the Android workload.

I had to stop caching each ProjectInstance in RunCommandSelector,
as doing so can cause:

》"artifacts\tmp\Debug\testing\ItDoesNotCall---A078BA19\DotnetRunDevices.csproj" (DeployToDevice target) (1) ->
》(ResolveFrameworkReferences target) ->
》  artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018: The "ResolveFrameworkReferences" task failed unexpectedly.
》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018: System.ArgumentException: An item with the same key has already been added. Key: Microsoft.NETCore.App
》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at System.Linq.Enumerable.SpanToDictionary[TSource,TKey](ReadOnlySpan`1 source, Func`2 keySelector, IEqualityComparer`1 comparer)
》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at System.Linq.Enumerable.ToDictionary[TSource,TKey](IEnumerable`1 source, Func`2 keySelector, IEqualityComparer`1 comparer)
》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at Microsoft.NET.Build.Tasks.ResolveFrameworkReferences.ExecuteCore() in src\Tasks\Microsoft.NET.Build.Tasks\ResolveFrameworkReferences.cs:line 29
》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at Microsoft.NET.Build.Tasks.TaskBase.Execute() in src\Tasks\Common\TaskBase.cs:line 36
》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at Microsoft.Build.BackEnd.TaskExecutionHost.Execute()
》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at Microsoft.Build.BackEnd.TaskBuilder.ExecuteInstantiatedTask(TaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask)

The ResolveFrameworkReferences target will add duplicate Microsoft.NETCore.App items within a ProjectInstance causing the exception above.

@jonathanpeppers jonathanpeppers force-pushed the dev/peppers/DeployToDevice branch 2 times, most recently from b05d995 to 09f9f0d Compare December 9, 2025 20:15
jonathanpeppers added a commit to dotnet/android that referenced this pull request Dec 9, 2025
Context: dotnet/sdk#52046
Context: https://github.com/dotnet/sdk/blob/c164a9bc1246c48191fb780992530f0fe975141b/documentation/specs/dotnet-run-for-maui.md

When the `DeployToDevice` target is run by the `dotnet run` pipeline,
we will no longer need to make the `ComputeRunArguments` target deploy
anything.

We are thinking this feature can ship in future .NET 11 and 10.0.200
SDKs.
@jonathanpeppers jonathanpeppers force-pushed the dev/peppers/DeployToDevice branch from 023e459 to cb6877a Compare December 10, 2025 18:56
Context: dotnet/android#10631
Context: dotnet/android#10640

Add support for calling the `DeployToDevice` MSBuild target during
`dotnet run`. The target is invoked after the build step (or with
--no-build) to enable deployment to physical devices or emulators.

The main change here is to create the `RunCommandSelector` earlier in
the `RunCommand` execution, so that it can be used both for selecting
the target framework and device before build, and for invoking the
`DeployToDevice` target after build.

I tested this by making `DotnetRunDevices.csproj` include a target:

    <Target Name="DeployToDevice" DependsOnTargets="ResolveFrameworkReferences">
      <Message Text="DeployToDevice: Deployed to device $(Device) with RuntimeIdentifier $(RuntimeIdentifier)" Importance="high" />
    </Target>

Where `DependsOnTargets="ResolveFrameworkReferences"` mimics what can
happen in the Android workload.

I had to stop caching each `ProjectInstance` in `RunCommandSelector`,
as doing so can cause:

    》"artifacts\tmp\Debug\testing\ItDoesNotCall---A078BA19\DotnetRunDevices.csproj" (DeployToDevice target) (1) ->
    》(ResolveFrameworkReferences target) ->
    》  artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018: The "ResolveFrameworkReferences" task failed unexpectedly.
    》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018: System.ArgumentException: An item with the same key has already been added. Key: Microsoft.NETCore.App
    》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
    》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
    》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at System.Linq.Enumerable.SpanToDictionary[TSource,TKey](ReadOnlySpan`1 source, Func`2 keySelector, IEqualityComparer`1 comparer)
    》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at System.Linq.Enumerable.ToDictionary[TSource,TKey](IEnumerable`1 source, Func`2 keySelector, IEqualityComparer`1 comparer)
    》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at Microsoft.NET.Build.Tasks.ResolveFrameworkReferences.ExecuteCore() in src\Tasks\Microsoft.NET.Build.Tasks\ResolveFrameworkReferences.cs:line 29
    》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at Microsoft.NET.Build.Tasks.TaskBase.Execute() in src\Tasks\Common\TaskBase.cs:line 36
    》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at Microsoft.Build.BackEnd.TaskExecutionHost.Execute()
    》artifacts\bin\redist\Debug\dotnet\sdk\11.0.100-dev\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(410,5): error MSB4018:    at Microsoft.Build.BackEnd.TaskBuilder.ExecuteInstantiatedTask(TaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask)

The `ResolveFrameworkReferences` target will add duplicate
`Microsoft.NETCore.App` items within a `ProjectInstance` causing the
exception above.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements support for calling the DeployToDevice MSBuild target during dotnet run to enable deployment to physical devices or emulators, as specified in the dotnet-run-for-maui.md specification. The implementation creates the RunCommandSelector earlier in the execution flow so it can be used for both device selection (before build) and deployment (after build).

Key changes:

  • Added TryDeployToDevice() method to invoke the DeployToDevice MSBuild target after the build step, even with --no-build
  • Fixed a critical bug where caching ProjectInstance caused duplicate framework reference errors by creating a fresh instance for each build operation
  • Added comprehensive test coverage for various deployment scenarios including explicit device specification, auto-selection, and --no-build mode

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Cli/dotnet/Commands/Run/RunCommand.cs Creates RunCommandSelector earlier in execution flow and adds deployment step that calls DeployToDevice target after build
src/Cli/dotnet/Commands/Run/RunCommandSelector.cs Adds TryDeployToDevice() method, removes ProjectInstance caching to fix state accumulation, adds HasValidProject property, simplifies constructor by getting global properties from MSBuildArgs
src/Cli/Microsoft.DotNet.Cli.Utils/Constants.cs Adds DeployToDevice constant for MSBuild target name
src/Cli/dotnet/Commands/CliCommandStrings.resx Adds RunCommandDeployFailed error message for deployment failures
src/Cli/dotnet/Commands/xlf/*.xlf Adds localization entries for new deployment error message with state="new"
test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs Adds 4 new tests for DeployToDevice functionality, removes unused ExpectedRid property, uses RuntimeInformation.RuntimeIdentifier directly
test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj Adds DeployToDevice target with ResolveFrameworkReferences dependency to simulate Android workload behavior

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants