-
Notifications
You must be signed in to change notification settings - Fork 695
/
Copy pathCpsPackageReferenceProject.cs
435 lines (371 loc) · 20.1 KB
/
CpsPackageReferenceProject.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft;
using Microsoft.VisualStudio.ProjectSystem;
using Microsoft.VisualStudio.ProjectSystem.Properties;
using Microsoft.VisualStudio.ProjectSystem.References;
using Microsoft.VisualStudio.Threading;
using NuGet.Commands;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Frameworks;
using NuGet.LibraryModel;
using NuGet.PackageManagement.VisualStudio.Exceptions;
using NuGet.PackageManagement.VisualStudio.Utility;
using NuGet.Packaging.Core;
using NuGet.ProjectManagement;
using NuGet.ProjectModel;
using NuGet.Versioning;
using NuGet.VisualStudio;
using PackageReference = NuGet.Packaging.PackageReference;
using Task = System.Threading.Tasks.Task;
namespace NuGet.PackageManagement.VisualStudio
{
/// <summary>
/// Represents a project object associated with Common Project System (CPS) project that has opt'd
/// into package references. This includes, but may not be limited to, .NET Project System,
/// C++/CLI (with PackageReference support) and MSIX deployment projects.
/// Key feature/difference is the project restore info is pushed by nomination API and stored in
/// a cache. Factory method retrieving the info from the cache should be provided.
/// </summary>
public class CpsPackageReferenceProject : PackageReferenceProject
{
private const string TargetFrameworkCondition = "TargetFramework";
private readonly IProjectSystemCache _projectSystemCache;
private readonly UnconfiguredProject _unconfiguredProject;
private List<(NuGetFramework TargetFramework, Dictionary<string, ProjectInstalledPackage> Packages)> _installedPackages = new List<(NuGetFramework, Dictionary<string, ProjectInstalledPackage>)>();
private List<(NuGetFramework TargetFramework, Dictionary<string, ProjectInstalledPackage> Packages)> _transitivePackages = new List<(NuGetFramework, Dictionary<string, ProjectInstalledPackage>)>();
private WeakReference<PackageSpec> _lastPackageSpec;
public CpsPackageReferenceProject(
string projectName,
string projectUniqueName,
string projectFullPath,
IProjectSystemCache projectSystemCache,
UnconfiguredProject unconfiguredProject,
INuGetProjectServices projectServices,
string projectId)
: base(projectName,
projectUniqueName,
projectFullPath)
{
Assumes.Present(projectFullPath);
Assumes.Present(projectSystemCache);
Assumes.Present(projectServices);
ProjectStyle = ProjectStyle.PackageReference;
_projectSystemCache = projectSystemCache;
_unconfiguredProject = unconfiguredProject;
ProjectServices = projectServices;
InternalMetadata.Add(NuGetProjectMetadataKeys.Name, _projectName);
InternalMetadata.Add(NuGetProjectMetadataKeys.UniqueName, _projectUniqueName);
InternalMetadata.Add(NuGetProjectMetadataKeys.FullPath, _projectFullPath);
InternalMetadata.Add(NuGetProjectMetadataKeys.ProjectId, projectId);
}
public override Task AddFileToProjectAsync(string filePath)
{
// sdk-style project system uses globbing to dynamically add files from project root into project
// so we dont need to do anything explicitly here.
return Task.CompletedTask;
}
private protected override Task<string> GetAssetsFilePathAsync(bool shouldThrow)
{
var packageSpec = GetPackageSpec();
if (packageSpec == null)
{
if (shouldThrow)
{
throw new ProjectNotNominatedException(
string.Format(Strings.ProjectNotLoaded_RestoreFailed, ProjectName));
}
else
{
return Task.FromResult<string>(null);
}
}
return Task.FromResult(Path.Combine(
packageSpec.RestoreMetadata.OutputPath,
LockFileFormat.AssetsFileName));
}
private PackageSpec GetPackageSpec()
{
DependencyGraphSpec projectRestoreInfo;
if (_projectSystemCache.TryGetProjectRestoreInfo(_projectFullPath, out projectRestoreInfo, out _))
{
return projectRestoreInfo.GetProjectSpec(_projectFullPath);
}
// if restore data was not found in the cache, meaning project nomination
// didn't happen yet or failed.
return null;
}
#region IDependencyGraphProject
public override string MSBuildProjectPath => _projectFullPath;
public override Task<(IReadOnlyList<PackageSpec> dgSpecs, IReadOnlyList<IAssetsLogMessage> additionalMessages)> GetPackageSpecsAndAdditionalMessagesAsync(DependencyGraphCacheContext context)
{
var projects = new List<PackageSpec>();
DependencyGraphSpec projectRestoreInfo;
IReadOnlyList<IAssetsLogMessage> additionalMessages;
if (!_projectSystemCache.TryGetProjectRestoreInfo(_projectFullPath, out projectRestoreInfo, out additionalMessages))
{
throw new ProjectNotNominatedException(
string.Format(Strings.ProjectNotLoaded_RestoreFailed, ProjectName));
}
// Apply ISettings when needed to the return values.
// This should not change the cached specs since they
// contain values such as CLEAR which need to be persisted
// and used here.
var originalProjects = projectRestoreInfo.Projects;
var settings = context?.Settings ?? NullSettings.Instance;
foreach (var originalProject in originalProjects)
{
var project = originalProject.Clone();
// Read restore settings from ISettings if it doesn't exist in the project
// NOTE: Very important that the original project is used in the arguments, because cloning sorts the sources and compromises how the sources will be evaluated
project.RestoreMetadata.PackagesPath = VSRestoreSettingsUtilities.GetPackagesPath(settings, originalProject);
project.RestoreMetadata.Sources = VSRestoreSettingsUtilities.GetSources(settings, originalProject);
project.RestoreMetadata.FallbackFolders = VSRestoreSettingsUtilities.GetFallbackFolders(settings, originalProject);
project.RestoreMetadata.ConfigFilePaths = GetConfigFilePaths(settings);
IgnoreUnsupportProjectReference(project);
projects.Add(project);
}
if (context != null)
{
PackageSpec ignore;
foreach (var project in projects
.Where(p => !context.PackageSpecCache.TryGetValue(
p.RestoreMetadata.ProjectUniqueName, out ignore)))
{
context.PackageSpecCache.Add(
project.RestoreMetadata.ProjectUniqueName,
project);
}
}
return Task.FromResult<(IReadOnlyList<PackageSpec>, IReadOnlyList<IAssetsLogMessage>)>((projects, additionalMessages));
}
private IList<string> GetConfigFilePaths(ISettings settings)
{
return settings.GetConfigFilePaths();
}
private void IgnoreUnsupportProjectReference(PackageSpec project)
{
foreach (var frameworkInfo in project.RestoreMetadata.TargetFrameworks)
{
var projectReferences = new List<ProjectRestoreReference>();
foreach (var projectReference in frameworkInfo.ProjectReferences)
{
if (ProjectType.IsSupportedProjectExtension(projectReference.ProjectPath))
{
projectReferences.Add(projectReference);
}
}
frameworkInfo.ProjectReferences = projectReferences;
}
}
#endregion
#region NuGetProject
/// <summary>
/// Gets the installed (top level) package references for this project.
/// </summary>
public async override Task<IEnumerable<PackageReference>> GetInstalledPackagesAsync(CancellationToken token)
{
ProjectPackages packages = await GetInstalledAndTransitivePackagesAsync(token);
return packages.InstalledPackages;
}
/// <summary>
/// Gets the both the installed (top level) and transitive package references for this project.
/// Returns the package reference as two separate lists (installed and transitive).
/// </summary>
public override async Task<ProjectPackages> GetInstalledAndTransitivePackagesAsync(CancellationToken token)
{
var packageSpec = GetPackageSpec();
if (packageSpec != null)
{
var frameworkSorter = new NuGetFrameworkSorter();
string assetsFilePath = await GetAssetsFilePathAsync();
var fileInfo = new FileInfo(assetsFilePath);
IList<LockFileTarget> targets = default;
PackageSpec lastPackageSpec = default;
_lastPackageSpec?.TryGetTarget(out lastPackageSpec);
if ((fileInfo.Exists && fileInfo.LastWriteTimeUtc > _lastTimeAssetsModified) || !ReferenceEquals(lastPackageSpec, packageSpec))
{
await TaskScheduler.Default;
if (fileInfo.Exists)
{
var lockFile = LockFileUtilities.GetLockFile(assetsFilePath, NullLogger.Instance);
if (!(lockFile is null))
{
targets = lockFile.Targets;
}
}
_lastTimeAssetsModified = fileInfo.LastWriteTimeUtc;
_lastPackageSpec = new WeakReference<PackageSpec>(packageSpec);
// clear the transitive packages cache, since we don't know when a dependency has been removed
_transitivePackages = new List<(NuGetFramework, Dictionary<string, ProjectInstalledPackage>)>();
}
List<PackageReference> installedPackages = packageSpec
.TargetFrameworks
.SelectMany(f => GetPackageReferencesForFramework(f.Dependencies, f.FrameworkName, _installedPackages, targets))
.GroupBy(p => p.PackageIdentity)
.Select(g => g.OrderBy(p => p.TargetFramework, frameworkSorter).First())
.ToList();
// get the transitive packages, excluding any already contained in the installed packages
List<PackageReference> transitivePackages = packageSpec
.TargetFrameworks
.SelectMany(f => GetTransitivePackageReferencesForFramework(f.FrameworkName, _installedPackages, _transitivePackages, targets))
.GroupBy(p => p.PackageIdentity)
.Select(g => g.OrderBy(p => p.TargetFramework, frameworkSorter).First())
.ToList();
return new ProjectPackages(installedPackages, transitivePackages);
}
else
{
return new ProjectPackages(Array.Empty<PackageReference>(), Array.Empty<PackageReference>());
}
}
private IEnumerable<PackageReference> GetPackageReferencesForFramework(
IEnumerable<LibraryDependency> libraries,
NuGetFramework targetFramework,
List<(NuGetFramework TargetFramework, Dictionary<string, ProjectInstalledPackage> Packages)> installedPackages,
IList<LockFileTarget> targets)
{
(NuGetFramework TargetFramework, Dictionary<string, ProjectInstalledPackage> Packages) targetFrameworkPackages = installedPackages.FirstOrDefault(t => t.TargetFramework.Equals(targetFramework));
if (targetFrameworkPackages.Packages == null)
{
targetFrameworkPackages.TargetFramework = targetFramework;
targetFrameworkPackages.Packages = new Dictionary<string, ProjectInstalledPackage>(StringComparer.OrdinalIgnoreCase);
installedPackages.Add(targetFrameworkPackages);
}
return GetPackageReferences(libraries, targetFramework, targetFrameworkPackages.Packages, targets);
}
private IReadOnlyList<PackageReference> GetTransitivePackageReferencesForFramework(
NuGetFramework targetFramework,
List<(NuGetFramework TargetFramework, Dictionary<string, ProjectInstalledPackage> Packages)> installedPackages,
List<(NuGetFramework TargetFramework, Dictionary<string, ProjectInstalledPackage> Packages)> transitivePackages,
IList<LockFileTarget> targets)
{
(NuGetFramework TargetFramework, Dictionary<string, ProjectInstalledPackage> Packages) targetFrameworkInstalledPackages = installedPackages.FirstOrDefault(t => t.TargetFramework.Equals(targetFramework));
(NuGetFramework TargetFramework, Dictionary<string, ProjectInstalledPackage> Packages) targetFrameworkTransitivePackages = transitivePackages.FirstOrDefault(t => t.TargetFramework.Equals(targetFramework));
if (targetFrameworkInstalledPackages.Packages == null)
{
targetFrameworkInstalledPackages.TargetFramework = targetFramework;
targetFrameworkInstalledPackages.Packages = new Dictionary<string, ProjectInstalledPackage>(StringComparer.OrdinalIgnoreCase);
installedPackages.Add(targetFrameworkInstalledPackages);
}
if (targetFrameworkTransitivePackages.Packages == null)
{
targetFrameworkTransitivePackages.TargetFramework = targetFramework;
targetFrameworkTransitivePackages.Packages = new Dictionary<string, ProjectInstalledPackage>(StringComparer.OrdinalIgnoreCase);
transitivePackages.Add(targetFrameworkTransitivePackages);
}
return GetTransitivePackageReferences(targetFramework, targetFrameworkInstalledPackages.Packages, targetFrameworkTransitivePackages.Packages, targets);
}
public override async Task<bool> InstallPackageAsync(
string packageId,
VersionRange range,
INuGetProjectContext nuGetProjectContext,
BuildIntegratedInstallationContext installationContext,
CancellationToken token)
{
// Right now, the UI only handles installation of specific versions, which is just the minimum version of
// the provided version range.
var formattedRange = range.MinVersion.ToNormalizedString();
nuGetProjectContext.Log(MessageLevel.Info, Strings.InstallingPackage, $"{packageId} {formattedRange}");
if (installationContext.SuccessfulFrameworks.Any() && installationContext.UnsuccessfulFrameworks.Any())
{
// This is the "partial install" case. That is, install the package to only a subset of the frameworks
// supported by this project.
var conditionalService = _unconfiguredProject
.Services
.ExportProvider
.GetExportedValue<IConditionalPackageReferencesService>();
if (conditionalService == null)
{
throw new InvalidOperationException(string.Format(
Strings.UnableToGetCPSPackageInstallationService,
_projectFullPath));
}
foreach (var framework in installationContext.SuccessfulFrameworks)
{
string originalFramework;
if (!installationContext.OriginalFrameworks.TryGetValue(framework, out originalFramework))
{
originalFramework = framework.GetShortFolderName();
}
var reference = await conditionalService.AddAsync(
packageId,
formattedRange,
TargetFrameworkCondition,
originalFramework);
// SuppressParent could be set to All if developmentDependency flag is true in package nuspec file.
if (installationContext.SuppressParent != LibraryIncludeFlagUtils.DefaultSuppressParent &&
installationContext.IncludeType != LibraryIncludeFlags.All)
{
await SetPackagePropertyValueAsync(
reference.Metadata,
ProjectItemProperties.PrivateAssets,
MSBuildStringUtility.Convert(LibraryIncludeFlagUtils.GetFlagString(installationContext.SuppressParent)));
await SetPackagePropertyValueAsync(
reference.Metadata,
ProjectItemProperties.IncludeAssets,
MSBuildStringUtility.Convert(LibraryIncludeFlagUtils.GetFlagString(installationContext.IncludeType)));
}
}
}
else
{
// Install the package to all frameworks.
var configuredProject = await _unconfiguredProject.GetSuggestedConfiguredProjectAsync();
var result = await configuredProject
.Services
.PackageReferences
.AddAsync(packageId, formattedRange);
// This is the update operation
if (!result.Added)
{
var existingReference = result.Reference;
await existingReference.Metadata.SetPropertyValueAsync("Version", formattedRange);
}
if (installationContext.SuppressParent != LibraryIncludeFlagUtils.DefaultSuppressParent &&
installationContext.IncludeType != LibraryIncludeFlags.All)
{
await SetPackagePropertyValueAsync(
result.Reference.Metadata,
ProjectItemProperties.PrivateAssets,
MSBuildStringUtility.Convert(LibraryIncludeFlagUtils.GetFlagString(installationContext.SuppressParent)));
await SetPackagePropertyValueAsync(
result.Reference.Metadata,
ProjectItemProperties.IncludeAssets,
MSBuildStringUtility.Convert(LibraryIncludeFlagUtils.GetFlagString(installationContext.IncludeType)));
}
}
return true;
}
private async Task SetPackagePropertyValueAsync(IProjectProperties metadata, string propertyName, string propertyValue)
{
await metadata.SetPropertyValueAsync(
propertyName,
propertyValue);
}
public override async Task<bool> UninstallPackageAsync(PackageIdentity packageIdentity, INuGetProjectContext nuGetProjectContext, CancellationToken token)
{
var configuredProject = await _unconfiguredProject.GetSuggestedConfiguredProjectAsync();
await configuredProject?.Services.PackageReferences.RemoveAsync(packageIdentity.Id);
return true;
}
public override Task<string> GetCacheFilePathAsync()
{
var spec = GetPackageSpec();
if (spec == null)
{
throw new ProjectNotNominatedException(
string.Format(Strings.ProjectNotLoaded_RestoreFailed, ProjectName));
}
return Task.FromResult(NoOpRestoreUtilities.GetProjectCacheFilePath(cacheRoot: spec.RestoreMetadata.OutputPath));
}
#endregion
}
}