Skip to content

Commit 9bb6a2c

Browse files
feat: SLNX support (#124)
* support slnx by using ms.visualstudio.solutionpersistance * compute absolute path for projects found in solution * bump solution persistance version * switch to notsupportedexception * Add SLNX Tests
1 parent 6923d4c commit 9bb6a2c

File tree

8 files changed

+137
-7
lines changed

8 files changed

+137
-7
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
<PackageVersion Include="Microsoft.Build.Prediction" Version="1.2.18" />
99
<PackageVersion Include="Microsoft.Build.Locator" Version="1.7.8" />
10+
<PackageVersion Include="Microsoft.VisualStudio.SolutionPersistence" Version="1.0.52" />
1011

1112
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
1213
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.12.1" Condition="'$(OS)' == 'Windows_NT'"/>

src/DotnetAffected.Core/DotnetAffected.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
<ItemGroup>
1212
<PackageReference Include="Microsoft.Build.Prediction" />
1313
<PackageReference Include="LibGit2Sharp" />
14+
<PackageReference Include="Microsoft.VisualStudio.SolutionPersistence" />
1415
</ItemGroup>
1516
</Project>

src/DotnetAffected.Core/ProjectDiscovery/ProjectDiscoveryManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public IEnumerable<string> DiscoverProjects(IDiscoveryOptions options)
1515
}
1616

1717
// When a filtering file is provided, use a specific discoverer based on its path.
18-
if (options.FilterFilePath.EndsWith(".sln"))
18+
if (options.FilterFilePath.EndsWith(".sln") || options.FilterFilePath.EndsWith(".slnx"))
1919
{
2020
return new SolutionFileProjectDiscoverer().DiscoverProjects(options);
2121
}

src/DotnetAffected.Core/ProjectDiscovery/SolutionFileProjectDiscoverer.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
11
using DotnetAffected.Abstractions;
2-
using Microsoft.Build.Construction;
2+
using Microsoft.VisualStudio.SolutionPersistence.Serializer;
3+
using System;
34
using System.Collections.Generic;
5+
using System.IO;
46
using System.Linq;
7+
using System.Threading;
58

69
namespace DotnetAffected.Core
710
{
811
internal class SolutionFileProjectDiscoverer : IProjectDiscoverer
912
{
1013
public IEnumerable<string> DiscoverProjects(IDiscoveryOptions options)
1114
{
12-
var solution = SolutionFile.Parse(options.FilterFilePath);
15+
// It should not be possible for this to be null based on call paths - but this makes the warning go away
16+
ArgumentNullException.ThrowIfNull(options.FilterFilePath);
1317

14-
return solution.ProjectsInOrder
15-
.Where(x => x.ProjectType != SolutionProjectType.SolutionFolder)
16-
.Select(x => x.AbsolutePath)
18+
var serializer = SolutionSerializers.GetSerializerByMoniker(options.FilterFilePath);
19+
20+
if (serializer is null) throw new NotSupportedException($"Filtering by {options.FilterFilePath} is not supported");
21+
22+
var solution = serializer.OpenAsync(options.FilterFilePath, CancellationToken.None).GetAwaiter().GetResult();
23+
24+
var solutionDir = Path.GetDirectoryName(options.FilterFilePath);
25+
26+
return solution.SolutionProjects
27+
.Select(x => Path.IsPathRooted(x.FilePath) ? x.FilePath : Path.Join(solutionDir, x.FilePath))
1728
.ToArray();
1829
}
1930
}

src/dotnet-affected/Commands/AffectedGlobalOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal static class AffectedGlobalOptions
2828
{
2929
"--filter-file-path"
3030
},
31-
description: "Path to a filter file (.sln) used to discover projects that may be affected.\n" +
31+
description: "Path to a filter file (.sln, .slnx) used to discover projects that may be affected.\n" +
3232
"When omitted, will search for project files inside --repository-path.");
3333

3434
public static readonly Option<bool> VerboseOption = new(aliases: new[]
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using DotnetAffected.Testing.Utils;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Xunit;
6+
7+
namespace DotnetAffected.Core.Tests
8+
{
9+
/// <summary>
10+
/// Tests for detecting changed projects when using a SolutionFile to filter.
11+
/// This should cover all tests where filtering should be applied
12+
/// </summary>
13+
public class ChangeDetectionUsingXmlSolutionTests
14+
: BaseDotnetAffectedTest
15+
{
16+
private readonly string _solutionPath = "test-solution.slnx";
17+
18+
protected override AffectedOptions Options => new AffectedOptions(
19+
this.Repository.Path,
20+
Path.Combine(this.Repository.Path, this._solutionPath));
21+
22+
[Fact]
23+
public async Task When_project_inside_solution_has_changes_project_should_have_changed()
24+
{
25+
// Create a project
26+
var projectName = "InventoryManagement";
27+
var msBuildProject = this.Repository.CreateCsProject(projectName);
28+
29+
// Create a solution which includes the project
30+
await this.Repository.CreateXmlSolutionAsync(_solutionPath, msBuildProject.FullPath);
31+
32+
Assert.Single(AffectedSummary.ProjectsWithChangedFiles);
33+
Assert.Empty(AffectedSummary.AffectedProjects);
34+
35+
var projectInfo = AffectedSummary.ProjectsWithChangedFiles.Single();
36+
Assert.Equal(projectName, projectInfo.GetProjectName());
37+
Assert.Equal(msBuildProject.FullPath, projectInfo.GetFullPath());
38+
}
39+
40+
[Fact]
41+
public async Task When_project_inside_solution_should_ignore_changes_outside_solution()
42+
{
43+
// Create a project
44+
var projectName = "InventoryManagement";
45+
var msBuildProject = this.Repository.CreateCsProject(projectName);
46+
47+
// Create a solution which includes the project
48+
await this.Repository.CreateXmlSolutionAsync(_solutionPath, msBuildProject.FullPath);
49+
50+
// Create a project that is outside the solution
51+
var outsiderproject = "OutsiderProject";
52+
this.Repository.CreateCsProject(outsiderproject);
53+
54+
Assert.Single(AffectedSummary.ProjectsWithChangedFiles);
55+
Assert.Empty(AffectedSummary.AffectedProjects);
56+
57+
var projectInfo = AffectedSummary.ProjectsWithChangedFiles.Single();
58+
Assert.Equal(projectName, projectInfo.GetProjectName());
59+
Assert.Equal(msBuildProject.FullPath, projectInfo.GetFullPath());
60+
}
61+
62+
[Fact]
63+
public async Task When_project_outside_solution_has_changed_nothing_should_be_affected()
64+
{
65+
// Create a project
66+
var projectName = "InventoryManagement";
67+
var msBuildProject = this.Repository.CreateCsProject(projectName);
68+
69+
// Create a solution which includes the project
70+
await this.Repository.CreateXmlSolutionAsync(_solutionPath, msBuildProject.FullPath);
71+
72+
// Commit so there are no changes
73+
this.Repository.StageAndCommit();
74+
75+
// Create a project that is outside the solution
76+
var outsiderName = "OutsiderProject";
77+
this.Repository.CreateCsProject(outsiderName);
78+
79+
Assert.Empty(AffectedSummary.AffectedProjects);
80+
}
81+
}
82+
}

test/DotnetAffected.Testing.Utils/Repository/TemporaryRepositoryExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,20 @@ public static async Task CreateSolutionAsync(
149149
await File.WriteAllTextAsync(solutionPath, solutionContents);
150150
}
151151

152+
public static async Task CreateXmlSolutionAsync(
153+
this TemporaryRepository repo,
154+
string solutionName,
155+
params string[] projectPaths)
156+
{
157+
var i = 0;
158+
var solutionContents = new SolutionFileBuilder { Projects = projectPaths.ToDictionary(p => i++.ToString()) }
159+
.BuildXmlSolution();
160+
161+
var solutionPath = Path.Combine(repo.Path, solutionName);
162+
163+
await File.WriteAllTextAsync(solutionPath, solutionContents);
164+
}
165+
152166
public static Task CreateTraversalProjectAsync(
153167
this TemporaryRepository repo,
154168
string traversalProjectPath,

test/DotnetAffected.Testing.Utils/SolutionFileBuilder.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,26 @@ public string BuildSolution()
9797

9898
return sb.ToString();
9999
}
100+
101+
public string BuildXmlSolution()
102+
{
103+
var sb = new StringBuilder();
104+
sb.AppendLine(@"<Solution>");
105+
sb.AppendLine(@" <Configurations>");
106+
sb.AppendLine(@" <Platform Name=""Any CPU"" />");
107+
sb.AppendLine(@" <Platform Name=""x64"" />");
108+
sb.AppendLine(@" <Platform Name=""x86"" />");
109+
sb.AppendLine(@" </Configurations>");
110+
111+
foreach (var project in Projects.Values)
112+
{
113+
sb.AppendLine(@$" <Project Path=""{project}"" />");
114+
115+
}
116+
117+
sb.AppendLine(@"</Solution>");
118+
119+
return sb.ToString();
120+
}
100121
}
101122
}

0 commit comments

Comments
 (0)