From b05d9956d7f9c54e7f7ca3f146a477024fa4db2c Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 4 Dec 2025 17:00:52 -0600 Subject: [PATCH] [dotnet-run] Implement `DeployToDevice` target invocation Context: https://github.com/dotnet/android/pull/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 --- .../Microsoft.DotNet.Cli.Utils/Constants.cs | 1 + .../dotnet/Commands/CliCommandStrings.resx | 3 + src/Cli/dotnet/Commands/Run/RunCommand.cs | 36 +++++-- .../dotnet/Commands/Run/RunCommandSelector.cs | 39 +++++++ .../Commands/xlf/CliCommandStrings.cs.xlf | 5 + .../Commands/xlf/CliCommandStrings.de.xlf | 5 + .../Commands/xlf/CliCommandStrings.es.xlf | 5 + .../Commands/xlf/CliCommandStrings.fr.xlf | 5 + .../Commands/xlf/CliCommandStrings.it.xlf | 5 + .../Commands/xlf/CliCommandStrings.ja.xlf | 5 + .../Commands/xlf/CliCommandStrings.ko.xlf | 5 + .../Commands/xlf/CliCommandStrings.pl.xlf | 5 + .../Commands/xlf/CliCommandStrings.pt-BR.xlf | 5 + .../Commands/xlf/CliCommandStrings.ru.xlf | 5 + .../Commands/xlf/CliCommandStrings.tr.xlf | 5 + .../xlf/CliCommandStrings.zh-Hans.xlf | 5 + .../xlf/CliCommandStrings.zh-Hant.xlf | 5 + .../DotnetRunDevices/DotnetRunDevices.csproj | 5 + .../Run/GivenDotnetRunSelectsDevice.cs | 102 +++++++++++++++++- 19 files changed, 239 insertions(+), 12 deletions(-) diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Constants.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/Constants.cs index f2038baae3ef..598d1dfe3483 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/Constants.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/Constants.cs @@ -30,6 +30,7 @@ public static class Constants public const string Build = nameof(Build); public const string ComputeRunArguments = nameof(ComputeRunArguments); public const string ComputeAvailableDevices = nameof(ComputeAvailableDevices); + public const string DeployToDevice = nameof(DeployToDevice); public const string CoreCompile = nameof(CoreCompile); // MSBuild item metadata diff --git a/src/Cli/dotnet/Commands/CliCommandStrings.resx b/src/Cli/dotnet/Commands/CliCommandStrings.resx index fbb06189dc5e..98e9add2b425 100644 --- a/src/Cli/dotnet/Commands/CliCommandStrings.resx +++ b/src/Cli/dotnet/Commands/CliCommandStrings.resx @@ -1717,6 +1717,9 @@ The default is to publish a framework-dependent application. The build failed. Fix the build errors and run again. + + Deployment to device failed. Fix any deployment errors and run again. + The launch profile "{0}" could not be applied. {1} diff --git a/src/Cli/dotnet/Commands/Run/RunCommand.cs b/src/Cli/dotnet/Commands/Run/RunCommand.cs index ba5801ff9b8c..e64ac879074d 100644 --- a/src/Cli/dotnet/Commands/Run/RunCommand.cs +++ b/src/Cli/dotnet/Commands/Run/RunCommand.cs @@ -148,10 +148,20 @@ public int Execute() FacadeLogger? logger = ProjectFileFullPath is not null ? LoggerUtility.DetermineBinlogger([.. MSBuildArgs.OtherMSBuildArgs], "dotnet-run") : null; + + // Create selector for project-based runs to handle framework/device selection and deploy + using var selector = ProjectFileFullPath is not null + ? new RunCommandSelector( + ProjectFileFullPath, + CommonRunHelpers.GetGlobalPropertiesFromArgs(MSBuildArgs), + Interactive, + logger) + : null; + try { // Pre-run evaluation: Handle target framework and device selection for project-based scenarios - if (ProjectFileFullPath is not null && !TrySelectTargetFrameworkAndDeviceIfNeeded(logger)) + if (selector is not null && !TrySelectTargetFrameworkAndDeviceIfNeeded(selector)) { // If --list-devices was specified, this is a successful exit return ListDevices ? 0 : 1; @@ -194,6 +204,17 @@ public int Execute() } } + // Deploy step: Call DeployToDevice target if available + // This must run even with --no-build, as the user may have selected a different device + if (selector is not null && !selector.TryDeployToDevice()) + { + // Only error if we have a valid project (not a .sln file, etc.) + if (selector.HasValidProject) + { + throw new GracefulException(CliCommandStrings.RunCommandDeployFailed); + } + } + ICommand targetCommand = GetTargetCommand(projectFactory, cachedRunProperties, logger); ApplyLaunchSettingsProfileToCommand(targetCommand, launchSettings); @@ -228,11 +249,11 @@ public int Execute() /// Uses a single RunCommandSelector instance for both operations, re-evaluating /// the project after framework selection to get the correct device list. /// - /// Optional logger for MSBuild operations (device selection) + /// The selector to use for framework and device selection /// True if we can continue, false if we should exit - private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger) + private bool TrySelectTargetFrameworkAndDeviceIfNeeded(RunCommandSelector selector) { - Debug.Assert(ProjectFileFullPath is not null); + Debug.Assert(selector is not null); var globalProperties = CommonRunHelpers.GetGlobalPropertiesFromArgs(MSBuildArgs); @@ -246,19 +267,16 @@ private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger) } // Optimization: If BOTH framework AND device are already specified (and we're not listing devices), - // we can skip both framework selection and device selection entirely + // we can skip device selection UI bool hasFramework = globalProperties.TryGetValue("TargetFramework", out var existingFramework) && !string.IsNullOrWhiteSpace(existingFramework); bool hasDevice = globalProperties.TryGetValue("Device", out var preSpecifiedDevice) && !string.IsNullOrWhiteSpace(preSpecifiedDevice); if (!ListDevices && hasFramework && hasDevice) { - // Both framework and device are pre-specified, no need to create selector or logger + // Both framework and device are pre-specified, skip device selection UI return true; } - // Create a single selector for both framework and device selection - using var selector = new RunCommandSelector(ProjectFileFullPath, globalProperties, Interactive, logger); - // Step 1: Select target framework if needed if (!selector.TrySelectTargetFramework(out string? selectedFramework)) { diff --git a/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs b/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs index 2faf4764a65d..06079ee4111a 100644 --- a/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs +++ b/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs @@ -31,6 +31,12 @@ internal sealed class RunCommandSelector : IDisposable private Microsoft.Build.Evaluation.Project? _project; private ProjectInstance? _projectInstance; + /// + /// Gets whether the selector has a valid project that can be evaluated. + /// This is false for .sln files or other invalid project files. + /// + public bool HasValidProject { get; private set; } + /// Path to the project file to evaluate /// Global MSBuild properties to use during evaluation /// Whether to prompt the user for selections @@ -101,6 +107,7 @@ public void InvalidateGlobalProperties(Dictionary updatedPropert _projectInstance = null; _collection?.Dispose(); _collection = null; + HasValidProject = false; } /// @@ -124,12 +131,14 @@ private bool OpenProjectIfNeeded([NotNullWhen(true)] out ProjectInstance? projec _project = _collection.LoadProject(_projectFilePath); _projectInstance = _project.CreateProjectInstance(); projectInstance = _projectInstance; + HasValidProject = true; return true; } catch (InvalidProjectFileException) { // Invalid project file, return false projectInstance = null; + HasValidProject = false; return false; } } @@ -433,4 +442,34 @@ public bool TrySelectDevice( return null; } } + + /// + /// Attempts to deploy to a device by calling the DeployToDevice MSBuild target if it exists. + /// This reuses the already-loaded project instance for performance. + /// + /// True if deployment succeeded or was skipped (no target), false if deployment failed + public bool TryDeployToDevice() + { + if (!OpenProjectIfNeeded(out var projectInstance)) + { + // Invalid project file + return false; + } + + // Check if the DeployToDevice target exists in the project + if (!projectInstance.Targets.ContainsKey(Constants.DeployToDevice)) + { + // Target doesn't exist, skip deploy step + return true; + } + + // Build the DeployToDevice target + var buildResult = projectInstance.Build( + targets: [Constants.DeployToDevice], + loggers: _binaryLogger is null ? null : [_binaryLogger], + remoteLoggers: null, + out _); + + return buildResult; + } } diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf index 3805b9b8be5c..136767b262f3 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf @@ -2687,6 +2687,11 @@ Ve výchozím nastavení je publikována aplikace závislá na architektuře.Sestavování... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. Spuštění cíle {0} ke zjištění příkazů spuštění pro tento projekt se nezdařilo. Opravte chyby a upozornění a spusťte je znovu. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf index ffc2aaf9640c..cff5cde37fa5 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf @@ -2687,6 +2687,11 @@ Standardmäßig wird eine Framework-abhängige Anwendung veröffentlicht.Buildvorgang wird ausgeführt... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. Das {0} Ziel ausführen, um zu ermitteln, dass ein Fehler bei den Ausführungsbefehlen für dieses Projekt aufgetreten ist. Beheben Sie die Fehler und Warnungen, und führen Sie dies erneut aus. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf index 0c5d385caff6..c07254581fb4 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf @@ -2687,6 +2687,11 @@ El valor predeterminado es publicar una aplicación dependiente del marco.Compilando... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. Error al ejecutar el destino {0} para detectar comandos de ejecución para este proyecto. Corrija los errores y advertencias y vuelva a ejecutarlo. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf index ad02d5f06a45..4d87622d05d2 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf @@ -2687,6 +2687,11 @@ La valeur par défaut est de publier une application dépendante du framework.Génération... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. L’exécution de la {0} cible pour découvrir les commandes d’exécution a échoué pour ce projet. Corrigez les erreurs et les avertissements, puis réexécutez. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf index 5bc254561ddf..9339191bf3bf 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf @@ -2687,6 +2687,11 @@ Per impostazione predefinita, viene generato un pacchetto dipendente dal framewo Compilazione... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. L'esecuzione della destinazione {0} per individuare i comandi di esecuzione non è riuscita per questo progetto. Correggere gli errori e gli avvisi ed eseguire di nuovo. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf index f942b99ce5d8..6b6ed718912e 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf @@ -2687,6 +2687,11 @@ The default is to publish a framework-dependent application. ビルドしています... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. このプロジェクトで実行コマンドを検出するための {0} ターゲットの実行に失敗しました。エラーと警告を修正して、もう一度実行してください。 diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf index 4f008556132d..e9e8dbecd20a 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf @@ -2687,6 +2687,11 @@ The default is to publish a framework-dependent application. 빌드하는 중... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. 이 프로젝트에 대해 실행 명령을 검색하기 위해 {0} 대상을 실행하지 못했습니다. 오류 및 경고를 수정하고 다시 실행합니다. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf index 9598de759b77..cf04d65e6d21 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf @@ -2687,6 +2687,11 @@ Domyślnie publikowana jest aplikacja zależna od struktury. Trwa kompilowanie... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. Uruchomienie obiektu docelowego {0} w celu odnalezienia poleceń przebiegu dla tego projektu nie powiodło się. Usuń błędy i ostrzeżenia, a następnie uruchom ponownie. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf index f92368671030..d8ad620ab52a 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf @@ -2687,6 +2687,11 @@ O padrão é publicar uma aplicação dependente de framework. Compilando... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. Falha na execução do destino {0} para descobrir comandos de execução para este projeto. Corrija os erros e avisos e execute novamente. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf index b5683e18c60d..1168336d6521 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf @@ -2687,6 +2687,11 @@ The default is to publish a framework-dependent application. Сборка… + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. Не удалось запустить цель {0} для обнаружения команд выполнения для этого проекта. Исправьте ошибки и предупреждения и повторите попытку. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf index e2aebc3732b3..9e40a55ab8d3 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf @@ -2687,6 +2687,11 @@ Varsayılan durum, çerçeveye bağımlı bir uygulama yayımlamaktır. Derleniyor... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. Çalıştırma komutlarını bulmak için {0} hedefini çalıştırma bu proje için başarısız oldu. Hataları ve uyarıları düzeltip yeniden çalıştırın. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf index a39b9ae22a1c..32ed189af92d 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf @@ -2687,6 +2687,11 @@ The default is to publish a framework-dependent application. 正在生成... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. 为此项目运行 {0} 目标以发现运行命令失败。请修复错误和警告,然后再次运行。 diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf index 424c4a1574d8..b6c8a83ad247 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf @@ -2687,6 +2687,11 @@ The default is to publish a framework-dependent application. 正在建置... + + Deployment to device failed. Fix any deployment errors and run again. + Deployment to device failed. Fix any deployment errors and run again. + + Running the {0} target to discover run commands failed for this project. Fix the errors and warnings and run again. 執行 {0} 目標以探索對此專案的執行命令失敗。修正錯誤和警告,然後重新執行。 diff --git a/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj b/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj index c0f6c2109c0f..00d7cc736580 100644 --- a/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj +++ b/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj @@ -49,4 +49,9 @@ + + + + + diff --git a/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs b/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs index 77428f8353a9..2498446a3d8b 100644 --- a/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs +++ b/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs @@ -16,8 +16,6 @@ public GivenDotnetRunSelectsDevice(ITestOutputHelper log) : base(log) { } - string ExpectedRid => OperatingSystem.IsWindows() ? "win" : (OperatingSystem.IsMacOS() ? "osx" : "linux"); - /// /// Helper method to assert conditions about MSBuild target execution in a binlog file /// @@ -179,7 +177,7 @@ public void ItAutoSelectsSingleDeviceWithoutPrompting(bool interactive) // Should auto-select the single device and run successfully result.Should().Pass() .And.HaveStdOutContaining("Device: single-device") - .And.HaveStdOutContaining($"RuntimeIdentifier: {ExpectedRid}"); + .And.HaveStdOutContaining($"RuntimeIdentifier: {RuntimeInformation.RuntimeIdentifier}"); // Verify the binlog file was created and the ComputeAvailableDevices target ran File.Exists(binlogPath).Should().BeTrue("the binlog file should be created"); @@ -280,4 +278,102 @@ public void ItPromptsForTargetFrameworkEvenWhenDeviceIsSpecified() result.Should().Fail() .And.HaveStdErrContaining("Your project targets multiple frameworks. Specify which framework to run using '--framework'"); } + + [Fact] + public void ItCallsDeployToDeviceTargetWhenDeviceIsSpecified() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + string deviceId = "test-device-1"; + string binlogPath = Path.Combine(testInstance.Path, "msbuild-dotnet-run.binlog"); + + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .Execute("--framework", ToolsetInfo.CurrentTargetFramework, "--device", deviceId, "-bl"); + + // Should run successfully + result.Should().Pass() + .And.HaveStdOutContaining($"Device: {deviceId}"); + + // Verify the binlog file was created and the DeployToDevice target ran + File.Exists(binlogPath).Should().BeTrue("the binlog file should be created"); + AssertTargetInBinlog(binlogPath, "DeployToDevice", + targets => targets.Should().NotBeEmpty("DeployToDevice target should have been executed")); + } + + [Fact] + public void ItCallsDeployToDeviceTargetEvenWithNoBuild() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + string deviceId = "test-device-1"; + string binlogPath = Path.Combine(testInstance.Path, "msbuild-dotnet-run.binlog"); + + // First build the project with the device so DeviceInfo gets generated + // Note: dotnet build doesn't support --device flag, use -p:Device= instead + new DotnetCommand(Log, "build") + .WithWorkingDirectory(testInstance.Path) + .Execute("--framework", ToolsetInfo.CurrentTargetFramework, $"-p:Device={deviceId}") + .Should().Pass(); + + // Now run with --no-build + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .Execute("--framework", ToolsetInfo.CurrentTargetFramework, "--device", deviceId, "--no-build", "-bl"); + + // Should run successfully + result.Should().Pass() + .And.HaveStdOutContaining($"Device: {deviceId}"); + + // Verify the binlog file was created and the DeployToDevice target ran + File.Exists(binlogPath).Should().BeTrue("the binlog file should be created"); + AssertTargetInBinlog(binlogPath, "DeployToDevice", + targets => targets.Should().NotBeEmpty("DeployToDevice target should have been executed even with --no-build")); + } + + [Fact] + public void ItDoesNotCallDeployToDeviceTargetWhenNoDeviceIsSpecified() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + string binlogPath = Path.Combine(testInstance.Path, "msbuild-dotnet-run.binlog"); + + // Run with auto-selection of single device + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .Execute("--framework", ToolsetInfo.CurrentTargetFramework, "-p:SingleDevice=true", "-bl"); + + // Should run successfully + result.Should().Pass() + .And.HaveStdOutContaining("Device: single-device"); + + // Verify the binlog file was created + File.Exists(binlogPath).Should().BeTrue("the binlog file should be created"); + + // DeployToDevice target should have been called since a device was selected + AssertTargetInBinlog(binlogPath, "DeployToDevice", + targets => targets.Should().NotBeEmpty("DeployToDevice target should have been executed when a device is selected")); + } + + [Fact] + public void ItPassesRuntimeIdentifierToDeployToDeviceTarget() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + string deviceId = "test-device-1"; + string rid = RuntimeInformation.RuntimeIdentifier; + + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .Execute("--framework", ToolsetInfo.CurrentTargetFramework, "--device", deviceId, "--runtime", rid); + + // Should run successfully and show the RuntimeIdentifier in the app output + result.Should().Pass() + .And.HaveStdOutContaining($"Device: {deviceId}") + .And.HaveStdOutContaining($"RuntimeIdentifier: {rid}"); + } }