diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index 6d078ef18b0..3a3d1277f94 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -37,6 +37,7 @@
+
$(GodotApiAssembliesDir)/GodotSharp.dll
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
index 3b761577ae8..52d628fd014 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
@@ -1,9 +1,12 @@
+using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Threading;
using Godot;
using Godot.NativeInterop;
-using GodotTools.Core;
-using static GodotTools.Internals.Globals;
+using Microsoft.VisualStudio.SolutionPersistence;
+using Microsoft.VisualStudio.SolutionPersistence.Serializer;
namespace GodotTools.Internals
{
@@ -73,17 +76,70 @@ namespace GodotTools.Internals
string? slnParentDir = (string?)ProjectSettings.GetSetting("dotnet/project/solution_directory");
if (string.IsNullOrEmpty(slnParentDir))
slnParentDir = "res://";
- else if (!slnParentDir.StartsWith("res://", System.StringComparison.Ordinal))
+ else if (!slnParentDir.StartsWith("res://", StringComparison.Ordinal))
slnParentDir = "res://" + slnParentDir;
// The csproj should be in the same folder as project.godot.
string csprojParentDir = "res://";
- _projectSlnPath = Path.Combine(ProjectSettings.GlobalizePath(slnParentDir),
- string.Concat(_projectAssemblyName, ".sln"));
-
+ // Set csproj path first and use it to find the sln/slnx file with the assembly
_projectCsProjPath = Path.Combine(ProjectSettings.GlobalizePath(csprojParentDir),
string.Concat(_projectAssemblyName, ".csproj"));
+
+ _projectSlnPath = FindSolutionFileWithAssemblyName(slnParentDir, _projectAssemblyName);
+ }
+
+ private static string FindSolutionFileWithAssemblyName(string directory, string assemblyName)
+ {
+ // Will convert ".." to load solutions from parent directory when appropriate
+ string slnAbsolutePath = Path.GetFullPath(ProjectSettings.GlobalizePath(directory));
+
+ List solutionFilePaths = new();
+ solutionFilePaths.AddRange(Directory.GetFiles(slnAbsolutePath, "*.sln"));
+ solutionFilePaths.AddRange(Directory.GetFiles(slnAbsolutePath, "*.slnx"));
+
+ if (solutionFilePaths.Count == 0)
+ return Path.Combine(slnAbsolutePath, $"{assemblyName}.sln");
+
+ List matchingSolutions = new();
+
+ foreach (string solutionFilePath in solutionFilePaths)
+ {
+ ISolutionSerializer? serializer = SolutionSerializers.GetSerializerByMoniker(solutionFilePath);
+ if (serializer is null)
+ continue;
+
+ string? solutionDirectory = Path.GetDirectoryName(solutionFilePath);
+ if (solutionDirectory is null)
+ continue;
+
+ var solution = serializer.OpenAsync(solutionFilePath, CancellationToken.None).Result;
+
+ foreach (var project in solution.SolutionProjects)
+ {
+ // Convert '\' path separators on Windows to '/' to match Godot's Unix style separators
+ var absoluteProjectFilePath = Path.GetFullPath(project.FilePath, solutionDirectory).Replace('\\', '/');
+
+ if (string.Equals(absoluteProjectFilePath, _projectCsProjPath, StringComparison.Ordinal))
+ matchingSolutions.Add(solutionFilePath);
+ }
+ }
+
+ switch (matchingSolutions.Count)
+ {
+ case 1:
+ return matchingSolutions[0];
+
+ case > 1:
+ GD.PushError(
+ $"Multiple solutions containing a project with assembly name '{assemblyName}' were found:\n"
+ + $"{string.Join('\n', matchingSolutions).Replace('\\', '/')}\n"
+ + "Please ensure only one solution contains the project assembly.\n"
+ + "If you have recently migrated to .slnx please ensure that you have removed the unused .sln.");
+ break;
+ }
+
+ return Path.Combine(slnAbsolutePath, $"{assemblyName}.sln");
}
private static string? _projectAssemblyName;