diff --git a/src/BuiltInTools/Watch/Process/LaunchSettingsProfile.cs b/src/BuiltInTools/Watch/Process/LaunchSettingsProfile.cs index 90dbadffb2cb..1e66687c5690 100644 --- a/src/BuiltInTools/Watch/Process/LaunchSettingsProfile.cs +++ b/src/BuiltInTools/Watch/Process/LaunchSettingsProfile.cs @@ -25,7 +25,7 @@ internal sealed class LaunchSettingsProfile internal static LaunchSettingsProfile? ReadLaunchProfile(string projectPath, string? launchProfileName, ILogger logger) { - var launchSettingsPath = LaunchSettingsLocator.TryFindLaunchSettings(projectPath, launchProfileName, (message, isError) => + var launchSettingsPath = LaunchSettings.TryFindLaunchSettingsFile(projectPath, launchProfileName, (message, isError) => { if (isError) { @@ -94,11 +94,16 @@ internal sealed class LaunchSettingsProfile return null; } - var defaultProfileKey = launchSettings.Profiles.FirstOrDefault(entry => entry.Value.CommandName == "Project").Key; + // Look for the first profile with a supported command name + // Note: These must match the command names supported by LaunchSettingsManager in src/Cli/dotnet/Commands/Run/LaunchSettings/ + var supportedCommandNames = new[] { "Project", "Executable" }; + var defaultProfileKey = launchSettings.Profiles.FirstOrDefault(entry => + entry.Value.CommandName != null && supportedCommandNames.Contains(entry.Value.CommandName, StringComparer.Ordinal)).Key; if (defaultProfileKey is null) { - logger.LogDebug("Unable to find 'Project' command in the default launch profile."); + logger.LogDebug("Unable to find a supported command name in the default launch profile. Supported types: {SupportedTypes}", + string.Join(", ", supportedCommandNames)); return null; } diff --git a/src/Cli/dotnet/Commands/CliCommandStrings.resx b/src/Cli/dotnet/Commands/CliCommandStrings.resx index 21d0dda1c0ae..4753042360fc 100644 --- a/src/Cli/dotnet/Commands/CliCommandStrings.resx +++ b/src/Cli/dotnet/Commands/CliCommandStrings.resx @@ -744,9 +744,6 @@ See https://aka.ms/dotnet-test/mtp for more information. Date - - (Default) - Uninstalling workload manifest {0} version {1}... @@ -766,10 +763,6 @@ See https://aka.ms/dotnet-test/mtp for more information. Description - - An error was encountered when reading '{0}': {1} - {0} is file path. {1} is exception message. - Show detail result of the query. @@ -812,11 +805,6 @@ See https://aka.ms/dotnet-test/mtp for more information. Download packages needed to install a workload to a folder that can be used for offline installation. - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. @@ -1033,18 +1021,6 @@ Make the profile names distinct. Latest Version Table lable - - A launch profile with the name '{0}' doesn't exist. - - - The launch profile type '{0}' is not supported. - - - A profile with the specified name isn't a valid JSON object. - - - The 'profiles' property of the launch settings document is not a JSON object. - List all projects in a solution file. @@ -2266,9 +2242,6 @@ and the corresponding package Ids for installed tools using the command Tool '{0}' failed to update due to the following: - - A usable launch profile could not be located. - Usage diff --git a/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs b/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs index d258b9cda966..68396f2951b6 100644 --- a/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs +++ b/src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs @@ -111,9 +111,8 @@ public override RunApiOutput Execute() environmentVariables: ReadOnlyDictionary.Empty, msbuildRestoreProperties: ReadOnlyDictionary.Empty); - runCommand.TryGetLaunchProfileSettingsIfNeeded(out var launchSettings); - var targetCommand = (Utils.Command)runCommand.GetTargetCommand(buildCommand.CreateProjectInstance, cachedRunProperties: null); - runCommand.ApplyLaunchSettingsProfileToCommand(targetCommand, launchSettings); + var result = runCommand.ReadLaunchProfileSettings(); + var targetCommand = (Utils.Command)runCommand.GetTargetCommand(result.Profile, buildCommand.CreateProjectInstance, cachedRunProperties: null); return new RunApiOutput.RunCommand { diff --git a/src/Cli/dotnet/Commands/Run/LaunchSettings/ILaunchSettingsProvider.cs b/src/Cli/dotnet/Commands/Run/LaunchSettings/ILaunchSettingsProvider.cs deleted file mode 100644 index 751c3926ef83..000000000000 --- a/src/Cli/dotnet/Commands/Run/LaunchSettings/ILaunchSettingsProvider.cs +++ /dev/null @@ -1,11 +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 System.Text.Json; - -namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; - -internal interface ILaunchSettingsProvider -{ - LaunchSettingsApplyResult TryGetLaunchSettings(string? launchProfileName, JsonElement model); -} diff --git a/src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsApplyResult.cs b/src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsApplyResult.cs deleted file mode 100644 index 6290de6e3c27..000000000000 --- a/src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsApplyResult.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; - -public class LaunchSettingsApplyResult(bool success, string? failureReason, ProjectLaunchSettingsModel? launchSettings = null) -{ - public bool Success { get; } = success; - - public string? FailureReason { get; } = failureReason; - - public ProjectLaunchSettingsModel? LaunchSettings { get; } = launchSettings; -} diff --git a/src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsManager.cs b/src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsManager.cs deleted file mode 100644 index a1776027476d..000000000000 --- a/src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsManager.cs +++ /dev/null @@ -1,145 +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 System.Diagnostics.CodeAnalysis; -using System.Text.Json; -using Microsoft.DotNet.Cli.Utils; - -namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; - -internal class LaunchSettingsManager -{ - private const string ProfilesKey = "profiles"; - private const string CommandNameKey = "commandName"; - private const string DefaultProfileCommandName = "Project"; - private static readonly IReadOnlyDictionary _providers; - - static LaunchSettingsManager() - { - _providers = new Dictionary - { - { ProjectLaunchSettingsProvider.CommandNameValue, new ProjectLaunchSettingsProvider() } - }; - } - - public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSettingsPath, string? profileName = null) - { - var launchSettingsJsonContents = File.ReadAllText(launchSettingsPath); - try - { - var jsonDocumentOptions = new JsonDocumentOptions - { - CommentHandling = JsonCommentHandling.Skip, - AllowTrailingCommas = true, - }; - - using (var document = JsonDocument.Parse(launchSettingsJsonContents, jsonDocumentOptions)) - { - var model = document.RootElement; - - if (model.ValueKind != JsonValueKind.Object || !model.TryGetProperty(ProfilesKey, out var profilesObject) || profilesObject.ValueKind != JsonValueKind.Object) - { - return new LaunchSettingsApplyResult(false, CliCommandStrings.LaunchProfilesCollectionIsNotAJsonObject); - } - - var selectedProfileName = profileName; - JsonElement profileObject; - if (string.IsNullOrEmpty(profileName)) - { - var firstProfileProperty = profilesObject.EnumerateObject().FirstOrDefault(IsDefaultProfileType); - selectedProfileName = firstProfileProperty.Value.ValueKind == JsonValueKind.Object ? firstProfileProperty.Name : null; - profileObject = firstProfileProperty.Value; - } - else // Find a profile match for the given profileName - { - IEnumerable caseInsensitiveProfileMatches = [.. profilesObject - .EnumerateObject() // p.Name shouldn't fail, as profileObject enumerables here are only created from an existing JsonObject - .Where(p => string.Equals(p.Name, profileName, StringComparison.OrdinalIgnoreCase))]; - - if (caseInsensitiveProfileMatches.Count() > 1) - { - throw new GracefulException(CliCommandStrings.DuplicateCaseInsensitiveLaunchProfileNames, - string.Join(",\n", caseInsensitiveProfileMatches.Select(p => $"\t{p.Name}").ToArray())); - } - else if (!caseInsensitiveProfileMatches.Any()) - { - return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.LaunchProfileDoesNotExist, profileName)); - } - else - { - profileObject = profilesObject.GetProperty(caseInsensitiveProfileMatches.First().Name); - } - - if (profileObject.ValueKind != JsonValueKind.Object) - { - return new LaunchSettingsApplyResult(false, CliCommandStrings.LaunchProfileIsNotAJsonObject); - } - } - - if (profileObject.ValueKind == default) - { - foreach (var prop in profilesObject.EnumerateObject()) - { - if (prop.Value.ValueKind == JsonValueKind.Object) - { - if (prop.Value.TryGetProperty(CommandNameKey, out var commandNameElement) && commandNameElement.ValueKind == JsonValueKind.String) - { - if (commandNameElement.GetString() is { } commandNameElementKey && _providers.ContainsKey(commandNameElementKey)) - { - profileObject = prop.Value; - break; - } - } - } - } - } - - if (profileObject.ValueKind == default) - { - return new LaunchSettingsApplyResult(false, CliCommandStrings.UsableLaunchProfileCannotBeLocated); - } - - if (!profileObject.TryGetProperty(CommandNameKey, out var finalCommandNameElement) - || finalCommandNameElement.ValueKind != JsonValueKind.String) - { - return new LaunchSettingsApplyResult(false, CliCommandStrings.UsableLaunchProfileCannotBeLocated); - } - - string? commandName = finalCommandNameElement.GetString(); - if (!TryLocateHandler(commandName, out ILaunchSettingsProvider? provider)) - { - return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.LaunchProfileHandlerCannotBeLocated, commandName)); - } - - return provider.TryGetLaunchSettings(selectedProfileName, profileObject); - } - } - catch (JsonException ex) - { - return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.DeserializationExceptionMessage, launchSettingsPath, ex.Message)); - } - } - - private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)]out ILaunchSettingsProvider? provider) - { - if (commandName == null) - { - provider = null; - return false; - } - - return _providers.TryGetValue(commandName, out provider); - } - - private static bool IsDefaultProfileType(JsonProperty profileProperty) - { - if (profileProperty.Value.ValueKind != JsonValueKind.Object - || !profileProperty.Value.TryGetProperty(CommandNameKey, out var commandNameElement) - || commandNameElement.ValueKind != JsonValueKind.String) - { - return false; - } - - return string.Equals(commandNameElement.GetString(), DefaultProfileCommandName, StringComparison.Ordinal); - } -} diff --git a/src/Cli/dotnet/Commands/Run/LaunchSettings/ProjectLaunchSettingsModel.cs b/src/Cli/dotnet/Commands/Run/LaunchSettings/ProjectLaunchSettingsModel.cs deleted file mode 100644 index 6c2c16ac81fb..000000000000 --- a/src/Cli/dotnet/Commands/Run/LaunchSettings/ProjectLaunchSettingsModel.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; - -public class ProjectLaunchSettingsModel -{ - public string? LaunchProfileName { get; set; } - - public string? CommandLineArgs { get; set; } - - public bool LaunchBrowser { get; set; } - - public string? LaunchUrl { get; set; } - - public string? ApplicationUrl { get; set; } - - public string? DotNetRunMessages { get; set; } - - public Dictionary EnvironmentVariables { get; } = new Dictionary(StringComparer.Ordinal); -} diff --git a/src/Cli/dotnet/Commands/Run/LaunchSettings/ProjectLaunchSettingsProvider.cs b/src/Cli/dotnet/Commands/Run/LaunchSettings/ProjectLaunchSettingsProvider.cs deleted file mode 100644 index 46c294f03b7a..000000000000 --- a/src/Cli/dotnet/Commands/Run/LaunchSettings/ProjectLaunchSettingsProvider.cs +++ /dev/null @@ -1,138 +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 System.Text.Json; - -namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; - -internal class ProjectLaunchSettingsProvider : ILaunchSettingsProvider -{ - public static readonly string CommandNameValue = "Project"; - - public static string CommandName => CommandNameValue; - - public LaunchSettingsApplyResult TryGetLaunchSettings(string? launchProfileName, JsonElement model) - { - var config = new ProjectLaunchSettingsModel - { - LaunchProfileName = launchProfileName - }; - - foreach (var property in model.EnumerateObject()) - { - if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.CommandLineArgs), StringComparison.OrdinalIgnoreCase)) - { - if (!TryGetStringValue(property.Value, out var commandLineArgsValue)) - { - return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.CouldNotConvertToString, property.Name)); - } - - config.CommandLineArgs = commandLineArgsValue; - } - else if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.LaunchBrowser), StringComparison.OrdinalIgnoreCase)) - { - if (!TryGetBooleanValue(property.Value, out var launchBrowserValue)) - { - return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.CouldNotConvertToBoolean, property.Name)); - } - - config.LaunchBrowser = launchBrowserValue; - } - else if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.LaunchUrl), StringComparison.OrdinalIgnoreCase)) - { - if (!TryGetStringValue(property.Value, out var launchUrlValue)) - { - return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.CouldNotConvertToString, property.Name)); - } - - config.LaunchUrl = launchUrlValue; - } - else if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.ApplicationUrl), StringComparison.OrdinalIgnoreCase)) - { - if (!TryGetStringValue(property.Value, out var applicationUrlValue)) - { - return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.CouldNotConvertToString, property.Name)); - } - - config.ApplicationUrl = applicationUrlValue; - } - else if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.DotNetRunMessages), StringComparison.OrdinalIgnoreCase)) - { - if (!TryGetStringValue(property.Value, out var dotNetRunMessages)) - { - return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.CouldNotConvertToString, property.Name)); - } - - config.DotNetRunMessages = dotNetRunMessages; - } - else if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.EnvironmentVariables), StringComparison.OrdinalIgnoreCase)) - { - if (property.Value.ValueKind != JsonValueKind.Object) - { - return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.ValueMustBeAnObject, property.Name)); - } - - foreach (var environmentVariable in property.Value.EnumerateObject()) - { - if (TryGetStringValue(environmentVariable.Value, out var environmentVariableValue)) - { - config.EnvironmentVariables[environmentVariable.Name] = environmentVariableValue!; - } - } - } - } - - return new LaunchSettingsApplyResult(true, null, config); - } - - private static bool TryGetBooleanValue(JsonElement element, out bool value) - { - switch (element.ValueKind) - { - case JsonValueKind.True: - value = true; - return true; - case JsonValueKind.False: - value = false; - return true; - case JsonValueKind.Number: - if (element.TryGetDouble(out var doubleValue)) - { - value = doubleValue != 0; - return true; - } - value = false; - return false; - case JsonValueKind.String: - return bool.TryParse(element.GetString(), out value); - default: - value = false; - return false; - } - } - - private static bool TryGetStringValue(JsonElement element, out string? value) - { - switch (element.ValueKind) - { - case JsonValueKind.True: - value = bool.TrueString; - return true; - case JsonValueKind.False: - value = bool.FalseString; - return true; - case JsonValueKind.Null: - value = string.Empty; - return true; - case JsonValueKind.Number: - value = element.GetRawText(); - return false; - case JsonValueKind.String: - value = element.GetString(); - return true; - default: - value = null; - return false; - } - } -} diff --git a/src/Cli/dotnet/Commands/Run/RunCommand.cs b/src/Cli/dotnet/Commands/Run/RunCommand.cs index 974e68c317ad..9351e2e7538c 100644 --- a/src/Cli/dotnet/Commands/Run/RunCommand.cs +++ b/src/Cli/dotnet/Commands/Run/RunCommand.cs @@ -11,11 +11,9 @@ using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Logging; using Microsoft.DotNet.Cli.CommandFactory; -using Microsoft.DotNet.Cli.Commands.Restore; -using Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; @@ -40,6 +38,9 @@ public class RunCommand /// public string? EntryPointFileFullPath { get; } + public string ProjectOrEntryPointPath => + ProjectFileFullPath ?? EntryPointFileFullPath!; + /// /// Whether dotnet run - is being executed. /// In that case, points to a temporary file @@ -124,9 +125,9 @@ public RunCommand( public int Execute() { - if (!TryGetLaunchProfileSettingsIfNeeded(out var launchSettings)) + if (NoBuild && NoCache) { - return 1; + throw new GracefulException(CliCommandStrings.CannotCombineOptions, RunCommandDefinition.NoCacheOption.Name, RunCommandDefinition.NoBuildOption.Name); } // Pre-run evaluation: Handle target framework selection for multi-targeted projects @@ -141,63 +142,56 @@ public int Execute() return 1; } + var launchProfileParseResult = ReadLaunchProfileSettings(); + if (launchProfileParseResult.FailureReason != null) + { + Reporter.Error.WriteLine(string.Format(CliCommandStrings.RunCommandExceptionCouldNotApplyLaunchSettings, LaunchProfileParser.GetLaunchProfileDisplayName(LaunchProfile), launchProfileParseResult.FailureReason).Bold().Red()); + } + Func? projectFactory = null; RunProperties? cachedRunProperties = null; - VirtualProjectBuildingCommand? virtualCommand = null; + VirtualProjectBuildingCommand? projectBuilder = null; if (ShouldBuild) { - if (string.Equals("true", launchSettings?.DotNetRunMessages, StringComparison.OrdinalIgnoreCase)) + if (launchProfileParseResult.Profile?.DotNetRunMessages == true) { Reporter.Output.WriteLine(CliCommandStrings.RunCommandBuilding); } - EnsureProjectIsBuilt(out projectFactory, out cachedRunProperties, out virtualCommand); + EnsureProjectIsBuilt(out projectFactory, out cachedRunProperties, out projectBuilder); } - else + else if (EntryPointFileFullPath is not null && launchProfileParseResult.Profile is not ExecutableLaunchProfile) { - if (NoCache) - { - throw new GracefulException(CliCommandStrings.CannotCombineOptions, RunCommandDefinition.NoCacheOption.Name, RunCommandDefinition.NoBuildOption.Name); - } + // The entry-point is not used to run the application if the launch profile specifies Executable command. - if (EntryPointFileFullPath is not null) - { - Debug.Assert(!ReadCodeFromStdin); - virtualCommand = CreateVirtualCommand(); - virtualCommand.MarkArtifactsFolderUsed(); + Debug.Assert(!ReadCodeFromStdin); + projectBuilder = CreateProjectBuilder(); + projectBuilder.MarkArtifactsFolderUsed(); - var cacheEntry = virtualCommand.GetPreviousCacheEntry(); - projectFactory = CanUseRunPropertiesForCscBuiltProgram(BuildLevel.None, cacheEntry) ? null : virtualCommand.CreateProjectInstance; - cachedRunProperties = cacheEntry?.Run; - } + var cacheEntry = projectBuilder.GetPreviousCacheEntry(); + projectFactory = CanUseRunPropertiesForCscBuiltProgram(BuildLevel.None, cacheEntry) ? null : projectBuilder.CreateProjectInstance; + cachedRunProperties = cacheEntry?.Run; } - try - { - ICommand targetCommand = GetTargetCommand(projectFactory, cachedRunProperties); - ApplyLaunchSettingsProfileToCommand(targetCommand, launchSettings); + var targetCommand = GetTargetCommand(launchProfileParseResult.Profile, projectFactory, cachedRunProperties); - // Env variables specified on command line override those specified in launch profile: - foreach (var (name, value) in EnvironmentVariables) - { - targetCommand.EnvironmentVariable(name, value); - } + // Send telemetry about the run operation + SendRunTelemetry(launchProfileParseResult.Profile, projectBuilder); - // Send telemetry about the run operation - SendRunTelemetry(launchSettings, virtualCommand); + // Ignore Ctrl-C for the remainder of the command's execution + Console.CancelKeyPress += (sender, e) => { e.Cancel = true; }; - // Ignore Ctrl-C for the remainder of the command's execution - Console.CancelKeyPress += (sender, e) => { e.Cancel = true; }; + return targetCommand.Execute().ExitCode; + } - return targetCommand.Execute().ExitCode; - } - catch (InvalidProjectFileException e) + internal ICommand GetTargetCommand(LaunchProfile? launchSettings, Func? projectFactory, RunProperties? cachedRunProperties) + => launchSettings switch { - throw new GracefulException( - string.Format(CliCommandStrings.RunCommandSpecifiedFileIsNotAValidProject, ProjectFileFullPath), - e); - } - } + null => GetTargetCommandForProject(launchSettings: null, projectFactory, cachedRunProperties), + ProjectLaunchProfile projectSettings => GetTargetCommandForProject(projectSettings, projectFactory, cachedRunProperties), + ExecutableLaunchProfile executableSettings => GetTargetCommandForExecutable(executableSettings), + _ => throw new InvalidOperationException() + }; /// /// Checks if target framework selection is needed for multi-targeted projects. @@ -293,51 +287,68 @@ private void ApplySelectedFramework(string? selectedFramework) } } - internal void ApplyLaunchSettingsProfileToCommand(ICommand targetCommand, ProjectLaunchSettingsModel? launchSettings) + private ICommand GetTargetCommandForExecutable(ExecutableLaunchProfile launchSettings) { - if (launchSettings == null) - { - return; - } + var workingDirectory = launchSettings.WorkingDirectory ?? Path.GetDirectoryName(ProjectOrEntryPointPath); + + var commandArgs = (NoLaunchProfileArguments || ApplicationArgs is not []) + ? ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(ApplicationArgs) + : launchSettings.CommandLineArgs ?? ""; + + var commandSpec = new CommandSpec(launchSettings.ExecutablePath, commandArgs); + var command = CommandFactoryUsingResolver.Create(commandSpec) + .WorkingDirectory(workingDirectory); + + SetEnvironmentVariables(command, launchSettings); + + return command; + } - if (!string.IsNullOrEmpty(launchSettings.ApplicationUrl)) + private void SetEnvironmentVariables(ICommand command, LaunchProfile? launchSettings) + { + // Handle Project-specific settings + if (launchSettings is ProjectLaunchProfile projectSettings) { - targetCommand.EnvironmentVariable("ASPNETCORE_URLS", launchSettings.ApplicationUrl); + if (!string.IsNullOrEmpty(projectSettings.ApplicationUrl)) + { + command.EnvironmentVariable("ASPNETCORE_URLS", projectSettings.ApplicationUrl); + } } - targetCommand.EnvironmentVariable("DOTNET_LAUNCH_PROFILE", launchSettings.LaunchProfileName); - - foreach (var entry in launchSettings.EnvironmentVariables) + if (launchSettings != null) { - string value = Environment.ExpandEnvironmentVariables(entry.Value); - //NOTE: MSBuild variables are not expanded like they are in VS - targetCommand.EnvironmentVariable(entry.Key, value); + command.EnvironmentVariable("DOTNET_LAUNCH_PROFILE", launchSettings.LaunchProfileName); + + foreach (var entry in launchSettings.EnvironmentVariables) + { + command.EnvironmentVariable(entry.Key, entry.Value); + } } - if (!NoLaunchProfileArguments && string.IsNullOrEmpty(targetCommand.CommandArgs) && launchSettings.CommandLineArgs != null) + // Env variables specified on command line override those specified in launch profile: + foreach (var (name, value) in EnvironmentVariables) { - targetCommand.SetCommandArgs(launchSettings.CommandLineArgs); + command.EnvironmentVariable(name, value); } } - internal bool TryGetLaunchProfileSettingsIfNeeded(out ProjectLaunchSettingsModel? launchSettingsModel) + internal LaunchProfileParseResult ReadLaunchProfileSettings() { - launchSettingsModel = default; if (NoLaunchProfile) { - return true; + return LaunchProfileParseResult.Success(model: null); } var launchSettingsPath = ReadCodeFromStdin ? null - : LaunchSettingsLocator.TryFindLaunchSettings( + : LaunchSettings.TryFindLaunchSettingsFile( projectOrEntryPointFilePath: ProjectFileFullPath ?? EntryPointFileFullPath!, launchProfile: LaunchProfile, static (message, isError) => (isError ? Reporter.Error : Reporter.Output).WriteLine(message)); if (launchSettingsPath is null) { - return true; + return LaunchProfileParseResult.Success(model: null); } if (!RunCommandVerbosity.IsQuiet()) @@ -345,39 +356,18 @@ internal bool TryGetLaunchProfileSettingsIfNeeded(out ProjectLaunchSettingsModel Reporter.Output.WriteLine(string.Format(CliCommandStrings.UsingLaunchSettingsFromMessage, launchSettingsPath)); } - string profileName = string.IsNullOrEmpty(LaunchProfile) ? CliCommandStrings.DefaultLaunchProfileDisplayName : LaunchProfile; - - try - { - var applyResult = LaunchSettingsManager.TryApplyLaunchSettings(launchSettingsPath, LaunchProfile); - if (!applyResult.Success) - { - Reporter.Error.WriteLine(string.Format(CliCommandStrings.RunCommandExceptionCouldNotApplyLaunchSettings, profileName, applyResult.FailureReason).Bold().Red()); - } - else - { - launchSettingsModel = applyResult.LaunchSettings; - } - } - catch (IOException ex) - { - Reporter.Error.WriteLine(string.Format(CliCommandStrings.RunCommandExceptionCouldNotApplyLaunchSettings, profileName).Bold().Red()); - Reporter.Error.WriteLine(ex.Message.Bold().Red()); - return false; - } - - return true; + return LaunchSettings.ReadProfileSettingsFromFile(launchSettingsPath, LaunchProfile); } - private void EnsureProjectIsBuilt(out Func? projectFactory, out RunProperties? cachedRunProperties, out VirtualProjectBuildingCommand? virtualCommand) + private void EnsureProjectIsBuilt(out Func? projectFactory, out RunProperties? cachedRunProperties, out VirtualProjectBuildingCommand? projectBuilder) { int buildResult; if (EntryPointFileFullPath is not null) { - virtualCommand = CreateVirtualCommand(); - buildResult = virtualCommand.Execute(); - projectFactory = CanUseRunPropertiesForCscBuiltProgram(virtualCommand.LastBuild.Level, virtualCommand.LastBuild.Cache?.PreviousEntry) ? null : virtualCommand.CreateProjectInstance; - cachedRunProperties = virtualCommand.LastBuild.Cache?.CurrentEntry.Run; + projectBuilder = CreateProjectBuilder(); + buildResult = projectBuilder.Execute(); + projectFactory = CanUseRunPropertiesForCscBuiltProgram(projectBuilder.LastBuild.Level, projectBuilder.LastBuild.Cache?.PreviousEntry) ? null : projectBuilder.CreateProjectInstance; + cachedRunProperties = projectBuilder.LastBuild.Cache?.CurrentEntry.Run; } else { @@ -385,7 +375,7 @@ private void EnsureProjectIsBuilt(out Func? projectFactory = null; cachedRunProperties = null; - virtualCommand = null; + projectBuilder = null; buildResult = new RestoringCommand( MSBuildArgs.CloneWithExplicitArgs([ProjectFileFullPath, .. MSBuildArgs.OtherMSBuildArgs]), NoRestore, @@ -406,7 +396,7 @@ private static bool CanUseRunPropertiesForCscBuiltProgram(BuildLevel level, RunF (level == BuildLevel.None && previousCache?.BuildLevel == BuildLevel.Csc); } - private VirtualProjectBuildingCommand CreateVirtualCommand() + private VirtualProjectBuildingCommand CreateProjectBuilder() { Debug.Assert(EntryPointFileFullPath != null); @@ -428,7 +418,6 @@ private VirtualProjectBuildingCommand CreateVirtualCommand() /// that will be used to build the project. `run` wants to operate silently if possible, /// so we disable as much MSBuild output as possible, unless we're forced to be interactive. /// - /// private MSBuildArgs SetupSilentBuildArgs(MSBuildArgs msbuildArgs) { msbuildArgs = msbuildArgs.CloneWithNoLogo(true); @@ -449,50 +438,72 @@ private MSBuildArgs SetupSilentBuildArgs(MSBuildArgs msbuildArgs) } } - internal ICommand GetTargetCommand(Func? projectFactory, RunProperties? cachedRunProperties) + private ICommand GetTargetCommandForProject(ProjectLaunchProfile? launchSettings, Func? projectFactory, RunProperties? cachedRunProperties) { + ICommand command; if (cachedRunProperties != null) { // We can skip project evaluation if we already evaluated the project during virtual build // or we have cached run properties in previous run (and this is a --no-build or skip-msbuild run). Reporter.Verbose.WriteLine("Getting target command: from cache."); - return CreateCommandFromRunProperties(cachedRunProperties.WithApplicationArguments(ApplicationArgs)); + command = CreateCommandFromRunProperties(cachedRunProperties.WithApplicationArguments(ApplicationArgs)); } - - if (projectFactory is null && ProjectFileFullPath is null) + else if (projectFactory is null && ProjectFileFullPath is null) { // If we are running a file-based app and projectFactory is null, it means csc was used instead of full msbuild. // So we can skip project evaluation to continue the optimized path. Debug.Assert(EntryPointFileFullPath is not null); Reporter.Verbose.WriteLine("Getting target command: for csc-built program."); - return CreateCommandForCscBuiltProgram(EntryPointFileFullPath, ApplicationArgs); + command = CreateCommandForCscBuiltProgram(EntryPointFileFullPath, ApplicationArgs); + } + else + { + Reporter.Verbose.WriteLine("Getting target command: evaluating project."); + FacadeLogger? logger = LoggerUtility.DetermineBinlogger([.. MSBuildArgs.OtherMSBuildArgs], "dotnet-run"); + + ProjectInstance project; + try + { + project = EvaluateProject(ProjectFileFullPath, projectFactory, MSBuildArgs, logger); + ValidatePreconditions(project); + InvokeRunArgumentsTarget(project, NoBuild, logger, MSBuildArgs); + } + finally + { + logger?.ReallyShutdown(); + } + + var runProperties = RunProperties.FromProject(project).WithApplicationArguments(ApplicationArgs); + command = CreateCommandFromRunProperties(runProperties); + } + + SetEnvironmentVariables(command, launchSettings); + + if (!NoLaunchProfileArguments && string.IsNullOrEmpty(command.CommandArgs) && launchSettings?.CommandLineArgs != null) + { + command.SetCommandArgs(launchSettings.CommandLineArgs); } - Reporter.Verbose.WriteLine("Getting target command: evaluating project."); - FacadeLogger? logger = LoggerUtility.DetermineBinlogger([.. MSBuildArgs.OtherMSBuildArgs], "dotnet-run"); - var project = EvaluateProject(ProjectFileFullPath, projectFactory, MSBuildArgs, logger); - ValidatePreconditions(project); - InvokeRunArgumentsTarget(project, NoBuild, logger, MSBuildArgs); - logger?.ReallyShutdown(); - var runProperties = RunProperties.FromProject(project).WithApplicationArguments(ApplicationArgs); - var command = CreateCommandFromRunProperties(runProperties); return command; static ProjectInstance EvaluateProject(string? projectFilePath, Func? projectFactory, MSBuildArgs msbuildArgs, ILogger? binaryLogger) { - Debug.Assert(projectFilePath is not null || projectFactory is not null); - var globalProperties = CommonRunHelpers.GetGlobalPropertiesFromArgs(msbuildArgs); - var collection = new ProjectCollection(globalProperties: globalProperties, loggers: binaryLogger is null ? null : [binaryLogger], toolsetDefinitionLocations: ToolsetDefinitionLocations.Default); - if (projectFilePath is not null) + if (projectFactory != null) { - return collection.LoadProject(projectFilePath).CreateProjectInstance(); + return projectFactory(collection); } - Debug.Assert(projectFactory is not null); - return projectFactory(collection); + try + { + return collection.LoadProject(projectFilePath).CreateProjectInstance(); + } + catch (InvalidProjectFileException e) + { + throw new GracefulException(string.Format(CliCommandStrings.RunCommandSpecifiedFileIsNotAValidProject, projectFilePath), e); + } } static void ValidatePreconditions(ProjectInstance project) @@ -833,14 +844,14 @@ public static ParseResult ModifyParseResultForShorthandProjectOption(ParseResult /// Sends telemetry about the run operation. /// private void SendRunTelemetry( - ProjectLaunchSettingsModel? launchSettings, - VirtualProjectBuildingCommand? virtualCommand) + LaunchProfile? launchSettings, + VirtualProjectBuildingCommand? projectBuilder) { try { - if (virtualCommand != null) + if (projectBuilder != null) { - SendFileBasedTelemetry(launchSettings, virtualCommand); + SendFileBasedTelemetry(launchSettings, projectBuilder); } else { @@ -861,13 +872,13 @@ private void SendRunTelemetry( /// Builds and sends telemetry data for file-based app runs. /// private void SendFileBasedTelemetry( - ProjectLaunchSettingsModel? launchSettings, - VirtualProjectBuildingCommand virtualCommand) + LaunchProfile? launchSettings, + VirtualProjectBuildingCommand projectBuilder) { Debug.Assert(EntryPointFileFullPath != null); var projectIdentifier = RunTelemetry.GetFileBasedIdentifier(EntryPointFileFullPath, Sha256Hasher.Hash); - var directives = virtualCommand.Directives; + var directives = projectBuilder.Directives; var sdkCount = RunTelemetry.CountSdks(directives); var packageReferenceCount = RunTelemetry.CountPackageReferences(directives); var projectReferenceCount = RunTelemetry.CountProjectReferences(directives); @@ -883,14 +894,14 @@ private void SendFileBasedTelemetry( packageReferenceCount: packageReferenceCount, projectReferenceCount: projectReferenceCount, additionalPropertiesCount: additionalPropertiesCount, - usedMSBuild: virtualCommand.LastBuild.Level is BuildLevel.All, - usedRoslynCompiler: virtualCommand.LastBuild.Level is BuildLevel.Csc); + usedMSBuild: projectBuilder.LastBuild.Level is BuildLevel.All, + usedRoslynCompiler: projectBuilder.LastBuild.Level is BuildLevel.Csc); } /// /// Builds and sends telemetry data for project-based app runs. /// - private void SendProjectBasedTelemetry(ProjectLaunchSettingsModel? launchSettings) + private void SendProjectBasedTelemetry(LaunchProfile? launchSettings) { Debug.Assert(ProjectFileFullPath != null); var projectIdentifier = RunTelemetry.GetProjectBasedIdentifier(ProjectFileFullPath, GetRepositoryRoot(), Sha256Hasher.Hash); diff --git a/src/Cli/dotnet/Commands/Run/RunTelemetry.cs b/src/Cli/dotnet/Commands/Run/RunTelemetry.cs index 3a42cd94c06a..fd45de6a6a63 100644 --- a/src/Cli/dotnet/Commands/Run/RunTelemetry.cs +++ b/src/Cli/dotnet/Commands/Run/RunTelemetry.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; -using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; -using Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Commands.Run; @@ -36,7 +35,7 @@ public static void TrackRunEvent( string projectIdentifier, string? launchProfile = null, bool noLaunchProfile = false, - ProjectLaunchSettingsModel? launchSettings = null, + LaunchProfile? launchSettings = null, int sdkCount = 1, int packageReferenceCount = 0, int projectReferenceCount = 0, diff --git a/src/Cli/dotnet/Commands/Test/MTP/Models.cs b/src/Cli/dotnet/Commands/Test/MTP/Models.cs index 8dae3d7cffce..50aa0520428b 100644 --- a/src/Cli/dotnet/Commands/Test/MTP/Models.cs +++ b/src/Cli/dotnet/Commands/Test/MTP/Models.cs @@ -4,7 +4,7 @@ using System.Collections; using System.Diagnostics; using Microsoft.DotNet.Cli.Commands.Run; -using Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Commands.Test; @@ -111,4 +111,4 @@ public bool MoveNext() } } -internal sealed record TestModule(RunProperties RunProperties, string? ProjectFullPath, string? TargetFramework, bool IsTestingPlatformApplication, ProjectLaunchSettingsModel? LaunchSettings, string TargetPath, string? DotnetRootArchVariableName); +internal sealed record TestModule(RunProperties RunProperties, string? ProjectFullPath, string? TargetFramework, bool IsTestingPlatformApplication, LaunchProfile? LaunchSettings, string TargetPath, string? DotnetRootArchVariableName); diff --git a/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs b/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs index 9179762e0914..d39b57871027 100644 --- a/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs +++ b/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs @@ -7,7 +7,6 @@ using Microsoft.Build.Evaluation.Context; using Microsoft.Build.Execution; using Microsoft.DotNet.Cli.Commands.Run; -using Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; using Microsoft.DotNet.ProjectTools; @@ -372,17 +371,17 @@ static RunProperties GetRunProperties(ProjectInstance project) } } - private static ProjectLaunchSettingsModel? TryGetLaunchProfileSettings(string projectDirectory, string projectNameWithoutExtension, string appDesignerFolder, BuildOptions buildOptions, string? profileName) + private static LaunchProfile? TryGetLaunchProfileSettings(string projectDirectory, string projectNameWithoutExtension, string appDesignerFolder, BuildOptions buildOptions, string? profileName) { if (buildOptions.NoLaunchProfile) { return null; } - var launchSettingsPath = LaunchSettingsLocator.GetPropertiesLaunchSettingsPath(projectDirectory, appDesignerFolder); + var launchSettingsPath = LaunchSettings.GetPropertiesLaunchSettingsPath(projectDirectory, appDesignerFolder); bool hasLaunchSettings = File.Exists(launchSettingsPath); - var runJsonPath = LaunchSettingsLocator.GetFlatLaunchSettingsPath(projectDirectory, projectNameWithoutExtension); + var runJsonPath = LaunchSettings.GetFlatLaunchSettingsPath(projectDirectory, projectNameWithoutExtension); bool hasRunJson = File.Exists(runJsonPath); if (hasLaunchSettings) @@ -407,13 +406,13 @@ static RunProperties GetRunProperties(ProjectInstance project) Reporter.Output.WriteLine(string.Format(CliCommandStrings.UsingLaunchSettingsFromMessage, launchSettingsPath)); } - var result = LaunchSettingsManager.TryApplyLaunchSettings(launchSettingsPath, profileName); - if (!result.Success) + var result = LaunchSettings.ReadProfileSettingsFromFile(launchSettingsPath, profileName); + if (!result.Successful) { Reporter.Error.WriteLine(string.Format(CliCommandStrings.RunCommandExceptionCouldNotApplyLaunchSettings, profileName, result.FailureReason).Bold().Red()); return null; } - return result.LaunchSettings; + return result.Profile; } } diff --git a/src/Cli/dotnet/Commands/Test/MTP/TestApplication.cs b/src/Cli/dotnet/Commands/Test/MTP/TestApplication.cs index 9708c9246017..be7a7371c707 100644 --- a/src/Cli/dotnet/Commands/Test/MTP/TestApplication.cs +++ b/src/Cli/dotnet/Commands/Test/MTP/TestApplication.cs @@ -10,6 +10,7 @@ using Microsoft.DotNet.Cli.Commands.Test.IPC.Serializers; using Microsoft.DotNet.Cli.Commands.Test.Terminal; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Commands.Test; @@ -103,12 +104,11 @@ private ProcessStartInfo CreateProcessStartInfo() processStartInfo.WorkingDirectory = Module.RunProperties.WorkingDirectory; } - if (Module.LaunchSettings is not null) + if (Module.LaunchSettings is ProjectLaunchProfile) { foreach (var entry in Module.LaunchSettings.EnvironmentVariables) { - string value = Environment.ExpandEnvironmentVariables(entry.Value); - processStartInfo.Environment[entry.Key] = value; + processStartInfo.Environment[entry.Key] = entry.Value; } // Env variables specified on command line override those specified in launch profile: diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf index 0270eee341c1..293774c36807 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf @@ -1051,11 +1051,6 @@ Další informace najdete na https://aka.ms/dotnet-test/mtp. Datum - - (Default) - (výchozí) - - Uninstalling workload manifest {0} version {1}... Probíhá odinstalace manifestu úlohy {0} verze {1}... @@ -1086,11 +1081,6 @@ Další informace najdete na https://aka.ms/dotnet-test/mtp. Popis - - An error was encountered when reading '{0}': {1} - Při čtení {0} došlo k chybě: {1} - {0} is file path. {1} is exception message. - Show detail result of the query. Zobrazit podrobné výsledky dotazu @@ -1192,15 +1182,6 @@ Další informace najdete na https://aka.ms/dotnet-test/mtp. Soubory ke stažení Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - Existuje několik spouštěcích profilů s názvy rozlišujícími malá a velká písmena, což není povoleno: -{0} -Nastavte odlišné názvy profilů. - - duration: doba trvání: @@ -1591,26 +1572,6 @@ Nastavte odlišné názvy profilů. Nejnovější verze Table lable - - A launch profile with the name '{0}' doesn't exist. - Profil spuštění s názvem {0} neexistuje. - - - - The launch profile type '{0}' is not supported. - Typ profilu spuštění {0} se nepodporuje. - - - - A profile with the specified name isn't a valid JSON object. - Profil se zadaným názvem není platný objekt JSON. - - - - The 'profiles' property of the launch settings document is not a JSON object. - Vlastnost profiles v dokumentu nastavení spuštění není objektem JSON. - - List all projects in a solution file. Vypíše seznam všech projektů v souboru řešení. @@ -3652,11 +3613,6 @@ příkazu „dotnet tool list“. Režim instalace úlohy se úspěšně aktualizoval na {0}. - - A usable launch profile could not be located. - Nenašel se použitelný profil spuštění. - - Usage Použití @@ -4138,4 +4094,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 a7b5754496b3..bf4889f2658d 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf @@ -1051,11 +1051,6 @@ Weitere Informationen finden Sie unter https://aka.ms/dotnet-test/mtp. Datum - - (Default) - (Standard) - - Uninstalling workload manifest {0} version {1}... Das Workloadmanifest {0} Version {1} wird deinstalliert... @@ -1086,11 +1081,6 @@ Weitere Informationen finden Sie unter https://aka.ms/dotnet-test/mtp. Beschreibung - - An error was encountered when reading '{0}': {1} - Beim Lesen von „{0}“ ist ein Fehler aufgetreten: {1} - {0} is file path. {1} is exception message. - Show detail result of the query. Hiermit wird das Ergebnis der Abfrage im Detail angezeigt. @@ -1192,15 +1182,6 @@ Weitere Informationen finden Sie unter https://aka.ms/dotnet-test/mtp. Downloads Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - Es gibt mehrere Startprofile mit Namen, bei denen die Groß-/Kleinschreibung beachtet wird, was nicht zulässig ist: -{0} -Erstellen Sie eindeutige Profilnamen. - - duration: Dauer: @@ -1591,26 +1572,6 @@ Erstellen Sie eindeutige Profilnamen. Aktuelle Version Table lable - - A launch profile with the name '{0}' doesn't exist. - Es ist kein Startprofil mit dem Namen "{0}" vorhanden. - - - - The launch profile type '{0}' is not supported. - Der Startprofiltyp "{0}" wird nicht unterstützt. - - - - A profile with the specified name isn't a valid JSON object. - Ein Profil mit dem angegebenen Namen ist kein gültiges JSON-Objekt. - - - - The 'profiles' property of the launch settings document is not a JSON object. - Die Eigenschaft "Profile" des Starteigenschaftendokuments ist kein JSON-Objekt. - - List all projects in a solution file. Listet alle Projekte in einer Projektmappendatei auf. @@ -3652,11 +3613,6 @@ und die zugehörigen Paket-IDs für installierte Tools über den Befehl Der Installationsmodus der Workload wurde erfolgreich aktualisiert, um {0} zu verwenden. - - A usable launch profile could not be located. - Es wurde kein verwendbares Startprofil gefunden. - - Usage Syntax @@ -4138,4 +4094,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 6181b0041e9b..529999c31c77 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf @@ -1051,11 +1051,6 @@ Consulte https://aka.ms/dotnet-test/mtp para obtener más información. Fecha - - (Default) - (Predeterminada) - - Uninstalling workload manifest {0} version {1}... Desinstalando el manifiesto de carga de trabajo {0} versión {1}... @@ -1086,11 +1081,6 @@ Consulte https://aka.ms/dotnet-test/mtp para obtener más información. Descripción - - An error was encountered when reading '{0}': {1} - Error al leer '{0}': {1} - {0} is file path. {1} is exception message. - Show detail result of the query. Muestra el resultado detallado de la consulta. @@ -1192,15 +1182,6 @@ Consulte https://aka.ms/dotnet-test/mtp para obtener más información. Descargas Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - Hay varios perfiles de inicio con nombres que distinguen mayúsculas de minúsculas, lo que no está permitido: -{0} -Defina nombres de perfiles distintos. - - duration: duración: @@ -1591,26 +1572,6 @@ Defina nombres de perfiles distintos. Última versión Table lable - - A launch profile with the name '{0}' doesn't exist. - No existe ningún perfil de inicio con el nombre "{0}". - - - - The launch profile type '{0}' is not supported. - No se admite el tipo de perfil de inicio "{0}". - - - - A profile with the specified name isn't a valid JSON object. - Un perfil con el nombre especificado no es un objeto JSON válido. - - - - The 'profiles' property of the launch settings document is not a JSON object. - La propiedad "profiles" del documento de configuración de inicio no es un objeto JSON. - - List all projects in a solution file. Enumera todos los proyectos de la solución. @@ -3652,11 +3613,6 @@ y los identificadores de los paquetes correspondientes a las herramientas instal El modo de instalación de la carga de trabajo se actualizó correctamente para usar {0}. - - A usable launch profile could not be located. - No se ha podido encontrar un perfil de inicio que se pueda usar. - - Usage Uso @@ -4138,4 +4094,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 ace7519eeb54..d70736f22461 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf @@ -1051,11 +1051,6 @@ Pour découvrir plus d’informations, consultez https://aka.ms/dotnet-test/mtp. Date - - (Default) - (Par défaut) - - Uninstalling workload manifest {0} version {1}... Désinstallation du manifeste de charge de travail {0} version {1}... @@ -1086,11 +1081,6 @@ Pour découvrir plus d’informations, consultez https://aka.ms/dotnet-test/mtp. Description - - An error was encountered when reading '{0}': {1} - Une erreur est survenue pendant la lecture de « {0} » : {1} - {0} is file path. {1} is exception message. - Show detail result of the query. Afficher le résultat détaillé de la requête. @@ -1192,15 +1182,6 @@ Pour découvrir plus d’informations, consultez https://aka.ms/dotnet-test/mtp. Téléchargements Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - Il existe plusieurs profils de lancement avec des noms sensibles à la casse, ce qui n'est pas autorisé : -{0} -Faites en sorte que les noms de profil soient distincts. - - duration: durée : @@ -1591,26 +1572,6 @@ Faites en sorte que les noms de profil soient distincts. Dernière version Table lable - - A launch profile with the name '{0}' doesn't exist. - Un profil de lancement avec le nom '{0}' n'existe pas. - - - - The launch profile type '{0}' is not supported. - Le type de profil de lancement '{0}' n'est pas pris en charge. - - - - A profile with the specified name isn't a valid JSON object. - Un profil avec le nom spécifié n'est pas un objet JSON valide. - - - - The 'profiles' property of the launch settings document is not a JSON object. - La propriété 'profiles' du document de paramètres de lancement n'est pas un objet JSON. - - List all projects in a solution file. Listez tous les projets d'un fichier solution. @@ -3652,11 +3613,6 @@ et les ID de package correspondants aux outils installés, utilisez la commande Le mode d’installation de la charge de travail a été mis à jour pour utiliser {0}. - - A usable launch profile could not be located. - Impossible de localiser un profil de lancement utilisable. - - Usage Utilisation @@ -4138,4 +4094,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 4b2fa0bd75c2..3b51ca4b1030 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf @@ -1051,11 +1051,6 @@ Per altre informazioni, vedere https://aka.ms/dotnet-test/mtp. Data - - (Default) - (Predefinita) - - Uninstalling workload manifest {0} version {1}... Disinstallazione del manifesto del carico di lavoro {0} versione {1} in corso... @@ -1086,11 +1081,6 @@ Per altre informazioni, vedere https://aka.ms/dotnet-test/mtp. Descrizione - - An error was encountered when reading '{0}': {1} - Si è verificato un errore durante la lettura di "{0}": {1} - {0} is file path. {1} is exception message. - Show detail result of the query. Mostra il risultato in dettaglio della query. @@ -1192,15 +1182,6 @@ Per altre informazioni, vedere https://aka.ms/dotnet-test/mtp. Download Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - Esistono diversi profili di avvio con nomi con distinzione tra maiuscole e minuscole, che non sono consentiti: -{0} -Rendi distinti i nomi profilo. - - duration: durata: @@ -1591,26 +1572,6 @@ Rendi distinti i nomi profilo. Ultima versione Table lable - - A launch profile with the name '{0}' doesn't exist. - Non esiste un profilo di avvio con il nome '{0}'. - - - - The launch profile type '{0}' is not supported. - Il tipo '{0}' del profilo di avvio non è supportato. - - - - A profile with the specified name isn't a valid JSON object. - Un profilo con il nome specificato non è un oggetto JSON valido. - - - - The 'profiles' property of the launch settings document is not a JSON object. - La proprietà 'profiles' del documento delle impostazioni di avvio non è un oggetto JSON. - - List all projects in a solution file. Elenca tutti i progetti presenti in un file di soluzione. @@ -3652,11 +3613,6 @@ e gli ID pacchetto corrispondenti per gli strumenti installati usando il comando La modalità di installazione del carico di lavoro è stata aggiornata per l'uso di {0}. - - A usable launch profile could not be located. - Non è stato trovato alcun profilo di avvio utilizzabile. - - Usage Sintassi @@ -4138,4 +4094,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 770f97d13c58..c836504745c1 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf @@ -1051,11 +1051,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 日付 - - (Default) - (既定) - - Uninstalling workload manifest {0} version {1}... ワークロード マニフェスト {0} のバージョン {1} をアンインストールしています... @@ -1086,11 +1081,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 説明 - - An error was encountered when reading '{0}': {1} - '{0}' の読み取り中にエラーが発生しました: {1} - {0} is file path. {1} is exception message. - Show detail result of the query. クエリの詳細な結果を表示します。 @@ -1192,15 +1182,6 @@ See https://aka.ms/dotnet-test/mtp for more information. ダウンロード Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - 大文字と小文字が区別される名前の起動プロファイルがいくつかありますが、これは許可されていません: -{0} -プロファイル名を区別できるようにしてください。 - - duration: 期間: @@ -1591,26 +1572,6 @@ Make the profile names distinct. 最新バージョン Table lable - - A launch profile with the name '{0}' doesn't exist. - '{0} ' という名前の起動プロファイルは存在しません。 - - - - The launch profile type '{0}' is not supported. - 起動プロファイルの種類 '{0}' はサポートされていません。 - - - - A profile with the specified name isn't a valid JSON object. - 指定された名前のプロファイルは、有効な JSON オブジェクトではありません。 - - - - The 'profiles' property of the launch settings document is not a JSON object. - 起動設定のドキュメントの 'profiles' プロパティが JSON オブジェクトではありません。 - - List all projects in a solution file. ソリューション ファイル内のすべてのプロジェクトを一覧表示します。 @@ -1927,7 +1888,6 @@ Tool '{1}' (version '{2}') was successfully installed. Entry is added to the man .NET Core NuGet パッケージ パッカー - OUTPUT_DIR OUTPUT_DIR @@ -3653,11 +3613,6 @@ and the corresponding package Ids for installed tools using the command {0} を使用するようにワークロード インストール モードが正常に更新されました。 - - A usable launch profile could not be located. - 使用可能な起動プロファイルが見つかりませんでした。 - - Usage 使用法 @@ -4139,4 +4094,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 9b4645aa1eb9..c33addce7195 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf @@ -1051,11 +1051,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 날짜 - - (Default) - (기본값) - - Uninstalling workload manifest {0} version {1}... 워크로드 매니페스트 {0} 버전 {1} 제거 중... @@ -1086,11 +1081,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 설명 - - An error was encountered when reading '{0}': {1} - '{0}'을 읽는 동안 오류가 발생했습니다. {1} - {0} is file path. {1} is exception message. - Show detail result of the query. 쿼리의 세부 결과를 표시합니다. @@ -1192,15 +1182,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 다운로드 Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - 대/소문자를 구분하는 이름을 가진 여러 시작 프로필이 있으며 이는 허용되지 않습니다. -{0} -고유한 프로필 이름을 사용하세요. - - duration: 기간: @@ -1591,26 +1572,6 @@ Make the profile names distinct. 최신 버전 Table lable - - A launch profile with the name '{0}' doesn't exist. - 이름이 '{0}'인 시작 프로필이 없습니다. - - - - The launch profile type '{0}' is not supported. - 시작 프로필 형식 '{0}'은(는) 지원되지 않습니다. - - - - A profile with the specified name isn't a valid JSON object. - 지정된 이름의 프로필은 유효한 JSON 개체가 아닙니다. - - - - The 'profiles' property of the launch settings document is not a JSON object. - 시작 설정 문서의 '프로필' 속성이 JSON 개체가 아닙니다. - - List all projects in a solution file. 솔루션 파일의 프로젝트를 모두 나열합니다. @@ -3652,11 +3613,6 @@ and the corresponding package Ids for installed tools using the command {0}을(를) 사용하도록 워크로드 설치 모드를 업데이트했습니다. - - A usable launch profile could not be located. - 사용할 수 있는 시작 프로필을 찾을 수 없습니다. - - Usage 사용법 @@ -4138,4 +4094,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 86137f2d874f..f89492285068 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf @@ -1051,11 +1051,6 @@ Aby uzyskać więcej informacji, zobacz https://aka.ms/dotnet-test/mtp. Data - - (Default) - (Domyślne) - - Uninstalling workload manifest {0} version {1}... Odinstalowanie manifestu obciążenia {0} w wersji {1}... @@ -1086,11 +1081,6 @@ Aby uzyskać więcej informacji, zobacz https://aka.ms/dotnet-test/mtp. Opis - - An error was encountered when reading '{0}': {1} - Napotkano błąd podczas odczytywania elementu „{0}”: {1} - {0} is file path. {1} is exception message. - Show detail result of the query. Pokaż szczegółowy wynik zapytania. @@ -1192,15 +1182,6 @@ Aby uzyskać więcej informacji, zobacz https://aka.ms/dotnet-test/mtp. Pliki do pobrania Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - Istnieje kilka profilów uruchamiania z nazwami z uwzględnieniem wielkości liter, co jest niedozwolone: -{0} -Rozróżnij nazwy profilów. - - duration: czas trwania: @@ -1591,26 +1572,6 @@ Rozróżnij nazwy profilów. Najnowsza wersja Table lable - - A launch profile with the name '{0}' doesn't exist. - Profil uruchamiania o nazwie „{0}” nie istnieje. - - - - The launch profile type '{0}' is not supported. - Typ profilu uruchamiania „{0}” nie jest obsługiwany. - - - - A profile with the specified name isn't a valid JSON object. - Profil o określonej nazwie nie jest prawidłowym obiektem JSON. - - - - The 'profiles' property of the launch settings document is not a JSON object. - Właściwość „profiles” dokumentu ustawień uruchamiania nie jest obiektem JSON. - - List all projects in a solution file. Wyświetl listę wszystkich projektów w pliku rozwiązania. @@ -3652,11 +3613,6 @@ i odpowiednie identyfikatory pakietów zainstalowanych narzędzi można znaleź Pomyślnie zaktualizowano tryb instalacji obciążenia w celu użycia {0}. - - A usable launch profile could not be located. - Nie można odnaleźć nadającego się do użytku profilu uruchamiania. - - Usage Użycie @@ -4138,4 +4094,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 112717418361..81287d03948f 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf @@ -1051,11 +1051,6 @@ Consulte https://aka.ms/dotnet-test/mtp para obter mais informações. Data - - (Default) - (Padrão) - - Uninstalling workload manifest {0} version {1}... Instalando o manifesto da carga de trabalho {0} versão {1}... @@ -1086,11 +1081,6 @@ Consulte https://aka.ms/dotnet-test/mtp para obter mais informações. Descrição - - An error was encountered when reading '{0}': {1} - Ocorreu um erro ao ler '{0}': {1} - {0} is file path. {1} is exception message. - Show detail result of the query. Mostrar o resultado detalhado da consulta. @@ -1192,15 +1182,6 @@ Consulte https://aka.ms/dotnet-test/mtp para obter mais informações. Downloads Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - Existem vários perfis de inicialização com nomes que diferenciam maiúsculas de minúsculas, o que não é permitido: -{0} -Diferencie os nomes dos perfis. - - duration: duração: @@ -1591,26 +1572,6 @@ Diferencie os nomes dos perfis. Última Versão Table lable - - A launch profile with the name '{0}' doesn't exist. - Um perfil de lançamento com o nome '{0}' não existe. - - - - The launch profile type '{0}' is not supported. - Não há suporte para o tipo de perfil de inicialização '{0}'. - - - - A profile with the specified name isn't a valid JSON object. - Um perfil com o nome especificado não é um objeto JSON válido. - - - - The 'profiles' property of the launch settings document is not a JSON object. - A propriedade 'perfis' do documento de configurações de inicialização não é um objeto JSON. - - List all projects in a solution file. Listar todos os projetos em um arquivo de solução. @@ -3652,11 +3613,6 @@ e as Ids de pacote correspondentes para as ferramentas instaladas usando o coman Modo de instalação da carga de trabalho atualizado com êxito para usar {0}. - - A usable launch profile could not be located. - Um perfil de inicialização utilizável não pôde ser localizado. - - Usage Uso @@ -4138,4 +4094,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 8d933a41c596..52e2408c6370 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf @@ -1051,11 +1051,6 @@ See https://aka.ms/dotnet-test/mtp for more information. Дата - - (Default) - (По умолчанию) - - Uninstalling workload manifest {0} version {1}... Удаление манифеста рабочей нагрузки {0} версии {1}... @@ -1086,11 +1081,6 @@ See https://aka.ms/dotnet-test/mtp for more information. Описание - - An error was encountered when reading '{0}': {1} - Произошла ошибка при чтении "{0}": {1} - {0} is file path. {1} is exception message. - Show detail result of the query. Отображение подробных сведений о результате запроса. @@ -1192,15 +1182,6 @@ See https://aka.ms/dotnet-test/mtp for more information. Скачивания Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - Существует несколько профилей, имена которых различаются только регистром, что запрещено: -{0} -Сделайте имена профилей разными. - - duration: длительность: @@ -1591,26 +1572,6 @@ Make the profile names distinct. Последняя версия Table lable - - A launch profile with the name '{0}' doesn't exist. - Профиль запуска с именем "{0}" не существует. - - - - The launch profile type '{0}' is not supported. - Тип профиля запуска "{0}" не поддерживается. - - - - A profile with the specified name isn't a valid JSON object. - Профиль с указанным именем не является допустимым объектом JSON. - - - - The 'profiles' property of the launch settings document is not a JSON object. - Свойство "profiles" документа параметров запуска не является объектом JSON. - - List all projects in a solution file. Вывод списка всех проектов в файле решения. @@ -3653,11 +3614,6 @@ and the corresponding package Ids for installed tools using the command Режим установки рабочей нагрузки изменен на {0}. - - A usable launch profile could not be located. - Не удалось найти подходящий для использования профиль запуска. - - Usage Использование @@ -4139,4 +4095,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 7587dc54d0b7..0bdf46014f3c 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf @@ -1051,11 +1051,6 @@ Daha fazla bilgi için https://aka.ms/dotnet-test/mtp adresine bakın. Tarih - - (Default) - (Varsayılan) - - Uninstalling workload manifest {0} version {1}... İş yükü bildirimi {0} sürümü {1} yükleniyor... @@ -1086,11 +1081,6 @@ Daha fazla bilgi için https://aka.ms/dotnet-test/mtp adresine bakın. Açıklama - - An error was encountered when reading '{0}': {1} - ‘{0}’ okunurken bir hata oluştu: {1} - {0} is file path. {1} is exception message. - Show detail result of the query. Sorgunun ayrıntılı sonucunu gösterir. @@ -1192,15 +1182,6 @@ Daha fazla bilgi için https://aka.ms/dotnet-test/mtp adresine bakın. İndirmeler Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - Adı büyük/küçük harfe duyarlı olan birkaç başlatma profili var ama bu duruma izin verilmiyor: -{0} -Lütfen profil adlarını değiştirin. - - duration: süre: @@ -1591,26 +1572,6 @@ Lütfen profil adlarını değiştirin. En Son Sürüm Table lable - - A launch profile with the name '{0}' doesn't exist. - '{0}' adlı bir başlatma profili yok. - - - - The launch profile type '{0}' is not supported. - '{0}' başlatma profili türü desteklenmiyor. - - - - A profile with the specified name isn't a valid JSON object. - Belirtilen adla bir profil, geçerli bir JSON nesnesi değil. - - - - The 'profiles' property of the launch settings document is not a JSON object. - Başlatma ayarları belgesinin 'profiles' özelliği bir JSON nesnesi değil. - - List all projects in a solution file. Bir çözüm dosyasındaki tüm projeleri listeler. @@ -3652,11 +3613,6 @@ karşılık gelen paket kimliklerini bulmak için İş yükü yükleme modu {0} kullanacak şekilde başarıyla güncelleştirildi. - - A usable launch profile could not be located. - Kullanılabilir bir başlatma profili bulunamadı. - - Usage Kullanım @@ -4138,4 +4094,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 e1197828c1d4..196eb2de4133 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf @@ -1051,11 +1051,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 日期 - - (Default) - (默认值) - - Uninstalling workload manifest {0} version {1}... 正在卸载工作负载清单 {0} 版本 {1}… @@ -1086,11 +1081,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 说明 - - An error was encountered when reading '{0}': {1} - 读取 ‘{0}’ 时遇到错误: {1} - {0} is file path. {1} is exception message. - Show detail result of the query. 显示查询的详细结果。 @@ -1192,15 +1182,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 下载 Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - 存在多个名称区分大小写的启动配置文件,这是不允许的: -{0} -将配置文件名称设为可区分的名称。 - - duration: 持续时间: @@ -1591,26 +1572,6 @@ Make the profile names distinct. 最新版本 Table lable - - A launch profile with the name '{0}' doesn't exist. - 名为“{0}”的启动配置文件不存在。 - - - - The launch profile type '{0}' is not supported. - 不支持启动配置文件类型“{0}”。 - - - - A profile with the specified name isn't a valid JSON object. - 具有指定名称的配置文件不是有效的 JSON 对象。 - - - - The 'profiles' property of the launch settings document is not a JSON object. - 启动设置文档的“profiles”属性不是 JSON 对象。 - - List all projects in a solution file. 列出解决方案文件中的所有项目。 @@ -3652,11 +3613,6 @@ and the corresponding package Ids for installed tools using the command 已成功更新工作负载安装模式以使用 {0}。 - - A usable launch profile could not be located. - 找不到可用的启动配置文件。 - - Usage 使用情况 @@ -4138,4 +4094,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 5d848f84c897..a11d81925055 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf @@ -1051,11 +1051,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 日期 - - (Default) - (預設) - - Uninstalling workload manifest {0} version {1}... 正解除安裝工作負載資訊清單 {0} 版本 {1}... @@ -1086,11 +1081,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 描述 - - An error was encountered when reading '{0}': {1} - 讀取「{0}」時發生錯誤: {1} - {0} is file path. {1} is exception message. - Show detail result of the query. 顯示查詢的詳細結果。 @@ -1192,15 +1182,6 @@ See https://aka.ms/dotnet-test/mtp for more information. 下載 Table lable - - There are several launch profiles with case-sensitive names, which isn't permitted: -{0} -Make the profile names distinct. - 數個啟動設定檔具有區分大小寫的名稱,這並不受允許: -{0} -請讓設定檔名稱相異。 - - duration: 期間: @@ -1591,26 +1572,6 @@ Make the profile names distinct. 最新版本 Table lable - - A launch profile with the name '{0}' doesn't exist. - 名稱為 '{0}' 的啟動設定檔不存在。 - - - - The launch profile type '{0}' is not supported. - 不支援啟動設定檔類型 '{0}'。 - - - - A profile with the specified name isn't a valid JSON object. - 具有指定名稱的設定檔不是有效的 JSON 物件。 - - - - The 'profiles' property of the launch settings document is not a JSON object. - 啟動設定文件的 'profiles' 屬性並非 JSON 物件。 - - List all projects in a solution file. 列出解決方案檔中的所有專案。 @@ -3652,11 +3613,6 @@ and the corresponding package Ids for installed tools using the command 已成功更新工作負載安裝模式以使用 {0}。 - - A usable launch profile could not be located. - 找不到可用的啟動設定檔。 - - Usage 使用方式 @@ -4138,4 +4094,4 @@ To display a value, specify the corresponding command-line option without provid - + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ExecutableLaunchProfile.cs b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ExecutableLaunchProfile.cs new file mode 100644 index 000000000000..67db2e0b93ce --- /dev/null +++ b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ExecutableLaunchProfile.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; + +namespace Microsoft.DotNet.ProjectTools; + +public sealed class ExecutableLaunchProfile : LaunchProfile +{ + public const string WorkingDirectoryPropertyName = "workingDirectory"; + public const string ExecutablePathPropertyName = "executablePath"; + + [JsonPropertyName("executablePath")] + public required string ExecutablePath { get; init; } + + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; init; } +} diff --git a/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ExecutableLaunchProfileParser.cs b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ExecutableLaunchProfileParser.cs new file mode 100644 index 000000000000..d6c161793fbd --- /dev/null +++ b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ExecutableLaunchProfileParser.cs @@ -0,0 +1,67 @@ +// 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.CodeAnalysis; +using System.Text.Json; + +namespace Microsoft.DotNet.ProjectTools; + +internal sealed class ExecutableLaunchProfileParser : LaunchProfileParser +{ + public const string CommandName = "Executable"; + + public static readonly ExecutableLaunchProfileParser Instance = new(); + + private ExecutableLaunchProfileParser() + { + } + + public override LaunchProfileParseResult ParseProfile(string launchSettingsPath, string? launchProfileName, string json) + { + var profile = JsonSerializer.Deserialize(json); + if (profile == null) + { + return LaunchProfileParseResult.Failure(Resources.LaunchProfileIsNotAJsonObject); + } + + if (!TryParseWorkingDirectory(launchSettingsPath, profile.WorkingDirectory, out var workingDirectory, out var error)) + { + return LaunchProfileParseResult.Failure(error); + } + + return LaunchProfileParseResult.Success(new ExecutableLaunchProfile + { + LaunchProfileName = launchProfileName, + ExecutablePath = ExpandVariables(profile.ExecutablePath), + CommandLineArgs = ParseCommandLineArgs(profile.CommandLineArgs), + WorkingDirectory = workingDirectory, + DotNetRunMessages = profile.DotNetRunMessages, + EnvironmentVariables = ParseEnvironmentVariables(profile.EnvironmentVariables), + }); + } + + private static bool TryParseWorkingDirectory(string launchSettingsPath, string? value, out string? workingDirectory, [NotNullWhen(false)] out string? error) + { + if (value == null) + { + workingDirectory = null; + error = null; + return true; + } + + var expandedValue = ExpandVariables(value); + + try + { + workingDirectory = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(launchSettingsPath)!, expandedValue)); + error = null; + return true; + } + catch + { + workingDirectory = null; + error = string.Format(Resources.Path0SpecifiedIn1IsInvalid, expandedValue, ExecutableLaunchProfile.WorkingDirectoryPropertyName); + return false; + } + } +} diff --git a/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchProfile.cs b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchProfile.cs new file mode 100644 index 000000000000..87f6275e83b3 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchProfile.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using System.Text.Json.Serialization; + +namespace Microsoft.DotNet.ProjectTools; + +public abstract class LaunchProfile +{ + [JsonIgnore] + public string? LaunchProfileName { get; init; } + + [JsonPropertyName("dotnetRunMessages")] + public bool DotNetRunMessages { get; init; } + + [JsonPropertyName("commandLineArgs")] + public string? CommandLineArgs { get; init; } + + [JsonPropertyName("environmentVariables")] + public ImmutableDictionary EnvironmentVariables { get; init; } = []; +} diff --git a/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchProfileParseResult.cs b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchProfileParseResult.cs new file mode 100644 index 000000000000..bf735a81cf0a --- /dev/null +++ b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchProfileParseResult.cs @@ -0,0 +1,29 @@ +// 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.CodeAnalysis; + +namespace Microsoft.DotNet.ProjectTools; + +public sealed class LaunchProfileParseResult +{ + public string? FailureReason { get; } + + public LaunchProfile? Profile { get; } + + private LaunchProfileParseResult(string? failureReason, LaunchProfile? profile) + { + FailureReason = failureReason; + Profile = profile; + } + + [MemberNotNullWhen(false, nameof(FailureReason))] + public bool Successful + => FailureReason == null; + + public static LaunchProfileParseResult Failure(string reason) + => new(reason, profile: null); + + public static LaunchProfileParseResult Success(LaunchProfile? model) + => new(failureReason: null, profile: model); +} diff --git a/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchProfileParser.cs b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchProfileParser.cs new file mode 100644 index 000000000000..0d7feb52be03 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchProfileParser.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; + +namespace Microsoft.DotNet.ProjectTools; + +internal abstract class LaunchProfileParser +{ + public abstract LaunchProfileParseResult ParseProfile(string launchSettingsPath, string? launchProfileName, string json); + + protected static string? ParseCommandLineArgs(string? value) + => value != null ? ExpandVariables(value) : null; + + public static string GetLaunchProfileDisplayName(string? launchProfile) + => string.IsNullOrEmpty(launchProfile) ? Resources.DefaultLaunchProfileDisplayName : launchProfile; + + protected static ImmutableDictionary ParseEnvironmentVariables(ImmutableDictionary values) + { + if (values.Count == 0) + { + return values; + } + + var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); + foreach (var (key, value) in values) + { + // override previously set variables: + builder[key] = ExpandVariables(value); + } + + return builder.ToImmutable(); + } + + // TODO: Expand MSBuild variables $(...): https://github.com/dotnet/sdk/issues/50157 + // See https://github.com/dotnet/project-system/blob/main/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Debug/DebugTokenReplacer.cs#L35-L57 + protected static string ExpandVariables(string value) + => Environment.ExpandEnvironmentVariables(value); +} diff --git a/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchSettings.cs b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchSettings.cs new file mode 100644 index 000000000000..5e22df2e18d7 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchSettings.cs @@ -0,0 +1,196 @@ +// 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 System.Text.Json; + +namespace Microsoft.DotNet.ProjectTools; + +public static class LaunchSettings +{ + private const string ProfilesKey = "profiles"; + private const string CommandNameKey = "commandName"; + + private static readonly IReadOnlyDictionary s_providers = new Dictionary + { + { ProjectLaunchProfileParser.CommandName, ProjectLaunchProfileParser.Instance }, + { ExecutableLaunchProfileParser.CommandName, ExecutableLaunchProfileParser.Instance } + }; + + public static IEnumerable SupportedProfileTypes => s_providers.Keys; + + + public static string GetPropertiesLaunchSettingsPath(string directoryPath, string propertiesDirectoryName) + => Path.Combine(directoryPath, propertiesDirectoryName, "launchSettings.json"); + + public static string GetFlatLaunchSettingsPath(string directoryPath, string projectNameWithoutExtension) + => Path.Join(directoryPath, $"{projectNameWithoutExtension}.run.json"); + + public static string? TryFindLaunchSettingsFile(string projectOrEntryPointFilePath, string? launchProfile, Action report) + { + var buildPathContainer = Path.GetDirectoryName(projectOrEntryPointFilePath); + Debug.Assert(buildPathContainer != null); + + // VB.NET projects store the launch settings file in the + // "My Project" directory instead of a "Properties" directory. + // TODO: use the `AppDesignerFolder` MSBuild property instead, which captures this logic already + var propsDirectory = string.Equals(Path.GetExtension(projectOrEntryPointFilePath), ".vbproj", StringComparison.OrdinalIgnoreCase) + ? "My Project" + : "Properties"; + + string launchSettingsPath = GetPropertiesLaunchSettingsPath(buildPathContainer, propsDirectory); + bool hasLaunchSetttings = File.Exists(launchSettingsPath); + + string appName = Path.GetFileNameWithoutExtension(projectOrEntryPointFilePath); + string runJsonPath = GetFlatLaunchSettingsPath(buildPathContainer, appName); + bool hasRunJson = File.Exists(runJsonPath); + + if (hasLaunchSetttings) + { + if (hasRunJson) + { + report(string.Format(Resources.RunCommandWarningRunJsonNotUsed, runJsonPath, launchSettingsPath), false); + } + + return launchSettingsPath; + } + + if (hasRunJson) + { + return runJsonPath; + } + + if (!string.IsNullOrEmpty(launchProfile)) + { + report(string.Format(Resources.RunCommandExceptionCouldNotLocateALaunchSettingsFile, launchProfile, $""" + {launchSettingsPath} + {runJsonPath} + """), true); + } + + return null; + } + + public static LaunchProfileParseResult ReadProfileSettingsFromFile(string launchSettingsPath, string? profileName = null) + { + try + { + var launchSettingsJsonContents = File.ReadAllText(launchSettingsPath); + + var jsonDocumentOptions = new JsonDocumentOptions + { + CommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + }; + + using (var document = JsonDocument.Parse(launchSettingsJsonContents, jsonDocumentOptions)) + { + var model = document.RootElement; + + if (model.ValueKind != JsonValueKind.Object || !model.TryGetProperty(ProfilesKey, out var profilesObject) || profilesObject.ValueKind != JsonValueKind.Object) + { + return LaunchProfileParseResult.Failure(Resources.LaunchProfilesCollectionIsNotAJsonObject); + } + + var selectedProfileName = profileName; + JsonElement profileObject; + if (string.IsNullOrEmpty(profileName)) + { + var firstProfileProperty = profilesObject.EnumerateObject().FirstOrDefault(IsDefaultProfileType); + selectedProfileName = firstProfileProperty.Value.ValueKind == JsonValueKind.Object ? firstProfileProperty.Name : null; + profileObject = firstProfileProperty.Value; + } + else // Find a profile match for the given profileName + { + IEnumerable caseInsensitiveProfileMatches = [.. profilesObject + .EnumerateObject() // p.Name shouldn't fail, as profileObject enumerables here are only created from an existing JsonObject + .Where(p => string.Equals(p.Name, profileName, StringComparison.OrdinalIgnoreCase))]; + + if (caseInsensitiveProfileMatches.Count() > 1) + { + return LaunchProfileParseResult.Failure(string.Format(Resources.DuplicateCaseInsensitiveLaunchProfileNames, + string.Join(",\n", caseInsensitiveProfileMatches.Select(p => $"\t{p.Name}")))); + } + + if (!caseInsensitiveProfileMatches.Any()) + { + return LaunchProfileParseResult.Failure(string.Format(Resources.LaunchProfileDoesNotExist, profileName)); + } + + profileObject = profilesObject.GetProperty(caseInsensitiveProfileMatches.First().Name); + + if (profileObject.ValueKind != JsonValueKind.Object) + { + return LaunchProfileParseResult.Failure(Resources.LaunchProfileIsNotAJsonObject); + } + } + + if (profileObject.ValueKind == default) + { + foreach (var prop in profilesObject.EnumerateObject()) + { + if (prop.Value.ValueKind == JsonValueKind.Object) + { + if (prop.Value.TryGetProperty(CommandNameKey, out var commandNameElement) && commandNameElement.ValueKind == JsonValueKind.String) + { + if (commandNameElement.GetString() is { } commandNameElementKey && s_providers.ContainsKey(commandNameElementKey)) + { + profileObject = prop.Value; + break; + } + } + } + } + } + + if (profileObject.ValueKind == default) + { + return LaunchProfileParseResult.Failure(Resources.UsableLaunchProfileCannotBeLocated); + } + + if (!profileObject.TryGetProperty(CommandNameKey, out var finalCommandNameElement) + || finalCommandNameElement.ValueKind != JsonValueKind.String) + { + return LaunchProfileParseResult.Failure(Resources.UsableLaunchProfileCannotBeLocated); + } + + string? commandName = finalCommandNameElement.GetString(); + if (!TryLocateHandler(commandName, out LaunchProfileParser? provider)) + { + return LaunchProfileParseResult.Failure(string.Format(Resources.LaunchProfileHandlerCannotBeLocated, commandName)); + } + + return provider.ParseProfile(launchSettingsPath, selectedProfileName, profileObject.GetRawText()); + } + } + catch (Exception ex) when (ex is JsonException or IOException) + { + return LaunchProfileParseResult.Failure(string.Format(Resources.DeserializationExceptionMessage, launchSettingsPath, ex.Message)); + } + } + + private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)] out LaunchProfileParser? provider) + { + if (commandName == null) + { + provider = null; + return false; + } + + return s_providers.TryGetValue(commandName, out provider); + } + + private static bool IsDefaultProfileType(JsonProperty profileProperty) + { + if (profileProperty.Value.ValueKind != JsonValueKind.Object + || !profileProperty.Value.TryGetProperty(CommandNameKey, out var commandNameElement) + || commandNameElement.ValueKind != JsonValueKind.String) + { + return false; + } + + var commandName = commandNameElement.GetString(); + return commandName != null && s_providers.ContainsKey(commandName); + } +} diff --git a/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchSettingsLocator.cs b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchSettingsLocator.cs deleted file mode 100644 index f20e3445dbfe..000000000000 --- a/src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchSettingsLocator.cs +++ /dev/null @@ -1,60 +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 System.Diagnostics; - -namespace Microsoft.DotNet.ProjectTools; - -public static class LaunchSettingsLocator -{ - public static string GetPropertiesLaunchSettingsPath(string directoryPath, string propertiesDirectoryName) - => Path.Combine(directoryPath, propertiesDirectoryName, "launchSettings.json"); - - public static string GetFlatLaunchSettingsPath(string directoryPath, string projectNameWithoutExtension) - => Path.Join(directoryPath, $"{projectNameWithoutExtension}.run.json"); - - public static string? TryFindLaunchSettings(string projectOrEntryPointFilePath, string? launchProfile, Action report) - { - var buildPathContainer = Path.GetDirectoryName(projectOrEntryPointFilePath); - Debug.Assert(buildPathContainer != null); - - // VB.NET projects store the launch settings file in the - // "My Project" directory instead of a "Properties" directory. - // TODO: use the `AppDesignerFolder` MSBuild property instead, which captures this logic already - var propsDirectory = string.Equals(Path.GetExtension(projectOrEntryPointFilePath), ".vbproj", StringComparison.OrdinalIgnoreCase) - ? "My Project" - : "Properties"; - - string launchSettingsPath = GetPropertiesLaunchSettingsPath(buildPathContainer, propsDirectory); - bool hasLaunchSetttings = File.Exists(launchSettingsPath); - - string appName = Path.GetFileNameWithoutExtension(projectOrEntryPointFilePath); - string runJsonPath = GetFlatLaunchSettingsPath(buildPathContainer, appName); - bool hasRunJson = File.Exists(runJsonPath); - - if (hasLaunchSetttings) - { - if (hasRunJson) - { - report(string.Format(Resources.RunCommandWarningRunJsonNotUsed, runJsonPath, launchSettingsPath), false); - } - - return launchSettingsPath; - } - - if (hasRunJson) - { - return runJsonPath; - } - - if (!string.IsNullOrEmpty(launchProfile)) - { - report(string.Format(Resources.RunCommandExceptionCouldNotLocateALaunchSettingsFile, launchProfile, $""" - {launchSettingsPath} - {runJsonPath} - """), true); - } - - return null; - } -} diff --git a/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ProjectLaunchProfile.cs b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ProjectLaunchProfile.cs new file mode 100644 index 000000000000..a107dc29db84 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ProjectLaunchProfile.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; + +namespace Microsoft.DotNet.ProjectTools; + +public sealed class ProjectLaunchProfile : LaunchProfile +{ + [JsonPropertyName("launchBrowser")] + public bool LaunchBrowser { get; init; } + + [JsonPropertyName("launchUrl")] + public string? LaunchUrl { get; init; } + + [JsonPropertyName("applicationUrl")] + public string? ApplicationUrl { get; init; } +} diff --git a/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ProjectLaunchProfileParser.cs b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ProjectLaunchProfileParser.cs new file mode 100644 index 000000000000..c1997d66f179 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectTools/LaunchSettings/ProjectLaunchProfileParser.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; + +namespace Microsoft.DotNet.ProjectTools; + +internal sealed class ProjectLaunchProfileParser : LaunchProfileParser +{ + public const string CommandName = "Project"; + + public static readonly ProjectLaunchProfileParser Instance = new(); + + private ProjectLaunchProfileParser() + { + } + + public override LaunchProfileParseResult ParseProfile(string launchSettingsPath, string? launchProfileName, string json) + { + var profile = JsonSerializer.Deserialize(json); + if (profile == null) + { + return LaunchProfileParseResult.Failure(Resources.LaunchProfileIsNotAJsonObject); + } + + return LaunchProfileParseResult.Success(new ProjectLaunchProfile + { + LaunchProfileName = launchProfileName, + CommandLineArgs = ParseCommandLineArgs(profile.CommandLineArgs), + LaunchBrowser = profile.LaunchBrowser, + LaunchUrl = profile.LaunchUrl, + ApplicationUrl = profile.ApplicationUrl, + DotNetRunMessages = profile.DotNetRunMessages, + EnvironmentVariables = ParseEnvironmentVariables(profile.EnvironmentVariables), + }); + } +} diff --git a/src/Microsoft.DotNet.ProjectTools/Resources.resx b/src/Microsoft.DotNet.ProjectTools/Resources.resx index 9de7f944cb9e..0c69ccc49b2f 100644 --- a/src/Microsoft.DotNet.ProjectTools/Resources.resx +++ b/src/Microsoft.DotNet.ProjectTools/Resources.resx @@ -125,4 +125,34 @@ Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} + + A profile with the specified name isn't a valid JSON object. + + + The 'profiles' property of the launch settings document is not a JSON object. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + A launch profile with the name '{0}' doesn't exist. + + + A usable launch profile could not be located. + + + The launch profile type '{0}' is not supported. + + + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + Path '{0}' specified in '{1}' is invalid. + + + (Default) + diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.cs.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.cs.xlf index f8d9db92ba48..c336c2caa5d5 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.cs.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.cs.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.de.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.de.xlf index ac6216d4b292..3538d5d20aec 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.de.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.de.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.es.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.es.xlf index 217f925c00e3..f409f1481066 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.es.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.es.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.fr.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.fr.xlf index e133378ad4e1..af0c9d337ebf 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.fr.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.fr.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.it.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.it.xlf index 5620ef6944b3..129c9df27274 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.it.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.it.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ja.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ja.xlf index 7dd1492ebe32..05e7fa1d39b8 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ja.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ja.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ko.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ko.xlf index 162f27b4efb9..7bc51a26dcd6 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ko.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ko.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.pl.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.pl.xlf index 6f0cecb49573..7e524550a5c8 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.pl.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.pl.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.pt-BR.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.pt-BR.xlf index 4d381aba1acf..2ff24ccb465b 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.pt-BR.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ru.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ru.xlf index 20c6d3be68ad..90e4e5879198 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ru.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.ru.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.tr.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.tr.xlf index ff180d8a45c3..80e2d671ae91 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.tr.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.tr.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.zh-Hans.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.zh-Hans.xlf index 7225552ec73b..6fd81c1778a3 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.zh-Hans.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.zh-Hant.xlf b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.zh-Hant.xlf index c99df83fdd56..eadb9ad89a71 100644 --- a/src/Microsoft.DotNet.ProjectTools/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.DotNet.ProjectTools/xlf/Resources.zh-Hant.xlf @@ -2,6 +2,50 @@ + + (Default) + (Default) + + + + An error was encountered when reading '{0}': {1} + An error was encountered when reading '{0}': {1} + {0} is file path. {1} is exception message. + + + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + There are several launch profiles with case-sensitive names, which isn't permitted: +{0} +Make the profile names distinct. + + + + A launch profile with the name '{0}' doesn't exist. + A launch profile with the name '{0}' doesn't exist. + + + + The launch profile type '{0}' is not supported. + The launch profile type '{0}' is not supported. + + + + A profile with the specified name isn't a valid JSON object. + A profile with the specified name isn't a valid JSON object. + + + + The 'profiles' property of the launch settings document is not a JSON object. + The 'profiles' property of the launch settings document is not a JSON object. + + + + Path '{0}' specified in '{1}' is invalid. + Path '{0}' specified in '{1}' is invalid. + + Cannot use launch profile '{0}' because the launch settings file could not be located. Locations tried: {1} @@ -14,6 +58,11 @@ Warning: Settings from '{0}' are not used because '{1}' has precedence. {0} is an app.run.json file path. {1} is a launchSettings.json file path. + + A usable launch profile could not be located. + A usable launch profile could not be located. + + \ No newline at end of file diff --git a/test/TestAssets/TestProjects/AppForExecutableProfile/AppForExecutableProfile.csproj b/test/TestAssets/TestProjects/AppForExecutableProfile/AppForExecutableProfile.csproj new file mode 100644 index 000000000000..a15a29bf12c2 --- /dev/null +++ b/test/TestAssets/TestProjects/AppForExecutableProfile/AppForExecutableProfile.csproj @@ -0,0 +1,8 @@ + + + + Exe + net10.0 + + + diff --git a/test/TestAssets/TestProjects/AppForExecutableProfile/Program.cs b/test/TestAssets/TestProjects/AppForExecutableProfile/Program.cs new file mode 100644 index 000000000000..3ab0d75acb96 --- /dev/null +++ b/test/TestAssets/TestProjects/AppForExecutableProfile/Program.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +class Program +{ + static void Main(string[] args) + { + // Print arguments + Console.WriteLine($"Arguments: [{string.Join(", ", args)}]"); + + // Print specific environment variables + var testVar = Environment.GetEnvironmentVariable("TEST_ENV_VAR"); + if (testVar != null) + { + Console.WriteLine($"TEST_ENV_VAR={testVar}"); + } + + var customVar = Environment.GetEnvironmentVariable("CUSTOM_VAR"); + if (customVar != null) + { + Console.WriteLine($"CUSTOM_VAR={customVar}"); + } + + // Print working directory + Console.WriteLine($"WorkingDirectory={Environment.CurrentDirectory}"); + } +} diff --git a/test/TestAssets/TestProjects/AppThatOutputsDotnetLaunchProfile/Program.cs b/test/TestAssets/TestProjects/AppThatOutputsDotnetLaunchProfile/Program.cs index 64d65aacc5fd..4371bec2bdc4 100644 --- a/test/TestAssets/TestProjects/AppThatOutputsDotnetLaunchProfile/Program.cs +++ b/test/TestAssets/TestProjects/AppThatOutputsDotnetLaunchProfile/Program.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Linq; namespace MSBuildTestApp { @@ -9,8 +10,11 @@ public class Program { public static void Main(string[] args) { - var value = Environment.GetEnvironmentVariable("DOTNET_LAUNCH_PROFILE"); - Console.WriteLine($"DOTNET_LAUNCH_PROFILE=<<<{value}>>>"); + Console.WriteLine($"DOTNET_LAUNCH_PROFILE=<<<{Environment.GetEnvironmentVariable("DOTNET_LAUNCH_PROFILE")}>>>"); + Console.WriteLine($"TEST_VAR1=<<<{Environment.GetEnvironmentVariable("TEST_VAR1")}>>>"); + Console.WriteLine($"TEST_VAR2=<<<{Environment.GetEnvironmentVariable("TEST_VAR2")}>>>"); + Console.WriteLine($"TEST_VAR3=<<<{Environment.GetEnvironmentVariable("TEST_VAR3")}>>>"); + Console.WriteLine($"ARGS={string.Join(",", args)}"); } } } diff --git a/test/TestAssets/TestProjects/AppWithDetailedExecutableProfile/AppWithDetailedExecutableProfile.csproj b/test/TestAssets/TestProjects/AppWithDetailedExecutableProfile/AppWithDetailedExecutableProfile.csproj new file mode 100644 index 000000000000..a15a29bf12c2 --- /dev/null +++ b/test/TestAssets/TestProjects/AppWithDetailedExecutableProfile/AppWithDetailedExecutableProfile.csproj @@ -0,0 +1,8 @@ + + + + Exe + net10.0 + + + diff --git a/test/TestAssets/TestProjects/AppWithDetailedExecutableProfile/Program.cs b/test/TestAssets/TestProjects/AppWithDetailedExecutableProfile/Program.cs new file mode 100644 index 000000000000..599159008a75 --- /dev/null +++ b/test/TestAssets/TestProjects/AppWithDetailedExecutableProfile/Program.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +Console.WriteLine("Main app - should not run when using executable profile"); diff --git a/test/TestAssets/TestProjects/AppWithDetailedExecutableProfile/Properties/launchSettings.json b/test/TestAssets/TestProjects/AppWithDetailedExecutableProfile/Properties/launchSettings.json new file mode 100644 index 000000000000..79ed1a1db94b --- /dev/null +++ b/test/TestAssets/TestProjects/AppWithDetailedExecutableProfile/Properties/launchSettings.json @@ -0,0 +1,24 @@ +{ + "profiles": { + "WithEnvironmentVariables": { + "commandName": "Executable", + "executablePath": "dotnet", + "commandLineArgs": "--version", + "environmentVariables": { + "TEST_ENV_VAR": "TestValue", + "CUSTOM_VAR": "CustomValue" + } + }, + "WithWorkingDirectory": { + "commandName": "Executable", + "executablePath": "dotnet", + "commandLineArgs": "--info", + "workingDirectory": "." + }, + "WithCommandLineOverride": { + "commandName": "Executable", + "executablePath": "dotnet", + "commandLineArgs": "--version" + } + } +} diff --git a/test/TestAssets/TestProjects/AppWithExecutableLaunchSettings/AppWithExecutableLaunchSettings.csproj b/test/TestAssets/TestProjects/AppWithExecutableLaunchSettings/AppWithExecutableLaunchSettings.csproj new file mode 100644 index 000000000000..9ba9b997ea9b --- /dev/null +++ b/test/TestAssets/TestProjects/AppWithExecutableLaunchSettings/AppWithExecutableLaunchSettings.csproj @@ -0,0 +1,9 @@ + + + + + Exe + $(CurrentTargetFramework) + $(LatestRuntimeIdentifiers) + + diff --git a/test/TestAssets/TestProjects/AppWithExecutableLaunchSettings/Program.cs b/test/TestAssets/TestProjects/AppWithExecutableLaunchSettings/Program.cs new file mode 100644 index 000000000000..94e191e6b66f --- /dev/null +++ b/test/TestAssets/TestProjects/AppWithExecutableLaunchSettings/Program.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace AppWithExecutableLaunchSettings +{ + public class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Hello from AppWithExecutableLaunchSettings!"); + } + } +} diff --git a/test/TestAssets/TestProjects/AppWithExecutableLaunchSettings/Properties/launchSettings.json b/test/TestAssets/TestProjects/AppWithExecutableLaunchSettings/Properties/launchSettings.json new file mode 100644 index 000000000000..8439556c8445 --- /dev/null +++ b/test/TestAssets/TestProjects/AppWithExecutableLaunchSettings/Properties/launchSettings.json @@ -0,0 +1,19 @@ +{ + "profiles": { + "ExecutableProfile": { + "commandName": "Executable", + "executablePath": "dotnet", + "commandLineArgs": "--version", + "workingDirectory": ".", + "environmentVariables": { + "TEST_VAR": "ExecutableValue" + } + }, + "ProjectProfile": { + "commandName": "Project", + "environmentVariables": { + "Message": "ProjectValue" + } + } + } +} diff --git a/test/TestAssets/TestProjects/TestAppWithLaunchSettings/Properties/launchSettings.json b/test/TestAssets/TestProjects/TestAppWithLaunchSettings/Properties/launchSettings.json index 3b6883ccd6c6..b40de994f4e0 100644 --- a/test/TestAssets/TestProjects/TestAppWithLaunchSettings/Properties/launchSettings.json +++ b/test/TestAssets/TestProjects/TestAppWithLaunchSettings/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "TestAppWithLaunchSettings": { "commandName": "Project", - "dotnetRunMessages": "true", + "dotnetRunMessages": true, "applicationUrl": "http://localhost:5000", "commandLineArgs": "TestAppCommandLineArguments SecondTestAppCommandLineArguments", "environmentVariables": { @@ -11,7 +11,7 @@ }, "Profile2": { "commandName": "Project", - "dotnetRunMessages": "true", + "dotnetRunMessages": true, "applicationUrl": "http://localhost:5000", "environmentVariables": { "DOTNET_LAUNCH_PROFILE": "XYZ", diff --git a/test/TestAssets/TestProjects/TestProjectWithLaunchSettings/Program.cs b/test/TestAssets/TestProjects/TestProjectWithLaunchSettings/Program.cs index 95fcbd501b11..4c90d8776176 100644 --- a/test/TestAssets/TestProjects/TestProjectWithLaunchSettings/Program.cs +++ b/test/TestAssets/TestProjects/TestProjectWithLaunchSettings/Program.cs @@ -1,4 +1,5 @@ -using Microsoft.Testing.Platform.Builder; +using System; +using Microsoft.Testing.Platform.Builder; using Microsoft.Testing.Platform.Capabilities.TestFramework; using Microsoft.Testing.Platform.Extensions.Messages; using Microsoft.Testing.Platform.Extensions.TestFramework; @@ -57,7 +58,7 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context) { Uid = "Test1", DisplayName = "Test1", - Properties = new PropertyBag(new SkippedTestNodeStateProperty("OK skipped!")), + Properties = new PropertyBag(new SkippedTestNodeStateProperty($"OK skipped! MY_VARIABLE_FROM_LAUNCH_SETTINGS={Environment.GetEnvironmentVariable("MY_VARIABLE_FROM_LAUNCH_SETTINGS")}")), })); context.Complete(); diff --git a/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunBuildsCsProj.cs b/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunBuildsCsProj.cs index c14cccd87ca5..fea920dd5511 100644 --- a/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunBuildsCsProj.cs +++ b/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunBuildsCsProj.cs @@ -579,6 +579,27 @@ public void ItGivesAnErrorWhenTheLaunchProfileNotFound() .And.HaveStdErrContaining(string.Format(CliCommandStrings.RunCommandExceptionCouldNotApplyLaunchSettings, "Third", "").Trim()); } + [Fact] + public void ItGivesAnErrorWhenTheLaunchProfileFileIsNotReadable() + { + var testAppName = "AppWithLaunchSettings"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); + + var testProjectDirectory = testInstance.Path; + var launchSettingsPath = Path.Combine(testProjectDirectory, "Properties", "launchSettings.json"); + + // open the file to prevent reading: + using var _ = File.Open(launchSettingsPath, FileMode.Open, FileAccess.Read, FileShare.None); + + new DotnetCommand(Log, "run") + .WithWorkingDirectory(testProjectDirectory) + .Execute("--launch-profile", "Third") + .Should().Pass() + .And.HaveStdOutContaining("(NO MESSAGE)") + .And.HaveStdErrContaining(string.Format(CliCommandStrings.RunCommandExceptionCouldNotApplyLaunchSettings, "Third", "").Trim()); + } + [Fact] public void ItGivesAnErrorWhenTheLaunchProfileCanNotBeHandled() { @@ -649,7 +670,7 @@ public void ItSkipsLaunchProfilesWhenThereIsNoUsableDefault() cmd.Should().Pass() .And.HaveStdOutContaining("(NO MESSAGE)") - .And.HaveStdErrContaining(string.Format(CliCommandStrings.RunCommandExceptionCouldNotApplyLaunchSettings, CliCommandStrings.DefaultLaunchProfileDisplayName, "").Trim()); + .And.HaveStdErrContaining(string.Format(CliCommandStrings.RunCommandExceptionCouldNotApplyLaunchSettings, ProjectTools.Resources.DefaultLaunchProfileDisplayName, "").Trim()); } [Fact] @@ -667,7 +688,7 @@ public void ItPrintsAnErrorWhenLaunchSettingsAreCorrupted() cmd.Should().Pass() .And.HaveStdOutContaining("(NO MESSAGE)") - .And.HaveStdErrContaining(string.Format(CliCommandStrings.RunCommandExceptionCouldNotApplyLaunchSettings, CliCommandStrings.DefaultLaunchProfileDisplayName, "").Trim()); + .And.HaveStdErrContaining(string.Format(CliCommandStrings.RunCommandExceptionCouldNotApplyLaunchSettings, ProjectTools.Resources.DefaultLaunchProfileDisplayName, "").Trim()); } [Fact] @@ -1028,5 +1049,37 @@ public void ItProvidesConsistentErrorMessageWhenProjectFileDoesNotExistWithNoBui hasExpectedErrorMessage.Should().BeTrue($"Expected error message to clearly indicate file doesn't exist, but got: {stderr}"); } } + + [Fact] + public void ItCanRunWithExecutableLaunchProfile() + { + var testInstance = _testAssetsManager.CopyTestAsset("TestAppWithLaunchSettings") + .WithSource(); + + var launchSettingsPath = Path.Combine(testInstance.Path, "Properties", "launchSettings.json"); + + File.WriteAllText(launchSettingsPath, """ + { + "profiles": { + "ExecutableProfile": { + "commandName": "Executable", + "executablePath": "dotnet", + "commandLineArgs": "--version" + } + } + } + """); + + new BuildCommand(testInstance) + .Execute() + .Should().Pass(); + + // The ExecutableProfile runs "dotnet --version" + new DotnetCommand(Log, "run", "--launch-profile", "ExecutableProfile") + .WithWorkingDirectory(testInstance.Path) + .Execute() + .Should().Pass() + .And.HaveStdOutContaining(TestContext.Current.ToolsetUnderTest.SdkVersion); + } } } diff --git a/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunBuildsVbProj.cs b/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunBuildsVbProj.cs index 751e606b730e..0a2486eec524 100644 --- a/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunBuildsVbProj.cs +++ b/test/dotnet.Tests/CommandTests/Run/GivenDotnetRunBuildsVbProj.cs @@ -45,7 +45,7 @@ public void ItFailsWhenTryingToUseLaunchProfileSharingTheSameNameWithAnotherProf .WithWorkingDirectory(testInstance.Path) .Execute("--launch-profile", "first"); - string expectedError = string.Format(CliCommandStrings.DuplicateCaseInsensitiveLaunchProfileNames, "\tfirst," + (OperatingSystem.IsWindows() ? "\r" : "") + "\n\tFIRST"); + string expectedError = string.Format(ProjectTools.Resources.DuplicateCaseInsensitiveLaunchProfileNames, "\tfirst," + (OperatingSystem.IsWindows() ? "\r" : "") + "\n\tFIRST"); runResult .Should() .Fail() @@ -68,7 +68,7 @@ public void ItFailsWithSpecificErrorMessageIfLaunchProfileDoesntExist() .Should() .Pass() .And - .HaveStdErrContaining(string.Format(CliCommandStrings.LaunchProfileDoesNotExist, invalidLaunchProfileName)); + .HaveStdErrContaining(string.Format(ProjectTools.Resources.LaunchProfileDoesNotExist, invalidLaunchProfileName)); } [Theory] diff --git a/test/dotnet.Tests/CommandTests/Run/RunCommandTests.cs b/test/dotnet.Tests/CommandTests/Run/RunCommandTests.cs new file mode 100644 index 000000000000..1bfa504ba4f6 --- /dev/null +++ b/test/dotnet.Tests/CommandTests/Run/RunCommandTests.cs @@ -0,0 +1,142 @@ +// 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.Run; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectTools; + +namespace Microsoft.DotNet.Cli.Run.Tests; + +public sealed class RunCommandTests(ITestOutputHelper log) : SdkTest(log) +{ + // The same syntax works on Windows and Unix ($VAR does not get expanded Unix). + private static string EnvironmentVariableReference(string name) + => $"%{name}%"; + + private static RunCommand CreateRunCommand( + string projectPath, + bool noLaunchProfileArguments = false, + string[]? applicationArgs = null) + => new( + noBuild: true, + projectFileFullPath: projectPath, + entryPointFileFullPath: null, + launchProfile: null, + noLaunchProfile: false, + noLaunchProfileArguments, + noRestore: false, + noCache: false, + interactive: false, + MSBuildArgs.FromOtherArgs([]), + applicationArgs: applicationArgs ?? [], + readCodeFromStdin: false, + environmentVariables: new Dictionary(), + msbuildRestoreProperties: new(new Dictionary())); + + [Fact] + public void EnvironmentVariableExpansion_Project() + { + var testAppName = "AppThatOutputsDotnetLaunchProfile"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); + + var testProjectDirectory = testInstance.Path; + var launchSettingsPath = Path.Combine(testProjectDirectory, "Properties", "launchSettings.json"); + + File.WriteAllText(launchSettingsPath, $$""" + { + "profiles": { + "First": { + "commandName": "Project", + "commandLineArgs": "arg1 arg2 arg3", + "environmentVariables": { + "TEST_VAR1": "{{EnvironmentVariableReference("VAR1")}}" + } + } + } + } + """); + + var cmd = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testProjectDirectory) + .WithEnvironmentVariable("VAR1", "VALUE1") + .Execute(); + + cmd.Should().Pass() + .And.HaveStdOutContaining("DOTNET_LAUNCH_PROFILE=<<>>") + .And.HaveStdOutContaining("TEST_VAR1=<<>>") + .And.HaveStdOutContaining("ARGS=arg1,arg2,arg3"); + + cmd.StdErr.Should().BeEmpty(); + } + + [Fact] + public void Executable_DefaultWorkingDirectory() + { + var root = _testAssetsManager.CreateTestDirectory().Path; + var dir = Path.Combine(root, "dir"); + + var launchSettingsPath = Path.Combine(dir, "launchSettings.json"); + var projectPath = Path.Combine(dir, "myproj.csproj"); + + var model = new ExecutableLaunchProfile() + { + LaunchProfileName = "MyProfile", + ExecutablePath = "executable", + EnvironmentVariables = [] + }; + + var runCommand = CreateRunCommand(projectPath); + var command = (Command)runCommand.GetTargetCommand(model, projectFactory: null, cachedRunProperties: null); + + Assert.Equal("executable", command.StartInfo.FileName); + Assert.Equal(dir, command.StartInfo.WorkingDirectory); + Assert.Equal("", command.StartInfo.Arguments); + } + + [Fact] + public void Executable_NoLaunchProfileArguments() + { + var root = _testAssetsManager.CreateTestDirectory().Path; + var dir = Path.Combine(root, "dir"); + + var launchSettingsPath = Path.Combine(dir, "launchSettings.json"); + var projectPath = Path.Combine(dir, "myproj.csproj"); + + var model = new ExecutableLaunchProfile() + { + LaunchProfileName = "MyProfile", + CommandLineArgs = "arg1 arg2", + ExecutablePath = "executable", + EnvironmentVariables = [] + }; + + var runCommand = CreateRunCommand(projectPath, noLaunchProfileArguments: true); + var command = (Command)runCommand.GetTargetCommand(model, projectFactory: null, cachedRunProperties: null); + + Assert.Equal("", command.StartInfo.Arguments); + } + + [Fact] + public void Executable_ApplicationArguments() + { + var root = _testAssetsManager.CreateTestDirectory().Path; + var dir = Path.Combine(root, "dir"); + + var launchSettingsPath = Path.Combine(dir, "launchSettings.json"); + var projectPath = Path.Combine(dir, "myproj.csproj"); + + var model = new ExecutableLaunchProfile() + { + LaunchProfileName = "MyProfile", + CommandLineArgs = "arg1 arg2", + ExecutablePath = "executable", + EnvironmentVariables = [] + }; + + var runCommand = CreateRunCommand(projectPath, applicationArgs: ["app 1", "app 2"]); + var command = (Command)runCommand.GetTargetCommand(model, projectFactory: null, cachedRunProperties: null); + + Assert.Equal("\"app 1\" \"app 2\"", command.StartInfo.Arguments); + } +} diff --git a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs index 38049ac50e16..ac3e989cfa21 100644 --- a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs @@ -2511,7 +2511,7 @@ Hello from Program Message: '' """); - // quiet runs here so that launch-profile useage messages don't impact test assertions + // quiet runs here so that launch-profile usage messages don't impact test assertions new DotnetCommand(Log, "run", "-v", "q", "Program.cs") .WithWorkingDirectory(testInstance.Path) .Execute() diff --git a/test/dotnet.Tests/CommandTests/Run/RunTelemetryTests.cs b/test/dotnet.Tests/CommandTests/Run/RunTelemetryTests.cs index db164163b90c..ae6425892777 100644 --- a/test/dotnet.Tests/CommandTests/Run/RunTelemetryTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/RunTelemetryTests.cs @@ -2,12 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; -using Microsoft.Build.Evaluation; -using Microsoft.Build.Execution; using Microsoft.DotNet.Cli.Commands.Run; -using Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; +using Microsoft.DotNet.ProjectTools; namespace Microsoft.DotNet.Cli.Run.Tests; @@ -256,9 +254,10 @@ public void TrackRunEvent_WithDefaultLaunchProfile_MarksTelemetryCorrectly() TelemetryEventEntry.EntryPosted += handler; - var launchSettings = new ProjectLaunchSettingsModel + var launchSettings = new ProjectLaunchProfile { - LaunchProfileName = "(Default)" + LaunchProfileName = "(Default)", + EnvironmentVariables = [], }; try diff --git a/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTests.cs b/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTests.cs index 685c6a4f5c18..3ccb65185fc5 100644 --- a/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTests.cs +++ b/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTests.cs @@ -14,6 +14,10 @@ public GivenDotnetTestBuildsAndRunsTests(ITestOutputHelper log) : base(log) { } + // The same syntax works on Windows and Unix ($VAR does not get expanded Unix). + private static string EnvironmentVariableReference(string name) + => $"%{name}%"; + [InlineData(TestingConstants.Debug)] [InlineData(TestingConstants.Release)] [Theory] @@ -122,16 +126,34 @@ public void RunTestProjectWithTests_ShouldReturnExitCodeSuccess(string configura public void RunTestProjectWithTestsAndLaunchSettings_ShouldReturnExitCodeSuccess( [CombinatorialValues(TestingConstants.Debug, TestingConstants.Release)] string configuration, bool runJson) { - TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithLaunchSettings", Guid.NewGuid().ToString()) + TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithLaunchSettings", identifier: $"{configuration}_{runJson}") .WithSource(); + var launchSettingsPath = Path.Join(testInstance.Path, "Properties", "launchSettings.json"); + var runJsonPath = Path.Join(testInstance.Path, "TestProjectWithLaunchSettings.run.json"); + + File.WriteAllText(launchSettingsPath, $$""" + { + "profiles": { + "ConsoleApp25": { + "commandName": "Project", + "commandLineArgs": "--from-launch-settings", + "environmentVariables": { + "MY_VARIABLE_FROM_LAUNCH_SETTINGS": "{{EnvironmentVariableReference("TEST_ENV_VAR")}}" + } + } + } + } + """); + if (runJson) { - File.Move(Path.Join(testInstance.Path, "Properties", "launchSettings.json"), Path.Join(testInstance.Path, "TestProjectWithLaunchSettings.run.json")); + File.Move(launchSettingsPath, runJsonPath); } CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable("TEST_ENV_VAR", "TestValue1") .Execute(TestCommandDefinition.ConfigurationOption.Name, configuration); if (!TestContext.IsLocalized()) @@ -140,6 +162,7 @@ public void RunTestProjectWithTestsAndLaunchSettings_ShouldReturnExitCodeSuccess .Should().Contain("Using launch settings from") .And.Contain(runJson ? "TestProjectWithLaunchSettings.run.json..." : $"Properties{Path.DirectorySeparatorChar}launchSettings.json...") .And.Contain("Test run summary: Passed!") + .And.Contain("MY_VARIABLE_FROM_LAUNCH_SETTINGS=TestValue1") .And.Contain("skipped Test1") .And.Contain("total: 2") .And.Contain("succeeded: 1") @@ -155,7 +178,7 @@ public void RunTestProjectWithTestsAndLaunchSettings_ShouldReturnExitCodeSuccess [Theory] public void RunTestProjectWithTestsAndNoLaunchSettings_ShouldReturnExitCodeSuccess(string configuration) { - TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithLaunchSettings", Guid.NewGuid().ToString()) + TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithLaunchSettings", identifier: configuration) .WithSource(); CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) @@ -169,12 +192,43 @@ public void RunTestProjectWithTestsAndNoLaunchSettings_ShouldReturnExitCodeSucce .And.NotContain("Using launch settings from"); } + [Fact] + public void RunTestProjectWithTestsAndLaunchSettingsAndExecutableProfile() + { + TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithLaunchSettings") + .WithSource(); + + var launchSettingsPath = Path.Join(testInstance.Path, "Properties", "launchSettings.json"); + + File.WriteAllText(launchSettingsPath, """ + { + "profiles": { + "Execute": { + "commandName": "Executable", + "executablePath": "dotnet", + "commandLineArgs": "--version" + } + } + } + """); + + CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) + .WithWorkingDirectory(testInstance.Path) + .Execute( + TestCommandDefinition.ConfigurationOption.Name, TestingConstants.Debug); + + result.StdOut.Should() + .Contain("Using launch settings from") + .And.Contain($"Properties{Path.DirectorySeparatorChar}launchSettings.json...") + .And.Contain("FAILED to find argument from launchSettings.json"); + } + [InlineData(TestingConstants.Debug)] [InlineData(TestingConstants.Release)] [Theory] public void RunTestProjectWithTestsAndNoLaunchSettingsArguments_ShouldReturnExitCodeSuccess(string configuration) { - TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithLaunchSettings", Guid.NewGuid().ToString()) + TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectWithLaunchSettings", identifier: configuration) .WithSource(); CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) diff --git a/test/dotnet.Tests/ProjectTools/LaunchSettingsParserTests.cs b/test/dotnet.Tests/ProjectTools/LaunchSettingsParserTests.cs new file mode 100644 index 000000000000..fd6041587b5e --- /dev/null +++ b/test/dotnet.Tests/ProjectTools/LaunchSettingsParserTests.cs @@ -0,0 +1,153 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; + +namespace Microsoft.DotNet.ProjectTools.Tests; + +public class LaunchSettingsParserTests +{ + private static readonly string s_environmentVariableName1 = $"TEST_VAR1_{GetUniqueName()}"; + private static readonly string s_environmentVariableName2 = $"TEST_VAR1_{GetUniqueName()}"; + private static readonly string s_environmentVariableNameUnset = $"TEST_VAR3_{GetUniqueName()}"; + + static LaunchSettingsParserTests() + { + Environment.SetEnvironmentVariable(s_environmentVariableName1, "ENV_VALUE1"); + Environment.SetEnvironmentVariable(s_environmentVariableName2, "ENV_VALUE2"); + } + + // The same syntax works on Windows and Unix ($VAR does not get expanded Unix). + private static string EnvironmentVariableReference(string name) + => $"%{name}%"; + + private static string GetUniqueName() + => Guid.NewGuid().ToString("N"); + + [Fact] + public void MissingExecutablePath() + { + var parser = ExecutableLaunchProfileParser.Instance; + + Assert.Throws(() => parser.ParseProfile("path", "Execute", """ + { + "commandName": "Executable" + } + """)); + } + + [Theory] + [InlineData("true", true)] + [InlineData("false", false)] + public void DotNetRunMessages_Executable(string value, bool expected) + { + var parser = ExecutableLaunchProfileParser.Instance; + + var result = parser.ParseProfile("path", "name", $$""" + { + "commandName": "Executable", + "executablePath": "executable", + "dotnetRunMessages": {{value}} + } + """); + + Assert.True(result.Successful); + Assert.NotNull(result.Profile); + Assert.Equal(expected, result.Profile.DotNetRunMessages); + } + + [Fact] + public void DotNetRunMessages_Error_Executable() + { + var parser = ProjectLaunchProfileParser.Instance; + + Assert.Throws(() => parser.ParseProfile("path", "name", $$""" + { + "commandName": "Executable", + "executablePath": "executable", + "dotnetRunMessages": "true" + } + """)); + } + + [Fact] + public void DotNetRunMessages_Error_Project() + { + var parser = ProjectLaunchProfileParser.Instance; + + Assert.Throws(() => parser.ParseProfile("path", "name", $$""" + { + "commandName": "Project", + "dotnetRunMessages": "true" + } + """)); + } + + [Fact] + public void EnvironmentVariableExpansion_Executable() + { + var root = Path.GetTempPath(); + var dir = Path.Combine(root, Guid.NewGuid().ToString()); + var launchSettingsPath = Path.Combine(dir, "launchSettings.json"); + + var parser = ExecutableLaunchProfileParser.Instance; + + var settings = parser.ParseProfile(launchSettingsPath, "MyProfile", $$""" + { + "commandName": "Executable", + "executablePath": "../path/{{EnvironmentVariableReference(s_environmentVariableName1)}}/executable", + "commandLineArgs": "arg1 {{EnvironmentVariableReference(s_environmentVariableName1)}} arg3", + "workingDirectory": "{{Path.Combine("..", EnvironmentVariableReference(s_environmentVariableName1)).Replace("\\", "\\\\")}}", + "environmentVariables": { + "{{s_environmentVariableNameUnset}}": "{{EnvironmentVariableReference(s_environmentVariableName2)}}", + "VAR1": "{{EnvironmentVariableReference(s_environmentVariableNameUnset)}}", + "VAR2": "ENV_VALUE2" + } + } + """); + + var model = Assert.IsType(settings.Profile); + + Assert.Equal("../path/ENV_VALUE1/executable", model.ExecutablePath); + Assert.Equal(Path.Combine(root, "ENV_VALUE1"), model.WorkingDirectory); + Assert.Equal("arg1 ENV_VALUE1 arg3", model.CommandLineArgs); + Assert.Equal( + [ + (s_environmentVariableNameUnset, "ENV_VALUE2"), + ("VAR1", EnvironmentVariableReference(s_environmentVariableNameUnset)), + ("VAR2", "ENV_VALUE2") + ], model.EnvironmentVariables.OrderBy(e => e.Key).Select(e => (e.Key, e.Value))); + } + + [Fact] + public void EnvironmentVariableExpansion_Project() + { + var root = Path.GetTempPath(); + var dir = Path.Combine(root, Guid.NewGuid().ToString()); + var launchSettingsPath = Path.Combine(dir, "launchSettings.json"); + + var parser = ProjectLaunchProfileParser.Instance; + + var settings = parser.ParseProfile(launchSettingsPath, "MyProfile", $$""" + { + "commandName": "Project", + "commandLineArgs": "arg1 {{EnvironmentVariableReference(s_environmentVariableName1)}} arg3", + "environmentVariables": { + "{{s_environmentVariableNameUnset}}": "{{EnvironmentVariableReference(s_environmentVariableName2)}}", + "VAR1": "{{EnvironmentVariableReference(s_environmentVariableNameUnset)}}", + "VAR2": "ENV_VALUE2" + } + } + """); + + var model = Assert.IsType(settings.Profile); + + Assert.Equal("arg1 ENV_VALUE1 arg3", model.CommandLineArgs); + Assert.Equal( + [ + (s_environmentVariableNameUnset, "ENV_VALUE2"), + ("VAR1", EnvironmentVariableReference(s_environmentVariableNameUnset)), + ("VAR2", "ENV_VALUE2") + ], model.EnvironmentVariables.OrderBy(e => e.Key).Select(e => (e.Key, e.Value))); + } +} diff --git a/test/dotnet.Tests/dotnet.Tests.csproj b/test/dotnet.Tests/dotnet.Tests.csproj index b249467a662b..d5c8e9a22b22 100644 --- a/test/dotnet.Tests/dotnet.Tests.csproj +++ b/test/dotnet.Tests/dotnet.Tests.csproj @@ -110,6 +110,6 @@ - +