-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Update Workspace.MSBuild to reference Microsoft.Build.Framework. #78647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
What is the effect of the change? Is it reducing the net msbuild surface we are depending on / making it harder to inadvertently use msbuild APIs that we should not be using? |
It looks like possibly a subset of the transitive dependencies need to be brought back to fix the build |
… into dev/jorobich/msbuild-reference
Removes the GetProjectsInSolution BuildHost API. Instead we can use the SolutionPersister library in processes to parse the projects from a solution file.
} | ||
|
||
var projects = await buildHost.GetProjectsInSolutionAsync(solutionFilePath, CancellationToken.None); | ||
var (_, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, CancellationToken.None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the case that solutionFilePath
points to a .slnf ReadSolutionFileAsync
returns the filtered solution's file path. Should we be assigning that path to the ProjectFactory.SolutionPath
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you basically asking whether to use the path to the slnf
or the path to the associated sln
? I think we would want to use the slnf
, which this PR appears to be doing right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, it is working today without this change and is currently using the .slnf path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say that's probably reasonable anyways; the only real thing the solution path is used for is caches, and I could imagine what we have to cache is different for different filters over the same solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(if that's bad, maybe the fix is we should be passing around the workspace path or something else for that...)
throw new ArgumentNullException(nameof(solutionFilePath)); | ||
} | ||
|
||
if (!_pathResolver.TryGetAbsoluteSolutionPath(solutionFilePath, baseDirectory: Directory.GetCurrentDirectory(), DiagnosticReportingMode.Throw, out var absoluteSolutionPath)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this bit into a new SolutionFileReader
class.
private readonly DiagnosticReporter? _diagnosticReporter; | ||
|
||
public PathResolver(DiagnosticReporter diagnosticReporter) | ||
public PathResolver(DiagnosticReporter? diagnosticReporter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made the DiagnosticReporter
optional since this will also be indirectly used by the LanguageServerProjectSystem
which does not use a reporter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be though?
<PackageReference Include="Microsoft.Extensions.Logging" /> | ||
<PackageReference Include="Microsoft.VisualStudio.SolutionPersistence" /> | ||
<PackageReference Include="Newtonsoft.Json" /> | ||
<PackageReference Include="System.Text.Json" Condition="'$(TargetFramework)' == 'net472'" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally brought in by Microsoft.Build
and is already available as part of .NET.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we only need this for net472?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
System.Text.Json is part of the .NET BCL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having it as a PackageReference for .NET TFM will pin the version to whatever we use in the build instead of allowing it to use the version included with newer runtimes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add all that as a comment.
src/Workspaces/MSBuild/Core/MSBuild/SolutionFileReader.SolutionFilterReader.cs
Show resolved
Hide resolved
src/Workspaces/MSBuild/Test/Resources/XmlSolutionFiles/CSharpXmlSolution.slnx
Show resolved
Hide resolved
var serializer = SolutionSerializers.GetSerializerByMoniker(solutionFilePath); | ||
if (serializer == null) | ||
{ | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does the serializer being null here mean? Are there different serializers for sln vs slnx?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! And I imagine when solution filter support is add it will be a separate serializer.
if (!projectFilter.IsEmpty) | ||
{ | ||
Contract.ThrowIfFalse(pathResolver.TryGetAbsoluteProjectPath(projectModel.FilePath, baseDirectory, DiagnosticReportingMode.Throw, out var absoluteProjectPath)); | ||
if (!projectFilter.Contains(absoluteProjectPath)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting, does the solution parsing library not give us a way to get the subset of projects out of a solution filter? We have to do that ourselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not presently. However, there is an open feature request microsoft/vs-solutionpersistence#85
<PackageReference Include="Microsoft.Extensions.Logging" /> | ||
<PackageReference Include="Microsoft.VisualStudio.SolutionPersistence" /> | ||
<PackageReference Include="Newtonsoft.Json" /> | ||
<PackageReference Include="System.Text.Json" Condition="'$(TargetFramework)' == 'net472'" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we only need this for net472?
|
||
public static async Task<(string AbsoluteSolutionPath, ImmutableArray<(string ProjectPath, string ProjectGuid)> Projects)> ReadSolutionFileAsync(string solutionFilePath, PathResolver pathResolver, CancellationToken cancellationToken) | ||
{ | ||
Contract.ThrowIfFalse(pathResolver.TryGetAbsoluteSolutionPath(solutionFilePath, baseDirectory: Directory.GetCurrentDirectory(), DiagnosticReportingMode.Throw, out var absoluteSolutionPath)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd expect this Directory.GetCurrentDirectory() code is only used in the classic MSBuildWorkspace path; we shouldn't have relative paths bouncing around in the language server at all, and I'd actually be a bit worried what it might resolve to at all...
var projects = await TryReadSolutionFileAsync(absoluteSolutionPath, pathResolver, projectFilter, cancellationToken).ConfigureAwait(false); | ||
if (!projects.HasValue) | ||
{ | ||
throw new Exception(string.Format(WorkspaceMSBuildResources.Failed_to_load_solution_0, absoluteSolutionPath)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the only case this can happen is the format of the solution file isn't recognized? Should we give a better message in this case?
var serializer = SolutionSerializers.GetSerializerByMoniker(solutionFilePath); | ||
if (serializer == null) | ||
{ | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have this just throw? Otherwise we're having to deal with the null return at the caller which isn't really helping much since it makes me think the rest of the method isn't supposed to throw (but absolutely would if something went wrong?)
foreach (var projectModel in solutionModel.SolutionProjects) | ||
{ | ||
// If we are filtering based on a solution filter, then we need to verify the project is included. | ||
if (!projectFilter.IsEmpty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have an empty filter, what happens? Should this use a nullable set to make it more clear?
Since System.Text.Json is part of the .NET BCL we do not want to add | ||
it as a package reference. Doing so will pin the version to what we use | ||
in our build instead of allowing it to use the version from the .NET Runtime | ||
we are running against. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is System.Text.Json extra special here, or should we be doing this for other references too?
@@ -35,13 +35,7 @@ public static bool TryRead(string filterFilename, PathResolver pathResolver, [No | |||
return false; | |||
} | |||
|
|||
if (!pathResolver.TryGetAbsoluteSolutionPath(solutionPath, baseDirectory, DiagnosticReportingMode.Throw, out solutionFilename)) | |||
{ | |||
// TryGetAbsoluteSolutionPath should throw before we get here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be good to bring this comment back -- since if you're not aware of the DiagnosticReportingMode bit this might seem a bit odd...
Can someone tell me how to read a slnx-file programatically after this change? Using await workspace.OpenSolutionAsync("some.slnx"); still throws exceptions. Is there another way? |
Workspace.MSBuild requires a MSBuild dependency because our API references their
ILogger
interface. This PR changes the MSBuild references to be Microsoft.Build.Framework, which is where the interface is defined.In order to fully drop the Microsoft.Build dependency I also pulled in the VS-SolutionPersister changes which bring solution parsing back in-proc.