Skip to content

Commit 1c7f1b2

Browse files
committed
Merge pull request #106031 from samuelll3d/fix-slnpath-resolve
C#: Fix loading correct solution file for projects in subdirectories
2 parents 19aeefd + b53af55 commit 1c7f1b2

File tree

2 files changed

+63
-6
lines changed

2 files changed

+63
-6
lines changed

modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
3838
<PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.12" />
3939
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
40+
<PackageReference Include="Microsoft.VisualStudio.SolutionPersistence" Version="1.0.52" />
4041
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
4142
<Reference Include="GodotSharp">
4243
<HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath>

modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
13
using System.Diagnostics.CodeAnalysis;
24
using System.IO;
5+
using System.Threading;
36
using Godot;
47
using Godot.NativeInterop;
5-
using GodotTools.Core;
6-
using static GodotTools.Internals.Globals;
8+
using Microsoft.VisualStudio.SolutionPersistence;
9+
using Microsoft.VisualStudio.SolutionPersistence.Serializer;
710

811
namespace GodotTools.Internals
912
{
@@ -73,17 +76,70 @@ public static void DetermineProjectLocation()
7376
string? slnParentDir = (string?)ProjectSettings.GetSetting("dotnet/project/solution_directory");
7477
if (string.IsNullOrEmpty(slnParentDir))
7578
slnParentDir = "res://";
76-
else if (!slnParentDir.StartsWith("res://", System.StringComparison.Ordinal))
79+
else if (!slnParentDir.StartsWith("res://", StringComparison.Ordinal))
7780
slnParentDir = "res://" + slnParentDir;
7881

7982
// The csproj should be in the same folder as project.godot.
8083
string csprojParentDir = "res://";
8184

82-
_projectSlnPath = Path.Combine(ProjectSettings.GlobalizePath(slnParentDir),
83-
string.Concat(_projectAssemblyName, ".sln"));
84-
85+
// Set csproj path first and use it to find the sln/slnx file with the assembly
8586
_projectCsProjPath = Path.Combine(ProjectSettings.GlobalizePath(csprojParentDir),
8687
string.Concat(_projectAssemblyName, ".csproj"));
88+
89+
_projectSlnPath = FindSolutionFileWithAssemblyName(slnParentDir, _projectAssemblyName);
90+
}
91+
92+
private static string FindSolutionFileWithAssemblyName(string directory, string assemblyName)
93+
{
94+
// Will convert ".." to load solutions from parent directory when appropriate
95+
string slnAbsolutePath = Path.GetFullPath(ProjectSettings.GlobalizePath(directory));
96+
97+
List<string> solutionFilePaths = new();
98+
solutionFilePaths.AddRange(Directory.GetFiles(slnAbsolutePath, "*.sln"));
99+
solutionFilePaths.AddRange(Directory.GetFiles(slnAbsolutePath, "*.slnx"));
100+
101+
if (solutionFilePaths.Count == 0)
102+
return Path.Combine(slnAbsolutePath, $"{assemblyName}.sln");
103+
104+
List<string> matchingSolutions = new();
105+
106+
foreach (string solutionFilePath in solutionFilePaths)
107+
{
108+
ISolutionSerializer? serializer = SolutionSerializers.GetSerializerByMoniker(solutionFilePath);
109+
if (serializer is null)
110+
continue;
111+
112+
string? solutionDirectory = Path.GetDirectoryName(solutionFilePath);
113+
if (solutionDirectory is null)
114+
continue;
115+
116+
var solution = serializer.OpenAsync(solutionFilePath, CancellationToken.None).Result;
117+
118+
foreach (var project in solution.SolutionProjects)
119+
{
120+
// Convert '\' path separators on Windows to '/' to match Godot's Unix style separators
121+
var absoluteProjectFilePath = Path.GetFullPath(project.FilePath, solutionDirectory).Replace('\\', '/');
122+
123+
if (string.Equals(absoluteProjectFilePath, _projectCsProjPath, StringComparison.Ordinal))
124+
matchingSolutions.Add(solutionFilePath);
125+
}
126+
}
127+
128+
switch (matchingSolutions.Count)
129+
{
130+
case 1:
131+
return matchingSolutions[0];
132+
133+
case > 1:
134+
GD.PushError(
135+
$"Multiple solutions containing a project with assembly name '{assemblyName}' were found:\n"
136+
+ $"{string.Join('\n', matchingSolutions).Replace('\\', '/')}\n"
137+
+ "Please ensure only one solution contains the project assembly.\n"
138+
+ "If you have recently migrated to .slnx please ensure that you have removed the unused .sln.");
139+
break;
140+
}
141+
142+
return Path.Combine(slnAbsolutePath, $"{assemblyName}.sln");
87143
}
88144

89145
private static string? _projectAssemblyName;

0 commit comments

Comments
 (0)