diff --git a/src/NuGetUtility/NuGetUtility.csproj b/src/NuGetUtility/NuGetUtility.csproj
index ffa0a9ce..12705054 100644
--- a/src/NuGetUtility/NuGetUtility.csproj
+++ b/src/NuGetUtility/NuGetUtility.csproj
@@ -21,7 +21,7 @@
- 10.0
+ 12.0
diff --git a/src/NuGetUtility/Program.cs b/src/NuGetUtility/Program.cs
index 58572a1d..90348156 100644
--- a/src/NuGetUtility/Program.cs
+++ b/src/NuGetUtility/Program.cs
@@ -23,6 +23,7 @@
using NuGetUtility.Wrapper.NuGetWrapper.ProjectModel;
using NuGetUtility.Wrapper.NuGetWrapper.Protocol;
using NuGetUtility.Wrapper.NuGetWrapper.Protocol.Core.Types;
+using NuGetUtility.Wrapper.SolutionPersistenceWrapper;
namespace NuGetUtility
{
@@ -119,8 +120,9 @@ private async Task OnExecuteAsync(CancellationToken cancellationToken)
IFileDownloader urlLicenseFileDownloader = GetFileDownloader(httpClient);
IOutputFormatter output = GetOutputFormatter();
- MsBuildAbstraction msBuild = new MsBuildAbstraction();
- var projectCollector = new ProjectsCollector(msBuild);
+ var solutionPersistance = new SolutionPersistanceWrapper();
+ var projectCollector = new ProjectsCollector(solutionPersistance);
+ var msBuild = new MsBuildAbstraction();
var projectReader = new ReferencedPackageReader(msBuild, new LockFileFactory(), GetPackagesConfigReader());
var validator = new LicenseValidator.LicenseValidator(licenseMappings,
allowedLicenses,
diff --git a/src/NuGetUtility/ReferencedPackagesReader/ProjectsCollector.cs b/src/NuGetUtility/ReferencedPackagesReader/ProjectsCollector.cs
index 2f58ec7a..7ab18c32 100644
--- a/src/NuGetUtility/ReferencedPackagesReader/ProjectsCollector.cs
+++ b/src/NuGetUtility/ReferencedPackagesReader/ProjectsCollector.cs
@@ -1,23 +1,23 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root
-using NuGetUtility.Wrapper.MsBuildWrapper;
+using NuGetUtility.Wrapper.SolutionPersistenceWrapper;
namespace NuGetUtility.ReferencedPackagesReader
{
public class ProjectsCollector
{
- private readonly IMsBuildAbstraction _msBuild;
- public ProjectsCollector(IMsBuildAbstraction msBuild)
+ private readonly ISolutionPersistanceWrapper _solutionPersistance;
+ public ProjectsCollector(ISolutionPersistanceWrapper solutionPersistance)
{
- _msBuild = msBuild;
+ _solutionPersistance = solutionPersistance;
}
public async Task> GetProjectsAsync(string inputPath)
{
return Path.GetExtension(inputPath).StartsWith(".sln")
- ? (await _msBuild.GetProjectsFromSolutionAsync(Path.GetFullPath(inputPath))).Where(File.Exists).Select(Path.GetFullPath)
- : new[] { Path.GetFullPath(inputPath) };
+ ? (await _solutionPersistance.GetProjectsFromSolutionAsync(Path.GetFullPath(inputPath))).Where(File.Exists).Select(Path.GetFullPath)
+ : [Path.GetFullPath(inputPath)];
}
}
}
diff --git a/src/NuGetUtility/Wrapper/MsBuildWrapper/IMsBuildAbstraction.cs b/src/NuGetUtility/Wrapper/MsBuildWrapper/IMsBuildAbstraction.cs
index 384c3db3..dea21a9e 100644
--- a/src/NuGetUtility/Wrapper/MsBuildWrapper/IMsBuildAbstraction.cs
+++ b/src/NuGetUtility/Wrapper/MsBuildWrapper/IMsBuildAbstraction.cs
@@ -6,6 +6,5 @@ namespace NuGetUtility.Wrapper.MsBuildWrapper
public interface IMsBuildAbstraction
{
IProject GetProject(string projectPath);
- Task> GetProjectsFromSolutionAsync(string inputPath);
}
}
diff --git a/src/NuGetUtility/Wrapper/MsBuildWrapper/MsBuildAbstraction.cs b/src/NuGetUtility/Wrapper/MsBuildWrapper/MsBuildAbstraction.cs
index 6ee33379..0168052e 100644
--- a/src/NuGetUtility/Wrapper/MsBuildWrapper/MsBuildAbstraction.cs
+++ b/src/NuGetUtility/Wrapper/MsBuildWrapper/MsBuildAbstraction.cs
@@ -3,8 +3,6 @@
using Microsoft.Build.Evaluation;
using Microsoft.Build.Locator;
-using Microsoft.VisualStudio.SolutionPersistence;
-using Microsoft.VisualStudio.SolutionPersistence.Serializer;
namespace NuGetUtility.Wrapper.MsBuildWrapper
{
@@ -33,14 +31,6 @@ public IProject GetProject(string projectPath)
return new ProjectWrapper(project);
}
- public async Task> GetProjectsFromSolutionAsync(string inputPath)
- {
- ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(inputPath) ?? throw new MsBuildAbstractionException("Failed to determine serializer for solution");
-
- Microsoft.VisualStudio.SolutionPersistence.Model.SolutionModel model = await serializer.OpenAsync(inputPath, CancellationToken.None);
- return model.SolutionProjects.Select(p => p.FilePath);
- }
-
private static void RegisterMsBuildLocatorIfNeeded()
{
if (!MSBuildLocator.IsRegistered)
diff --git a/src/NuGetUtility/Wrapper/SolutionPersistenceWrapper/ISolutionPersistanceWrapper.cs b/src/NuGetUtility/Wrapper/SolutionPersistenceWrapper/ISolutionPersistanceWrapper.cs
new file mode 100644
index 00000000..b8ea1512
--- /dev/null
+++ b/src/NuGetUtility/Wrapper/SolutionPersistenceWrapper/ISolutionPersistanceWrapper.cs
@@ -0,0 +1,10 @@
+// Licensed to the projects contributors.
+// The license conditions are provided in the LICENSE file located in the project root
+
+namespace NuGetUtility.Wrapper.SolutionPersistenceWrapper
+{
+ public interface ISolutionPersistanceWrapper
+ {
+ Task> GetProjectsFromSolutionAsync(string inputPath);
+ }
+}
diff --git a/src/NuGetUtility/Wrapper/SolutionPersistenceWrapper/SolutionPersistanceException.cs b/src/NuGetUtility/Wrapper/SolutionPersistenceWrapper/SolutionPersistanceException.cs
new file mode 100644
index 00000000..429d6632
--- /dev/null
+++ b/src/NuGetUtility/Wrapper/SolutionPersistenceWrapper/SolutionPersistanceException.cs
@@ -0,0 +1,14 @@
+// Licensed to the projects contributors.
+// The license conditions are provided in the LICENSE file located in the project root
+
+namespace NuGetUtility.Wrapper.SolutionPersistenceWrapper
+{
+ public class SolutionPersistanceException : Exception
+ {
+ public SolutionPersistanceException(string message)
+ : base(message) { }
+
+ public SolutionPersistanceException(string message, Exception inner)
+ : base(message, inner) { }
+ }
+}
diff --git a/src/NuGetUtility/Wrapper/SolutionPersistenceWrapper/SolutionPersistanceWrapper.cs b/src/NuGetUtility/Wrapper/SolutionPersistenceWrapper/SolutionPersistanceWrapper.cs
new file mode 100644
index 00000000..11cc8802
--- /dev/null
+++ b/src/NuGetUtility/Wrapper/SolutionPersistenceWrapper/SolutionPersistanceWrapper.cs
@@ -0,0 +1,20 @@
+// Licensed to the projects contributors.
+// The license conditions are provided in the LICENSE file located in the project root
+
+using Microsoft.VisualStudio.SolutionPersistence;
+using Microsoft.VisualStudio.SolutionPersistence.Serializer;
+
+namespace NuGetUtility.Wrapper.SolutionPersistenceWrapper
+{
+ public class SolutionPersistanceWrapper : ISolutionPersistanceWrapper
+ {
+ public async Task> GetProjectsFromSolutionAsync(string inputPath)
+ {
+ ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(inputPath) ?? throw new SolutionPersistanceException("Failed to determine serializer for solution");
+
+ Microsoft.VisualStudio.SolutionPersistence.Model.SolutionModel model = await serializer.OpenAsync(inputPath, CancellationToken.None);
+ string? solutionPath = Path.GetDirectoryName(inputPath);
+ return model.SolutionProjects.Select(p => solutionPath is null ? p.FilePath : Path.Combine(solutionPath, p.FilePath));
+ }
+ }
+}
diff --git a/tests/NuGetUtility.Test/ReferencedPackagesReader/ProjectsCollectorTest.cs b/tests/NuGetUtility.Test/ReferencedPackagesReader/ProjectsCollectorTest.cs
index b023b63f..5e0c44d7 100644
--- a/tests/NuGetUtility.Test/ReferencedPackagesReader/ProjectsCollectorTest.cs
+++ b/tests/NuGetUtility.Test/ReferencedPackagesReader/ProjectsCollectorTest.cs
@@ -5,7 +5,7 @@
using NSubstitute;
using NuGetUtility.ReferencedPackagesReader;
using NuGetUtility.Test.Helper.ShuffelledEnumerable;
-using NuGetUtility.Wrapper.MsBuildWrapper;
+using NuGetUtility.Wrapper.SolutionPersistenceWrapper;
namespace NuGetUtility.Test.ReferencedPackagesReader
{
@@ -22,10 +22,10 @@ public ProjectsCollectorTest()
public void SetUp()
{
_fixture = new Fixture();
- _msBuild = Substitute.For();
- _uut = new ProjectsCollector(_msBuild);
+ _solutionPersistanceWrapper = Substitute.For();
+ _uut = new ProjectsCollector(_solutionPersistanceWrapper);
}
- private IMsBuildAbstraction _msBuild = null!;
+ private ISolutionPersistanceWrapper _solutionPersistanceWrapper = null!;
private ProjectsCollector _uut = null!;
private Fixture _fixture = null!;
private readonly VerifySettings _osPlatformSpecificVerifySettings;
@@ -38,7 +38,7 @@ public async Task GetProjects_Should_ReturnProjectsAsListDirectly(string project
{
IEnumerable result = await _uut.GetProjectsAsync(projectFile);
Assert.That(result, Is.EqualTo(new[] { Path.GetFullPath(projectFile) }));
- await _msBuild.DidNotReceive().GetProjectsFromSolutionAsync(Arg.Any());
+ await _solutionPersistanceWrapper.DidNotReceive().GetProjectsFromSolutionAsync(Arg.Any());
}
[TestCase("A.sln")]
@@ -49,7 +49,7 @@ public async Task GetProjects_Should_QueryMsBuildToGetProjectsForSolutionFiles(s
{
_ = await _uut.GetProjectsAsync(solutionFile);
- await _msBuild.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
+ await _solutionPersistanceWrapper.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
}
[TestCase("A.sln")]
@@ -58,12 +58,12 @@ public async Task GetProjects_Should_QueryMsBuildToGetProjectsForSolutionFiles(s
[TestCase("C.slnx")]
public async Task GetProjects_Should_ReturnEmptyArray_If_SolutionContainsNoProjects(string solutionFile)
{
- _msBuild.GetProjectsFromSolutionAsync(Arg.Any()).Returns(Task.FromResult(Enumerable.Empty()));
+ _solutionPersistanceWrapper.GetProjectsFromSolutionAsync(Arg.Any()).Returns(Task.FromResult(Enumerable.Empty()));
IEnumerable result = await _uut.GetProjectsAsync(solutionFile);
Assert.That(result, Is.Empty);
- await _msBuild.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
+ await _solutionPersistanceWrapper.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
}
[TestCase("A.sln")]
@@ -73,12 +73,12 @@ public async Task GetProjects_Should_ReturnEmptyArray_If_SolutionContainsNoProje
public async Task GetProjects_Should_ReturnEmptyArray_If_SolutionContainsProjectsThatDontExist(string solutionFile)
{
IEnumerable projects = _fixture.CreateMany();
- _msBuild.GetProjectsFromSolutionAsync(Arg.Any()).Returns(Task.FromResult(projects));
+ _solutionPersistanceWrapper.GetProjectsFromSolutionAsync(Arg.Any()).Returns(Task.FromResult(projects));
IEnumerable result = await _uut.GetProjectsAsync(solutionFile);
Assert.That(result, Is.Empty);
- await _msBuild.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
+ await _solutionPersistanceWrapper.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
}
[TestCase("A.sln")]
@@ -89,12 +89,12 @@ public async Task GetProjects_Should_ReturnArrayOfProjects_If_SolutionContainsPr
{
string[] projects = _fixture.CreateMany().ToArray();
CreateFiles(projects);
- _msBuild.GetProjectsFromSolutionAsync(Arg.Any()).Returns(Task.FromResult>(projects));
+ _solutionPersistanceWrapper.GetProjectsFromSolutionAsync(Arg.Any()).Returns(Task.FromResult>(projects));
IEnumerable result = await _uut.GetProjectsAsync(solutionFile);
Assert.That(result, Is.EqualTo(projects.Select(Path.GetFullPath)));
- await _msBuild.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
+ await _solutionPersistanceWrapper.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
}
[TestCase("A.sln")]
@@ -108,29 +108,39 @@ public async Task GetProjects_Should_ReturnOnlyExistingProjectsInSolutionFile(st
CreateFiles(existingProjects);
- _msBuild.GetProjectsFromSolutionAsync(Arg.Any())
+ _solutionPersistanceWrapper.GetProjectsFromSolutionAsync(Arg.Any())
.Returns(existingProjects.Concat(missingProjects).Shuffle(54321));
IEnumerable result = await _uut.GetProjectsAsync(solutionFile);
Assert.That(result, Is.EquivalentTo(existingProjects.Select(Path.GetFullPath)));
- await _msBuild.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
+ await _solutionPersistanceWrapper.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
}
[Test]
public async Task GetProjectsFromSolution_Should_ReturnProjectsInActualSolutionFileRelativePath()
{
- var msbuild = new MsBuildAbstraction();
- IEnumerable result = await msbuild.GetProjectsFromSolutionAsync("../../../../targets/Projects.sln");
- await Verify(string.Join(",", result), _osPlatformSpecificVerifySettings);
+ var solutionPersistance = new SolutionPersistanceWrapper();
+ string solutionFolder = Path.GetFullPath("../../../../targets");
+ string solutionFileName = "Projects.sln";
+ IEnumerable result = await solutionPersistance.GetProjectsFromSolutionAsync(Path.Combine(solutionFolder, solutionFileName));
+
+ Assert.That(result.Select(Path.IsPathRooted), Is.All.True);
+
+ await Verify(string.Join(",", result.Select(p => GetPathRelativeTo(solutionFolder, p))), _osPlatformSpecificVerifySettings);
}
[Test]
public async Task GetProjectsFromXmlSolution_Should_ReturnProjectsInActualSolutionFileRelativePath()
{
- var msbuild = new MsBuildAbstraction();
- IEnumerable result = await msbuild.GetProjectsFromSolutionAsync("../../../../targets/slnx/slnx.slnx");
- await Verify(string.Join(",", result), _osPlatformSpecificVerifySettings);
+ var solutionPersistance = new SolutionPersistanceWrapper();
+ string solutionFolder = Path.GetFullPath("../../../../targets/slnx");
+ string solutionFileName = "slnx.slnx";
+ IEnumerable result = await solutionPersistance.GetProjectsFromSolutionAsync(Path.Combine(solutionFolder, solutionFileName));
+
+ Assert.That(result.Select(Path.IsPathRooted), Is.All.True);
+
+ await Verify(string.Join(",", result.Select(p => GetPathRelativeTo(solutionFolder, p))), _osPlatformSpecificVerifySettings);
}
private static void CreateFiles(IEnumerable files)
@@ -140,5 +150,24 @@ private static void CreateFiles(IEnumerable files)
File.WriteAllBytes(file, Array.Empty());
}
}
+
+ private static string GetPathRelativeTo(string relativeTo, string path)
+#if NETFRAMEWORK
+ {
+ // Require trailing backslash for path
+ relativeTo = relativeTo.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
+ relativeTo += Path.DirectorySeparatorChar;
+
+ Uri baseUri = new Uri(relativeTo);
+ Uri fullUri = new Uri(path);
+
+ Uri relativeUri = baseUri.MakeRelativeUri(fullUri);
+
+ return relativeUri.ToString().Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+
+ }
+#else
+ => Path.GetRelativePath(relativeTo, path);
+#endif
}
}