From b53af55462b3eb2429739af3a14455479279506d Mon Sep 17 00:00:00 2001 From: samuelll3d <105659428+samuelll3d@users.noreply.github.com> Date: Sat, 28 Jun 2025 15:02:46 +0300 Subject: [PATCH] Find solution file by project assembly name Instead of looking for a solution file with the same name as the project and its assembly, this commit updates the logic to find all .sln and .slnx files in the specified solution directory. If no matching solution is found, it will fall back to the old behaviour. This commit will also consider .. markings to go up one directory level allowing for Godot projects as part of multi-project solutions. Co-authored-by: Eric Johnson --- .../GodotTools/GodotTools/GodotTools.csproj | 1 + .../GodotTools/Internals/GodotSharpDirs.cs | 68 +++++++++++++++++-- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index 6d078ef18b..3a3d1277f9 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 3b761577ae..52d628fd01 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;