Skip to content

Commit

Permalink
Emit warning for projects detected in multiple drives (#549)
Browse files Browse the repository at this point in the history
* Emit warning for projects detected in multiple drives

* PR feedback
  • Loading branch information
mruxmohan4 authored Nov 16, 2023
1 parent 3d323fc commit 6cd5b1d
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 51 deletions.
65 changes: 50 additions & 15 deletions src/Microsoft.VisualStudio.SlnGen.UnitTests/SlnFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Xunit;

namespace Microsoft.VisualStudio.SlnGen.UnitTests
Expand Down Expand Up @@ -106,7 +107,7 @@ public void CustomConfigurationAndPlatforms()

string solutionFilePath = GetTempFileName();

slnFile.Save(solutionFilePath, useFolders: false);
slnFile.Save(solutionFilePath, useFolders: false, new TestLogger());

SolutionFile solutionFile = SolutionFile.Parse(solutionFilePath);

Expand Down Expand Up @@ -193,7 +194,7 @@ public void CustomConfigurationAndPlatformsWithAlwaysBuildDisabled()

string solutionFilePath = GetTempFileName();

slnFile.Save(solutionFilePath, useFolders: false, alwaysBuild: false);
slnFile.Save(solutionFilePath, useFolders: false, new TestLogger(), alwaysBuild: false);

SolutionFile solutionFile = SolutionFile.Parse(solutionFilePath);

Expand Down Expand Up @@ -232,7 +233,7 @@ public void CustomConfigurationAndPlatforms_IgnoresInvalidValues()

string solutionFilePath = GetTempFileName();

slnFile.Save(solutionFilePath, useFolders: false);
slnFile.Save(solutionFilePath, useFolders: false, new TestLogger());

SolutionFile solutionFile = SolutionFile.Parse(solutionFilePath);

Expand Down Expand Up @@ -314,7 +315,7 @@ public void CustomConfigurationAndPlatforms_MapsAnyCPU()

string solutionFilePath = GetTempFileName();

slnFile.Save(solutionFilePath, useFolders: false);
slnFile.Save(solutionFilePath, useFolders: false, new TestLogger());

SolutionFile solutionFile = SolutionFile.Parse(solutionFilePath);

Expand Down Expand Up @@ -354,7 +355,7 @@ public void ExistingSolutionIsReused()

slnFile.AddProjects(new[] { project });

slnFile.Save(path, useFolders: false);
slnFile.Save(path, useFolders: false, new TestLogger());

SlnFile.TryParseExistingSolution(path, out Guid solutionGuid, out _).ShouldBeTrue();

Expand Down Expand Up @@ -554,7 +555,7 @@ public void ProjectConfigurationPlatformOrderingSameAsProjects()

string path = Path.GetTempFileName();

slnFile.Save(path, useFolders: false);
slnFile.Save(path, useFolders: false, new TestLogger());

string directoryName = new DirectoryInfo(TestRootPath).Name;

Expand Down Expand Up @@ -636,7 +637,7 @@ public void ProjectSolutionFolders()

slnFile.AddProjects(projects, new Dictionary<string, Guid>(), projects[1].FullPath);
slnFile.AddSolutionItems(solutionItems);
slnFile.Save(solutionFilePath, useFolders: false);
slnFile.Save(solutionFilePath, useFolders: false, new TestLogger());

SolutionFile s = SolutionFile.Parse(solutionFilePath);

Expand Down Expand Up @@ -672,7 +673,7 @@ public void SaveToCustomLocationCreatesDirectory()

SlnFile slnFile = new SlnFile();

slnFile.Save(fullPath, useFolders: false);
slnFile.Save(fullPath, useFolders: false, new TestLogger());

File.Exists(fullPath).ShouldBeTrue();
}
Expand Down Expand Up @@ -891,7 +892,7 @@ public void WithFoldersDoNotIgnoreMainProject()

slnFile.AddProjects(projects, new Dictionary<string, Guid>(), projects[1].FullPath);
slnFile.AddSolutionItems(solutionItems);
slnFile.Save(solutionFilePath, true);
slnFile.Save(solutionFilePath, true, new TestLogger());

SolutionFile solutionFile = SolutionFile.Parse(solutionFilePath);

Expand Down Expand Up @@ -949,7 +950,7 @@ public void WithFoldersIgnoreMainProject()

slnFile.AddProjects(projects, new Dictionary<string, Guid>());
slnFile.AddSolutionItems(solutionItems);
slnFile.Save(solutionFilePath, true);
slnFile.Save(solutionFilePath, true, new TestLogger());

SolutionFile solutionFile = SolutionFile.Parse(solutionFilePath);

Expand Down Expand Up @@ -1016,7 +1017,7 @@ public void WithFoldersDoesNotCreateRootFolder(bool ignoreMainProject, bool coll

slnFile.AddProjects(projects, new Dictionary<string, Guid>(), ignoreMainProject ? null : projects[1].FullPath);
slnFile.AddSolutionItems(solutionItems);
slnFile.Save(solutionFilePath, useFolders: true, collapseFolders: collapseFolders);
slnFile.Save(solutionFilePath, useFolders: true, new TestLogger(), collapseFolders: collapseFolders);

SolutionFile solutionFile = SolutionFile.Parse(solutionFilePath);

Expand Down Expand Up @@ -1072,7 +1073,7 @@ public void VisualStudioVersionIsWritten()
SolutionGuid = new Guid("{6370DE27-36B7-44AE-B47A-1ECF4A6D740A}"),
};

slnFile.Save(solutionFilePath, useFolders: false);
slnFile.Save(solutionFilePath, useFolders: false, new TestLogger());

File.ReadAllText(solutionFilePath).ShouldBe(
@"Microsoft Visual Studio Solution File, Format Version 12.00
Expand Down Expand Up @@ -1109,7 +1110,7 @@ public void Save_WithSolutionItemsAddedToSpecificFolder_SolutionItemsExistInSpec
slnFile.AddSolutionItems("docs", new[] { Path.Combine(this.TestRootPath, "README.md") });

// Act
slnFile.Save(solutionFilePath, useFolders: false);
slnFile.Save(solutionFilePath, useFolders: false, new TestLogger());

// Assert
File.ReadAllText(solutionFilePath).ShouldBe(
Expand All @@ -1135,6 +1136,40 @@ public void Save_WithSolutionItemsAddedToSpecificFolder_SolutionItemsExistInSpec
StringCompareShould.IgnoreLineEndings);
}

[Fact]
public void EmitWarningForProjectsOnMultipleDrives()
{
bool isWindowsPlatform = Utility.RunningOnWindows;
SlnProject projectA = new ()
{
Name = "ProjectA",
FullPath = isWindowsPlatform ? @"A:\ProjectA\ProjectA.vcxitems" : "/dev/ProjectA/ProjectA.vcxitems",
ProjectGuid = new Guid("C95D800E-F016-4167-8E1B-1D3FF94CE2E2"),
ProjectTypeGuid = new Guid("88152E7E-47E3-45C8-B5D3-DDB15B2F0435"),
};

SlnProject projectB = new ()
{
Name = "ProjectB",
FullPath = isWindowsPlatform ? @"B:\ProjectB\ProjectB.vcxitems" : "/mnt/ProjectB/ProjectB.vcxitems",
ProjectGuid = new Guid("EAD108BE-AC70-41E6-A8C3-450C545FDC0E"),
ProjectTypeGuid = new Guid("F38341C3-343F-421A-AE68-94CD9ADCD32F"),
};

TestLogger logger = new ();
SlnFile slnFile = new ();
SlnProject[] projects = new[] { projectA, projectB };
string solutionFilePath = @$"X:\{Path.GetRandomFileName()}";
StringBuilderTextWriter writer = new (new StringBuilder(), new List<string>());

slnFile.AddProjects(projects);
slnFile.Save(solutionFilePath, writer, useFolders: true, logger);

logger.Errors.Count.ShouldBe(0);
logger.Warnings.Count.ShouldBe(1);
logger.Warnings.FirstOrDefault().Message.ShouldContain("Detected folder on a different drive from the root solution path");
}

[Theory]
[InlineData(true)]
[InlineData(false)]
Expand All @@ -1155,7 +1190,7 @@ public void SlnProject_IsBuildable_ReflectedAsProjectConfigurationInSolutionIncl
};

slnFile.AddProjects(new[] { slnProject });
slnFile.Save(solutionFilePath, useFolders: false);
slnFile.Save(solutionFilePath, useFolders: false, new TestLogger());

ValidateProjectInSolution(
(slnProject, projectInSolution) =>
Expand All @@ -1178,7 +1213,7 @@ private void ValidateProjectInSolution(Action<SlnProject, ProjectInSolution> cus
SlnFile slnFile = new SlnFile();

slnFile.AddProjects(projects);
slnFile.Save(solutionFilePath, useFolders);
slnFile.Save(solutionFilePath, useFolders, new TestLogger());

SolutionFile solutionFile = SolutionFile.Parse(solutionFilePath);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation.
//
// Licensed under the MIT license.

using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Microsoft.VisualStudio.SlnGen.UnitTests
{
/// <summary>
/// Extends <see cref="TextWriter" /> for unit tests to capture writing text.
/// </summary>
public class StringBuilderTextWriter : TextWriter
{
private readonly List<string> _lines;
private readonly StringBuilder _lineStringBuilder = new StringBuilder();
private readonly StringBuilder _stringBuilder;

public StringBuilderTextWriter(StringBuilder stringBuilder, List<string> lines)
{
_stringBuilder = stringBuilder;
_lines = lines;
}

public override Encoding Encoding => Encoding.Unicode;

public override void Write(char value)
{
_lineStringBuilder.Append(value);

_stringBuilder.Append(value);

if (value == '\n')
{
_lines.Add(_lineStringBuilder.ToString());

_lineStringBuilder.Clear();
}
}
}
}
29 changes: 0 additions & 29 deletions src/Microsoft.VisualStudio.SlnGen.UnitTests/TestConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,34 +63,5 @@ public event ConsoleCancelEventHandler CancelKeyPress
public void ResetColor()
{
}

private class StringBuilderTextWriter : TextWriter
{
private readonly List<string> _lines;
private readonly StringBuilder _lineStringBuilder = new StringBuilder();
private readonly StringBuilder _stringBuilder;

public StringBuilderTextWriter(StringBuilder stringBuilder, List<string> lines)
{
_stringBuilder = stringBuilder;
_lines = lines;
}

public override Encoding Encoding => Encoding.Unicode;

public override void Write(char value)
{
_lineStringBuilder.Append(value);

_stringBuilder.Append(value);

if (value == '\n')
{
_lines.Add(_lineStringBuilder.ToString());

_lineStringBuilder.Clear();
}
}
}
}
}
31 changes: 25 additions & 6 deletions src/Microsoft.VisualStudio.SlnGen/SlnFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public static (string solutionFileFullPath, int customProjectTypeGuidCount, int

if (!logger.HasLoggedErrors)
{
solution.Save(solutionFileFullPath, arguments.EnableFolders(), arguments.EnableCollapseFolders(), arguments.EnableAlwaysBuild());
solution.Save(solutionFileFullPath, arguments.EnableFolders(), logger, arguments.EnableCollapseFolders(), arguments.EnableAlwaysBuild());
}

return (solutionFileFullPath, customProjectTypeGuids.Count, solutionItems.Count, solution.SolutionGuid);
Expand Down Expand Up @@ -362,9 +362,10 @@ public void AddSolutionItems(string folderPath, IEnumerable<string> items)
/// </summary>
/// <param name="path">The full path to the file to write to.</param>
/// <param name="useFolders">Specifies if folders should be created.</param>
/// <param name="logger">A <see cref="ISlnGenLogger" /> to use for logging.</param>
/// <param name="collapseFolders">An optional value indicating whether or not folders containing a single item should be collapsed into their parent folder.</param>
/// <param name="alwaysBuild">An optional value indicating whether or not to always include the project in the build even if it has no matching configuration.</param>
public void Save(string path, bool useFolders, bool collapseFolders = false, bool alwaysBuild = true)
public void Save(string path, bool useFolders, ISlnGenLogger logger, bool collapseFolders = false, bool alwaysBuild = true)
{
string directoryName = Path.GetDirectoryName(path);

Expand All @@ -377,7 +378,7 @@ public void Save(string path, bool useFolders, bool collapseFolders = false, boo

using StreamWriter writer = new StreamWriter(fileStream, Encoding.UTF8);

Save(path, writer, useFolders, collapseFolders, alwaysBuild);
Save(path, writer, useFolders, logger, collapseFolders, alwaysBuild);
}

/// <summary>
Expand All @@ -386,9 +387,10 @@ public void Save(string path, bool useFolders, bool collapseFolders = false, boo
/// <param name="rootPath">A root path for the solution to make other paths relative to.</param>
/// <param name="writer">The <see cref="TextWriter" /> to save the solution file to.</param>
/// <param name="useFolders">Specifies if folders should be created.</param>
/// <param name="logger">A <see cref="ISlnGenLogger" /> to use for logging.</param>
/// <param name="collapseFolders">An optional value indicating whether or not folders containing a single item should be collapsed into their parent folder.</param>
/// <param name="alwaysBuild">An optional value indicating whether or not to always include the project in the build even if it has no matching configuration.</param>
internal void Save(string rootPath, TextWriter writer, bool useFolders, bool collapseFolders = false, bool alwaysBuild = true)
internal void Save(string rootPath, TextWriter writer, bool useFolders, ISlnGenLogger logger, bool collapseFolders = false, bool alwaysBuild = true)
{
writer.WriteLine(Header, _fileFormatVersion);

Expand All @@ -400,7 +402,6 @@ internal void Save(string rootPath, TextWriter writer, bool useFolders, bool col
}

List<SlnProject> sortedProjects = _projects.OrderBy(i => i.IsMainProject ? 0 : 1).ThenBy(i => i.FullPath).ToList();

foreach (SlnProject project in sortedProjects)
{
string solutionPath = project.FullPath.ToRelativePath(rootPath).ToSolutionPath();
Expand Down Expand Up @@ -441,9 +442,27 @@ internal void Save(string rootPath, TextWriter writer, bool useFolders, bool col

if (hierarchy != null)
{
bool logDriveWarning = false;
string rootPathDrive = Path.GetPathRoot(rootPath);
foreach (SlnFolder folder in hierarchy.Folders)
{
string projectSolutionPath = (useFolders ? folder.FullPath.ToRelativePath(rootPath) : folder.FullPath).ToSolutionPath();
bool useSeparateDrive = false;
bool hasFullPath = !string.IsNullOrEmpty(folder.FullPath);
if (hasFullPath)
{
string folderPathDrive = Path.GetPathRoot(folder.FullPath);
if (!string.Equals(rootPathDrive, folderPathDrive, StringComparison.OrdinalIgnoreCase))
{
useSeparateDrive = true;
if (!logDriveWarning)
{
logger.LogWarning($"Detected folder on a different drive from the root solution path {rootPath}. This folder should not be committed to source control since it does not contain a simple, relative path and is not guaranteed to work across machines.");
logDriveWarning = true;
}
}
}

string projectSolutionPath = (useFolders && !useSeparateDrive && hasFullPath ? folder.FullPath.ToRelativePath(rootPath) : folder.FullPath).ToSolutionPath();

// Try to preserve the folder GUID if a matching relative folder path was parsed from an existing solution
if (ExistingProjectGuids != null && ExistingProjectGuids.TryGetValue(projectSolutionPath, out Guid projectGuid))
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.VisualStudio.SlnGen/SlnFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public SlnFolder(string path)
}

/// <summary>
/// Gets the <see cref="Guid" /> of the folder.
/// Gets or sets the <see cref="Guid" /> of the folder.
/// </summary>
public Guid FolderGuid { get; set; }

Expand Down

0 comments on commit 6cd5b1d

Please sign in to comment.