Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/NuGetUtility/NuGetUtility.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net472' ">
<LangVersion>10.0</LangVersion>
<LangVersion>12.0</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
6 changes: 4 additions & 2 deletions src/NuGetUtility/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -119,8 +120,9 @@ private async Task<int> 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,
Expand Down
12 changes: 6 additions & 6 deletions src/NuGetUtility/ReferencedPackagesReader/ProjectsCollector.cs
Original file line number Diff line number Diff line change
@@ -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<IEnumerable<string>> 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)];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ namespace NuGetUtility.Wrapper.MsBuildWrapper
public interface IMsBuildAbstraction
{
IProject GetProject(string projectPath);
Task<IEnumerable<string>> GetProjectsFromSolutionAsync(string inputPath);
}
}
10 changes: 0 additions & 10 deletions src/NuGetUtility/Wrapper/MsBuildWrapper/MsBuildAbstraction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -33,14 +31,6 @@ public IProject GetProject(string projectPath)
return new ProjectWrapper(project);
}

public async Task<IEnumerable<string>> 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IEnumerable<string>> GetProjectsFromSolutionAsync(string inputPath);
}
}
Original file line number Diff line number Diff line change
@@ -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) { }
}
}
Original file line number Diff line number Diff line change
@@ -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<IEnumerable<string>> 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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -22,10 +22,10 @@ public ProjectsCollectorTest()
public void SetUp()
{
_fixture = new Fixture();
_msBuild = Substitute.For<IMsBuildAbstraction>();
_uut = new ProjectsCollector(_msBuild);
_solutionPersistanceWrapper = Substitute.For<ISolutionPersistanceWrapper>();
_uut = new ProjectsCollector(_solutionPersistanceWrapper);
}
private IMsBuildAbstraction _msBuild = null!;
private ISolutionPersistanceWrapper _solutionPersistanceWrapper = null!;
private ProjectsCollector _uut = null!;
private Fixture _fixture = null!;
private readonly VerifySettings _osPlatformSpecificVerifySettings;
Expand All @@ -38,7 +38,7 @@ public async Task GetProjects_Should_ReturnProjectsAsListDirectly(string project
{
IEnumerable<string> result = await _uut.GetProjectsAsync(projectFile);
Assert.That(result, Is.EqualTo(new[] { Path.GetFullPath(projectFile) }));
await _msBuild.DidNotReceive().GetProjectsFromSolutionAsync(Arg.Any<string>());
await _solutionPersistanceWrapper.DidNotReceive().GetProjectsFromSolutionAsync(Arg.Any<string>());
}

[TestCase("A.sln")]
Expand All @@ -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")]
Expand All @@ -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<string>()).Returns(Task.FromResult(Enumerable.Empty<string>()));
_solutionPersistanceWrapper.GetProjectsFromSolutionAsync(Arg.Any<string>()).Returns(Task.FromResult(Enumerable.Empty<string>()));

IEnumerable<string> 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")]
Expand All @@ -73,12 +73,12 @@ public async Task GetProjects_Should_ReturnEmptyArray_If_SolutionContainsNoProje
public async Task GetProjects_Should_ReturnEmptyArray_If_SolutionContainsProjectsThatDontExist(string solutionFile)
{
IEnumerable<string> projects = _fixture.CreateMany<string>();
_msBuild.GetProjectsFromSolutionAsync(Arg.Any<string>()).Returns(Task.FromResult(projects));
_solutionPersistanceWrapper.GetProjectsFromSolutionAsync(Arg.Any<string>()).Returns(Task.FromResult(projects));

IEnumerable<string> 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")]
Expand All @@ -89,12 +89,12 @@ public async Task GetProjects_Should_ReturnArrayOfProjects_If_SolutionContainsPr
{
string[] projects = _fixture.CreateMany<string>().ToArray();
CreateFiles(projects);
_msBuild.GetProjectsFromSolutionAsync(Arg.Any<string>()).Returns(Task.FromResult<IEnumerable<string>>(projects));
_solutionPersistanceWrapper.GetProjectsFromSolutionAsync(Arg.Any<string>()).Returns(Task.FromResult<IEnumerable<string>>(projects));

IEnumerable<string> 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")]
Expand All @@ -108,29 +108,39 @@ public async Task GetProjects_Should_ReturnOnlyExistingProjectsInSolutionFile(st

CreateFiles(existingProjects);

_msBuild.GetProjectsFromSolutionAsync(Arg.Any<string>())
_solutionPersistanceWrapper.GetProjectsFromSolutionAsync(Arg.Any<string>())
.Returns(existingProjects.Concat(missingProjects).Shuffle(54321));

IEnumerable<string> 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<string> 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<string> 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<string> 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<string> 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<string> files)
Expand All @@ -140,5 +150,24 @@ private static void CreateFiles(IEnumerable<string> files)
File.WriteAllBytes(file, Array.Empty<byte>());
}
}

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
}
}