Skip to content

Commit 79c777a

Browse files
authored
Fix building slnf with @ in the path (#11421)
* fix building slnf with @ in the path * use Path.GetFullPath(Path.Combine( )) instead of Path.GetFullPat(str1, str2) because it is not available for .net framework * add more symbols to the test @, %, $ * normalize solution path in the slnf before combinening with directory path * add escape param to FileUtilities.GetFulllPath * be more specific in doc comment for FileUtilities.GetFullPath * Clarify GetFullPath escape behavior * Add comment to preserve special symbols in path
1 parent 00b81e7 commit 79c777a

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

src/Build.UnitTests/Construction/SolutionFilter_Tests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,56 @@ public void ParseSolutionFilter(bool convertToSlnx)
284284
}
285285
}
286286

287+
[Fact]
288+
public void SolutionFilterWithSpecialSymbolInThePath()
289+
{
290+
using TestEnvironment testEnvironment = TestEnvironment.Create();
291+
TransientTestFolder folder = testEnvironment.CreateFolder(createFolder: true);
292+
// Create folder with special symbols in the name
293+
folder = testEnvironment.CreateFolder(Path.Combine(folder.Path, $"test@folder%special$symbols"), createFolder: true);
294+
// Create simple solution and simple solution filter
295+
TransientTestFile sln = testEnvironment.CreateFile(folder, "SimpleSolution.sln",
296+
"""
297+
Microsoft Visual Studio Solution File, Format Version 12.00
298+
# Visual Studio Version 17
299+
VisualStudioVersion = 17.0.31903.59
300+
MinimumVisualStudioVersion = 10.0.40219.1
301+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolutionTest", "SolutionTest.csproj", "{767AA460-C33F-41C3-A8B6-4DA283263A51}"
302+
EndProject
303+
Global
304+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
305+
Debug|Any CPU = Debug|Any CPU
306+
Release|Any CPU = Release|Any CPU
307+
EndGlobalSection
308+
GlobalSection(SolutionProperties) = preSolution
309+
HideSolutionNode = FALSE
310+
EndGlobalSection
311+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
312+
{767AA460-C33F-41C3-A8B6-4DA283263A51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
313+
{767AA460-C33F-41C3-A8B6-4DA283263A51}.Debug|Any CPU.Build.0 = Debug|Any CPU
314+
{767AA460-C33F-41C3-A8B6-4DA283263A51}.Release|Any CPU.ActiveCfg = Release|Any CPU
315+
{767AA460-C33F-41C3-A8B6-4DA283263A51}.Release|Any CPU.Build.0 = Release|Any CPU
316+
EndGlobalSection
317+
EndGlobal
318+
""");
319+
TransientTestFile slnf = testEnvironment.CreateFile(folder, "SimpleSolution.slnf",
320+
"""
321+
{
322+
"solution": {
323+
"path": "SimpleSolution.sln",
324+
"projects": [
325+
"SolutionTest.csproj"
326+
]
327+
}
328+
}
329+
""");
330+
331+
SolutionFile sp = SolutionFile.Parse(slnf.Path);
332+
333+
// just assert that no error is thrown
334+
Assert.True(sp.ProjectShouldBuild("SolutionTest.csproj"));
335+
}
336+
287337
private static string ConvertToSlnx(string slnPath)
288338
{
289339
string slnxPath = slnPath + "x";

src/Build/Construction/Solution/SolutionFile.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,8 @@ internal static string ParseSolutionFromSolutionFilter(string solutionFilterFile
658658
JsonDocumentOptions options = new JsonDocumentOptions() { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip };
659659
JsonDocument text = JsonDocument.Parse(File.ReadAllText(solutionFilterFile), options);
660660
solution = text.RootElement.GetProperty("solution");
661-
return FileUtilities.GetFullPath(solution.GetProperty("path").GetString(), Path.GetDirectoryName(solutionFilterFile));
661+
// We do NOT want to escape in order to preserve symbols like @, %, $ etc.
662+
return FileUtilities.GetFullPath(solution.GetProperty("path").GetString(), Path.GetDirectoryName(solutionFilterFile), escape: false);
662663
}
663664
catch (Exception e) when (e is JsonException || e is KeyNotFoundException || e is InvalidOperationException)
664665
{

src/Shared/FileUtilities.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -752,14 +752,20 @@ internal static bool HasExtension(string fileName, string[] allowedExtensions)
752752
/// </summary>
753753
/// <param name="fileSpec">The file spec to get the full path of.</param>
754754
/// <param name="currentDirectory"></param>
755-
/// <returns>full path</returns>
756-
internal static string GetFullPath(string fileSpec, string currentDirectory)
755+
/// <param name="escape">Whether to escape the path after getting the full path.</param>
756+
/// <returns>Full path to the file, escaped if not specified otherwise.</returns>
757+
internal static string GetFullPath(string fileSpec, string currentDirectory, bool escape = true)
757758
{
758759
// Sending data out of the engine into the filesystem, so time to unescape.
759760
fileSpec = FixFilePath(EscapingUtilities.UnescapeAll(fileSpec));
760761

761-
// Data coming back from the filesystem into the engine, so time to escape it back.
762-
string fullPath = EscapingUtilities.Escape(NormalizePath(Path.Combine(currentDirectory, fileSpec)));
762+
string fullPath = NormalizePath(Path.Combine(currentDirectory, fileSpec));
763+
// In some cases we might want to NOT escape in order to preserve symbols like @, %, $ etc.
764+
if (escape)
765+
{
766+
// Data coming back from the filesystem into the engine, so time to escape it back.
767+
fullPath = EscapingUtilities.Escape(fullPath);
768+
}
763769

764770
if (NativeMethodsShared.IsWindows && !EndsWithSlash(fullPath))
765771
{

0 commit comments

Comments
 (0)