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 řá