Skip to content

Commit

Permalink
add proper slnx support
Browse files Browse the repository at this point in the history
  • Loading branch information
sensslen committed Feb 3, 2025
1 parent f2d0399 commit 2df8257
Show file tree
Hide file tree
Showing 17 changed files with 56 additions and 49 deletions.
13 changes: 13 additions & 0 deletions src/NuGetUtility/Extensions/TaskExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root

namespace NuGetUtility.Extensions
{
public static class TaskExtensions
{
public static async Task<IEnumerable<T1>> SelectManyAsync<T, T1>(this IEnumerable<T> enumeration, Func<T, Task<IEnumerable<T1>>> func)
{
return (await Task.WhenAll(enumeration.Select(func))).SelectMany(s => s);
}
}
}
1 change: 1 addition & 0 deletions src/NuGetUtility/NuGetUtility.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.7.8" />
<PackageReference Include="Microsoft.VisualStudio.SolutionPersistence" Version="1.0.28" />
<PackageReference Include="NuGet.Commands" Version="6.12.1" />
<PackageReference Include="NuGet.Packaging" Version="6.12.1" />
<PackageReference Include="Tethys.SPDX.ExpressionParser" Version="2.1.2" />
Expand Down
2 changes: 1 addition & 1 deletion src/NuGetUtility/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ private async Task<int> OnExecuteAsync(CancellationToken cancellationToken)
ignoredPackages);

string[] excludedProjects = GetExcludedProjects();
IEnumerable<string> projects = inputFiles.SelectMany(projectCollector.GetProjects).Where(p => !Array.Exists(excludedProjects, ignored => p.Like(ignored)));
IEnumerable<string> projects = (await inputFiles.SelectManyAsync(projectCollector.GetProjectsAsync)).Where(p => !Array.Exists(excludedProjects, ignored => p.Like(ignored)));
IEnumerable<ProjectWithReferencedPackages> packagesForProject = GetPackagesPerProject(projects, projectReader, out IReadOnlyCollection<Exception>? projectReaderExceptions);
IAsyncEnumerable<ReferencedPackageWithContext> downloadedLicenseInformation =
packagesForProject.SelectMany(p => GetPackageInformations(p, overridePackageInformation, cancellationToken));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ public ProjectsCollector(IMsBuildAbstraction msBuild)
_msBuild = msBuild;
}

public IEnumerable<string> GetProjects(string inputPath)
public async Task<IEnumerable<string>> GetProjectsAsync(string inputPath)
{
return Path.GetExtension(inputPath).StartsWith(".sln")
? _msBuild.GetProjectsFromSolution(Path.GetFullPath(inputPath)).Where(File.Exists).Select(Path.GetFullPath)
? (await _msBuild.GetProjectsFromSolutionAsync(Path.GetFullPath(inputPath))).Where(File.Exists).Select(Path.GetFullPath)
: new[] { Path.GetFullPath(inputPath) };
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ namespace NuGetUtility.Wrapper.MsBuildWrapper
public interface IMsBuildAbstraction
{
IProject GetProject(string projectPath);
IEnumerable<string> GetProjectsFromSolution(string inputPath);
Task<IEnumerable<string>> GetProjectsFromSolutionAsync(string inputPath);
}
}
12 changes: 7 additions & 5 deletions src/NuGetUtility/Wrapper/MsBuildWrapper/MsBuildAbstraction.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root

using Microsoft.Build.Construction;
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 @@ -32,11 +33,12 @@ public IProject GetProject(string projectPath)
return new ProjectWrapper(project);
}

public IEnumerable<string> GetProjectsFromSolution(string inputPath)
public async Task<IEnumerable<string>> GetProjectsFromSolutionAsync(string inputPath)
{
string absolutePath = Path.IsPathRooted(inputPath) ? inputPath : Path.Combine(Environment.CurrentDirectory, inputPath);
var sln = SolutionFile.Parse(absolutePath);
return sln.ProjectsInOrder.Select(p => p.AbsolutePath);
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using NuGetUtility.LicenseValidator;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;

namespace NuGetUtility.Test.LicenseValidator
{
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
{SolutionDirectory}tests/targets/PackageReferenceProject/PackageReferenceProject.csproj,{SolutionDirectory}tests/targets/PackagesConfigProject/PackagesConfigProject.csproj,{SolutionDirectory}tests/targets/ProjectWithoutNugetReferences/ProjectWithoutNugetReferences.csproj,{SolutionDirectory}tests/targets/ProjectWithTransitiveNuget/ProjectWithTransitiveNuget.csproj,{SolutionDirectory}tests/targets/ProjectWithTransitiveReferences/ProjectWithTransitiveReferences.csproj
{CurrentDirectory}tests/targets/PackageReferenceProject/PackageReferenceProject.csproj,{CurrentDirectory}tests/targets/PackagesConfigProject/PackagesConfigProject.csproj,{CurrentDirectory}tests/targets/ProjectWithoutNugetReferences/ProjectWithoutNugetReferences.csproj,{CurrentDirectory}tests/targets/ProjectWithTransitiveNuget/ProjectWithTransitiveNuget.csproj,{CurrentDirectory}tests/targets/ProjectWithTransitiveReferences/ProjectWithTransitiveReferences.csproj
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{SolutionDirectory}tests/targets/PackageReferenceProject/PackageReferenceProject.csproj,{SolutionDirectory}tests/targets/PackagesConfigProject/PackagesConfigProject.csproj,{SolutionDirectory}tests/targets/ProjectWithoutNugetReferences/ProjectWithoutNugetReferences.csproj,{SolutionDirectory}tests/targets/ProjectWithTransitiveNuget/ProjectWithTransitiveNuget.csproj,{SolutionDirectory}tests/targets/ProjectWithTransitiveReferences/ProjectWithTransitiveReferences.csproj
{CurrentDirectory}tests/targets/PackageReferenceProject/PackageReferenceProject.csproj,{CurrentDirectory}tests/targets/PackagesConfigProject/PackagesConfigProject.csproj,{CurrentDirectory}tests/targets/ProjectWithoutNugetReferences/ProjectWithoutNugetReferences.csproj,{CurrentDirectory}tests/targets/ProjectWithTransitiveNuget/ProjectWithTransitiveNuget.csproj,{CurrentDirectory}tests/targets/ProjectWithTransitiveReferences/ProjectWithTransitiveReferences.csproj
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{SolutionDirectory}tests\targets\PackageReferenceProject\PackageReferenceProject.csproj,{SolutionDirectory}tests\targets\PackagesConfigProject\PackagesConfigProject.csproj,{SolutionDirectory}tests\targets\ProjectWithoutNugetReferences\ProjectWithoutNugetReferences.csproj,{SolutionDirectory}tests\targets\ProjectWithTransitiveNuget\ProjectWithTransitiveNuget.csproj,{SolutionDirectory}tests\targets\ProjectWithTransitiveReferences\ProjectWithTransitiveReferences.csproj
{CurrentDirectory}PackageReferenceProject\PackageReferenceProject.csproj,{CurrentDirectory}PackagesConfigProject\PackagesConfigProject.csproj,{CurrentDirectory}ProjectWithoutNugetReferences\ProjectWithoutNugetReferences.csproj,{CurrentDirectory}ProjectWithTransitiveNuget\ProjectWithTransitiveNuget.csproj,{CurrentDirectory}ProjectWithTransitiveReferences\ProjectWithTransitiveReferences.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ProjectDirectory}bin\Debug\ProjectWithTransitiveNuget\ProjectWithTransitiveNuget.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ProjectDirectory}bin/Debug/ProjectWithTransitiveNuget/ProjectWithTransitiveNuget.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ProjectDirectory}bin/Debug/ProjectWithTransitiveNuget/ProjectWithTransitiveNuget.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,110 +34,102 @@ public void SetUp()
[TestCase("B.fsproj")]
[TestCase("C.vbproj")]
[TestCase("D.dbproj")]
public void GetProjects_Should_ReturnProjectsAsListDirectly(string projectFile)
public async Task GetProjects_Should_ReturnProjectsAsListDirectly(string projectFile)
{
IEnumerable<string> result = _uut.GetProjects(projectFile);
IEnumerable<string> result = await _uut.GetProjectsAsync(projectFile);
Assert.That(result, Is.EqualTo(new[] { Path.GetFullPath(projectFile) }));
_msBuild.DidNotReceive().GetProjectsFromSolution(Arg.Any<string>());
await _msBuild.DidNotReceive().GetProjectsFromSolutionAsync(Arg.Any<string>());
}

[TestCase("A.sln")]
[TestCase("B.sln")]
[TestCase("C.sln")]
[TestCase("A.slnx")]
public void GetProjects_Should_QueryMsBuildToGetProjectsForSolutionFiles(string solutionFile)
public async Task GetProjects_Should_QueryMsBuildToGetProjectsForSolutionFiles(string solutionFile)
{
_ = _uut.GetProjects(solutionFile);
_ = await _uut.GetProjectsAsync(solutionFile);

_msBuild.Received(1).GetProjectsFromSolution(Path.GetFullPath(solutionFile));
await _msBuild.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
}

[TestCase("A.sln")]
[TestCase("B.sln")]
[TestCase("C.sln")]
[TestCase("C.slnx")]
public void GetProjects_Should_ReturnEmptyArray_If_SolutionContainsNoProjects(string solutionFile)
public async Task GetProjects_Should_ReturnEmptyArray_If_SolutionContainsNoProjects(string solutionFile)
{
_msBuild.GetProjectsFromSolution(Arg.Any<string>()).Returns(Enumerable.Empty<string>());
_msBuild.GetProjectsFromSolutionAsync(Arg.Any<string>()).Returns(Task.FromResult(Enumerable.Empty<string>()));

IEnumerable<string> result = _uut.GetProjects(solutionFile);
IEnumerable<string> result = await _uut.GetProjectsAsync(solutionFile);
Assert.That(result, Is.Empty);

_msBuild.Received(1).GetProjectsFromSolution(Path.GetFullPath(solutionFile));
await _msBuild.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
}

[TestCase("A.sln")]
[TestCase("B.sln")]
[TestCase("C.sln")]
[TestCase("B.slnx")]
public void GetProjects_Should_ReturnEmptyArray_If_SolutionContainsProjectsThatDontExist(string solutionFile)
public async Task GetProjects_Should_ReturnEmptyArray_If_SolutionContainsProjectsThatDontExist(string solutionFile)
{
IEnumerable<string> projects = _fixture.CreateMany<string>();
_msBuild.GetProjectsFromSolution(Arg.Any<string>()).Returns(projects);
_msBuild.GetProjectsFromSolutionAsync(Arg.Any<string>()).Returns(Task.FromResult(projects));

IEnumerable<string> result = _uut.GetProjects(solutionFile);
IEnumerable<string> result = await _uut.GetProjectsAsync(solutionFile);
Assert.That(result, Is.Empty);

_msBuild.Received(1).GetProjectsFromSolution(Path.GetFullPath(solutionFile));
await _msBuild.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
}

[TestCase("A.sln")]
[TestCase("B.sln")]
[TestCase("C.sln")]
[TestCase("C.slnx")]
public void GetProjects_Should_ReturnArrayOfProjects_If_SolutionContainsProjectsThatDoExist(string solutionFile)
public async Task GetProjects_Should_ReturnArrayOfProjects_If_SolutionContainsProjectsThatDoExist(string solutionFile)
{
string[] projects = _fixture.CreateMany<string>().ToArray();
CreateFiles(projects);
_msBuild.GetProjectsFromSolution(Arg.Any<string>()).Returns(projects);
_msBuild.GetProjectsFromSolutionAsync(Arg.Any<string>()).Returns(Task.FromResult<IEnumerable<string>>(projects));

IEnumerable<string> result = _uut.GetProjects(solutionFile);
IEnumerable<string> result = await _uut.GetProjectsAsync(solutionFile);
Assert.That(result, Is.EqualTo(projects.Select(Path.GetFullPath)));

_msBuild.Received(1).GetProjectsFromSolution(Path.GetFullPath(solutionFile));
await _msBuild.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
}

[TestCase("A.sln")]
[TestCase("B.sln")]
[TestCase("C.sln")]
[TestCase("A.slnx")]
public void GetProjects_Should_ReturnOnlyExistingProjectsInSolutionFile(string solutionFile)
public async Task GetProjects_Should_ReturnOnlyExistingProjectsInSolutionFile(string solutionFile)
{
string[] existingProjects = _fixture.CreateMany<string>().ToArray();
IEnumerable<string> missingProjects = _fixture.CreateMany<string>();

CreateFiles(existingProjects);

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

IEnumerable<string> result = _uut.GetProjects(solutionFile);
IEnumerable<string> result = await _uut.GetProjectsAsync(solutionFile);
Assert.That(result, Is.EquivalentTo(existingProjects.Select(Path.GetFullPath)));

_msBuild.Received(1).GetProjectsFromSolution(Path.GetFullPath(solutionFile));
await _msBuild.Received(1).GetProjectsFromSolutionAsync(Path.GetFullPath(solutionFile));
}

[Test]
public async Task GetProjectsFromSolution_Should_ReturnProjectsInActualSolutionFileRelativePath()
{
var msbuild = new MsBuildAbstraction();
IEnumerable<string> result = msbuild.GetProjectsFromSolution("../../../../targets/Projects.sln");
await Verify(string.Join(",", result), _osPlatformSpecificVerifySettings);
}

[Test, Ignore("Ignore this specific test as long as msbuild does not fully support slnx solutions everywhere")]
public async Task GetProjectsFromXmlSolution_Should_ReturnProjectsInActualSolutionFileRelativePath()
{
var msbuild = new MsBuildAbstraction();
IEnumerable<string> result = msbuild.GetProjectsFromSolution("../../../../targets/slnx/slnx.slnx");
IEnumerable<string> result = await msbuild.GetProjectsFromSolutionAsync("../../../../targets/Projects.sln");
await Verify(string.Join(",", result), _osPlatformSpecificVerifySettings);
}

[Test]
public async Task GetProjectsFromSolution_Should_ReturnProjectsInActualSolutionFileAbsolutePath()
public async Task GetProjectsFromXmlSolution_Should_ReturnProjectsInActualSolutionFileRelativePath()
{
var msbuild = new MsBuildAbstraction();
IEnumerable<string> result = msbuild.GetProjectsFromSolution(Path.GetFullPath("../../../../targets/Projects.sln"));
IEnumerable<string> result = await msbuild.GetProjectsFromSolutionAsync("../../../../targets/slnx/slnx.slnx");
await Verify(string.Join(",", result), _osPlatformSpecificVerifySettings);
}

Expand Down

0 comments on commit 2df8257

Please sign in to comment.