Skip to content

Commit 729bd2f

Browse files
authored
Ensure sln load uses project absolute paths (#78772)
fixes issue found by integration tests in dotnet/vscode-csharp#8331 The new solution parser API gives us relative paths, but contracts in the project system require absolute paths ``` 2025-05-29 20:42:14.888 [info] [Error - 8:42:14 PM] [solution/open] [LSP] System.InvalidOperationException: Unexpected false - file LanguageServerProjectLoader.cs line 195 at Microsoft.CodeAnalysis.Contract.Fail(String message, Int32 lineNumber, String filePath) in /_/src/Dependencies/Contracts/Contract.cs:line 161 at Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.LanguageServerProjectLoader.ReloadProjectAsync(ProjectToLoad projectToLoad, ToastErrorReporter toastErrorReporter, BuildHostProcessManager buildHostProcessManager, CancellationToken cancellationToken) in /_/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs:line 195 at Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.LanguageServerProjectLoader.<>c.<<ReloadProjectsAsync>b__16_0>d.MoveNext() in /_/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs:line 158 --- End of stack trace from previous location --- at Microsoft.CodeAnalysis.Shared.Utilities.ProducerConsumer`1.<>c__DisplayClass11_0`3.<<RunParallelChannelAsync>b__2>d.MoveNext() in /_/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ProducerConsumer.cs:line 256 --- End of stack trace from previous location --- at System.Threading.Tasks.Parallel.<>c__57`1.<<ForEachAsync>b__57_0>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.CodeAnalysis.Shared.Utilities.ProducerConsumer`1.<>c__14`2.<<RunChannelAsync>b__14_3>d.MoveNext() in /_/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ProducerConsumer.cs:line 385 --- End of stack trace from previous location --- at Microsoft.CodeAnalysis.Shared.Utilities.ProducerConsumer`1.PerformActionAndCloseWriterAsync[TArgs](Func`3 action, TArgs args, ChannelWriter`1 writer, CancellationToken cancellationToken) in /_/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ProducerConsumer.cs:line 402 at Microsoft.CodeAnalysis.Shared.Utilities.ProducerConsumer`1.RunChannelAsync[TArgs,TResult](ProducerConsumerOptions options, Func`4 produceItems, Func`4 consumeItems, TArgs args, CancellationToken cancellationToken) in /_/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ProducerConsumer.cs:line 362 at Microsoft.CodeAnalysis.Shared.Utilities.ProducerConsumer`1.RunParallelAsync[TSource,TArgs](IAsyncEnumerable`1 source, Func`5 produceItems, TArgs args, CancellationToken cancellationToken) in /_/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ProducerConsumer.cs:line 229 at Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.LanguageServerProjectLoader.ReloadProjectsAsync(ImmutableSegmentedList`1 projectPathsToLoadOrReload, CancellationToken cancellationToken) in /_/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs:line 153 at Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.LanguageServerProjectLoader.ReloadProjectsAsync(ImmutableSegmentedList`1 projectPathsToLoadOrReload, CancellationToken cancellationToken) in /_/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs:line 179 at Microsoft.CodeAnalysis.Threading.AsyncBatchingWorkQueue`1.<>c__DisplayClass2_0.<<Convert>b__0>d.MoveNext() in /_/src/Dependencies/Threading/AsyncBatchingWorkQueue`1.cs:line 40 --- End of stack trace from previous location --- at Microsoft.CodeAnalysis.Threading.AsyncBatchingWorkQueue`2.ProcessNextBatchAsync() in /_/src/Dependencies/Threading/AsyncBatchingWorkQueue`2.cs:line 274 at Microsoft.CodeAnalysis.Threading.AsyncBatchingWorkQueue`2.<AddWork>g__ContinueAfterDelayAsync|16_1(Task lastTask) in /_/src/Dependencies/Threading/AsyncBatchingWorkQueue`2.cs:line 221 at Microsoft.CodeAnalysis.Threading.AsyncBatchingWorkQueue`2.WaitUntilCurrentBatchCompletesAsync() in /_/src/Dependencies/Threading/AsyncBatchingWorkQueue`2.cs:line 238 at Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.LanguageServerProjectSystem.OpenSolutionAsync(String solutionFilePath) in /_/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs:line 65 at Microsoft.CommonLanguageServerProtocol.Framework.QueueItem`1.StartRequestAsync[TRequest,TResponse](TRequest request, TRequestContext context, IMethodHandler handler, String language, CancellationToken cancellationToken) in /_/src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs:line 203 ```
2 parents c5dafa2 + 15406f5 commit 729bd2f

File tree

6 files changed

+54
-42
lines changed

6 files changed

+54
-42
lines changed

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public async Task OpenSolutionAsync(string solutionFilePath)
5757
_logger.LogInformation(string.Format(LanguageServerResources.Loading_0, solutionFilePath));
5858
ProjectFactory.SolutionPath = solutionFilePath;
5959

60-
var (_, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, CancellationToken.None);
60+
var (_, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, DiagnosticReportingMode.Throw, CancellationToken.None);
6161
foreach (var (path, guid) in projects)
6262
{
6363
await BeginLoadingProjectAsync(path, guid);

src/Workspaces/MSBuild/Core/MSBuild/MSBuildProjectLoader.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,20 +167,20 @@ public async Task<SolutionInfo> LoadSolutionInfoAsync(
167167
throw new ArgumentNullException(nameof(solutionFilePath));
168168
}
169169

170-
var (absoluteSolutionPath, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, _pathResolver, cancellationToken).ConfigureAwait(false);
170+
var reportingMode = GetReportingModeForUnrecognizedProjects();
171+
172+
var reportingOptions = new DiagnosticReportingOptions(
173+
onPathFailure: reportingMode,
174+
onLoaderFailure: reportingMode);
175+
176+
var (absoluteSolutionPath, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, _pathResolver, reportingMode, cancellationToken).ConfigureAwait(false);
171177
var projectPaths = projects.SelectAsArray(p => p.ProjectPath);
172178

173179
using (_dataGuard.DisposableWait(cancellationToken))
174180
{
175181
SetSolutionProperties(absoluteSolutionPath);
176182
}
177183

178-
var reportingMode = GetReportingModeForUnrecognizedProjects();
179-
180-
var reportingOptions = new DiagnosticReportingOptions(
181-
onPathFailure: reportingMode,
182-
onLoaderFailure: reportingMode);
183-
184184
var buildHostProcessManager = new BuildHostProcessManager(Properties, loggerFactory: _loggerFactory);
185185
await using var _ = buildHostProcessManager.ConfigureAwait(false);
186186

src/Workspaces/MSBuild/Core/MSBuild/SolutionFileReader.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

@@ -14,12 +14,12 @@ namespace Microsoft.CodeAnalysis.MSBuild;
1414

1515
internal partial class SolutionFileReader
1616
{
17-
public static Task<(string AbsoluteSolutionPath, ImmutableArray<(string ProjectPath, string ProjectGuid)> Projects)> ReadSolutionFileAsync(string solutionFilePath, CancellationToken cancellationToken)
17+
public static Task<(string AbsoluteSolutionPath, ImmutableArray<(string ProjectPath, string ProjectGuid)> Projects)> ReadSolutionFileAsync(string solutionFilePath, DiagnosticReportingMode diagnosticReportingMode, CancellationToken cancellationToken)
1818
{
19-
return ReadSolutionFileAsync(solutionFilePath, new PathResolver(diagnosticReporter: null), cancellationToken);
19+
return ReadSolutionFileAsync(solutionFilePath, new PathResolver(diagnosticReporter: null), diagnosticReportingMode, cancellationToken);
2020
}
2121

22-
public static async Task<(string AbsoluteSolutionPath, ImmutableArray<(string ProjectPath, string ProjectGuid)> Projects)> ReadSolutionFileAsync(string solutionFilePath, PathResolver pathResolver, CancellationToken cancellationToken)
22+
public static async Task<(string AbsoluteSolutionPath, ImmutableArray<(string ProjectPath, string ProjectGuid)> Projects)> ReadSolutionFileAsync(string solutionFilePath, PathResolver pathResolver, DiagnosticReportingMode diagnosticReportingMode, CancellationToken cancellationToken)
2323
{
2424
Contract.ThrowIfFalse(pathResolver.TryGetAbsoluteSolutionPath(solutionFilePath, baseDirectory: Directory.GetCurrentDirectory(), DiagnosticReportingMode.Throw, out var absoluteSolutionPath));
2525

@@ -31,7 +31,7 @@ internal partial class SolutionFileReader
3131
throw new Exception(string.Format(WorkspaceMSBuildResources.Failed_to_load_solution_filter_0, solutionFilePath));
3232
}
3333

34-
var projects = await TryReadSolutionFileAsync(absoluteSolutionPath, pathResolver, projectFilter, cancellationToken).ConfigureAwait(false);
34+
var projects = await TryReadSolutionFileAsync(absoluteSolutionPath, pathResolver, projectFilter, diagnosticReportingMode, cancellationToken).ConfigureAwait(false);
3535
if (!projects.HasValue)
3636
{
3737
throw new Exception(string.Format(WorkspaceMSBuildResources.Failed_to_load_solution_0, absoluteSolutionPath));
@@ -40,7 +40,7 @@ internal partial class SolutionFileReader
4040
return (absoluteSolutionPath, projects.Value);
4141
}
4242

43-
private static async Task<ImmutableArray<(string ProjectPath, string ProjectGuid)>?> TryReadSolutionFileAsync(string solutionFilePath, PathResolver pathResolver, ImmutableHashSet<string> projectFilter, CancellationToken cancellationToken)
43+
private static async Task<ImmutableArray<(string ProjectPath, string ProjectGuid)>?> TryReadSolutionFileAsync(string solutionFilePath, PathResolver pathResolver, ImmutableHashSet<string> projectFilter, DiagnosticReportingMode diagnosticReportingMode, CancellationToken cancellationToken)
4444
{
4545
var serializer = SolutionSerializers.GetSerializerByMoniker(solutionFilePath);
4646
if (serializer == null)
@@ -57,17 +57,21 @@ internal partial class SolutionFileReader
5757
var builder = ImmutableArray.CreateBuilder<(string ProjectPath, string ProjectGuid)>();
5858
foreach (var projectModel in solutionModel.SolutionProjects)
5959
{
60-
// If we are filtering based on a solution filter, then we need to verify the project is included.
61-
if (!projectFilter.IsEmpty)
60+
// If we didn't get an absolute path skip the file. The solution may have an invalid project in it,
61+
// but we don't want to throw on that here. The path resolver will throw / report a diagnostic if it couldn't resolve the path.
62+
if (pathResolver.TryGetAbsoluteProjectPath(projectModel.FilePath, baseDirectory, diagnosticReportingMode, out var absoluteProjectPath))
6263
{
63-
Contract.ThrowIfFalse(pathResolver.TryGetAbsoluteProjectPath(projectModel.FilePath, baseDirectory, DiagnosticReportingMode.Throw, out var absoluteProjectPath));
64-
if (!projectFilter.Contains(absoluteProjectPath))
64+
// If we are filtering based on a solution filter, then we need to verify the project is included.
65+
if (!projectFilter.IsEmpty)
6566
{
66-
continue;
67+
if (!projectFilter.Contains(absoluteProjectPath))
68+
{
69+
continue;
70+
}
6771
}
68-
}
6972

70-
builder.Add((projectModel.FilePath, projectModel.Id.ToString()));
73+
builder.Add((absoluteProjectPath, projectModel.Id.ToString()));
74+
}
7175
}
7276

7377
return builder.ToImmutable();

src/Workspaces/MSBuild/Test/Resources/SolutionFiles/EmptyLineBetweenProjectBlock.sln

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 2012
4-
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "ConsoleApplication2", "ConsoleApplication2\ConsoleApplication2.vbproj", "{378156D6-840C-47CC-A465-639548363FF7}"
4+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpProject", "CSharpProject\CSharpProject.csproj", "{686DD036-86AA-443E-8A10-DDB43266A8C4}"
55
EndProject
66

77
# Comment to test the handling of the parsing of .sln file with comments b/n project block
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApplication1", "ConsoleApplication1\ConsoleApplication1.csproj", "{BF7B38CE-F5CA-4467-A3CF-D1796CE2BAC0}"
8+
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "VisualBasicProject", "VisualBasicProject\VisualBasicProject.vbproj", "{AC25ECDA-DE94-4FCF-A688-EB3A2BE3670C}"
99
EndProject
1010
Global
1111
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1212
Debug|Any CPU = Debug|Any CPU
1313
Release|Any CPU = Release|Any CPU
1414
EndGlobalSection
1515
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16-
{378156D6-840C-47CC-A465-639548363FF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17-
{378156D6-840C-47CC-A465-639548363FF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
18-
{378156D6-840C-47CC-A465-639548363FF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
19-
{378156D6-840C-47CC-A465-639548363FF7}.Release|Any CPU.Build.0 = Release|Any CPU
20-
{BF7B38CE-F5CA-4467-A3CF-D1796CE2BAC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21-
{BF7B38CE-F5CA-4467-A3CF-D1796CE2BAC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
22-
{BF7B38CE-F5CA-4467-A3CF-D1796CE2BAC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
23-
{BF7B38CE-F5CA-4467-A3CF-D1796CE2BAC0}.Release|Any CPU.Build.0 = Release|Any CPU
16+
{686DD036-86AA-443E-8A10-DDB43266A8C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17+
{686DD036-86AA-443E-8A10-DDB43266A8C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
18+
{686DD036-86AA-443E-8A10-DDB43266A8C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
19+
{686DD036-86AA-443E-8A10-DDB43266A8C4}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{AC25ECDA-DE94-4FCF-A688-EB3A2BE3670C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{AC25ECDA-DE94-4FCF-A688-EB3A2BE3670C}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{AC25ECDA-DE94-4FCF-A688-EB3A2BE3670C}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{AC25ECDA-DE94-4FCF-A688-EB3A2BE3670C}.Release|Any CPU.Build.0 = Release|Any CPU
2424
EndGlobalSection
2525
GlobalSection(SolutionProperties) = preSolution
2626
HideSolutionNode = FALSE

src/Workspaces/MSBuild/Test/VisualStudioMSBuildWorkspaceTests.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,13 +1002,11 @@ public async Task TestOpenSolution_WithLockedFile_LoadsWithEmptyText()
10021002
public async Task TestOpenSolution_WithInvalidProjectPath_SkipTrue_SucceedsWithFailureEvent()
10031003
{
10041004
// when skipped we should see a diagnostic for the invalid project
1005-
1006-
CreateFiles(GetSimpleCSharpSolutionFiles()
1007-
.WithFile(@"TestSolution.sln", Resources.SolutionFiles.InvalidProjectPath));
1008-
10091005
var solutionFilePath = GetSolutionFileName(@"TestSolution.sln");
1006+
CreateFiles(GetSimpleCSharpProjectFiles()
1007+
.WithFile(solutionFilePath, Resources.SolutionFiles.InvalidProjectPath));
10101008

1011-
using var workspace = CreateMSBuildWorkspace(throwOnWorkspaceFailed: false);
1009+
using var workspace = CreateMSBuildWorkspace(throwOnWorkspaceFailed: false, skipUnrecognizedProjects: true);
10121010
var solution = await workspace.OpenSolutionAsync(solutionFilePath);
10131011
Assert.Single(workspace.Diagnostics);
10141012
}
@@ -1050,7 +1048,7 @@ public async Task TestOpenSolution_WithNonExistentProject_SkipTrue_SucceedsWithF
10501048
.WithFile(@"TestSolution.sln", Resources.SolutionFiles.NonExistentProject));
10511049
var solutionFilePath = GetSolutionFileName(@"TestSolution.sln");
10521050

1053-
using var workspace = CreateMSBuildWorkspace(throwOnWorkspaceFailed: false);
1051+
using var workspace = CreateMSBuildWorkspace(throwOnWorkspaceFailed: false, skipUnrecognizedProjects: true);
10541052
var solution = await workspace.OpenSolutionAsync(solutionFilePath);
10551053

10561054
Assert.Single(workspace.Diagnostics);
@@ -2653,11 +2651,12 @@ public async Task TestOpenSolution_SolutionFileHasEmptyLinesAndWhitespaceOnlyLin
26532651
[WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/531543")]
26542652
public async Task TestOpenSolution_SolutionFileHasEmptyLineBetweenProjectBlock()
26552653
{
2656-
var files = new FileSet(
2657-
(@"TestSolution.sln", Resources.SolutionFiles.EmptyLineBetweenProjectBlock));
2654+
var solutionFilePath = GetSolutionFileName("TestSolution.sln");
2655+
var files = GetSimpleCSharpProjectFiles()
2656+
.Concat(GetSimpleVisualBasicProjectFiles())
2657+
.Concat([(solutionFilePath, Resources.SolutionFiles.EmptyLineBetweenProjectBlock)]);
26582658

26592659
CreateFiles(files);
2660-
var solutionFilePath = GetSolutionFileName("TestSolution.sln");
26612660

26622661
using var workspace = CreateMSBuildWorkspace(throwOnWorkspaceFailed: false);
26632662
var solution = await workspace.OpenSolutionAsync(solutionFilePath);

src/Workspaces/MSBuild/Test/WorkspaceTestBase.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Generic;
99
using System.Diagnostics;
1010
using System.IO;
11+
using System.Linq;
1112
using Microsoft.CodeAnalysis.Test.Utilities;
1213
using Microsoft.CodeAnalysis.UnitTests.TestFiles;
1314
using Roslyn.Test.Utilities;
@@ -84,18 +85,26 @@ protected void CreateCSharpFiles()
8485
}
8586

8687
protected static FileSet GetSimpleCSharpSolutionFiles()
88+
{
89+
return GetSimpleCSharpProjectFiles().WithFile(@"TestSolution.sln", Resources.SolutionFiles.CSharp);
90+
}
91+
92+
protected static FileSet GetSimpleCSharpProjectFiles()
8793
{
8894
return new FileSet(
89-
(@"TestSolution.sln", Resources.SolutionFiles.CSharp),
9095
(@"CSharpProject\CSharpProject.csproj", Resources.ProjectFiles.CSharp.CSharpProject),
9196
(@"CSharpProject\CSharpClass.cs", Resources.SourceFiles.CSharp.CSharpClass),
9297
(@"CSharpProject\Properties\AssemblyInfo.cs", Resources.SourceFiles.CSharp.AssemblyInfo));
9398
}
9499

95100
protected static FileSet GetSimpleVisualBasicSolutionFiles()
101+
{
102+
return GetSimpleVisualBasicProjectFiles().WithFile(@"TestSolution.sln", Resources.SolutionFiles.VisualBasic);
103+
}
104+
105+
protected static FileSet GetSimpleVisualBasicProjectFiles()
96106
{
97107
return new FileSet(
98-
(@"TestSolution.sln", Resources.SolutionFiles.VisualBasic),
99108
(@"VisualBasicProject\VisualBasicProject.vbproj", Resources.ProjectFiles.VisualBasic.VisualBasicProject),
100109
(@"VisualBasicProject\VisualBasicClass.vb", Resources.SourceFiles.VisualBasic.VisualBasicClass),
101110
(@"VisualBasicProject\My Project\Application.Designer.vb", Resources.SourceFiles.VisualBasic.Application_Designer),

0 commit comments

Comments
 (0)