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;