dependabot-nuget 0.321.3 → 0.322.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Packages.props +22 -22
  3. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/Program.cs +21 -7
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +19 -11
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs +19 -9
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +21 -14
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/FrameworkCheckCommand.cs +8 -5
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +29 -16
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +20 -19
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +2 -1
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +1 -1
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +10 -23
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +9 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +15 -232
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +1 -154
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -12
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/GlobalJsonBuildFile.cs +5 -13
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/PrivateSourceTimedOutException.cs +12 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +4 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceTimedOut.cs +10 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/AzurePackageDetailFinder.cs +30 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/DetailedPullRequestBodyGenerator.cs +237 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/GitHubPackageDetailFinder.cs +101 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/GitLabPackageDetailFinder.cs +107 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/HttpFetcher.cs +32 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/IHttpFetcher.cs +30 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/IPackageDetailFinder.cs +47 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/IPullRequestBodyGenerator.cs +11 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/SimplePullRequestBodyGenerator.cs +15 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestTextGenerator.cs +7 -3
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +3 -525
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandler.cs +2 -2
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/GroupUpdateAllVersionsHandler.cs +4 -4
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandler.cs +2 -2
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandler.cs +2 -2
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandler.cs +2 -2
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/FileWriterWorker.cs +85 -35
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/XmlFileWriter.cs +27 -8
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +1 -856
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationBase.cs +18 -7
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +16 -200
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +21 -556
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +9 -73
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +2 -2
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/DependencySolver/MSBuildDependencySolverTests.cs +1 -1
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +1 -20
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +0 -2
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +3 -62
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +13 -563
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +20 -269
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +2 -2
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/GlobalJsonBuildFileTests.cs +0 -1
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs +131 -131
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs +1 -0
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/JobErrorBaseTests.cs +7 -0
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +11 -0
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +0 -203
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestBodyGenerator/DetailedPullRequestBodyGeneratorTests.cs +871 -0
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestBodyGenerator/IPackageDetailFinderTests.cs +28 -0
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestBodyGenerator/TestHttpFetcher.cs +23 -0
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestTextTests.cs +24 -24
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +14 -12
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandlerTests.cs +6 -6
  64. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/GroupUpdateAllVersionsHandlerTests.cs +18 -18
  65. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandlerTests.cs +15 -15
  66. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandlerTests.cs +21 -21
  67. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandlerTests.cs +15 -15
  68. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlersTestsBase.cs +1 -8
  69. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests.cs +2 -2
  70. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests_MiscellaneousTests.cs +45 -0
  71. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests.cs +111 -0
  72. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackageReferenceUpdaterTests.cs +1 -159
  73. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +26 -660
  74. data/helpers/lib/NuGetUpdater/global.json +1 -1
  75. metadata +18 -10
  76. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunResult.cs +0 -13
  77. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestMessageTests.cs +0 -296
  78. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +0 -3592
  79. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatePermittedAndMessageTests.cs +0 -457
  80. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +0 -378
  81. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterHelperTests.cs +0 -175
@@ -1,8 +1,5 @@
1
1
  using System.Collections.Immutable;
2
2
  using System.Text.Json;
3
- using System.Text.RegularExpressions;
4
-
5
- using Microsoft.Language.Xml;
6
3
 
7
4
  using NuGet.Frameworks;
8
5
  using NuGet.Versioning;
@@ -25,170 +22,6 @@ namespace NuGetUpdater.Core;
25
22
  /// </remarks>
26
23
  internal static class PackageReferenceUpdater
27
24
  {
28
- public static async Task<IEnumerable<UpdateOperationBase>> UpdateDependencyAsync(
29
- string repoRootPath,
30
- string projectPath,
31
- string dependencyName,
32
- string previousDependencyVersion,
33
- string newDependencyVersion,
34
- bool isTransitive,
35
- ExperimentsManager experimentsManager,
36
- ILogger logger)
37
- {
38
- // PackageReference project; modify the XML directly
39
- logger.Info(" Running 'PackageReference' project direct XML update");
40
-
41
- (ImmutableArray<ProjectBuildFile> buildFiles, string[] tfms) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(repoRootPath, projectPath);
42
-
43
- // Get the set of all top-level dependencies in the current project
44
- var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
45
- var isDependencyTopLevel = topLevelDependencies.Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
46
- if (isDependencyTopLevel)
47
- {
48
- var packageMapper = DotNetPackageCorrelationManager.GetPackageMapper();
49
- // TODO: this is slow
50
- var isSdkReplacementPackage = packageMapper.RuntimePackages.Runtimes.Any(r =>
51
- {
52
- return r.Value.Packages.Any(p => dependencyName.Equals(p.Key, StringComparison.Ordinal));
53
- });
54
- if (isSdkReplacementPackage)
55
- {
56
- // If we're updating a top level SDK replacement package, the version listed in the project file won't
57
- // necessarily match the resolved version that caused the update because the SDK might have replaced
58
- // the package. To handle this scenario, we pretend the version we're searching for is the actual
59
- // version in the file, not the resolved version. This allows us to keep a strict equality check when
60
- // finding the file to update.
61
- previousDependencyVersion = topLevelDependencies.First(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase)).Version!;
62
- }
63
- }
64
-
65
- if (!await DoesDependencyRequireUpdateAsync(repoRootPath, projectPath, tfms, topLevelDependencies, dependencyName, newDependencyVersion, experimentsManager, logger))
66
- {
67
- return [];
68
- }
69
-
70
- var updateOperations = new List<UpdateOperationBase>();
71
- var peerDependencies = await GetUpdatedPeerDependenciesAsync(repoRootPath, projectPath, tfms, dependencyName, newDependencyVersion, experimentsManager, logger);
72
- if (experimentsManager.UseLegacyDependencySolver)
73
- {
74
- if (isTransitive)
75
- {
76
- var updatedFiles = await UpdateTransitiveDependencyAsync(repoRootPath, projectPath, dependencyName, newDependencyVersion, buildFiles, experimentsManager, logger);
77
- updateOperations.Add(new PinnedUpdate()
78
- {
79
- DependencyName = dependencyName,
80
- NewVersion = NuGetVersion.Parse(newDependencyVersion),
81
- UpdatedFiles = [.. updatedFiles],
82
- });
83
- }
84
- else
85
- {
86
- if (peerDependencies is null)
87
- {
88
- return updateOperations;
89
- }
90
-
91
- var topLevelUpdateOperations = await UpdateTopLevelDepdendency(repoRootPath, buildFiles, tfms, dependencyName, previousDependencyVersion, newDependencyVersion, peerDependencies, experimentsManager, logger);
92
- updateOperations.AddRange(topLevelUpdateOperations);
93
- }
94
- }
95
- else
96
- {
97
- if (peerDependencies is null)
98
- {
99
- return updateOperations;
100
- }
101
-
102
- var conflictResolutionUpdateOperations = await UpdateDependencyWithConflictResolution(
103
- repoRootPath,
104
- buildFiles,
105
- tfms,
106
- projectPath,
107
- dependencyName,
108
- previousDependencyVersion,
109
- newDependencyVersion,
110
- isTransitive,
111
- peerDependencies,
112
- experimentsManager,
113
- logger);
114
- updateOperations.AddRange(conflictResolutionUpdateOperations);
115
- }
116
-
117
- if (!await AreDependenciesCoherentAsync(repoRootPath, projectPath, dependencyName, buildFiles, tfms, experimentsManager, logger))
118
- {
119
- // should we return an empty set because we failed?
120
- return updateOperations;
121
- }
122
-
123
- await SaveBuildFilesAsync(buildFiles, logger);
124
- return updateOperations;
125
- }
126
-
127
- public static async Task<IEnumerable<UpdateOperationBase>> UpdateDependencyWithConflictResolution(
128
- string repoRootPath,
129
- ImmutableArray<ProjectBuildFile> buildFiles,
130
- string[] targetFrameworks,
131
- string projectPath,
132
- string dependencyName,
133
- string previousDependencyVersion,
134
- string newDependencyVersion,
135
- bool isTransitive,
136
- IDictionary<string, string> peerDependencies,
137
- ExperimentsManager experimentsManager,
138
- ILogger logger)
139
- {
140
- var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToImmutableArray();
141
- var isDependencyTopLevel = topLevelDependencies.Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
142
- var dependenciesToUpdate = new[] { new Dependency(dependencyName, newDependencyVersion, DependencyType.PackageReference) }.ToImmutableArray();
143
- var updateOperations = new List<UpdateOperationBase>();
144
-
145
- // update the initial dependency...
146
- var (_, updateOperationsPerformed) = TryUpdateDependencyVersion(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, logger);
147
- updateOperations.AddRange(updateOperationsPerformed);
148
-
149
- // ...and the peer dependencies...
150
- foreach (var (packageName, packageVersion) in peerDependencies.Where(kvp => string.Compare(kvp.Key, dependencyName, StringComparison.OrdinalIgnoreCase) != 0))
151
- {
152
- (_, updateOperationsPerformed) = TryUpdateDependencyVersion(buildFiles, packageName, previousDependencyVersion: null, newDependencyVersion: packageVersion, logger);
153
- updateOperations.AddRange(updateOperationsPerformed);
154
- }
155
-
156
- // ...and everything else
157
- foreach (var projectFile in buildFiles)
158
- {
159
- foreach (var tfm in targetFrameworks)
160
- {
161
- var resolvedDependencies = await MSBuildHelper.ResolveDependencyConflicts(repoRootPath, projectFile.Path, tfm, topLevelDependencies, dependenciesToUpdate, experimentsManager, logger);
162
- if (resolvedDependencies is null)
163
- {
164
- logger.Warn($" Unable to resolve dependency conflicts for {projectFile.Path}.");
165
- continue;
166
- }
167
-
168
- var isDependencyInResolutionSet = resolvedDependencies.Value.Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
169
- if (isTransitive && !isDependencyTopLevel && isDependencyInResolutionSet)
170
- {
171
- // a transitive dependency had to be pinned; add it here
172
- var updatedFiles = await UpdateTransitiveDependencyAsync(repoRootPath, projectPath, dependencyName, newDependencyVersion, buildFiles, experimentsManager, logger);
173
- }
174
-
175
- // update all resolved dependencies that aren't the initial dependency
176
- foreach (var resolvedDependency in resolvedDependencies.Value
177
- .Where(d => !d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
178
- .Where(d => d.Version is not null))
179
- {
180
- (_, updateOperationsPerformed) = TryUpdateDependencyVersion(buildFiles, resolvedDependency.Name, previousDependencyVersion: null, newDependencyVersion: resolvedDependency.Version!, logger);
181
- updateOperations.AddRange(updateOperationsPerformed);
182
- }
183
-
184
- updateOperationsPerformed = await ComputeUpdateOperations(repoRootPath, projectPath, tfm, topLevelDependencies, dependenciesToUpdate, resolvedDependencies.Value, experimentsManager, logger);
185
- updateOperations.AddRange(updateOperationsPerformed.Select(u => u with { UpdatedFiles = [projectFile.Path] }));
186
- }
187
- }
188
-
189
- return updateOperations;
190
- }
191
-
192
25
  internal static async Task<IEnumerable<UpdateOperationBase>> ComputeUpdateOperations(
193
26
  string repoRoot,
194
27
  string projectPath,
@@ -285,7 +118,7 @@ internal static class PackageReferenceUpdater
285
118
  {
286
119
  // generate project.assets.json
287
120
  var parsedTargetFramework = NuGetFramework.Parse(targetFramework);
288
- var tempProject = await MSBuildHelper.CreateTempProjectAsync(tempDir, repoRoot, projectPath, targetFramework, topLevelDependencies, experimentsManager, logger, importDependencyTargets: !experimentsManager.UseDirectDiscovery);
121
+ var tempProject = await MSBuildHelper.CreateTempProjectAsync(tempDir, repoRoot, projectPath, targetFramework, topLevelDependencies, experimentsManager, logger, importDependencyTargets: false);
289
122
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["build", tempProject, "/t:_ReportDependencies"], tempDir.FullName, experimentsManager);
290
123
  var assetsJsonPath = Path.Join(tempDir.FullName, "obj", "project.assets.json");
291
124
  var assetsJsonContent = await File.ReadAllTextAsync(assetsJsonPath);
@@ -332,692 +165,4 @@ internal static class PackageReferenceUpdater
332
165
  tempDir.Delete(recursive: true);
333
166
  }
334
167
  }
335
-
336
- /// <summary>
337
- /// Verifies that the package does not already satisfy the requested dependency version.
338
- /// </summary>
339
- /// <returns>Returns false if the package is not found or does not need to be updated.</returns>
340
- private static async Task<bool> DoesDependencyRequireUpdateAsync(
341
- string repoRootPath,
342
- string projectPath,
343
- string[] tfms,
344
- Dependency[] topLevelDependencies,
345
- string dependencyName,
346
- string newDependencyVersion,
347
- ExperimentsManager experimentsManager,
348
- ILogger logger)
349
- {
350
- var newDependencyNuGetVersion = NuGetVersion.Parse(newDependencyVersion);
351
-
352
- bool packageFound = false;
353
- bool needsUpdate = false;
354
-
355
- foreach (var tfm in tfms)
356
- {
357
- var dependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(
358
- repoRootPath,
359
- projectPath,
360
- tfm,
361
- topLevelDependencies,
362
- experimentsManager,
363
- logger);
364
- foreach (var dependency in dependencies)
365
- {
366
- var packageName = dependency.Name;
367
- var packageVersion = dependency.Version;
368
- if (packageVersion is null)
369
- {
370
- continue;
371
- }
372
-
373
- if (packageName.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
374
- {
375
- packageFound = true;
376
-
377
- var nugetVersion = NuGetVersion.Parse(packageVersion);
378
- if (nugetVersion < newDependencyNuGetVersion)
379
- {
380
- needsUpdate = true;
381
- break;
382
- }
383
- }
384
- }
385
-
386
- if (packageFound && needsUpdate)
387
- {
388
- break;
389
- }
390
- }
391
-
392
- // Skip updating the project if the dependency does not exist in the graph
393
- if (!packageFound)
394
- {
395
- logger.Info($" Package [{dependencyName}] Does not exist as a dependency in [{projectPath}].");
396
- return false;
397
- }
398
-
399
- // Skip updating the project if the dependency version meets or exceeds the newDependencyVersion
400
- if (!needsUpdate)
401
- {
402
- logger.Info($" Package [{dependencyName}] already meets the requested dependency version in [{projectPath}].");
403
- return false;
404
- }
405
-
406
- return true;
407
- }
408
-
409
- /// <returns>The updated files.</returns>
410
- internal static async Task<IEnumerable<string>> UpdateTransitiveDependencyAsync(
411
- string repoRootPath,
412
- string projectPath,
413
- string dependencyName,
414
- string newDependencyVersion,
415
- ImmutableArray<ProjectBuildFile> buildFiles,
416
- ExperimentsManager experimentsManager,
417
- ILogger logger
418
- )
419
- {
420
- IEnumerable<string> updatedFiles;
421
- var directoryPackagesWithPinning = buildFiles.OfType<ProjectBuildFile>()
422
- .FirstOrDefault(bf => IsCpmTransitivePinningEnabled(bf));
423
- if (directoryPackagesWithPinning is not null)
424
- {
425
- updatedFiles = PinTransitiveDependency(directoryPackagesWithPinning, dependencyName, newDependencyVersion, logger);
426
- }
427
- else
428
- {
429
- updatedFiles = await AddTransitiveDependencyAsync(repoRootPath, projectPath, dependencyName, newDependencyVersion, experimentsManager, logger);
430
-
431
- // files directly modified on disk by an external tool need to be refreshed in-memory
432
- foreach (var updatedFile in updatedFiles)
433
- {
434
- var matchingBuildFile = buildFiles.FirstOrDefault(bf => PathComparer.Instance.Compare(updatedFile, bf.Path) == 0);
435
- if (matchingBuildFile is not null)
436
- {
437
- var updatedContents = await File.ReadAllTextAsync(updatedFile);
438
- matchingBuildFile.Update(ProjectBuildFile.Parse(updatedContents));
439
- }
440
- }
441
- }
442
-
443
- return updatedFiles;
444
- }
445
-
446
- private static bool IsCpmTransitivePinningEnabled(ProjectBuildFile buildFile)
447
- {
448
- var buildFileName = Path.GetFileName(buildFile.Path);
449
- if (!buildFileName.Equals("Directory.Packages.props", StringComparison.OrdinalIgnoreCase))
450
- {
451
- return false;
452
- }
453
-
454
- var propertyElements = buildFile.PropertyNodes;
455
-
456
- var isCpmEnabledValue = propertyElements.FirstOrDefault(e =>
457
- e.Name.Equals("ManagePackageVersionsCentrally", StringComparison.OrdinalIgnoreCase))?.GetContentValue();
458
- if (isCpmEnabledValue is null || !string.Equals(isCpmEnabledValue, "true", StringComparison.OrdinalIgnoreCase))
459
- {
460
- return false;
461
- }
462
-
463
- var isTransitivePinningEnabled = propertyElements.FirstOrDefault(e =>
464
- e.Name.Equals("CentralPackageTransitivePinningEnabled", StringComparison.OrdinalIgnoreCase))?.GetContentValue();
465
- return isTransitivePinningEnabled is not null && string.Equals(isTransitivePinningEnabled, "true", StringComparison.OrdinalIgnoreCase);
466
- }
467
-
468
- /// <returns>The updated files.</returns>
469
- private static IEnumerable<string> PinTransitiveDependency(ProjectBuildFile directoryPackages, string dependencyName, string newDependencyVersion, ILogger logger)
470
- {
471
- var existingPackageVersionElement = directoryPackages.ItemNodes
472
- .Where(e => e.Name.Equals("PackageVersion", StringComparison.OrdinalIgnoreCase) &&
473
- e.Attributes.Any(a => a.Name.Equals("Include", StringComparison.OrdinalIgnoreCase) &&
474
- a.Value.Equals(dependencyName, StringComparison.OrdinalIgnoreCase)))
475
- .FirstOrDefault();
476
-
477
- logger.Info($" Pinning [{dependencyName}/{newDependencyVersion}] as a package version.");
478
-
479
- var lastPackageVersion = directoryPackages.ItemNodes
480
- .Where(e => e.Name.Equals("PackageVersion", StringComparison.OrdinalIgnoreCase))
481
- .LastOrDefault();
482
-
483
- if (lastPackageVersion is null)
484
- {
485
- logger.Info($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not pinned.");
486
- return [];
487
- }
488
-
489
- var lastItemGroup = lastPackageVersion.Parent;
490
-
491
- IXmlElementSyntax updatedItemGroup;
492
- if (existingPackageVersionElement is null)
493
- {
494
- // need to add a new entry
495
- logger.Info(" New PackageVersion element added.");
496
- var leadingTrivia = lastPackageVersion.AsNode.GetLeadingTrivia();
497
- var packageVersionElement = XmlExtensions.CreateSingleLineXmlElementSyntax("PackageVersion", new SyntaxList<SyntaxNode>(leadingTrivia))
498
- .WithAttribute("Include", dependencyName)
499
- .WithAttribute("Version", newDependencyVersion);
500
- updatedItemGroup = lastItemGroup.AddChild(packageVersionElement);
501
- }
502
- else
503
- {
504
- IXmlElementSyntax updatedPackageVersionElement;
505
- var versionAttribute = existingPackageVersionElement.Attributes.FirstOrDefault(a => a.Name.Equals("Version", StringComparison.OrdinalIgnoreCase));
506
- if (versionAttribute is null)
507
- {
508
- // need to add the version
509
- logger.Info(" Adding version attribute to element.");
510
- updatedPackageVersionElement = existingPackageVersionElement.WithAttribute("Version", newDependencyVersion);
511
- }
512
- else if (!versionAttribute.Value.Equals(newDependencyVersion, StringComparison.OrdinalIgnoreCase))
513
- {
514
- // need to update the version
515
- logger.Info($" Updating version attribute of [{versionAttribute.Value}].");
516
- var updatedVersionAttribute = versionAttribute.WithValue(newDependencyVersion);
517
- updatedPackageVersionElement = existingPackageVersionElement.ReplaceAttribute(versionAttribute, updatedVersionAttribute);
518
- }
519
- else
520
- {
521
- logger.Info(" Existing PackageVersion element version was already correct.");
522
- return [];
523
- }
524
-
525
- updatedItemGroup = lastItemGroup.ReplaceChildElement(existingPackageVersionElement, updatedPackageVersionElement);
526
- }
527
-
528
- var updatedXml = directoryPackages.Contents.ReplaceNode(lastItemGroup.AsNode, updatedItemGroup.AsNode);
529
- directoryPackages.Update(updatedXml);
530
-
531
- return [directoryPackages.Path];
532
- }
533
-
534
- /// <returns>The updated files.</returns>
535
- private static async Task<IEnumerable<string>> AddTransitiveDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string newDependencyVersion, ExperimentsManager experimentsManager, ILogger logger)
536
- {
537
- var updatedFiles = new List<string>() { projectPath }; // assume this worked unless...
538
- var projectDirectory = Path.GetDirectoryName(projectPath)!;
539
- await MSBuildHelper.HandleGlobalJsonAsync(projectDirectory, repoRootPath, experimentsManager, async () =>
540
- {
541
- logger.Info($" Adding [{dependencyName}/{newDependencyVersion}] as a top-level package reference.");
542
-
543
- // see https://learn.microsoft.com/nuget/consume-packages/install-use-packages-dotnet-cli
544
- var (exitCode, stdout, stderr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(
545
- ["add", projectPath, "package", dependencyName, "--version", newDependencyVersion],
546
- projectDirectory,
547
- experimentsManager
548
- );
549
- MSBuildHelper.ThrowOnError(stdout);
550
- if (exitCode != 0)
551
- {
552
- logger.Warn($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
553
- updatedFiles = [];
554
- }
555
-
556
- // output might contain a line like this:
557
- // info : PackageReference for package 'Some.Package' added to 'C:\project.csproj' and PackageVersion added to central package management file 'C:\Directory.Packages.props'.
558
- // we explicitly want to pull out this: ^^^^^^^^^^^^^^^^^^^^^^^^^^^
559
- // so we can report all files updated on disk
560
- var match = Regex.Match(stdout, @"added to central package management file '(?<CentralPackageManagementFilePath>[^']+)'");
561
- if (match.Success)
562
- {
563
- var cpmFilePath = match.Groups["CentralPackageManagementFilePath"].Value;
564
- updatedFiles.Add(cpmFilePath);
565
- }
566
-
567
- return exitCode;
568
- }, logger, retainMSBuildSdks: true);
569
-
570
- return updatedFiles;
571
- }
572
-
573
- /// <summary>
574
- /// Gets the set of peer dependencies that need to be updated.
575
- /// </summary>
576
- /// <returns>Returns null if there are conflicting versions.</returns>
577
- private static async Task<Dictionary<string, string>?> GetUpdatedPeerDependenciesAsync(
578
- string repoRootPath,
579
- string projectPath,
580
- string[] tfms,
581
- string dependencyName,
582
- string newDependencyVersion,
583
- ExperimentsManager experimentsManager,
584
- ILogger logger)
585
- {
586
- var newDependency = new[] { new Dependency(dependencyName, newDependencyVersion, DependencyType.Unknown) };
587
- var tfmsAndDependencies = new Dictionary<string, ImmutableArray<Dependency>>();
588
- foreach (var tfm in tfms)
589
- {
590
- var dependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, newDependency, experimentsManager, logger);
591
- tfmsAndDependencies[tfm] = dependencies;
592
- }
593
-
594
- var unupgradableTfms = tfmsAndDependencies.Where(kvp => !kvp.Value.Any()).Select(kvp => kvp.Key);
595
- if (unupgradableTfms.Any())
596
- {
597
- logger.Info($" The following target frameworks could not find packages to upgrade: {string.Join(", ", unupgradableTfms)}");
598
- return null;
599
- }
600
-
601
- var conflictingPackageVersionsFound = false;
602
- var packagesAndVersions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
603
- foreach (var (_, dependencies) in tfmsAndDependencies)
604
- {
605
- foreach (var dependency in dependencies)
606
- {
607
- var packageName = dependency.Name;
608
- var packageVersion = dependency.Version;
609
- if (packagesAndVersions.TryGetValue(packageName, out var existingVersion) &&
610
- existingVersion != packageVersion)
611
- {
612
- logger.Info($" Package [{packageName}] tried to update to version [{packageVersion}], but found conflicting package version of [{existingVersion}].");
613
- conflictingPackageVersionsFound = true;
614
- }
615
- else
616
- {
617
- packagesAndVersions[packageName] = packageVersion!;
618
- }
619
- }
620
- }
621
-
622
- // stop update process if we find conflicting package versions
623
- if (conflictingPackageVersionsFound)
624
- {
625
- return null;
626
- }
627
-
628
- return packagesAndVersions;
629
- }
630
-
631
- private static async Task<IEnumerable<UpdateOperationBase>> UpdateTopLevelDepdendency(
632
- string repoRootPath,
633
- ImmutableArray<ProjectBuildFile> buildFiles,
634
- string[] targetFrameworks,
635
- string dependencyName,
636
- string previousDependencyVersion,
637
- string newDependencyVersion,
638
- IDictionary<string, string> peerDependencies,
639
- ExperimentsManager experimentsManager,
640
- ILogger logger)
641
- {
642
- // update dependencies...
643
- var updateOperations = new List<UpdateOperationBase>();
644
- var (updateResult, updateOperationsPerformed) = TryUpdateDependencyVersion(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, logger);
645
- if (updateResult == UpdateResult.NotFound)
646
- {
647
- logger.Info($" Root package [{dependencyName}/{previousDependencyVersion}] was not updated; skipping dependencies.");
648
- return [];
649
- }
650
-
651
- updateOperations.AddRange(updateOperationsPerformed);
652
-
653
- foreach (var (packageName, packageVersion) in peerDependencies.Where(kvp => string.Compare(kvp.Key, dependencyName, StringComparison.OrdinalIgnoreCase) != 0))
654
- {
655
- (_, updateOperationsPerformed) = TryUpdateDependencyVersion(buildFiles, packageName, previousDependencyVersion: null, newDependencyVersion: packageVersion, logger);
656
- updateOperations.AddRange(updateOperationsPerformed);
657
- }
658
-
659
- // ...and make them all coherent
660
- var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToImmutableArray();
661
- foreach (ProjectBuildFile projectFile in buildFiles)
662
- {
663
- foreach (string tfm in targetFrameworks)
664
- {
665
- var resolvedDependencies = await MSBuildHelper.ResolveDependencyConflictsWithBruteForce(repoRootPath, projectFile.Path, tfm, topLevelDependencies, experimentsManager, logger);
666
- if (resolvedDependencies is null)
667
- {
668
- logger.Info($" Unable to resolve dependency conflicts for {projectFile.Path}.");
669
- continue;
670
- }
671
-
672
- // ensure the originally requested dependency was resolved to the correct version
673
- var specificResolvedDependency = resolvedDependencies.Value.Where(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
674
- if (specificResolvedDependency is null)
675
- {
676
- logger.Info($" Unable to resolve requested dependency for {dependencyName} in {projectFile.Path}.");
677
- continue;
678
- }
679
-
680
- if (!newDependencyVersion.Equals(specificResolvedDependency.Version, StringComparison.OrdinalIgnoreCase))
681
- {
682
- logger.Info($" Inconsistent resolution for {dependencyName}; attempted upgrade to {newDependencyVersion} but resolved {specificResolvedDependency.Version}.");
683
- continue;
684
- }
685
-
686
- // update all versions
687
- foreach (Dependency resolvedDependency in resolvedDependencies.Value
688
- .Where(d => !d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
689
- .Where(d => d.Version is not null))
690
- {
691
- (_, updateOperationsPerformed) = TryUpdateDependencyVersion(buildFiles, resolvedDependency.Name, previousDependencyVersion: null, newDependencyVersion: resolvedDependency.Version!, logger);
692
- updateOperations.AddRange(updateOperationsPerformed);
693
- }
694
-
695
- updateOperationsPerformed = await ComputeUpdateOperations(repoRootPath, projectFile.Path, tfm, topLevelDependencies, [new Dependency(dependencyName, newDependencyVersion, DependencyType.PackageReference)], resolvedDependencies.Value, experimentsManager, logger);
696
- updateOperations.AddRange(updateOperationsPerformed.Select(u => u with { UpdatedFiles = [projectFile.Path] }));
697
- }
698
- }
699
-
700
- return updateOperations;
701
- }
702
-
703
- /// <returns>The updated files.</returns>
704
- internal static (UpdateResult, IEnumerable<UpdateOperationBase>) TryUpdateDependencyVersion(
705
- ImmutableArray<ProjectBuildFile> buildFiles,
706
- string dependencyName,
707
- string? previousDependencyVersion,
708
- string newDependencyVersion,
709
- ILogger logger)
710
- {
711
- var foundCorrect = false;
712
- var foundUnsupported = false;
713
- var updateWasPerformed = false;
714
- var propertyNames = new List<string>();
715
- var updateOperations = new List<UpdateOperationBase>();
716
-
717
- // First we locate all the PackageReference, GlobalPackageReference, or PackageVersion which set the Version
718
- // or VersionOverride attribute. In the simplest case we can update the version attribute directly then move
719
- // on. When property substitution is used we have to additionally search for the property containing the version.
720
-
721
- foreach (var buildFile in buildFiles)
722
- {
723
- var updateNodes = new List<XmlNodeSyntax>();
724
- var packageNodes = FindPackageNodes(buildFile, dependencyName);
725
-
726
- var previousPackageVersion = previousDependencyVersion;
727
-
728
- foreach (var packageNode in packageNodes)
729
- {
730
- var versionAttribute = packageNode.GetAttribute("Version", StringComparison.OrdinalIgnoreCase)
731
- ?? packageNode.GetAttribute("VersionOverride", StringComparison.OrdinalIgnoreCase);
732
- var versionElement = packageNode.Elements.FirstOrDefault(e => e.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))
733
- ?? packageNode.Elements.FirstOrDefault(e => e.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase));
734
- if (versionAttribute is not null)
735
- {
736
- // Is this the case where version is specified with property substitution?
737
- if (MSBuildHelper.TryGetPropertyName(versionAttribute.Value, out var propertyName))
738
- {
739
- propertyNames.Add(propertyName);
740
- }
741
- // Is this the case that the version is specified directly in the package node?
742
- else
743
- {
744
- var currentVersion = versionAttribute.Value.TrimStart('[', '(').TrimEnd(']', ')');
745
- if (currentVersion.Contains(',') || currentVersion.Contains('*'))
746
- {
747
- logger.Warn($" Found unsupported [{packageNode.Name}] version attribute value [{versionAttribute.Value}] in [{buildFile.RelativePath}].");
748
- foundUnsupported = true;
749
- }
750
- else if (string.Equals(currentVersion, previousDependencyVersion, StringComparison.Ordinal))
751
- {
752
- logger.Info($" Found incorrect [{packageNode.Name}] version attribute in [{buildFile.RelativePath}].");
753
- updateNodes.Add(versionAttribute);
754
- updateOperations.Add(new DirectUpdate()
755
- {
756
- DependencyName = dependencyName,
757
- NewVersion = NuGetVersion.Parse(newDependencyVersion),
758
- UpdatedFiles = [buildFile.Path],
759
- });
760
- }
761
- else if (previousDependencyVersion == null && NuGetVersion.TryParse(currentVersion, out var previousVersion))
762
- {
763
- var newVersion = NuGetVersion.Parse(newDependencyVersion);
764
- if (previousVersion < newVersion)
765
- {
766
- previousPackageVersion = currentVersion;
767
-
768
- logger.Info($" Found incorrect peer [{packageNode.Name}] version attribute in [{buildFile.RelativePath}].");
769
- updateNodes.Add(versionAttribute);
770
- updateOperations.Add(new DirectUpdate()
771
- {
772
- DependencyName = dependencyName,
773
- NewVersion = NuGetVersion.Parse(newDependencyVersion),
774
- UpdatedFiles = [buildFile.Path],
775
- });
776
- }
777
- }
778
- else if (string.Equals(currentVersion, newDependencyVersion, StringComparison.Ordinal))
779
- {
780
- logger.Info($" Found correct [{packageNode.Name}] version attribute in [{buildFile.RelativePath}].");
781
- foundCorrect = true;
782
- }
783
- }
784
- }
785
- else if (versionElement is not null)
786
- {
787
- var versionValue = versionElement.GetContentValue();
788
- if (MSBuildHelper.TryGetPropertyName(versionValue, out var propertyName))
789
- {
790
- propertyNames.Add(propertyName);
791
- }
792
- else
793
- {
794
- var currentVersion = versionValue.TrimStart('[', '(').TrimEnd(']', ')');
795
- if (currentVersion.Contains(',') || currentVersion.Contains('*'))
796
- {
797
- logger.Info($" Found unsupported [{packageNode.Name}] version node value [{versionValue}] in [{buildFile.RelativePath}].");
798
- foundUnsupported = true;
799
- }
800
- else if (currentVersion == previousDependencyVersion)
801
- {
802
- logger.Info($" Found incorrect [{packageNode.Name}] version node in [{buildFile.RelativePath}].");
803
- if (versionElement is XmlElementSyntax elementSyntax)
804
- {
805
- updateNodes.Add(elementSyntax);
806
- updateOperations.Add(new DirectUpdate()
807
- {
808
- DependencyName = dependencyName,
809
- NewVersion = NuGetVersion.Parse(newDependencyVersion),
810
- UpdatedFiles = [buildFile.Path],
811
- });
812
- }
813
- else
814
- {
815
- throw new InvalidDataException("A concrete type was required for updateNodes. This should not happen.");
816
- }
817
- }
818
- else if (previousDependencyVersion == null && NuGetVersion.TryParse(currentVersion, out var previousVersion))
819
- {
820
- var newVersion = NuGetVersion.Parse(newDependencyVersion);
821
- if (previousVersion < newVersion)
822
- {
823
- previousPackageVersion = currentVersion;
824
-
825
- logger.Info($" Found incorrect peer [{packageNode.Name}] version node in [{buildFile.RelativePath}].");
826
- if (versionElement is XmlElementSyntax elementSyntax)
827
- {
828
- updateNodes.Add(elementSyntax);
829
- updateOperations.Add(new DirectUpdate()
830
- {
831
- DependencyName = dependencyName,
832
- NewVersion = NuGetVersion.Parse(newDependencyVersion),
833
- UpdatedFiles = [buildFile.Path],
834
- });
835
- }
836
- else
837
- {
838
- // This only exists for completeness in case we ever add a new type of node we don't want to silently ignore them.
839
- throw new InvalidDataException("A concrete type was required for updateNodes. This should not happen.");
840
- }
841
- }
842
- }
843
- else if (currentVersion == newDependencyVersion)
844
- {
845
- logger.Info($" Found correct [{packageNode.Name}] version node in [{buildFile.RelativePath}].");
846
- foundCorrect = true;
847
- }
848
- }
849
- }
850
- else
851
- {
852
- // We weren't able to find the version node. Central package management?
853
- logger.Warn(" Found package reference but was unable to locate version information.");
854
- }
855
- }
856
-
857
- if (updateNodes.Count > 0)
858
- {
859
- var updatedXml = buildFile.Contents
860
- .ReplaceNodes(updateNodes, (_, n) =>
861
- {
862
- if (n is XmlAttributeSyntax attributeSyntax)
863
- {
864
- return attributeSyntax.WithValue(attributeSyntax.Value.Replace(previousPackageVersion!, newDependencyVersion));
865
- }
866
-
867
- if (n is XmlElementSyntax elementsSyntax)
868
- {
869
- var modifiedContent = elementsSyntax.GetContentValue().Replace(previousPackageVersion!, newDependencyVersion);
870
-
871
- var textSyntax = SyntaxFactory.XmlText(SyntaxFactory.Token(null, SyntaxKind.XmlTextLiteralToken, null, modifiedContent));
872
- return elementsSyntax.WithContent(SyntaxFactory.SingletonList(textSyntax));
873
- }
874
-
875
- throw new InvalidDataException($"Unsupported SyntaxType {n.GetType().Name} marked for update");
876
- });
877
- buildFile.Update(updatedXml);
878
- updateWasPerformed = true;
879
- }
880
- }
881
-
882
- // If property substitution was used to set the Version, we must search for the property containing
883
- // the version string. Since it could also be populated by property substitution this search repeats
884
- // with the each new property name until the version string is located.
885
-
886
- var processedPropertyNames = new HashSet<string>();
887
-
888
- for (int propertyNameIndex = 0; propertyNameIndex < propertyNames.Count; propertyNameIndex++)
889
- {
890
- var propertyName = propertyNames[propertyNameIndex];
891
- if (processedPropertyNames.Contains(propertyName))
892
- {
893
- continue;
894
- }
895
-
896
- processedPropertyNames.Add(propertyName);
897
-
898
- foreach (var buildFile in buildFiles)
899
- {
900
- var updateProperties = new List<XmlElementSyntax>();
901
- var propertyElements = buildFile.PropertyNodes
902
- .Where(e => e.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
903
-
904
- var previousPackageVersion = previousDependencyVersion;
905
-
906
- foreach (var propertyElement in propertyElements)
907
- {
908
- var propertyContents = propertyElement.GetContentValue();
909
-
910
- // Is this the case where this property contains another property substitution?
911
- if (MSBuildHelper.TryGetPropertyName(propertyContents, out var propName))
912
- {
913
- propertyNames.Add(propName);
914
- }
915
- // Is this the case that the property contains the version?
916
- else
917
- {
918
- var currentVersion = propertyContents.TrimStart('[', '(').TrimEnd(']', ')');
919
- if (currentVersion.Contains(',') || currentVersion.Contains('*'))
920
- {
921
- logger.Warn($" Found unsupported version property [{propertyElement.Name}] value [{propertyContents}] in [{buildFile.RelativePath}].");
922
- foundUnsupported = true;
923
- }
924
- else if (currentVersion == previousDependencyVersion)
925
- {
926
- logger.Info($" Found incorrect version property [{propertyElement.Name}] in [{buildFile.RelativePath}].");
927
- updateProperties.Add((XmlElementSyntax)propertyElement.AsNode);
928
- }
929
- else if (previousDependencyVersion is null && NuGetVersion.TryParse(currentVersion, out var previousVersion))
930
- {
931
- var newVersion = NuGetVersion.Parse(newDependencyVersion);
932
- if (previousVersion < newVersion)
933
- {
934
- previousPackageVersion = currentVersion;
935
-
936
- logger.Info($" Found incorrect peer version property [{propertyElement.Name}] in [{buildFile.RelativePath}].");
937
- updateProperties.Add((XmlElementSyntax)propertyElement.AsNode);
938
- }
939
- }
940
- else if (currentVersion == newDependencyVersion)
941
- {
942
- logger.Info($" Found correct version property [{propertyElement.Name}] in [{buildFile.RelativePath}].");
943
- foundCorrect = true;
944
- }
945
- }
946
- }
947
-
948
- if (updateProperties.Count > 0)
949
- {
950
- var updatedXml = buildFile.Contents
951
- .ReplaceNodes(updateProperties, (o, n) => n.WithContent(o.GetContentValue().Replace(previousPackageVersion!, newDependencyVersion)).AsNode);
952
- buildFile.Update(updatedXml);
953
- updateWasPerformed = true;
954
- }
955
- }
956
- }
957
-
958
- var updateResult = updateWasPerformed
959
- ? UpdateResult.Updated
960
- : foundCorrect
961
- ? UpdateResult.Correct
962
- : foundUnsupported
963
- ? UpdateResult.NotSupported
964
- : UpdateResult.NotFound;
965
- return (updateResult, updateOperations);
966
- }
967
-
968
- private static IEnumerable<IXmlElementSyntax> FindPackageNodes(
969
- ProjectBuildFile buildFile,
970
- string packageName)
971
- => buildFile.PackageItemNodes.Where(e =>
972
- {
973
- // Attempt to get "Include" or "Update" attribute values
974
- var includeOrUpdateValue = e.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase)
975
- ?? e.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase);
976
- // Trim and split if there's a valid value
977
- var packageNames = includeOrUpdateValue?
978
- .Trim()
979
- .Split(';', StringSplitOptions.RemoveEmptyEntries)
980
- .Select(t => t.Trim())
981
- .Where(t => t.Equals(packageName.Trim(), StringComparison.OrdinalIgnoreCase));
982
- // Check if there's a matching package name and a non-null version attribute
983
- return packageNames?.Any() == true &&
984
- (e.GetAttributeOrSubElementValue("Version", StringComparison.OrdinalIgnoreCase)
985
- ?? e.GetAttributeOrSubElementValue("VersionOverride", StringComparison.OrdinalIgnoreCase)) is not null;
986
- });
987
-
988
- private static async Task<bool> AreDependenciesCoherentAsync(
989
- string repoRootPath,
990
- string projectPath,
991
- string dependencyName,
992
- ImmutableArray<ProjectBuildFile> buildFiles,
993
- string[] tfms,
994
- ExperimentsManager experimentsManager,
995
- ILogger logger
996
- )
997
- {
998
- var updatedTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
999
- foreach (var tfm in tfms)
1000
- {
1001
- var updatedPackages = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, updatedTopLevelDependencies, experimentsManager, logger);
1002
- var dependenciesAreCoherent = await MSBuildHelper.DependenciesAreCoherentAsync(repoRootPath, projectPath, tfm, updatedPackages, experimentsManager, logger);
1003
- if (!dependenciesAreCoherent)
1004
- {
1005
- logger.Warn($" Package [{dependencyName}] could not be updated in [{projectPath}] because it would cause a dependency conflict.");
1006
- return false;
1007
- }
1008
- }
1009
-
1010
- return true;
1011
- }
1012
-
1013
- private static async Task SaveBuildFilesAsync(ImmutableArray<ProjectBuildFile> buildFiles, ILogger logger)
1014
- {
1015
- foreach (var buildFile in buildFiles)
1016
- {
1017
- if (await buildFile.SaveAsync())
1018
- {
1019
- logger.Info($" Saved [{buildFile.RelativePath}].");
1020
- }
1021
- }
1022
- }
1023
168
  }