Skip to content

Commit 59d74d4

Browse files
committed
Merge LaunchSettingsLocator and LaunchSettingsManager into LaunchSettings
1 parent 6c87075 commit 59d74d4

File tree

6 files changed

+191
-211
lines changed

6 files changed

+191
-211
lines changed

src/BuiltInTools/Watch/Process/LaunchSettingsProfile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ internal sealed class LaunchSettingsProfile
2525

2626
internal static LaunchSettingsProfile? ReadLaunchProfile(string projectPath, string? launchProfileName, ILogger logger)
2727
{
28-
var launchSettingsPath = LaunchSettingsLocator.TryFindLaunchSettings(projectPath, launchProfileName, (message, isError) =>
28+
var launchSettingsPath = LaunchSettingsManager.TryFindLaunchSettingsFile(projectPath, launchProfileName, (message, isError) =>
2929
{
3030
if (isError)
3131
{

src/Cli/dotnet/Commands/Run/RunCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ internal LaunchProfileSettings ReadLaunchProfileSettings()
341341

342342
var launchSettingsPath = ReadCodeFromStdin
343343
? null
344-
: LaunchSettingsLocator.TryFindLaunchSettings(
344+
: LaunchSettingsManager.TryFindLaunchSettingsFile(
345345
projectOrEntryPointFilePath: ProjectFileFullPath ?? EntryPointFileFullPath!,
346346
launchProfile: LaunchProfile,
347347
static (message, isError) => (isError ? Reporter.Error : Reporter.Output).WriteLine(message));

src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,10 +378,10 @@ static RunProperties GetRunProperties(ProjectInstance project)
378378
return null;
379379
}
380380

381-
var launchSettingsPath = LaunchSettingsLocator.GetPropertiesLaunchSettingsPath(projectDirectory, appDesignerFolder);
381+
var launchSettingsPath = LaunchSettingsManager.GetPropertiesLaunchSettingsPath(projectDirectory, appDesignerFolder);
382382
bool hasLaunchSettings = File.Exists(launchSettingsPath);
383383

384-
var runJsonPath = LaunchSettingsLocator.GetFlatLaunchSettingsPath(projectDirectory, projectNameWithoutExtension);
384+
var runJsonPath = LaunchSettingsManager.GetFlatLaunchSettingsPath(projectDirectory, projectNameWithoutExtension);
385385
bool hasRunJson = File.Exists(runJsonPath);
386386

387387
if (hasLaunchSettings)

src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchSettings.cs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,203 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Immutable;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Text.Json;
58

69
namespace Microsoft.DotNet.ProjectTools;
710

811
public abstract class LaunchSettings
912
{
13+
private const string ProfilesKey = "profiles";
14+
private const string CommandNameKey = "commandName";
15+
16+
private static readonly IReadOnlyDictionary<string, LaunchProfileParser> s_providers = new Dictionary<string, LaunchProfileParser>
17+
{
18+
{ ProjectLaunchSettingsParser.CommandName, ProjectLaunchSettingsParser.Instance },
19+
{ ExecutableLaunchSettingsParser.CommandName, ExecutableLaunchSettingsParser.Instance }
20+
};
21+
22+
public static IEnumerable<string> SupportedProfileTypes => s_providers.Keys;
23+
1024
public string? LaunchProfileName { get; init; }
1125

1226
public bool DotNetRunMessages { get; init; }
1327

1428
public string? CommandLineArgs { get; init; }
1529

1630
public required ImmutableDictionary<string, string> EnvironmentVariables { get; init; }
31+
32+
public static string GetPropertiesLaunchSettingsPath(string directoryPath, string propertiesDirectoryName)
33+
=> Path.Combine(directoryPath, propertiesDirectoryName, "launchSettings.json");
34+
35+
public static string GetFlatLaunchSettingsPath(string directoryPath, string projectNameWithoutExtension)
36+
=> Path.Join(directoryPath, $"{projectNameWithoutExtension}.run.json");
37+
38+
public static string? TryFindLaunchSettingsFile(string projectOrEntryPointFilePath, string? launchProfile, Action<string, bool> report)
39+
{
40+
var buildPathContainer = Path.GetDirectoryName(projectOrEntryPointFilePath);
41+
Debug.Assert(buildPathContainer != null);
42+
43+
// VB.NET projects store the launch settings file in the
44+
// "My Project" directory instead of a "Properties" directory.
45+
// TODO: use the `AppDesignerFolder` MSBuild property instead, which captures this logic already
46+
var propsDirectory = string.Equals(Path.GetExtension(projectOrEntryPointFilePath), ".vbproj", StringComparison.OrdinalIgnoreCase)
47+
? "My Project"
48+
: "Properties";
49+
50+
string launchSettingsPath = GetPropertiesLaunchSettingsPath(buildPathContainer, propsDirectory);
51+
bool hasLaunchSetttings = File.Exists(launchSettingsPath);
52+
53+
string appName = Path.GetFileNameWithoutExtension(projectOrEntryPointFilePath);
54+
string runJsonPath = GetFlatLaunchSettingsPath(buildPathContainer, appName);
55+
bool hasRunJson = File.Exists(runJsonPath);
56+
57+
if (hasLaunchSetttings)
58+
{
59+
if (hasRunJson)
60+
{
61+
report(string.Format(Resources.RunCommandWarningRunJsonNotUsed, runJsonPath, launchSettingsPath), false);
62+
}
63+
64+
return launchSettingsPath;
65+
}
66+
67+
if (hasRunJson)
68+
{
69+
return runJsonPath;
70+
}
71+
72+
if (!string.IsNullOrEmpty(launchProfile))
73+
{
74+
report(string.Format(Resources.RunCommandExceptionCouldNotLocateALaunchSettingsFile, launchProfile, $"""
75+
{launchSettingsPath}
76+
{runJsonPath}
77+
"""), true);
78+
}
79+
80+
return null;
81+
}
82+
83+
public static LaunchProfileSettings ReadProfileSettingsFromFile(string launchSettingsPath, string? profileName = null)
84+
{
85+
try
86+
{
87+
var launchSettingsJsonContents = File.ReadAllText(launchSettingsPath);
88+
89+
var jsonDocumentOptions = new JsonDocumentOptions
90+
{
91+
CommentHandling = JsonCommentHandling.Skip,
92+
AllowTrailingCommas = true,
93+
};
94+
95+
using (var document = JsonDocument.Parse(launchSettingsJsonContents, jsonDocumentOptions))
96+
{
97+
var model = document.RootElement;
98+
99+
if (model.ValueKind != JsonValueKind.Object || !model.TryGetProperty(ProfilesKey, out var profilesObject) || profilesObject.ValueKind != JsonValueKind.Object)
100+
{
101+
return LaunchProfileSettings.Failure(Resources.LaunchProfilesCollectionIsNotAJsonObject);
102+
}
103+
104+
var selectedProfileName = profileName;
105+
JsonElement profileObject;
106+
if (string.IsNullOrEmpty(profileName))
107+
{
108+
var firstProfileProperty = profilesObject.EnumerateObject().FirstOrDefault(IsDefaultProfileType);
109+
selectedProfileName = firstProfileProperty.Value.ValueKind == JsonValueKind.Object ? firstProfileProperty.Name : null;
110+
profileObject = firstProfileProperty.Value;
111+
}
112+
else // Find a profile match for the given profileName
113+
{
114+
IEnumerable<JsonProperty> caseInsensitiveProfileMatches = [.. profilesObject
115+
.EnumerateObject() // p.Name shouldn't fail, as profileObject enumerables here are only created from an existing JsonObject
116+
.Where(p => string.Equals(p.Name, profileName, StringComparison.OrdinalIgnoreCase))];
117+
118+
if (caseInsensitiveProfileMatches.Count() > 1)
119+
{
120+
return LaunchProfileSettings.Failure(string.Format(Resources.DuplicateCaseInsensitiveLaunchProfileNames,
121+
string.Join(",\n", caseInsensitiveProfileMatches.Select(p => $"\t{p.Name}"))));
122+
}
123+
124+
if (!caseInsensitiveProfileMatches.Any())
125+
{
126+
return LaunchProfileSettings.Failure(string.Format(Resources.LaunchProfileDoesNotExist, profileName));
127+
}
128+
129+
profileObject = profilesObject.GetProperty(caseInsensitiveProfileMatches.First().Name);
130+
131+
if (profileObject.ValueKind != JsonValueKind.Object)
132+
{
133+
return LaunchProfileSettings.Failure(Resources.LaunchProfileIsNotAJsonObject);
134+
}
135+
}
136+
137+
if (profileObject.ValueKind == default)
138+
{
139+
foreach (var prop in profilesObject.EnumerateObject())
140+
{
141+
if (prop.Value.ValueKind == JsonValueKind.Object)
142+
{
143+
if (prop.Value.TryGetProperty(CommandNameKey, out var commandNameElement) && commandNameElement.ValueKind == JsonValueKind.String)
144+
{
145+
if (commandNameElement.GetString() is { } commandNameElementKey && s_providers.ContainsKey(commandNameElementKey))
146+
{
147+
profileObject = prop.Value;
148+
break;
149+
}
150+
}
151+
}
152+
}
153+
}
154+
155+
if (profileObject.ValueKind == default)
156+
{
157+
return LaunchProfileSettings.Failure(Resources.UsableLaunchProfileCannotBeLocated);
158+
}
159+
160+
if (!profileObject.TryGetProperty(CommandNameKey, out var finalCommandNameElement)
161+
|| finalCommandNameElement.ValueKind != JsonValueKind.String)
162+
{
163+
return LaunchProfileSettings.Failure(Resources.UsableLaunchProfileCannotBeLocated);
164+
}
165+
166+
string? commandName = finalCommandNameElement.GetString();
167+
if (!TryLocateHandler(commandName, out LaunchProfileParser? provider))
168+
{
169+
return LaunchProfileSettings.Failure(string.Format(Resources.LaunchProfileHandlerCannotBeLocated, commandName));
170+
}
171+
172+
return provider.ParseProfile(launchSettingsPath, selectedProfileName, profileObject.GetRawText());
173+
}
174+
}
175+
catch (Exception ex) when (ex is JsonException or IOException)
176+
{
177+
return LaunchProfileSettings.Failure(string.Format(Resources.DeserializationExceptionMessage, launchSettingsPath, ex.Message));
178+
}
179+
}
180+
181+
private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)] out LaunchProfileParser? provider)
182+
{
183+
if (commandName == null)
184+
{
185+
provider = null;
186+
return false;
187+
}
188+
189+
return s_providers.TryGetValue(commandName, out provider);
190+
}
191+
192+
private static bool IsDefaultProfileType(JsonProperty profileProperty)
193+
{
194+
if (profileProperty.Value.ValueKind != JsonValueKind.Object
195+
|| !profileProperty.Value.TryGetProperty(CommandNameKey, out var commandNameElement)
196+
|| commandNameElement.ValueKind != JsonValueKind.String)
197+
{
198+
return false;
199+
}
200+
201+
var commandName = commandNameElement.GetString();
202+
return commandName != null && s_providers.ContainsKey(commandName);
203+
}
17204
}

src/Microsoft.DotNet.ProjectTools/LaunchSettings/LaunchSettingsLocator.cs

Lines changed: 0 additions & 60 deletions
This file was deleted.

0 commit comments

Comments
 (0)