From 3933774675ce54e1d66d1cd58ace7bb6356ad23f Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 4 Dec 2025 17:00:52 -0600 Subject: [PATCH 1/2] [dotnet-run] Implement `DeployToDevice` target invocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/dotnet/android/pull/10631 Context: https://github.com/dotnet/android/pull/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: 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. --- .../Microsoft.DotNet.Cli.Utils/Constants.cs | 1 + .../dotnet/Commands/CliCommandStrings.resx | 3 + src/Cli/dotnet/Commands/Run/RunCommand.cs | 27 +++-- .../dotnet/Commands/Run/RunCommandSelector.cs | 54 ++++++++-- .../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, 236 insertions(+), 21 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 fb5dffd622f5..a2a54a47d5c5 100644 --- a/src/Cli/dotnet/Commands/Run/RunCommand.cs +++ b/src/Cli/dotnet/Commands/Run/RunCommand.cs @@ -157,7 +157,10 @@ public int Execute() try { // Pre-run evaluation: Handle target framework and device selection for project-based scenarios - if (ProjectFileFullPath is not null && !TrySelectTargetFrameworkAndDeviceIfNeeded(logger)) + using var selector = ProjectFileFullPath is not null + ? new RunCommandSelector(ProjectFileFullPath, Interactive, MSBuildArgs, logger) + : null; + if (selector is not null && !TrySelectTargetFrameworkAndDeviceIfNeeded(selector)) { // If --list-devices was specified, this is a successful exit return ListDevices ? 0 : 1; @@ -200,6 +203,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); @@ -234,12 +248,10 @@ 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 RunCommandSelector instance to use for 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); - var globalProperties = CommonRunHelpers.GetGlobalPropertiesFromArgs(MSBuildArgs); // If user specified --device on command line, add it to global properties and MSBuildArgs @@ -258,13 +270,10 @@ private bool TrySelectTargetFrameworkAndDeviceIfNeeded(FacadeLogger? logger) 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 return true; } - // Create a single selector for both framework and device selection - using var selector = new RunCommandSelector(ProjectFileFullPath, globalProperties, Interactive, MSBuildArgs, 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 f10239673125..73286ec192a8 100644 --- a/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs +++ b/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs @@ -31,21 +31,25 @@ internal sealed class RunCommandSelector : IDisposable private ProjectCollection? _collection; 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 + /// MSBuild arguments containing properties and verbosity settings /// Optional binary logger for MSBuild operations. The logger will not be disposed by this class. public RunCommandSelector( string projectFilePath, - Dictionary globalProperties, bool isInteractive, MSBuildArgs msbuildArgs, FacadeLogger? binaryLogger = null) { _projectFilePath = projectFilePath; - _globalProperties = globalProperties; + _globalProperties = CommonRunHelpers.GetGlobalPropertiesFromArgs(msbuildArgs); _isInteractive = isInteractive; _msbuildArgs = msbuildArgs; _binaryLogger = binaryLogger; @@ -102,9 +106,9 @@ public void InvalidateGlobalProperties(Dictionary updatedPropert // Dispose existing project to force re-evaluation _project = null; - _projectInstance = null; _collection?.Dispose(); _collection = null; + HasValidProject = false; } /// @@ -114,8 +118,9 @@ private bool OpenProjectIfNeeded([NotNullWhen(true)] out ProjectInstance? projec { if (_project is not null) { - Debug.Assert(_projectInstance is not null); - projectInstance = _projectInstance; + // Create a fresh ProjectInstance for each build operation + // to avoid accumulating state (existing item groups) from previous builds + projectInstance = _project.CreateProjectInstance(); return true; } @@ -126,14 +131,15 @@ private bool OpenProjectIfNeeded([NotNullWhen(true)] out ProjectInstance? projec loggers: GetLoggers(), toolsetDefinitionLocations: ToolsetDefinitionLocations.Default); _project = _collection.LoadProject(_projectFilePath); - _projectInstance = _project.CreateProjectInstance(); - projectInstance = _projectInstance; + projectInstance = _project.CreateProjectInstance(); + HasValidProject = true; return true; } catch (InvalidProjectFileException) { // Invalid project file, return false projectInstance = null; + HasValidProject = false; return false; } } @@ -461,6 +467,36 @@ public bool TrySelectDevice( } } + /// + /// 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: GetLoggers(), + remoteLoggers: null, + out _); + + return buildResult; + } + /// /// Gets the list of loggers to use for MSBuild operations. /// Creates a fresh console logger each time to avoid disposal issues when calling Build() multiple times. 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 1214d95cdf76..04ce32c6db1d 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}"); + } } From 136bf9a04ad9c1232b2f4faed46c4530b5ce4203 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 12 Dec 2025 10:17:54 -0600 Subject: [PATCH 2/2] Update test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../CommandTests/Run/GivenDotnetRunSelectsDevice.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs b/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs index 2498446a3d8b..c2c1948b12a5 100644 --- a/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs +++ b/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs @@ -334,7 +334,7 @@ public void ItCallsDeployToDeviceTargetEvenWithNoBuild() } [Fact] - public void ItDoesNotCallDeployToDeviceTargetWhenNoDeviceIsSpecified() + public void ItCallsDeployToDeviceTargetWhenDeviceIsAutoSelected() { var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") .WithSource();