-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Automatically detect missing NuGet packages and restore #70851
Conversation
f69f9c2
to
98a3d40
Compare
Does this still work if the only change is removing a package reference? Why not just always run a restore after open? NuGet is expected to have a fast-path NOP if you run a restore after it was already up-to-date. |
It works for both - we'll detect a missing project.assets.json and run a restore if that is the case. |
...ageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs
Outdated
Show resolved
Hide resolved
...anguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectDependencyHelper.cs
Show resolved
Hide resolved
...ageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs
Outdated
Show resolved
Hide resolved
|
||
if (projectFileInfo.PackageReferences.IsEmpty) | ||
{ | ||
// If there are no package references then there are no unresolved dependencies. |
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 about stuff from the SDK itself? I admit I don't know the details here but I'm just asking.
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.
Missing stuff from the SDK should be covered under the presence of the project.assets.json. I think the only potential problem would be if you change TFM, but I'm not quite certain how to detect that...
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.
Looks like there is some information in the assets file on the target frameworks that change if you modify TFMs. Going to take a look to see if its as simple as checking if the TFMs in the project match the TFMs in the lock file.
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.
Missing stuff from the SDK should be covered under the presence of the project.assets.json.
I wasn't sure about the case where we've seen during dogfooding where you upgrade to some SDK version and the various implicit packs aren't actually in feeds yet.
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 added some code to look at target framework changes as well. However I don't think it will be easy to detect SDK version changes - we'd have to track and listen to changes in the global.json or look at which SDK the build host used to load the project (if you manually upgrade without having a global.json). I don't think that is necessarily worth doing.
...Server/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreParams.cs
Show resolved
Hide resolved
src/VisualStudio/Core/Test.Next/Options/VisualStudioOptionStorageTests.cs
Outdated
Show resolved
Hide resolved
...anguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectDependencyHelper.cs
Outdated
Show resolved
Hide resolved
{ | ||
var message = string.Format(LanguageServerResources.Project_0_has_unresolved_dependencies, projectFileInfo.FilePath) | ||
+ Environment.NewLine | ||
+ string.Join(Environment.NewLine, unresolved.Select(r => $"{r.Name}-{r.VersionRange}")); |
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 indenting this or something?
@dibarbet: Regarding @sharwell's point: What about something like:
At that point the project.assets.json will exist and will contain all the things restored. Does that need to run a restore again to remove the reference from the project? I don't know if the underlying SDK tasks will ignore that or will still operate on the last restore. |
After offline discussion - to ensure we run restore when a package reference is removed or a version changes (but still satisfies the range), going to look at diffing the last set of the package reference items we saw on the project, than fall back to project.assets.json checks (in case things change on disk). |
98a3d40
to
696bf99
Compare
696bf99
to
809fd2f
Compare
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.
Feedback is all naming/ordering things and could be done as a follow-up.
if (packageReferenceItems == null) | ||
{ | ||
return ImmutableArray<PackageReference>.Empty; | ||
} |
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.
This can happen? We aren't doing that for any of the other GetItems() checks above; do those need to be corrected too?
@@ -154,6 +160,7 @@ public override string ToString() | |||
ImmutableArray<DocumentFileInfo> additionalDocuments, | |||
ImmutableArray<DocumentFileInfo> analyzerConfigDocuments, | |||
ImmutableArray<ProjectFileReference> projectReferences, | |||
ImmutableArray<PackageReference> packageReferences, |
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.
Did you want to keep the constructor sorted with the properties?
{ | ||
private const string UnresolvedProjectDependenciesName = "workspace/_roslyn_projectHasUnresolvedDependencies"; | ||
|
||
internal static bool HasUnresolvedDependencies(ProjectFileInfo newProjectFileInfo, ProjectFileInfo? previousProjectFileInfo, ILogger logger) |
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.
internal static bool HasUnresolvedDependencies(ProjectFileInfo newProjectFileInfo, ProjectFileInfo? previousProjectFileInfo, ILogger logger) | |
internal static bool NeedsRestore(ProjectFileInfo newProjectFileInfo, ProjectFileInfo? previousProjectFileInfo, ILogger logger) |
Since this does a lot more than just check for unresolved dependencies now.
if (newProjectFileInfo.TargetFramework != previousProjectFileInfo.TargetFramework) | ||
{ | ||
// If the target framework has changed then we need to run a restore. | ||
return true; | ||
} |
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.
This will never happen, since we use TFM as a part of the identity of the project:
Line 86 in 73b4ebd
Contract.ThrowIfFalse(newProjectInfo.TargetFramework == _mostRecentFileInfo.TargetFramework); |
If we want to diff on TFM addition/removal we'd need to do that elsewhere.
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.
Ah will remove, but this will still work- it will hit the case where we don't have any previous project file info and we'll trigger a restore.
return true; | ||
} | ||
|
||
if (!newPackageReferences.SetEquals(previousPackageReferences)) |
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.
We should really just teach SetEquals to do the length check first, but that can be later.
private static bool CheckProjectAssetsForUnresolvedDependencies(ProjectFileInfo projectFileInfo, ILogger logger) | ||
{ | ||
var projectAssetsPath = projectFileInfo.ProjectAssetsFilePath; | ||
if (!File.Exists(projectAssetsPath)) |
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.
Today I learned File.Exists() takes a null. I'm not sure how I feel about that.
@@ -26,7 +26,7 @@ internal class ProjectLoadTelemetryReporter(ILoggerFactory loggerFactory, Server | |||
/// so that we are able to compare data accurately. | |||
/// See https://github.com/OmniSharp/omnisharp-roslyn/blob/b2e64c6006beed49460f063117793f42ab2a8a5c/src/OmniSharp.MSBuild/ProjectLoadListener.cs#L36 | |||
/// </summary> | |||
public async Task ReportProjectLoadTelemetryAsync(Dictionary<ProjectFileInfo, (ImmutableArray<CommandLineReference> MetadataReferences, OutputKind OutputKind)> projectFileInfos, ProjectToLoad projectToLoad, CancellationToken cancellationToken) | |||
public async Task ReportProjectLoadTelemetryAsync(Dictionary<ProjectFileInfo, (ImmutableArray<CommandLineReference> MetadataReferences, OutputKind OutputKind, bool HasUnresolvedDependencies)> projectFileInfos, ProjectToLoad projectToLoad, CancellationToken cancellationToken) |
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.
public async Task ReportProjectLoadTelemetryAsync(Dictionary<ProjectFileInfo, (ImmutableArray<CommandLineReference> MetadataReferences, OutputKind OutputKind, bool HasUnresolvedDependencies)> projectFileInfos, ProjectToLoad projectToLoad, CancellationToken cancellationToken) | |
public async Task ReportProjectLoadTelemetryAsync(Dictionary<ProjectFileInfo, (ImmutableArray<CommandLineReference> MetadataReferences, OutputKind OutputKind, bool NeedsRestore)> projectFileInfos, ProjectToLoad projectToLoad, CancellationToken cancellationToken) |
@@ -185,11 +185,13 @@ public async ValueTask<(ImmutableArray<CommandLineReference>, OutputKind)> Updat | |||
|
|||
WatchProjectAssetsFile(newProjectInfo, _fileChangeContext); | |||
|
|||
var hasUnresolvedDependencies = ProjectDependencyHelper.HasUnresolvedDependencies(newProjectInfo, _mostRecentFileInfo, logger); |
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.
Also rename to "needs restore".
@@ -195,7 +212,7 @@ private async ValueTask LoadOrReloadProjectsAsync(ImmutableSegmentedList<Project | |||
return binaryLogPath; | |||
} | |||
|
|||
private async Task<(LSP.MessageType? FailureType, BuildHostProcessKind? PreferredKind)> LoadOrReloadProjectAsync(ProjectToLoad projectToLoad, BuildHostProcessManager buildHostProcessManager, CancellationToken cancellationToken) | |||
private async Task<(LSP.MessageType? FailureType, BuildHostProcessKind? PreferredKind, bool HasUnresolvedDependencies)> LoadOrReloadProjectAsync(ProjectToLoad projectToLoad, BuildHostProcessManager buildHostProcessManager, CancellationToken cancellationToken) |
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.
private async Task<(LSP.MessageType? FailureType, BuildHostProcessKind? PreferredKind, bool HasUnresolvedDependencies)> LoadOrReloadProjectAsync(ProjectToLoad projectToLoad, BuildHostProcessManager buildHostProcessManager, CancellationToken cancellationToken) | |
private async Task<(LSP.MessageType? FailureType, BuildHostProcessKind? PreferredKind, bool NeedsRestore)> LoadOrReloadProjectAsync(ProjectToLoad projectToLoad, BuildHostProcessManager buildHostProcessManager, CancellationToken cancellationToken) |
responding to feedback in a followup |
Implements basic support for automatic nuget restore in the C# standalone project system. This is done by checking the project.assets.json to ensure all project dependencies are present.
Client PR: dotnet/vscode-csharp#6676
Looks like:
![automatic_nuget_restore](https://private-user-images.githubusercontent.com/5749229/283563030-b20bc78a-9d88-4a4c-8790-18e651593982.gif?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk4Nzg0NDQsIm5iZiI6MTczOTg3ODE0NCwicGF0aCI6Ii81NzQ5MjI5LzI4MzU2MzAzMC1iMjBiYzc4YS05ZDg4LTRhNGMtODc5MC0xOGU2NTE1OTM5ODIuZ2lmP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDIxOCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTAyMThUMTEyOTA0WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZjUzOGU3ZGZmZTZlNzk0OGYwNTU1M2JkZmM1MDE0MTNiODIzODU0ZmVjOGYyNDQ5MjZkZmNiZjBiNzc2ZjgyNiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.XdUfUX_xGVC3WhLpbJL0IgOYjcb-mhpK5HuU_LOhz44)