Skip to content

Commit 0b0b18e

Browse files
[dotnet-run] Implement DeployToDevice target invocation
Context: dotnet/android#10631 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. - Create `RunCommandSelector` once per run for framework/device selection and deployment - Call `DeployToDevice` target if it exists in the project - Reuse cached `ProjectInstance` for performance - Add localized message for deployment failures - Added tests
1 parent ac22bcf commit 0b0b18e

19 files changed

+226
-12
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public static class Constants
3030
public const string Build = nameof(Build);
3131
public const string ComputeRunArguments = nameof(ComputeRunArguments);
3232
public const string ComputeAvailableDevices = nameof(ComputeAvailableDevices);
33+
public const string DeployToDevice = nameof(DeployToDevice);
3334
public const string CoreCompile = nameof(CoreCompile);
3435

3536
// MSBuild item metadata

src/Cli/dotnet/Commands/CliCommandStrings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,6 +1717,9 @@ The default is to publish a framework-dependent application.</value>
17171717
<data name="RunCommandException" xml:space="preserve">
17181718
<value>The build failed. Fix the build errors and run again.</value>
17191719
</data>
1720+
<data name="RunCommandDeployFailed" xml:space="preserve">
1721+
<value>Deployment to device failed. Fix any deployment errors and run again.</value>
1722+
</data>
17201723
<data name="RunCommandExceptionCouldNotApplyLaunchSettings" xml:space="preserve">
17211724
<value>The launch profile "{0}" could not be applied.
17221725
{1}</value>

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,20 @@ public int Execute()
148148
FacadeLogger? logger = ProjectFileFullPath is not null
149149
? LoggerUtility.DetermineBinlogger([.. MSBuildArgs.OtherMSBuildArgs], "dotnet-run")
150150
: null;
151+
152+
// Create selector for project-based runs to handle framework/device selection and deploy
153+
using var selector = ProjectFileFullPath is not null
154+
? new RunCommandSelector(
155+
ProjectFileFullPath,
156+
CommonRunHelpers.GetGlobalPropertiesFromArgs(MSBuildArgs),
157+
Interactive,
158+
logger)
159+
: null;
160+
151161
try
152162
{
153163
// Pre-run evaluation: Handle target framework and device selection for project-based scenarios
154-
if (ProjectFileFullPath is not null && !TrySelectTargetFrameworkAndDeviceIfNeeded(logger))
164+
if (selector is not null && !TrySelectTargetFrameworkAndDeviceIfNeeded(selector))
155165
{
156166
// If --list-devices was specified, this is a successful exit
157167
return ListDevices ? 0 : 1;
@@ -194,6 +204,13 @@ public int Execute()
194204
}
195205
}
196206

207+
// Deploy step: Call DeployToDevice target if available
208+
// This must run even with --no-build, as the user may have selected a different device
209+
if (selector is not null && !selector.TryDeployToDevice())
210+
{
211+
throw new GracefulException(CliCommandStrings.RunCommandDeployFailed);
212+
}
213+
197214
ICommand targetCommand = GetTargetCommand(projectFactory, cachedRunProperties, logger);
198215
ApplyLaunchSettingsProfileToCommand(targetCommand, launchSettings);
199216

@@ -228,11 +245,11 @@ public int Execute()
228245
/// Uses a single RunCommandSelector instance for both operations, re-evaluating
229246
/// the project after framework selection to get the correct device list.
230247
/// </summary>
231-
/// <param name="logger">Optional logger for MSBuild operations (device selection)</param>
248+
/// <param name="selector">The selector to use for framework and device selection</param>
232249
/// <returns>True if we can continue, false if we should exit</returns>
233-
private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger)
250+
private bool TrySelectTargetFrameworkAndDeviceIfNeeded(RunCommandSelector selector)
234251
{
235-
Debug.Assert(ProjectFileFullPath is not null);
252+
Debug.Assert(selector is not null);
236253

237254
var globalProperties = CommonRunHelpers.GetGlobalPropertiesFromArgs(MSBuildArgs);
238255

@@ -246,19 +263,16 @@ private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger)
246263
}
247264

248265
// Optimization: If BOTH framework AND device are already specified (and we're not listing devices),
249-
// we can skip both framework selection and device selection entirely
266+
// we can skip device selection UI
250267
bool hasFramework = globalProperties.TryGetValue("TargetFramework", out var existingFramework) && !string.IsNullOrWhiteSpace(existingFramework);
251268
bool hasDevice = globalProperties.TryGetValue("Device", out var preSpecifiedDevice) && !string.IsNullOrWhiteSpace(preSpecifiedDevice);
252269

253270
if (!ListDevices && hasFramework && hasDevice)
254271
{
255-
// Both framework and device are pre-specified, no need to create selector or logger
272+
// Both framework and device are pre-specified, skip device selection UI
256273
return true;
257274
}
258275

259-
// Create a single selector for both framework and device selection
260-
using var selector = new RunCommandSelector(ProjectFileFullPath, globalProperties, Interactive, logger);
261-
262276
// Step 1: Select target framework if needed
263277
if (!selector.TrySelectTargetFramework(out string? selectedFramework))
264278
{

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,4 +433,34 @@ public bool TrySelectDevice(
433433
return null;
434434
}
435435
}
436+
437+
/// <summary>
438+
/// Attempts to deploy to a device by calling the DeployToDevice MSBuild target if it exists.
439+
/// This reuses the already-loaded project instance for performance.
440+
/// </summary>
441+
/// <returns>True if deployment succeeded or was skipped (no target), false if deployment failed</returns>
442+
public bool TryDeployToDevice()
443+
{
444+
if (!OpenProjectIfNeeded(out var projectInstance))
445+
{
446+
// Invalid project file
447+
return false;
448+
}
449+
450+
// Check if the DeployToDevice target exists in the project
451+
if (!projectInstance.Targets.ContainsKey(Constants.DeployToDevice))
452+
{
453+
// Target doesn't exist, skip deploy step
454+
return true;
455+
}
456+
457+
// Build the DeployToDevice target
458+
var buildResult = projectInstance.Build(
459+
targets: [Constants.DeployToDevice],
460+
loggers: _binaryLogger is null ? null : [_binaryLogger],
461+
remoteLoggers: null,
462+
out _);
463+
464+
return buildResult;
465+
}
436466
}

src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)