-
Notifications
You must be signed in to change notification settings - Fork 11
Description
Run the following program:
using System.Text;
using Microsoft.VisualStudio.SolutionPersistence.Model;
using Microsoft.VisualStudio.SolutionPersistence.Serializer;
var solutionModel = new SolutionModel();
solutionModel.AddProject(@"Core\Lib1.csproj");
solutionModel.AddProject(@"Core/Lib1.csproj");
Console.WriteLine("After adding a project:");
Console.WriteLine("Projects: " + string.Join(", ", solutionModel.SolutionProjects.Select(x => x.FilePath)));
using var memoryStream = new MemoryStream();
await SolutionSerializers.SlnXml.SaveAsync(memoryStream, solutionModel, CancellationToken.None);
memoryStream.Position = 0;
solutionModel = await SolutionSerializers.SlnXml.OpenAsync(memoryStream, CancellationToken.None);
Console.WriteLine("After saving and re-opening the solution:");
Console.WriteLine("Projects: " + string.Join(", ", solutionModel.SolutionProjects.Select(x => x.FilePath)));Actual result:
After adding a project:
Projects: Core\Lib1.csproj, Core/Lib1.csproj
Unhandled exception. Microsoft.VisualStudio.SolutionPersistence.Model.SolutionException: Duplicate item 'Core/Lib1.csproj' of type 'Project'. (Parameter 'value')
You can add projects that share the same path that only differ on directory separator characters and solution model will see them as different projects.
However, the model just adds two projects that are effectively the same until you attempt to serialize and deserialize it back - then it normalizes the paths and finally notices the duplication
Expected result:
Either, the model rejects project paths with \ like it does for solution folders,
or the model automatically normalizes the separators
Notes:
The current behavior basically makes it users' responsibility to conform to the undocumented standard path format - I've only found this documentation on an internal static method that acknowledges that the model expects / directory separators.
vs-solutionpersistence/src/Microsoft.VisualStudio.SolutionPersistence/Utilities/PathExtensions.cs
Lines 10 to 20 in a5c8b17
| /// <summary> | |
| /// Converts a serialized path that uses backslashes to a model path that uses the platform's directory separator. | |
| /// This is used by the .sln serializer. | |
| /// </summary> | |
| [return: NotNullIfNotNull(nameof(persistencePath))] | |
| internal static string? ConvertBackslashToModel(string? persistencePath) | |
| { | |
| return persistencePath.IsNullOrEmpty() || IsWindows || !persistencePath.Contains('\\') ? | |
| persistencePath : | |
| persistencePath.Replace('\\', Path.DirectorySeparatorChar); | |
| } |
This normalization also depends on PathExtensions.IsWindows which leads to solution file being inconsistently serialized on different OSs - the following code has different output based on which OS is running it:
using System.Text;
using Microsoft.VisualStudio.SolutionPersistence.Model;
using Microsoft.VisualStudio.SolutionPersistence.Serializer;
var solutionModel = new SolutionModel();
solutionModel.AddProject(@"Core\ConsoleApp.csproj");
using var memoryStream = new MemoryStream();
await SolutionSerializers.SlnXml.SaveAsync(memoryStream, solutionModel, CancellationToken.None);
memoryStream.Position = 0;
using var reader = new StreamReader(memoryStream, new UTF8Encoding(false), leaveOpen: true);
Console.WriteLine(reader.ReadToEnd());On macOS:
<Solution>
<Project Path="Core\ConsoleApp.csproj" />
</Solution>
On Windows:
<Solution>
<Project Path="Core/ConsoleApp.csproj" />
</Solution>
Note that the directory separator got normalized to / on Windows but not on macOS - unless you normalize the paths yourself before adding a project to the solution model, the solution file will depend on the OS of the user