diff --git a/documentation/specs/dotnet-run-for-maui.md b/documentation/specs/dotnet-run-for-maui.md index 6ee879a91010..4dc6178e4409 100644 --- a/documentation/specs/dotnet-run-for-maui.md +++ b/documentation/specs/dotnet-run-for-maui.md @@ -56,21 +56,24 @@ to make extensible for .NET MAUI (and future) scenarios. ```xml - - - + + + - - - - + + + + ``` -_NOTE: each workload can decide which metadata values for `%(Type)` -and `%(Status)` are useful, filtering offline devices, etc. The output -above would be analogous to running `adb devices`, `xcrun simctl list -devices`, or `xcrun devicectl list devices`._ +_NOTE: each workload can decide which metadata values for `%(Type)`, +`%(Status)`, and `%(RuntimeIdentifier)` are useful, filtering offline +devices, etc. The output above would be analogous to running `adb +devices`, `xcrun simctl list devices`, or `xcrun devicectl list +devices`. The `%(RuntimeIdentifier)` metadata is optional but +recommended, as it allows the build system to pass the appropriate RID +to subsequent build, deploy, and run steps._ * Continuing on... @@ -81,7 +84,8 @@ devices`, or `xcrun devicectl list devices`._ `--device` switch. Listing the options returned by the `ComputeAvailableDevices` MSBuild target. -* `build`: unchanged, but is passed `-p:Device`. +* `build`: unchanged, but is passed `-p:Device` and optionally `-p:RuntimeIdentifier` + if the selected device provided a `%(RuntimeIdentifier)` metadata value. * `deploy` @@ -89,16 +93,19 @@ devices`, or `xcrun devicectl list devices`._ iOS or Android workload, etc. * Call the MSBuild target, passing in the identifier for the selected - `-p:Device` global MSBuild property. + `-p:Device` global MSBuild property, and optionally `-p:RuntimeIdentifier` + if the selected device provided a `%(RuntimeIdentifier)` metadata value. * This step needs to run, even with `--no-build`, as you may have selected a different device. -* `ComputeRunArguments`: unchanged, but is passed `-p:Device`. +* `ComputeRunArguments`: unchanged, but is passed `-p:Device` and optionally + `-p:RuntimeIdentifier` if the selected device provided a `%(RuntimeIdentifier)` + metadata value. * `run`: unchanged. `ComputeRunArguments` should have set a valid `$(RunCommand)` and `$(RunArguments)` using the value supplied by - `-p:Device`. + `-p:Device` and optionally `-p:RuntimeIdentifier`. ## New `dotnet run` Command-line Switches @@ -139,6 +146,19 @@ A new `--device` switch will: * The iOS and Android workloads will know how to interpret `$(Device)` to select an appropriate device, emulator, or simulator. +## Binary Logs for Device Selection + +When using the `-bl:` argument with `dotnet run`, binary logs (`.binlog` files) +are created to help diagnose issues with device selection and the build process. + +For device selection operations (when calling the `ComputeAvailableDevices` target), +the binlog files follow this naming pattern: + +* If you specify `-bl:filename.binlog`, the actual file created will be + `filename-dotnet-run-devices.binlog` +* If you specify `-bl` without a filename, the file created will be + `msbuild-dotnet-run-devices.binlog` + ## What about Launch Profiles? The iOS and Android workloads ignore all diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Constants.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/Constants.cs index 0c5aef161367..f2038baae3ef 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/Constants.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/Constants.cs @@ -29,6 +29,7 @@ public static class Constants // MSBuild targets public const string Build = nameof(Build); public const string ComputeRunArguments = nameof(ComputeRunArguments); + public const string ComputeAvailableDevices = nameof(ComputeAvailableDevices); 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 7df5c89ae6c4..fbb06189dc5e 100644 --- a/src/Cli/dotnet/Commands/CliCommandStrings.resx +++ b/src/Cli/dotnet/Commands/CliCommandStrings.resx @@ -1786,6 +1786,30 @@ Your project targets multiple frameworks. Specify which framework to run using ' The target runtime to run for. + + The device identifier to use for running the application. + + + DEVICE + + + List available devices for running the application. + + + Available devices: + + + No devices are available for this project. + + + Select a device to run on: + + + Move up and down to reveal more devices + + + 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: + Path to <application>.runtimeconfig.json file. diff --git a/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs b/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs index 4a48c4054d79..d6037a58c746 100644 --- a/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs +++ b/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs @@ -102,6 +102,8 @@ public override RunApiOutput Execute() launchProfile: null, noLaunchProfile: false, noLaunchProfileArguments: false, + device: null, + listDevices: false, noRestore: false, noCache: false, interactive: false, diff --git a/src/Cli/dotnet/Commands/Run/RunCommand.cs b/src/Cli/dotnet/Commands/Run/RunCommand.cs index 09cfbe91720f..05e868fce78c 100644 --- a/src/Cli/dotnet/Commands/Run/RunCommand.cs +++ b/src/Cli/dotnet/Commands/Run/RunCommand.cs @@ -13,9 +13,9 @@ using Microsoft.Build.Framework; using Microsoft.Build.Logging; using Microsoft.DotNet.Cli.CommandFactory; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; -using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; @@ -85,6 +85,16 @@ public class RunCommand /// public bool NoLaunchProfileArguments { get; } + /// + /// Device identifier to use for running the application. + /// + public string? Device { get; } + + /// + /// Whether to list available devices and exit. + /// + public bool ListDevices { get; } + /// unparsed/arbitrary CLI tokens to be passed to the running application public RunCommand( bool noBuild, @@ -93,6 +103,8 @@ public RunCommand( string? launchProfile, bool noLaunchProfile, bool noLaunchProfileArguments, + string? device, + bool listDevices, bool noRestore, bool noCache, bool interactive, @@ -112,6 +124,8 @@ public RunCommand( LaunchProfile = launchProfile; NoLaunchProfile = noLaunchProfile; NoLaunchProfileArguments = noLaunchProfileArguments; + Device = device; + ListDevices = listDevices; ApplicationArgs = applicationArgs; Interactive = interactive; NoRestore = noRestore; @@ -128,10 +142,11 @@ public int Execute() return 1; } - // Pre-run evaluation: Handle target framework selection for multi-targeted projects - if (ProjectFileFullPath is not null && !TrySelectTargetFrameworkIfNeeded()) + // Pre-run evaluation: Handle target framework and device selection for project-based scenarios + if (ProjectFileFullPath is not null && !TrySelectTargetFrameworkAndDeviceIfNeeded()) { - return 1; + // If --list-devices was specified, this is a successful exit + return ListDevices ? 0 : 1; } // For file-based projects, check for multi-targeting before building @@ -199,26 +214,88 @@ public int Execute() } /// - /// Checks if target framework selection is needed for multi-targeted projects. - /// If needed and we're in interactive mode, prompts the user to select a framework. - /// If needed and we're in non-interactive mode, shows an error. + /// Checks if target framework selection and device selection are needed. + /// Uses a single RunCommandSelector instance for both operations, re-evaluating + /// the project after framework selection to get the correct device list. /// /// True if we can continue, false if we should exit - private bool TrySelectTargetFrameworkIfNeeded() + private bool TrySelectTargetFrameworkAndDeviceIfNeeded() { Debug.Assert(ProjectFileFullPath is not null); var globalProperties = CommonRunHelpers.GetGlobalPropertiesFromArgs(MSBuildArgs); - if (TargetFrameworkSelector.TrySelectTargetFramework( - ProjectFileFullPath, - globalProperties, - Interactive, - out string? selectedFramework)) + + // If user specified --device on command line, add it to global properties and MSBuildArgs + if (!string.IsNullOrWhiteSpace(Device)) + { + globalProperties["Device"] = Device; + var properties = new Dictionary { { "Device", Device } }; + var additionalProperties = new ReadOnlyDictionary(properties); + MSBuildArgs = MSBuildArgs.CloneWithAdditionalProperties(additionalProperties); + } + + // 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 + 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 + return true; + } + + // Create a single selector for both framework and device selection + FacadeLogger? logger = LoggerUtility.DetermineBinlogger([.. MSBuildArgs.OtherMSBuildArgs], "dotnet-run-devices"); + using var selector = new RunCommandSelector(ProjectFileFullPath, globalProperties, Interactive, logger); + + // Step 1: Select target framework if needed + if (!selector.TrySelectTargetFramework(out string? selectedFramework)) + { + return false; + } + + if (selectedFramework is not null) { ApplySelectedFramework(selectedFramework); + + // Re-evaluate project with the selected framework so device selection sees the right devices + var properties = CommonRunHelpers.GetGlobalPropertiesFromArgs(MSBuildArgs); + selector.InvalidateGlobalProperties(properties); + } + + // Step 2: Check if device is now pre-specified after framework selection + if (!ListDevices && hasDevice) + { + // Device was pre-specified, we can skip device selection return true; } + // Step 3: Select device if needed + if (selector.TrySelectDevice( + ListDevices, + out string? selectedDevice, + out string? runtimeIdentifier)) + { + // If a device was selected (either by user or by prompt), apply it to MSBuildArgs + if (selectedDevice is not null) + { + var properties = new Dictionary { { "Device", selectedDevice } }; + + // If the device provided a RuntimeIdentifier, add it too + if (!string.IsNullOrEmpty(runtimeIdentifier)) + { + properties["RuntimeIdentifier"] = runtimeIdentifier; + } + + var additionalProperties = new ReadOnlyDictionary(properties); + MSBuildArgs = MSBuildArgs.CloneWithAdditionalProperties(additionalProperties); + } + + // If ListDevices was set, we return true but the caller will exit after listing + return !ListDevices; + } + return false; } @@ -246,8 +323,8 @@ private bool TrySelectTargetFrameworkForFileBasedProject() return true; // Not multi-targeted } - // Use TargetFrameworkSelector to handle multi-target selection (or single framework selection) - if (TargetFrameworkSelector.TrySelectTargetFramework(frameworks, Interactive, out string? selectedFramework)) + // Use RunCommandSelector to handle multi-target selection (or single framework selection) + if (RunCommandSelector.TrySelectTargetFramework(frameworks, Interactive, out string? selectedFramework)) { ApplySelectedFramework(selectedFramework); return true; @@ -805,6 +882,8 @@ public static RunCommand FromParseResult(ParseResult parseResult) launchProfile: launchProfile, noLaunchProfile: parseResult.HasOption(RunCommandParser.NoLaunchProfileOption), noLaunchProfileArguments: parseResult.HasOption(RunCommandParser.NoLaunchProfileArgumentsOption), + device: parseResult.GetValue(RunCommandParser.DeviceOption), + listDevices: parseResult.HasOption(RunCommandParser.ListDevicesOption), noRestore: parseResult.HasOption(RunCommandParser.NoRestoreOption) || parseResult.HasOption(RunCommandParser.NoBuildOption), noCache: parseResult.HasOption(RunCommandParser.NoCacheOption), interactive: parseResult.GetValue(RunCommandParser.InteractiveOption), diff --git a/src/Cli/dotnet/Commands/Run/RunCommandParser.cs b/src/Cli/dotnet/Commands/Run/RunCommandParser.cs index 0f851f575c7e..787ab2a3b863 100644 --- a/src/Cli/dotnet/Commands/Run/RunCommandParser.cs +++ b/src/Cli/dotnet/Commands/Run/RunCommandParser.cs @@ -48,6 +48,18 @@ internal static class RunCommandParser Description = CliCommandStrings.CommandOptionNoLaunchProfileArgumentsDescription }; + public static readonly Option DeviceOption = new("--device") + { + Description = CliCommandStrings.CommandOptionDeviceDescription, + HelpName = CliCommandStrings.CommandOptionDeviceHelpName + }; + + public static readonly Option ListDevicesOption = new("--list-devices") + { + Description = CliCommandStrings.CommandOptionListDevicesDescription, + Arity = ArgumentArity.Zero + }; + public static readonly Option NoBuildOption = new("--no-build") { Description = CliCommandStrings.CommandOptionNoBuildDescription, @@ -98,6 +110,8 @@ private static Command ConstructCommand() command.Options.Add(PropertyOption); command.Options.Add(LaunchProfileOption); command.Options.Add(NoLaunchProfileOption); + command.Options.Add(DeviceOption); + command.Options.Add(ListDevicesOption); command.Options.Add(NoBuildOption); command.Options.Add(InteractiveOption); command.Options.Add(NoRestoreOption); diff --git a/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs b/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs new file mode 100644 index 000000000000..205f607e07cc --- /dev/null +++ b/src/Cli/dotnet/Commands/Run/RunCommandSelector.cs @@ -0,0 +1,439 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Exceptions; +using Microsoft.Build.Execution; +using Microsoft.DotNet.Cli.Utils; +using Spectre.Console; + +namespace Microsoft.DotNet.Cli.Commands.Run; + +/// +/// Handles target framework and device selection for dotnet run. +/// Caches the project instance to avoid reloading it multiple times. +/// +internal sealed class RunCommandSelector : IDisposable +{ + // Spectre.Console markup color constants + private const string CyanMarkup = "[cyan]"; + private const string GrayMarkup = "[gray]"; + private const string EndMarkup = "[/]"; + + private readonly string _projectFilePath; + private readonly Dictionary _globalProperties; + private readonly FacadeLogger? _binaryLogger; + private readonly bool _isInteractive; + + private ProjectCollection? _collection; + private Microsoft.Build.Evaluation.Project? _project; + private ProjectInstance? _projectInstance; + private bool _disposed; + + public RunCommandSelector( + string projectFilePath, + Dictionary globalProperties, + bool isInteractive, + FacadeLogger? binaryLogger = null) + { + _projectFilePath = projectFilePath; + _globalProperties = globalProperties; + _isInteractive = isInteractive; + _binaryLogger = binaryLogger; + } + + /// + /// Evaluates the project to determine if target framework selection is needed. + /// If the project has multiple target frameworks and none was specified, prompts the user to select one. + /// + /// The selected target framework, or null if not needed + /// True if we should continue, false if we should exit with error + public bool TrySelectTargetFramework(out string? selectedFramework) + { + selectedFramework = null; + + // If a framework is already specified, no need to prompt + if (_globalProperties.TryGetValue("TargetFramework", out var existingFramework) && !string.IsNullOrWhiteSpace(existingFramework)) + { + return true; + } + + // Evaluate the project to get TargetFrameworks + if (!OpenProjectIfNeeded(out var projectInstance)) + { + // Invalid project file, return true to continue for normal error handling + return true; + } + string targetFrameworks = projectInstance.GetPropertyValue("TargetFrameworks"); + + // If there's no TargetFrameworks property or only one framework, no selection needed + if (string.IsNullOrWhiteSpace(targetFrameworks)) + { + return true; + } + + // parse the TargetFrameworks property and make sure to account for any additional whitespace + // users may have added for formatting reasons. + var frameworks = targetFrameworks.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + return TrySelectTargetFramework(frameworks, _isInteractive, out selectedFramework); + } + + /// + /// Invalidates the loaded project with updated global properties. + /// This is needed after framework selection to get the correct device list for that framework. + /// + public void InvalidateGlobalProperties(Dictionary updatedProperties) + { + // Update our stored global properties + foreach (var (key, value) in updatedProperties) + { + _globalProperties[key] = value; + } + + // Dispose existing project to force re-evaluation + _project = null; + _projectInstance = null; + _collection?.Dispose(); + _collection = null; + } + + /// + /// Opens the project if it hasn't been opened yet. + /// + private bool OpenProjectIfNeeded([NotNullWhen(true)] out ProjectInstance? projectInstance) + { + if (_project is not null) + { + Debug.Assert(_projectInstance is not null); + projectInstance = _projectInstance; + return true; + } + + try + { + _collection = new ProjectCollection( + globalProperties: _globalProperties, + loggers: null, + toolsetDefinitionLocations: ToolsetDefinitionLocations.Default); + _project = _collection.LoadProject(_projectFilePath); + _projectInstance = _project.CreateProjectInstance(); + projectInstance = _projectInstance; + return true; + } + catch (InvalidProjectFileException) + { + // Invalid project file, return false + projectInstance = null; + return false; + } + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _binaryLogger?.ReallyShutdown(); + _collection?.Dispose(); + _disposed = true; + } + + /// + /// Handles target framework selection when given an array of frameworks. + /// If there's only one framework, selects it automatically. + /// If there are multiple frameworks, prompts the user (interactive) or shows an error (non-interactive). + /// + /// Array of target frameworks to choose from + /// Whether we're running in interactive mode (can prompt user) + /// The selected target framework, or null if selection was cancelled + /// True if we should continue, false if we should exit with error + public static bool TrySelectTargetFramework(string[] frameworks, bool isInteractive, out string? selectedFramework) + { + // If there's only one framework in the TargetFrameworks, we do need to pick it to force the subsequent builds/evaluations + // to act against the correct 'view' of the project + if (frameworks.Length == 1) + { + selectedFramework = frameworks[0]; + return true; + } + + if (isInteractive) + { + selectedFramework = PromptForTargetFramework(frameworks); + return selectedFramework != null; + } + else + { + Reporter.Error.WriteLine(string.Format(CliCommandStrings.RunCommandExceptionUnableToRunSpecifyFramework, "--framework")); + Reporter.Error.WriteLine(); + Reporter.Error.WriteLine(CliCommandStrings.RunCommandAvailableTargetFrameworks); + Reporter.Error.WriteLine(); + + for (int i = 0; i < frameworks.Length; i++) + { + Reporter.Error.WriteLine($" {i + 1}. {frameworks[i]}"); + } + + Reporter.Error.WriteLine(); + Reporter.Error.WriteLine($"{CliCommandStrings.RunCommandExampleText}: dotnet run --framework {frameworks[0]}"); + Reporter.Error.WriteLine(); + selectedFramework = null; + return false; + } + } + + /// + /// Prompts the user to select a target framework from the available options using Spectre.Console. + /// + private static string? PromptForTargetFramework(string[] frameworks) + { + try + { + var prompt = new SelectionPrompt() + .Title($"{CyanMarkup}{Markup.Escape(CliCommandStrings.RunCommandSelectTargetFrameworkPrompt)}{EndMarkup}") + .PageSize(10) + .MoreChoicesText($"{GrayMarkup}({Markup.Escape(CliCommandStrings.RunCommandMoreFrameworksText)}){EndMarkup}") + .AddChoices(frameworks) + .EnableSearch() + .SearchPlaceholderText(CliCommandStrings.RunCommandSearchPlaceholderText); + + return Spectre.Console.AnsiConsole.Prompt(prompt); + } + catch (Exception) + { + // If Spectre.Console fails (e.g., terminal doesn't support it), return null + return null; + } + } + + /// + /// Represents a device item returned from the ComputeAvailableDevices MSBuild target. + /// + public record DeviceItem(string Id, string? Description, string? Type, string? Status, string? RuntimeIdentifier); + + /// + /// Computes available devices by calling the ComputeAvailableDevices MSBuild target if it exists. + /// + /// List of available devices if the target exists, null otherwise + /// True if the target was found and executed, false otherwise + public bool TryComputeAvailableDevices(out List? devices) + { + devices = null; + + if (!OpenProjectIfNeeded(out var projectInstance)) + { + // Invalid project file, return false + return false; + } + + // Check if the ComputeAvailableDevices target exists + if (!projectInstance.Targets.ContainsKey(Constants.ComputeAvailableDevices)) + { + return false; + } + + // Build the target + var buildResult = projectInstance.Build( + targets: [Constants.ComputeAvailableDevices], + loggers: _binaryLogger is null ? null : [_binaryLogger], + remoteLoggers: null, + out var targetOutputs); + + if (!buildResult) + { + return false; + } + + // Get the Devices items from the target output + if (!targetOutputs.TryGetValue(Constants.ComputeAvailableDevices, out var targetResult)) + { + return false; + } + + devices = new(targetResult.Items.Length); + + foreach (var item in targetResult.Items) + { + devices.Add(new DeviceItem( + item.ItemSpec, + item.GetMetadata("Description"), + item.GetMetadata("Type"), + item.GetMetadata("Status"), + item.GetMetadata("RuntimeIdentifier") + )); + } + + return true; + } + + /// + /// Attempts to select a device for running the application. + /// If devices are available and none was specified, prompts the user to select one (interactive mode) + /// or shows an error (non-interactive mode). + /// + /// Whether to list devices and exit + /// The selected device, or null if not needed + /// The RuntimeIdentifier for the selected device, or null if not provided + /// True if we should continue, false if we should exit + public bool TrySelectDevice( + bool listDevices, + out string? selectedDevice, + out string? runtimeIdentifier) + { + selectedDevice = null; + runtimeIdentifier = null; + + // Try to get available devices from the project + bool targetExists = TryComputeAvailableDevices(out var devices); + + // If the target doesn't exist, continue without device selection + if (!targetExists) + { + // No device support in this project + return true; + } + + // Target exists - check if we have devices + if (devices is null || devices.Count == 0) + { + if (listDevices) + { + Reporter.Output.WriteLine(CliCommandStrings.RunCommandNoDevicesAvailable); + return true; + } + + // Target exists but no devices available - this is an error + Reporter.Error.WriteLine(CliCommandStrings.RunCommandNoDevicesAvailable); + return false; + } + + // If listing devices, display them and exit + if (listDevices) + { + Reporter.Output.WriteLine(CliCommandStrings.RunCommandAvailableDevices); + Reporter.Output.WriteLine(); + + for (int i = 0; i < devices.Count; i++) + { + var device = devices[i]; + var displayBuilder = new StringBuilder($" {i + 1}. {device.Id}"); + + if (!string.IsNullOrWhiteSpace(device.Description)) + { + displayBuilder.Append($" - {device.Description}"); + } + + if (!string.IsNullOrWhiteSpace(device.Type)) + { + displayBuilder.Append($" ({device.Type}"); + if (!string.IsNullOrWhiteSpace(device.Status)) + { + displayBuilder.Append($", {device.Status}"); + } + displayBuilder.Append(')'); + } + else if (!string.IsNullOrWhiteSpace(device.Status)) + { + displayBuilder.Append($" ({device.Status})"); + } + + Reporter.Output.WriteLine(displayBuilder.ToString()); + } + + Reporter.Output.WriteLine(); + Reporter.Output.WriteLine($"{CliCommandStrings.RunCommandExampleText}: dotnet run --device {devices[0].Id}"); + Reporter.Output.WriteLine(); + return true; + } + + // If there's only one device, automatically select it (similar to single framework selection) + if (devices.Count == 1) + { + selectedDevice = devices[0].Id; + runtimeIdentifier = devices[0].RuntimeIdentifier; + return true; + } + + + + if (_isInteractive) + { + var deviceItem = PromptForDevice(devices); + if (deviceItem is null) + { + return false; + } + + selectedDevice = deviceItem.Id; + runtimeIdentifier = deviceItem.RuntimeIdentifier; + return true; + } + else + { + Reporter.Error.WriteLine(string.Format(CliCommandStrings.RunCommandExceptionUnableToRunSpecifyDevice, "--device")); + Reporter.Error.WriteLine(); + Reporter.Error.WriteLine(CliCommandStrings.RunCommandAvailableDevices); + Reporter.Error.WriteLine(); + + for (int i = 0; i < devices.Count; i++) + { + var device = devices[i]; + var displayText = $" {i + 1}. {device.Id}"; + + if (!string.IsNullOrWhiteSpace(device.Description)) + { + displayText += $" - {device.Description}"; + } + + Reporter.Error.WriteLine(displayText); + } + + Reporter.Error.WriteLine(); + Reporter.Error.WriteLine($"{CliCommandStrings.RunCommandExampleText}: dotnet run --device {devices[0].Id}"); + Reporter.Error.WriteLine(); + return false; + } + } + + /// + /// Prompts the user to select a device from the available options using Spectre.Console. + /// + private static DeviceItem? PromptForDevice(List devices) + { + List<(string Display, DeviceItem Device)> choices = new(devices.Count); + foreach (var d in devices) + { + var display = d.Id; + if (!string.IsNullOrWhiteSpace(d.Description)) + { + display += $" - {d.Description}"; + } + choices.Add((display, d)); + } + + try + { + var prompt = new SelectionPrompt<(string Display, DeviceItem Device)>() + .Title($"{CyanMarkup}{Markup.Escape(CliCommandStrings.RunCommandSelectDevicePrompt)}{EndMarkup}") + .PageSize(10) + .MoreChoicesText($"{GrayMarkup}({Markup.Escape(CliCommandStrings.RunCommandMoreDevicesText)}){EndMarkup}") + .AddChoices(choices) + .UseConverter(choice => choice.Display) + .EnableSearch() + .SearchPlaceholderText(CliCommandStrings.RunCommandSearchPlaceholderText); + + var (Display, Device) = Spectre.Console.AnsiConsole.Prompt(prompt); + return Device; + } + catch (Exception) + { + // If Spectre.Console fails (e.g., terminal doesn't support it), return null + return null; + } + } +} diff --git a/src/Cli/dotnet/Commands/Run/TargetFrameworkSelector.cs b/src/Cli/dotnet/Commands/Run/TargetFrameworkSelector.cs deleted file mode 100644 index 82f8d7c152ba..000000000000 --- a/src/Cli/dotnet/Commands/Run/TargetFrameworkSelector.cs +++ /dev/null @@ -1,130 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Build.Evaluation; -using Microsoft.Build.Exceptions; -using Microsoft.DotNet.Cli.Utils; -using Spectre.Console; - -namespace Microsoft.DotNet.Cli.Commands.Run; - -internal static class TargetFrameworkSelector -{ - /// - /// Evaluates the project to determine if target framework selection is needed. - /// If the project has multiple target frameworks and none was specified, prompts the user to select one. - /// - /// Path to the project file - /// Global properties for MSBuild evaluation - /// Whether we're running in interactive mode (can prompt user) - /// The selected target framework, or null if not needed - /// True if we should continue, false if we should exit with error - public static bool TrySelectTargetFramework( - string projectFilePath, - Dictionary globalProperties, - bool isInteractive, - out string? selectedFramework) - { - selectedFramework = null; - - // If a framework is already specified, no need to prompt - if (globalProperties.TryGetValue("TargetFramework", out var existingFramework) && !string.IsNullOrWhiteSpace(existingFramework)) - { - return true; - } - - // Evaluate the project to get TargetFrameworks - string targetFrameworks; - try - { - using var collection = new ProjectCollection(globalProperties: globalProperties); - var project = collection.LoadProject(projectFilePath); - targetFrameworks = project.GetPropertyValue("TargetFrameworks"); - } - catch (InvalidProjectFileException) - { - // Invalid project file, return true to continue for normal error handling - return true; - } - - // If there's no TargetFrameworks property or only one framework, no selection needed - if (string.IsNullOrWhiteSpace(targetFrameworks)) - { - return true; - } - - // parse the TargetFrameworks property and make sure to account for any additional whitespace - // users may have added for formatting reasons. - var frameworks = targetFrameworks.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - - return TrySelectTargetFramework(frameworks, isInteractive, out selectedFramework); - } - - /// - /// Handles target framework selection when given an array of frameworks. - /// If there's only one framework, selects it automatically. - /// If there are multiple frameworks, prompts the user (interactive) or shows an error (non-interactive). - /// - /// Array of target frameworks to choose from - /// Whether we're running in interactive mode (can prompt user) - /// The selected target framework, or null if selection was cancelled - /// True if we should continue, false if we should exit with error - public static bool TrySelectTargetFramework(string[] frameworks, bool isInteractive, out string? selectedFramework) - { - // If there's only one framework in the TargetFrameworks, we do need to pick it to force the subsequent builds/evaluations - // to act against the correct 'view' of the project - if (frameworks.Length == 1) - { - selectedFramework = frameworks[0]; - return true; - } - - if (isInteractive) - { - selectedFramework = PromptForTargetFramework(frameworks); - return selectedFramework != null; - } - else - { - Reporter.Error.WriteLine(string.Format(CliCommandStrings.RunCommandExceptionUnableToRunSpecifyFramework, "--framework")); - Reporter.Error.WriteLine(); - Reporter.Error.WriteLine(CliCommandStrings.RunCommandAvailableTargetFrameworks); - Reporter.Error.WriteLine(); - - for (int i = 0; i < frameworks.Length; i++) - { - Reporter.Error.WriteLine($" {i + 1}. {frameworks[i]}"); - } - - Reporter.Error.WriteLine(); - Reporter.Error.WriteLine($"{CliCommandStrings.RunCommandExampleText}: dotnet run --framework {frameworks[0]}"); - Reporter.Error.WriteLine(); - selectedFramework = null; - return false; - } - } - - /// - /// Prompts the user to select a target framework from the available options using Spectre.Console. - /// - private static string? PromptForTargetFramework(string[] frameworks) - { - try - { - var prompt = new SelectionPrompt() - .Title($"[cyan]{Markup.Escape(CliCommandStrings.RunCommandSelectTargetFrameworkPrompt)}[/]") - .PageSize(10) - .MoreChoicesText($"[grey]({Markup.Escape(CliCommandStrings.RunCommandMoreFrameworksText)})[/]") - .AddChoices(frameworks) - .EnableSearch() - .SearchPlaceholderText(CliCommandStrings.RunCommandSearchPlaceholderText); - - return Spectre.Console.AnsiConsole.Prompt(prompt); - } - catch (Exception) - { - // If Spectre.Console fails (e.g., terminal doesn't support it), return null - return null; - } - } -} diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf index 530a26354f44..3805b9b8be5c 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf @@ -92,6 +92,21 @@ .NET Builder + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. Nezobrazovat úvodní nápis ani zprávu o autorských právech @@ -2657,6 +2672,11 @@ Ve výchozím nastavení je publikována aplikace závislá na architektuře.Příkaz rozhraní .NET pro spuštění + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ Cílem spustitelného projektu by mělo být TFM (například net5.0) a jeho Out Aktuální {1} je {2}. {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' Cílem projektu je více architektur. Pomocí parametru {0} určete, která architektura se má spustit. + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. Upozornění NETSDK1174: Zkratka -p pro --project je zastaralá. Použijte prosím --project. @@ -2739,6 +2774,11 @@ Cílem projektu je více architektur. Pomocí parametru {0} určete, která arch Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ Pokud chcete zobrazit hodnotu, zadejte odpovídající volbu příkazového řá - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf index ab4361302430..ffc2aaf9640c 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf @@ -92,6 +92,21 @@ .NET-Generator + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. Zeigt kein Startbanner und keine Copyrightmeldung an. @@ -2657,6 +2672,11 @@ Standardmäßig wird eine Framework-abhängige Anwendung veröffentlicht..NET-Befehl "Run" + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ Ein ausführbares Projekt muss ein ausführbares TFM (z. B. net5.0) und den Outp {1} lautet aktuell "{2}". {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' Ihr Projekt verwendet mehrere Zielframeworks. Geben Sie über "{0}" an, welches Framework ausgeführt werden soll. + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. Warnung NETSDK1174: Die Abkürzung von „-p“ für „--project“ ist veraltet. Verwenden Sie „--project“. @@ -2739,6 +2774,11 @@ Ihr Projekt verwendet mehrere Zielframeworks. Geben Sie über "{0}" an, welches Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ Um einen Wert anzuzeigen, geben Sie die entsprechende Befehlszeilenoption an, oh - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf index bc287e0c9fef..0c5d385caff6 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf @@ -92,6 +92,21 @@ Generador para .NET + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. No mostrar la pancarta de inicio ni el mensaje de copyright. @@ -2657,6 +2672,11 @@ El valor predeterminado es publicar una aplicación dependiente del marco.Ejecutar comando de .NET + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ Un proyecto ejecutable debe tener como destino un TFM ejecutable (por ejemplo, n El valor actual de {1} es "{2}". {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' Su proyecto tiene como destino varias plataformas. Especifique la que quiere usar mediante "{0}". + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. Advertencia NETSDK1174: La abreviatura de -p para --project está en desuso. Use --project. @@ -2739,6 +2774,11 @@ Su proyecto tiene como destino varias plataformas. Especifique la que quiere usa Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ Para mostrar un valor, especifique la opción de línea de comandos correspondie - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf index e5c2de783ebe..ad02d5f06a45 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf @@ -92,6 +92,21 @@ Générateur .NET + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. N'affiche pas la bannière de démarrage ni le message de copyright. @@ -2657,6 +2672,11 @@ La valeur par défaut est de publier une application dépendante du framework.Commande d'exécution .NET + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ Un projet exécutable doit cibler un TFM exécutable (par exemple, net5.0) et av Le {1} actuel est '{2}'. {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' Votre projet cible plusieurs frameworks. Spécifiez le framework à exécuter à l'aide de '{0}'. + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. AVERTISSEMENT NETSDK1174 : l’abréviation de-p pour--project est déconseillée. Veuillez utiliser--project. @@ -2739,6 +2774,11 @@ Votre projet cible plusieurs frameworks. Spécifiez le framework à exécuter à Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ Pour afficher une valeur, spécifiez l’option de ligne de commande corresponda - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf index 3ab38a656439..5bc254561ddf 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf @@ -92,6 +92,21 @@ Generatore .NET + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. Evita la visualizzazione del messaggio di avvio o di copyright. @@ -2657,6 +2672,11 @@ Per impostazione predefinita, viene generato un pacchetto dipendente dal framewo Comando esecuzione .NET + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ Un progetto eseguibile deve essere destinato a un TFM eseguibile, ad esempio net Il valore corrente di {1} è '{2}'. {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' Il progetto è destinato a più framework. Specificare il framework da eseguire con '{0}'. + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. Avviso NETSDK1174: l'abbreviazione di -p per --project è deprecata. Usare --project. @@ -2739,6 +2774,11 @@ Il progetto è destinato a più framework. Specificare il framework da eseguire Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ Per visualizzare un valore, specifica l'opzione della riga di comando corrispond - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf index 7ac6cb329cbb..f942b99ce5d8 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf @@ -92,6 +92,21 @@ .NET ビルダー + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. 著作権情報を表示しません。 @@ -1927,7 +1942,6 @@ Tool '{1}' (version '{2}') was successfully installed. Entry is added to the man .NET Core NuGet パッケージ パッカー - OUTPUT_DIR OUTPUT_DIR @@ -2658,6 +2672,11 @@ The default is to publish a framework-dependent application. .NET Run コマンド + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2718,6 +2737,11 @@ The current OutputType is '{2}'. 現在の {1} は '{2}' です。 {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2725,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' プロジェクトは複数のフレームワークを対象としています。'{0}' を使用して、実行するフレームワークを指定してください。 + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. 警告 NETSDK1174: --project の省略形である -p は推奨されていません。--project を使用してください。 @@ -2740,6 +2774,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4154,4 +4193,4 @@ To display a value, specify the corresponding command-line option without provid - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf index 3f02783a89b3..4f008556132d 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf @@ -92,6 +92,21 @@ .NET 작성기 + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. 시작 배너 또는 저작권 메시지를 표시하지 않습니다. @@ -2657,6 +2672,11 @@ The default is to publish a framework-dependent application. .NET 실행 명령 + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ The current OutputType is '{2}'. 현재 {1}은(는) '{2}'입니다. {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' 프로젝트에서 여러 프레임워크를 대상으로 합니다. '{0}'을(를) 사용하여 실행할 프레임워크를 지정하세요. + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. 경고 NETSDK1174: --project에 대한 약어 -p는 더 이상 사용되지 않습니다. --project를 사용하세요. @@ -2739,6 +2774,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ To display a value, specify the corresponding command-line option without provid - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf index b34e91cd5c6b..9598de759b77 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf @@ -92,6 +92,21 @@ Konstruktor platformy .NET + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. Nie wyświetlaj baneru początkowego ani komunikatu o prawach autorskich. @@ -2657,6 +2672,11 @@ Domyślnie publikowana jest aplikacja zależna od struktury. Uruchamianie polecenia platformy .NET + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ Projekt z możliwością uruchamiania musi mieć moniker TFM z możliwością ur Bieżący element {1}: „{2}”. {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' Projekt ma wiele platform docelowych. Określ platformę do uruchomienia przy użyciu elementu „{0}”. + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. Ostrzeżenie NETSDK1174: Skrót -p dla polecenia --project jest przestarzały. Użyj polecenia --project. @@ -2739,6 +2774,11 @@ Projekt ma wiele platform docelowych. Określ platformę do uruchomienia przy u Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ Aby wyświetlić wartość, należy podać odpowiednią opcję wiersza poleceń - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf index be8b5a36f9b3..f92368671030 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf @@ -92,6 +92,21 @@ Construtor do .NET + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. Não exibe a faixa de inicialização ou a mensagem de direitos autorais. @@ -2657,6 +2672,11 @@ O padrão é publicar uma aplicação dependente de framework. Comando Run do .NET + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ Um projeto executável deve ter como destino um TFM executável (por exemplo, o O {1} atual é '{2}'. {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' Ele tem diversas estruturas como destino. Especifique que estrutura executar usando '{0}'. + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. Aviso NETSDK1174: a abreviação de-p para--project é preterida. Use --project. @@ -2739,6 +2774,11 @@ Ele tem diversas estruturas como destino. Especifique que estrutura executar usa Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ Para exibir um valor, especifique a opção de linha de comando correspondente s - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf index ae78354e65d4..b5683e18c60d 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf @@ -92,6 +92,21 @@ Построитель .NET + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. Не отображать начальный баннер или сообщение об авторских правах. @@ -2657,6 +2672,11 @@ The default is to publish a framework-dependent application. Команда .NET Run + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ The current OutputType is '{2}'. Текущий {1} — "{2}". {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' Проект предназначен для нескольких платформ. Укажите платформу, для которой следует запустить проект, с помощью "{0}". + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. Предупреждение NETSDK1174: сокращение "-p" для "--project" не рекомендуется. Используйте "--project". @@ -2739,6 +2774,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4154,4 +4194,4 @@ To display a value, specify the corresponding command-line option without provid - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf index 4b7e0ae61cf6..e2aebc3732b3 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf @@ -92,6 +92,21 @@ .NET Oluşturucusu + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. Başlangıç bandını veya telif hakkı iletisini görüntüleme. @@ -2657,6 +2672,11 @@ Varsayılan durum, çerçeveye bağımlı bir uygulama yayımlamaktır. .NET Run Komutu + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ The current OutputType is '{2}'. Geçerli {1}: '{2}'. {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' Projeniz birden fazla Framework'ü hedefliyor. '{0}' kullanarak hangi Framework'ün çalıştırılacağını belirtin. + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. Uyarı NETSDK1174: --project için -p kısaltması kullanımdan kaldırıldı. Lütfen --project kullanın. @@ -2739,6 +2774,11 @@ Projeniz birden fazla Framework'ü hedefliyor. '{0}' kullanarak hangi Framework' Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ Bir değeri görüntülemek için, bir değer sağlamadan ilgili komut satırı - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf index fa02c9282394..a39b9ae22a1c 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf @@ -92,6 +92,21 @@ .NET 生成器 + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. 不显示启动版权标志或版权消息。 @@ -2657,6 +2672,11 @@ The default is to publish a framework-dependent application. .NET 运行命令 + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ The current OutputType is '{2}'. 当前的 {1} 为“{2}”。 {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' 你的项目面向多个框架。请指定要使用“{0}”运行的框架。 + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. 警告 NETSDK1174: 已弃用使用缩写“-p”来代表“--project”。请使用“--project”。 @@ -2739,6 +2774,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ To display a value, specify the corresponding command-line option without provid - + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf index 1bac2017f833..424c4a1574d8 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf @@ -92,6 +92,21 @@ .NET 產生器 + + The device identifier to use for running the application. + The device identifier to use for running the application. + + + + DEVICE + DEVICE + + + + List available devices for running the application. + List available devices for running the application. + + Do not display the startup banner or the copyright message. 不顯示啟始資訊或著作權訊息。 @@ -2657,6 +2672,11 @@ The default is to publish a framework-dependent application. .NET 執行命令 + + Available devices: + Available devices: + + Available target frameworks: Available target frameworks: @@ -2717,6 +2737,11 @@ The current OutputType is '{2}'. 目前的 {1} 為 '{2}'。 {0} is project file path. {1} is dotnet framework version. {2} is the project output type.{Locked="OutputType"}{Locked="Exe"} + + 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: + 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: + + Unable to run your project Your project targets multiple frameworks. Specify which framework to run using '{0}'. @@ -2724,11 +2749,21 @@ Your project targets multiple frameworks. Specify which framework to run using ' 您的專案以多重架構為目標。請使用 '{0}' 指定要執行的架構。 + + Move up and down to reveal more devices + Move up and down to reveal more devices + + Move up and down to reveal more frameworks Move up and down to reveal more frameworks + + No devices are available for this project. + No devices are available for this project. + + Warning NETSDK1174: The abbreviation of -p for --project is deprecated. Please use --project. 警告 NETSDK1174: --project 已取代縮寫 -p。請使用 --project。 @@ -2739,6 +2774,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' Type to search + + Select a device to run on: + Select a device to run on: + + Select the target framework to run: Select the target framework to run: @@ -4153,4 +4193,4 @@ To display a value, specify the corresponding command-line option without provid - + \ No newline at end of file diff --git a/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj b/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj new file mode 100644 index 000000000000..df9db9f96908 --- /dev/null +++ b/test/TestAssets/TestProjects/DotnetRunDevices/DotnetRunDevices.csproj @@ -0,0 +1,56 @@ + + + + Exe + net8.0;net9.0;$(CurrentTargetFramework) + + + + + + + + + + + + + + + + + + + + + + + + + + + $(IntermediateOutputPath)DeviceInfo.cs + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/TestAssets/TestProjects/DotnetRunDevices/Program.cs b/test/TestAssets/TestProjects/DotnetRunDevices/Program.cs new file mode 100644 index 000000000000..5d9e6d61d1e7 --- /dev/null +++ b/test/TestAssets/TestProjects/DotnetRunDevices/Program.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace DotNetRunDevices +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello from multi-targeted app!"); + Console.WriteLine($"Target Framework: {AppContext.TargetFrameworkName}"); + Console.WriteLine($"Runtime: {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}"); + + // DeviceInfo class is generated at build time when Device property is set + Console.WriteLine($"Device: {DeviceInfo.Device}"); + Console.WriteLine($"RuntimeIdentifier: {DeviceInfo.RuntimeIdentifier}"); + } + } +} diff --git a/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs b/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs new file mode 100644 index 000000000000..8f3f8ff8bddf --- /dev/null +++ b/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunSelectsDevice.cs @@ -0,0 +1,268 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.Cli.Commands; + +namespace Microsoft.DotNet.Cli.Run.Tests; + +/// +/// Integration tests for device selection in dotnet run +/// +public class GivenDotnetRunSelectsDevice : SdkTest +{ + public GivenDotnetRunSelectsDevice(ITestOutputHelper log) : base(log) + { + } + + string ExpectedRid => OperatingSystem.IsWindows() ? "win" : (OperatingSystem.IsMacOS() ? "osx" : "linux"); + + [Fact] + public void ItFailsInNonInteractiveMode_WhenMultipleDevicesAvailableAndNoneSpecified() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .Execute("--framework", "net8.0", "--no-interactive"); + + result.Should().Fail() + .And.HaveStdErrContaining(string.Format(CliCommandStrings.RunCommandExceptionUnableToRunSpecifyDevice, "--device")); + } + + [Fact] + public void ItListsDevicesForSpecifiedFramework() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .Execute("--framework", "net8.0", "--list-devices"); + + result.Should().Pass() + .And.HaveStdOutContaining("test-device-1") + .And.HaveStdOutContaining("test-device-2") + .And.HaveStdOutContaining("Emulator"); + } + + [Theory] + [InlineData("net8.0", "test-device-1")] + [InlineData("net8.0", "test-device-2")] + [InlineData("net9.0", "test-device-3")] + [InlineData("net9.0", "test-device-4")] + public void ItRunsDifferentDevicesInMultiTargetedApp(string targetFramework, string deviceId) + { + // Skip net8.0 and net9.0 on arm64 as they may not be available on CI + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64 && + (targetFramework == "net8.0" || targetFramework == "net9.0")) + { + return; + } + + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .Execute("--framework", targetFramework, "--device", deviceId) + .Should().Pass() + .And.HaveStdOutContaining($"Device: {deviceId}"); + } + + [Fact] + public void ItShowsErrorMessageWithAvailableDevices_InNonInteractiveMode() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .Execute("--framework", "net8.0", "--no-interactive"); + + result.Should().Fail() + .And.HaveStdErrContaining(string.Format(CliCommandStrings.RunCommandExceptionUnableToRunSpecifyDevice, "--device")) + .And.HaveStdErrContaining("test-device-1") + .And.HaveStdErrContaining("test-device-2"); + } + + [Fact] + public void ItDoesNotPromptForDeviceWhenComputeAvailableDevicesTargetDoesNotExist() + { + var testInstance = _testAssetsManager.CopyTestAsset( + "NETFrameworkReferenceNETStandard20", + testAssetSubdirectory: TestAssetSubdirectories.DesktopTestProjects) + .WithSource(); + + string projectDirectory = Path.Combine(testInstance.Path, "MultiTFMTestApp"); + + // This project doesn't have ComputeAvailableDevices target, so it should just run + new DotnetCommand(Log, "run") + .WithWorkingDirectory(projectDirectory) + .Execute("--framework", ToolsetInfo.CurrentTargetFramework) + .Should().Pass() + .And.HaveStdOutContaining("This string came from the test library!"); + } + + [Fact] + public void ItTreatsEmptyDeviceSpecificationAsNotSpecified() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .Execute("--framework", "net8.0", "-p:Device=", "--no-interactive"); + + result.Should().Fail() + .And.HaveStdErrContaining(string.Format(CliCommandStrings.RunCommandExceptionUnableToRunSpecifyDevice, "--device")); + } + + [Fact] + public void ItWorksWithDevicePropertySyntax() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + string deviceId = "test-device-1"; + new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .Execute("--framework", "net8.0", $"-p:Device={deviceId}") + .Should().Pass() + .And.HaveStdOutContaining($"Device: {deviceId}"); + } + + [Fact] + public void ItWorksWithDeviceWithoutRuntimeIdentifier() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + string deviceId = "test-device-2"; + new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .Execute("--framework", "net8.0", "--device", deviceId) + .Should().Pass() + .And.HaveStdOutContaining($"Device: {deviceId}") + .And.HaveStdOutContaining("RuntimeIdentifier:"); + } + + [Theory] + [InlineData(true)] // interactive + [InlineData(false)] // non-interactive + public void ItAutoSelectsSingleDeviceWithoutPrompting(bool interactive) + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + var command = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path); + + var args = new List { "--framework", "net8.0", "-p:SingleDevice=true" }; + if (!interactive) + { + args.Add("--no-interactive"); + } + + var result = command.Execute(args.ToArray()); + + // Should auto-select the single device and run successfully + result.Should().Pass() + .And.HaveStdOutContaining("Device: single-device") + .And.HaveStdOutContaining($"RuntimeIdentifier: {ExpectedRid}"); + } + + [Fact] + public void ItCreatesBinlogWhenRequestedForDeviceSelection() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + // When /bl:device-list.binlog is specified, the verb is appended + string binlogPath = Path.Combine(testInstance.Path, "device-list-dotnet-run-devices.binlog"); + + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .Execute("--framework", "net8.0", "--list-devices", "/bl:device-list.binlog"); + + result.Should().Pass() + .And.HaveStdOutContaining("test-device-1"); + + // Verify the binlog file was created + File.Exists(binlogPath).Should().BeTrue("the binlog file should be created when /bl: argument is provided"); + } + + [Fact] + public void ItFailsWhenNoDevicesAreAvailable() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .Execute("--framework", "net8.0", "-p:NoDevices=true", "--no-interactive"); + + result.Should().Fail() + .And.HaveStdErrContaining(CliCommandStrings.RunCommandNoDevicesAvailable); + } + + [Theory] + [InlineData("--device")] + [InlineData("-p:Device=")] + public void ItDoesNotRunComputeAvailableDevicesWhenDeviceIsPreSpecified(string deviceArgPrefix) + { + string deviceId = "test-device-2"; + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + string deviceSelectionBinlogPath = Path.Combine(testInstance.Path, "msbuild-dotnet-run-devices.binlog"); + + var args = new List { "--framework", "net8.0" }; + if (deviceArgPrefix == "--device") + { + args.Add("--device"); + args.Add(deviceId); + } + else + { + args.Add($"{deviceArgPrefix}{deviceId}"); + } + args.Add("-bl"); + + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .Execute(args.ToArray()); + + // Should run successfully + result.Should().Pass() + .And.HaveStdOutContaining($"Device: {deviceId}"); + + // Verify the device selection binlog file was NOT created + File.Exists(deviceSelectionBinlogPath).Should().BeFalse( + "the device selection binlog should not be created when device is pre-specified because ComputeAvailableDevices target should not run"); + } + + [Fact] + public void ItPromptsForTargetFrameworkEvenWhenDeviceIsSpecified() + { + var testInstance = _testAssetsManager.CopyTestAsset("DotnetRunDevices") + .WithSource(); + + string deviceId = "test-device-1"; + + // Don't specify --framework, only specify --device + // This should fail in non-interactive mode because framework selection is still needed + var result = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .Execute("--device", deviceId, "--no-interactive"); + + // Should fail with framework selection error, not device selection error + result.Should().Fail() + .And.HaveStdErrContaining("Your project targets multiple frameworks. Specify which framework to run using '--framework'"); + } +} diff --git a/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh b/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh index 653744929d21..a1067b7a5c85 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh +++ b/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh @@ -1379,7 +1379,7 @@ _testhost_run() { prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() - opts="--configuration --framework --runtime --project --file --launch-profile --no-launch-profile --no-build --interactive --no-restore --no-cache --self-contained --no-self-contained --verbosity --arch --os --disable-build-servers --artifacts-path --environment --help" + opts="--configuration --framework --runtime --project --file --launch-profile --no-launch-profile --device --list-devices --no-build --interactive --no-restore --no-cache --self-contained --no-self-contained --verbosity --arch --os --disable-build-servers --artifacts-path --environment --help" if [[ $COMP_CWORD == "$1" ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) diff --git a/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 b/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 index b040d0120188..68047e6a569e 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 +++ b/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 @@ -852,6 +852,8 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--launch-profile', '--launch-profile', [CompletionResultType]::ParameterName, "The name of the launch profile (if any) to use when launching the application.") [CompletionResult]::new('--launch-profile', '-lp', [CompletionResultType]::ParameterName, "The name of the launch profile (if any) to use when launching the application.") [CompletionResult]::new('--no-launch-profile', '--no-launch-profile', [CompletionResultType]::ParameterName, "Do not attempt to use launchSettings.json or [app].run.json to configure the application.") + [CompletionResult]::new('--device', '--device', [CompletionResultType]::ParameterName, "The device identifier to use for running the application.") + [CompletionResult]::new('--list-devices', '--list-devices', [CompletionResultType]::ParameterName, "List available devices for running the application.") [CompletionResult]::new('--no-build', '--no-build', [CompletionResultType]::ParameterName, "Do not build the project before running. Implies --no-restore.") [CompletionResult]::new('--interactive', '--interactive', [CompletionResultType]::ParameterName, "Allows the command to stop and wait for user input or action (for example to complete authentication).") [CompletionResult]::new('--no-restore', '--no-restore', [CompletionResultType]::ParameterName, "Do not restore the project before building.") diff --git a/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh b/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh index caa0d8d97a29..76e21d528fca 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh +++ b/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh @@ -896,6 +896,8 @@ _testhost() { '--launch-profile=[The name of the launch profile (if any) to use when launching the application.]:LAUNCH_PROFILE: ' \ '-lp=[The name of the launch profile (if any) to use when launching the application.]:LAUNCH_PROFILE: ' \ '--no-launch-profile[Do not attempt to use launchSettings.json or \[app\].run.json to configure the application.]' \ + '--device=[The device identifier to use for running the application.]:DEVICE: ' \ + '--list-devices[List available devices for running the application.]' \ '--no-build[Do not build the project before running. Implies --no-restore.]' \ '--interactive=[Allows the command to stop and wait for user input or action (for example to complete authentication).]: :((False\:"False" True\:"True" ))' \ '--no-restore[Do not restore the project before building.]' \