Skip to content

Commit 0ed4f02

Browse files
authored
Merge pull request #8850 from tmeschter/230206-RefactorPackageRestore
Move PackageRestoreDataSource to Microsoft.VisualStudio.ProjectSystem.Managed
2 parents 962b2f0 + 431d104 commit 0ed4f02

File tree

74 files changed

+1548
-1099
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1548
-1099
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
2+
3+
using Microsoft.VisualStudio.Composition;
4+
using Microsoft.VisualStudio.Imaging;
5+
using Microsoft.VisualStudio.ProjectSystem.VS.UI.InfoBarService;
6+
7+
namespace Microsoft.VisualStudio.Notifications;
8+
9+
/// <summary>
10+
/// An implementation of <see cref="INonModalNotificationService"/> that delegates to the VS info bar.
11+
/// </summary>
12+
[Export(typeof(INonModalNotificationService))]
13+
internal class NonModalNotificationService : INonModalNotificationService
14+
{
15+
private readonly IInfoBarService _infoBarService;
16+
17+
[ImportingConstructor]
18+
public NonModalNotificationService(IInfoBarService infoBarService)
19+
{
20+
_infoBarService = infoBarService;
21+
}
22+
23+
public Task ShowMessageAsync(string message, CancellationToken cancellationToken)
24+
{
25+
return _infoBarService.ShowInfoBarAsync(message, KnownMonikers.StatusInformation, cancellationToken);
26+
}
27+
28+
public Task ShowWarningAsync(string message, CancellationToken cancellationToken)
29+
{
30+
return _infoBarService.ShowInfoBarAsync(message, KnownMonikers.StatusWarning, cancellationToken);
31+
}
32+
33+
public Task ShowErrorAsync(string message, CancellationToken cancellationToken)
34+
{
35+
return _infoBarService.ShowInfoBarAsync(message, KnownMonikers.StatusError, cancellationToken);
36+
}
37+
}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
2+
3+
using Microsoft.Internal.Performance;
4+
using Microsoft.VisualStudio.ProjectSystem.PackageRestore;
5+
using Microsoft.VisualStudio.Threading;
6+
using NuGet.SolutionRestoreManager;
7+
8+
namespace Microsoft.VisualStudio.ProjectSystem.VS.PackageRestore;
9+
10+
[Export(typeof(INuGetRestoreService))]
11+
[Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))]
12+
[AppliesTo(ProjectCapability.PackageReferences)]
13+
internal class NuGetRestoreService : OnceInitializedOnceDisposed, INuGetRestoreService, IProjectDynamicLoadComponent, IVsProjectRestoreInfoSource
14+
{
15+
private readonly UnconfiguredProject _project;
16+
private readonly IVsSolutionRestoreService3 _solutionRestoreService3;
17+
private readonly IVsSolutionRestoreService4 _solutionRestoreService4;
18+
private readonly IProjectAsynchronousTasksService _projectAsynchronousTasksService;
19+
private readonly IProjectFaultHandlerService _faultHandlerService;
20+
21+
/// <summary>
22+
/// Save the configured project versions that might get nominations.
23+
/// </summary>
24+
private readonly Dictionary<ProjectConfiguration, IComparable> _savedNominatedConfiguredVersion = new();
25+
26+
/// <summary>
27+
/// Re-usable task that completes when there is a new nomination
28+
/// </summary>
29+
private TaskCompletionSource<bool>? _whenNominatedTask;
30+
31+
private bool _enabled;
32+
private bool _restoring;
33+
private bool _updatesCompleted;
34+
35+
[ImportingConstructor]
36+
public NuGetRestoreService(
37+
UnconfiguredProject project,
38+
IVsSolutionRestoreService3 solutionRestoreService3,
39+
IVsSolutionRestoreService4 solutionRestoreService4,
40+
[Import(ExportContractNames.Scopes.UnconfiguredProject)] IProjectAsynchronousTasksService projectAsynchronousTasksService,
41+
IProjectFaultHandlerService faultHandlerService)
42+
{
43+
_project = project;
44+
_solutionRestoreService3 = solutionRestoreService3;
45+
_solutionRestoreService4 = solutionRestoreService4;
46+
_projectAsynchronousTasksService = projectAsynchronousTasksService;
47+
_faultHandlerService = faultHandlerService;
48+
}
49+
50+
public async Task<bool> NominateAsync(ProjectRestoreInfo restoreData, IReadOnlyCollection<PackageRestoreConfiguredInput> inputVersions, CancellationToken cancellationToken)
51+
{
52+
try
53+
{
54+
_restoring = true;
55+
56+
Task<bool> restoreOperation = _solutionRestoreService3.NominateProjectAsync(_project.FullPath, new VsProjectRestoreInfo(restoreData), cancellationToken);
57+
58+
SaveNominatedConfiguredVersions(inputVersions);
59+
60+
return await restoreOperation;
61+
}
62+
finally
63+
{
64+
CodeMarkers.Instance.CodeMarker(CodeMarkerTimerId.PerfPackageRestoreEnd);
65+
_restoring = false;
66+
}
67+
}
68+
69+
public Task UpdateWithoutNominationAsync(IReadOnlyCollection<PackageRestoreConfiguredInput> inputVersions)
70+
{
71+
SaveNominatedConfiguredVersions(inputVersions);
72+
73+
return Task.CompletedTask;
74+
}
75+
76+
public void NotifyComplete()
77+
{
78+
lock (SyncObject)
79+
{
80+
_updatesCompleted = true;
81+
_whenNominatedTask?.TrySetCanceled();
82+
}
83+
}
84+
85+
public void NotifyFaulted(Exception e)
86+
{
87+
lock (SyncObject)
88+
{
89+
_updatesCompleted = true;
90+
_whenNominatedTask?.SetException(e);
91+
}
92+
}
93+
94+
public Task WhenNominated(CancellationToken cancellationToken)
95+
{
96+
lock (SyncObject)
97+
{
98+
if (cancellationToken.IsCancellationRequested)
99+
{
100+
cancellationToken.ThrowIfCancellationRequested();
101+
}
102+
103+
if (!CheckIfHasPendingNomination())
104+
{
105+
return Task.CompletedTask;
106+
}
107+
108+
_whenNominatedTask ??= new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
109+
}
110+
111+
return _whenNominatedTask.Task.WithCancellation(cancellationToken);
112+
}
113+
114+
public string Name => _project.FullPath;
115+
116+
public bool HasPendingNomination => CheckIfHasPendingNomination();
117+
118+
public Task LoadAsync()
119+
{
120+
_enabled = true;
121+
122+
EnsureInitialized();
123+
124+
return Task.CompletedTask;
125+
}
126+
127+
public Task UnloadAsync()
128+
{
129+
lock (SyncObject)
130+
{
131+
_enabled = false;
132+
133+
_whenNominatedTask?.TrySetCanceled();
134+
}
135+
136+
return Task.CompletedTask;
137+
}
138+
139+
protected override void Initialize()
140+
{
141+
RegisterProjectRestoreInfoSource();
142+
}
143+
144+
protected override void Dispose(bool disposing)
145+
{
146+
}
147+
148+
private void SaveNominatedConfiguredVersions(IReadOnlyCollection<PackageRestoreConfiguredInput> configuredInputs)
149+
{
150+
lock (SyncObject)
151+
{
152+
_savedNominatedConfiguredVersion.Clear();
153+
154+
foreach (var configuredInput in configuredInputs)
155+
{
156+
_savedNominatedConfiguredVersion[configuredInput.ProjectConfiguration] = configuredInput.ConfiguredProjectVersion;
157+
}
158+
159+
if (_whenNominatedTask is not null)
160+
{
161+
if (_whenNominatedTask.TrySetResult(true))
162+
{
163+
_whenNominatedTask = null;
164+
}
165+
}
166+
}
167+
}
168+
169+
private bool CheckIfHasPendingNomination()
170+
{
171+
lock (SyncObject)
172+
{
173+
Assumes.Present(_project.Services.ActiveConfiguredProjectProvider);
174+
Assumes.Present(_project.Services.ActiveConfiguredProjectProvider.ActiveConfiguredProject);
175+
176+
// Nuget should not wait for projects that failed DTB
177+
if (!_enabled || _updatesCompleted)
178+
{
179+
return false;
180+
}
181+
182+
// Avoid possible deadlock.
183+
// Because RestoreCoreAsync() is called inside a dataflow block it will not be called with new data
184+
// until the old task finishes. So, if the project gets nominating restore, it will not get updated data.
185+
if (_restoring)
186+
{
187+
return false;
188+
}
189+
190+
ConfiguredProject? activeConfiguredProject = _project.Services.ActiveConfiguredProjectProvider.ActiveConfiguredProject;
191+
192+
// After the first nomination, we should check the saved nominated version
193+
return IsSavedNominationOutOfDate(activeConfiguredProject);
194+
}
195+
}
196+
197+
private bool IsSavedNominationOutOfDate(ConfiguredProject activeConfiguredProject)
198+
{
199+
if (!_savedNominatedConfiguredVersion.TryGetValue(activeConfiguredProject.ProjectConfiguration,
200+
out IComparable latestSavedVersionForActiveConfiguredProject) ||
201+
activeConfiguredProject.ProjectVersion.IsLaterThan(latestSavedVersionForActiveConfiguredProject))
202+
{
203+
return true;
204+
}
205+
206+
if (_savedNominatedConfiguredVersion.Count == 1)
207+
{
208+
return false;
209+
}
210+
211+
foreach (ConfiguredProject loadedProject in activeConfiguredProject.UnconfiguredProject.LoadedConfiguredProjects)
212+
{
213+
if (_savedNominatedConfiguredVersion.TryGetValue(loadedProject.ProjectConfiguration, out IComparable savedProjectVersion))
214+
{
215+
if (loadedProject.ProjectVersion.IsLaterThan(savedProjectVersion))
216+
{
217+
return true;
218+
}
219+
}
220+
}
221+
222+
return false;
223+
}
224+
225+
private void RegisterProjectRestoreInfoSource()
226+
{
227+
// Register before this project receives any data flows containing possible nominations.
228+
// This is needed because we need to register before any nuget restore or before the solution load.
229+
#pragma warning disable RS0030 // Do not used banned APIs
230+
var registerRestoreInfoSourceTask = Task.Run(() =>
231+
{
232+
return _solutionRestoreService4.RegisterRestoreInfoSourceAsync(this, _projectAsynchronousTasksService.UnloadCancellationToken);
233+
});
234+
#pragma warning restore RS0030 // Do not used banned APIs
235+
236+
_faultHandlerService.Forget(registerRestoreInfoSourceTask, _project, ProjectFaultSeverity.Recoverable);
237+
}
238+
}

0 commit comments

Comments
 (0)