dependabot-nuget 0.240.0 → 0.241.0

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/build +45 -0
  3. data/helpers/lib/NuGetUpdater/.editorconfig +364 -0
  4. data/helpers/lib/NuGetUpdater/.gitignore +5 -0
  5. data/helpers/lib/NuGetUpdater/Directory.Build.props +10 -0
  6. data/helpers/lib/NuGetUpdater/Directory.Common.props +16 -0
  7. data/helpers/lib/NuGetUpdater/NuGetProjects/Directory.Build.props +14 -0
  8. data/helpers/lib/NuGetUpdater/NuGetProjects/Directory.Build.targets +7 -0
  9. data/helpers/lib/NuGetUpdater/NuGetProjects/Directory.Packages.props +29 -0
  10. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Build.Tasks/NuGet.Build.Tasks.csproj +27 -0
  11. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/AssemblyMetadataExtractor.cs +203 -0
  12. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/NuGet.CommandLine.csproj +33 -0
  13. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Commands/NuGet.Commands.csproj +26 -0
  14. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Common/NuGet.Common.csproj +21 -0
  15. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Config +6 -0
  16. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Configuration/NuGet.Configuration.csproj +24 -0
  17. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Credentials/NuGet.Credentials.csproj +20 -0
  18. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.DependencyResolver.Core/NuGet.DependencyResolver.Core.csproj +22 -0
  19. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Frameworks/NuGet.Frameworks.csproj +17 -0
  20. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.LibraryModel/NuGet.LibraryModel.csproj +17 -0
  21. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.PackageManagement/NuGet.PackageManagement.csproj +27 -0
  22. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Packaging/NuGet.Packaging.csproj +28 -0
  23. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.ProjectModel/NuGet.ProjectModel.csproj +20 -0
  24. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Protocol/NuGet.Protocol.csproj +21 -0
  25. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Resolver/NuGet.Resolver.csproj +20 -0
  26. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Versioning/NuGet.Versioning.csproj +17 -0
  27. data/helpers/lib/NuGetUpdater/NuGetProjects/README.md +1 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/FrameworkCheckCommand.cs +35 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +43 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/NuGetUpdater.Cli.csproj +20 -0
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +31 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.FrameworkCheck.cs +42 -0
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +323 -0
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/NuGetUpdater.Cli.Test.csproj +24 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Dependency.cs +3 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyType.cs +12 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/BuildFile.cs +97 -0
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/DotNetToolsJsonBuildFile.cs +24 -0
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/GlobalJsonBuildFile.cs +25 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +32 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +31 -0
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +94 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/XmlBuildFile.cs +14 -0
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs +39 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs +73 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs +146 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +27 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +316 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs +87 -0
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/ConfigurationFile.cs +3 -0
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +66 -0
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +48 -0
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +172 -0
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +498 -0
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateResult.cs +7 -0
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +105 -0
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +222 -0
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/Logger.cs +24 -0
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +443 -0
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +15 -0
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +69 -0
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +66 -0
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs +124 -0
  64. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/DotNetToolsJsonBuildFileTests.cs +52 -0
  65. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/GlobalJsonBuildFileTests.cs +63 -0
  66. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/PackagesConfigBuildFileTests.cs +63 -0
  67. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs +154 -0
  68. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs +64 -0
  69. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs +122 -0
  70. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs +68 -0
  71. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +23 -0
  72. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +36 -0
  73. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestExtensions.cs +15 -0
  74. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +79 -0
  75. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs +201 -0
  76. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +147 -0
  77. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +225 -0
  78. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +217 -0
  79. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +94 -0
  80. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +938 -0
  81. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +2177 -0
  82. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs +239 -0
  83. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +394 -0
  84. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterHelperTests.cs +179 -0
  85. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +238 -0
  86. data/helpers/lib/NuGetUpdater/NuGetUpdater.sln +152 -0
  87. data/helpers/lib/NuGetUpdater/xunit.runner.json +4 -0
  88. data/lib/dependabot/nuget/metadata_finder.rb +4 -4
  89. metadata +91 -5
@@ -0,0 +1,498 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Collections.Immutable;
4
+ using System.IO;
5
+ using System.Linq;
6
+ using System.Threading.Tasks;
7
+
8
+ using Microsoft.Build.Evaluation;
9
+ using Microsoft.Language.Xml;
10
+
11
+ using NuGet.Versioning;
12
+
13
+ namespace NuGetUpdater.Core;
14
+
15
+ internal static partial class SdkPackageUpdater
16
+ {
17
+ public static async Task UpdateDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive, Logger logger)
18
+ {
19
+ // SDK-style project, modify the XML directly
20
+ logger.Log(" Running for SDK-style project");
21
+ var buildFiles = await MSBuildHelper.LoadBuildFiles(repoRootPath, projectPath);
22
+
23
+ var newDependencyNuGetVersion = NuGetVersion.Parse(newDependencyVersion);
24
+
25
+ // update all dependencies, including transitive
26
+ var tfms = MSBuildHelper.GetTargetFrameworkMonikers(buildFiles);
27
+
28
+ // Get the set of all top-level dependencies in the current project
29
+ var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependenyInfos(buildFiles).ToArray();
30
+
31
+ var packageFoundInDependencies = false;
32
+ var packageNeedsUpdating = false;
33
+
34
+ foreach (var tfm in tfms)
35
+ {
36
+ var dependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, topLevelDependencies, logger);
37
+ foreach (var (packageName, packageVersion, _, _, _) in dependencies)
38
+ {
39
+ if (packageName.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
40
+ {
41
+ packageFoundInDependencies = true;
42
+
43
+ var nugetVersion = NuGetVersion.Parse(packageVersion);
44
+ if (nugetVersion < newDependencyNuGetVersion)
45
+ {
46
+ packageNeedsUpdating = true;
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ // Skip updating the project if the dependency does not exist in the graph
53
+ if (!packageFoundInDependencies)
54
+ {
55
+ logger.Log($" Package [{dependencyName}] Does not exist as a dependency in [{projectPath}].");
56
+ return;
57
+ }
58
+
59
+ // Skip updating the project if the dependency version meets or exceeds the newDependencyVersion
60
+ if (!packageNeedsUpdating)
61
+ {
62
+ logger.Log($" Package [{dependencyName}] already meets the requested dependency version in [{projectPath}].");
63
+ return;
64
+ }
65
+
66
+ var newDependency = new[] { new Dependency(dependencyName, newDependencyVersion, DependencyType.Unknown) };
67
+ var tfmsAndDependencies = new Dictionary<string, Dependency[]>();
68
+ foreach (var tfm in tfms)
69
+ {
70
+ var dependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, newDependency, logger);
71
+ tfmsAndDependencies[tfm] = dependencies;
72
+ }
73
+
74
+ // stop update process if we find conflicting package versions
75
+ var conflictingPackageVersionsFound = false;
76
+ var packagesAndVersions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
77
+ foreach (var (tfm, dependencies) in tfmsAndDependencies)
78
+ {
79
+ foreach (var (packageName, packageVersion, _, _, _) in dependencies)
80
+ {
81
+ if (packagesAndVersions.TryGetValue(packageName, out var existingVersion) &&
82
+ existingVersion != packageVersion)
83
+ {
84
+ logger.Log($" Package [{packageName}] tried to update to version [{packageVersion}], but found conflicting package version of [{existingVersion}].");
85
+ conflictingPackageVersionsFound = true;
86
+ }
87
+ else
88
+ {
89
+ packagesAndVersions[packageName] = packageVersion!;
90
+ }
91
+ }
92
+ }
93
+
94
+ if (conflictingPackageVersionsFound)
95
+ {
96
+ return;
97
+ }
98
+
99
+ var unupgradableTfms = tfmsAndDependencies.Where(kvp => !kvp.Value.Any()).Select(kvp => kvp.Key);
100
+ if (unupgradableTfms.Any())
101
+ {
102
+ logger.Log($" The following target frameworks could not find packages to upgrade: {string.Join(", ", unupgradableTfms)}");
103
+ return;
104
+ }
105
+
106
+ if (isTransitive)
107
+ {
108
+ var directoryPackagesWithPinning = buildFiles.OfType<ProjectBuildFile>()
109
+ .FirstOrDefault(bf => IsCpmTransitivePinningEnabled(bf));
110
+ if (directoryPackagesWithPinning is not null)
111
+ {
112
+ PinTransitiveDependency(directoryPackagesWithPinning, dependencyName, newDependencyVersion, logger);
113
+ }
114
+ else
115
+ {
116
+ await AddTransitiveDependencyAsync(projectPath, dependencyName, newDependencyVersion, logger);
117
+ }
118
+ }
119
+ else
120
+ {
121
+ await UpdateTopLevelDepdendencyAsync(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, packagesAndVersions, logger);
122
+ }
123
+
124
+ var updatedTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependenyInfos(buildFiles);
125
+ foreach (var tfm in tfms)
126
+ {
127
+ var updatedPackages = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, updatedTopLevelDependencies.ToArray(), logger);
128
+ var dependenciesAreCoherent = await MSBuildHelper.DependenciesAreCoherentAsync(repoRootPath, projectPath, tfm, updatedPackages, logger);
129
+ if (!dependenciesAreCoherent)
130
+ {
131
+ logger.Log($" Package [{dependencyName}] could not be updated in [{projectPath}] because it would cause a dependency conflict.");
132
+ return;
133
+ }
134
+ }
135
+
136
+ foreach (var buildFile in buildFiles)
137
+ {
138
+ if (await buildFile.SaveAsync())
139
+ {
140
+ logger.Log($" Saved [{buildFile.RepoRelativePath}].");
141
+ }
142
+ }
143
+ }
144
+
145
+ private static bool IsCpmTransitivePinningEnabled(ProjectBuildFile buildFile)
146
+ {
147
+ var buildFileName = Path.GetFileName(buildFile.Path);
148
+ if (!buildFileName.Equals("Directory.Packages.props", StringComparison.OrdinalIgnoreCase))
149
+ {
150
+ return false;
151
+ }
152
+
153
+ var propertyElements = buildFile.PropertyNodes;
154
+
155
+ var isCpmEnabledValue = propertyElements.FirstOrDefault(e =>
156
+ e.Name.Equals("ManagePackageVersionsCentrally", StringComparison.OrdinalIgnoreCase))?.GetContentValue();
157
+ if (isCpmEnabledValue is null || !string.Equals(isCpmEnabledValue, "true", StringComparison.OrdinalIgnoreCase))
158
+ {
159
+ return false;
160
+ }
161
+
162
+ var isTransitivePinningEnabled = propertyElements.FirstOrDefault(e =>
163
+ e.Name.Equals("CentralPackageTransitivePinningEnabled", StringComparison.OrdinalIgnoreCase))?.GetContentValue();
164
+ return isTransitivePinningEnabled is not null && string.Equals(isTransitivePinningEnabled, "true", StringComparison.OrdinalIgnoreCase);
165
+ }
166
+
167
+ private static void PinTransitiveDependency(ProjectBuildFile directoryPackages, string dependencyName, string newDependencyVersion, Logger logger)
168
+ {
169
+ var existingPackageVersionElement = directoryPackages.ItemNodes
170
+ .Where(e => e.Name.Equals("PackageVersion", StringComparison.OrdinalIgnoreCase) &&
171
+ e.Attributes.Any(a => a.Name.Equals("Include", StringComparison.OrdinalIgnoreCase) &&
172
+ a.Value.Equals(dependencyName, StringComparison.OrdinalIgnoreCase)))
173
+ .FirstOrDefault();
174
+
175
+ logger.Log($" Pinning [{dependencyName}/{newDependencyVersion}] as a package version.");
176
+
177
+ var lastPackageVersion = directoryPackages.ItemNodes
178
+ .Where(e => e.Name.Equals("PackageVersion", StringComparison.OrdinalIgnoreCase))
179
+ .LastOrDefault();
180
+
181
+ if (lastPackageVersion is null)
182
+ {
183
+ logger.Log($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not pinned.");
184
+ return;
185
+ }
186
+
187
+ var lastItemGroup = lastPackageVersion.Parent;
188
+
189
+ IXmlElementSyntax updatedItemGroup;
190
+ if (existingPackageVersionElement is null)
191
+ {
192
+ // need to add a new entry
193
+ logger.Log(" New PackageVersion element added.");
194
+ var leadingTrivia = lastPackageVersion.AsNode.GetLeadingTrivia();
195
+ var packageVersionElement = XmlExtensions.CreateSingleLineXmlElementSyntax("PackageVersion", new SyntaxList<SyntaxNode>(leadingTrivia))
196
+ .WithAttribute("Include", dependencyName)
197
+ .WithAttribute("Version", newDependencyVersion);
198
+ updatedItemGroup = lastItemGroup.AddChild(packageVersionElement);
199
+ }
200
+ else
201
+ {
202
+ IXmlElementSyntax updatedPackageVersionElement;
203
+ var versionAttribute = existingPackageVersionElement.Attributes.FirstOrDefault(a => a.Name.Equals("Version", StringComparison.OrdinalIgnoreCase));
204
+ if (versionAttribute is null)
205
+ {
206
+ // need to add the version
207
+ logger.Log(" Adding version attribute to element.");
208
+ updatedPackageVersionElement = existingPackageVersionElement.WithAttribute("Version", newDependencyVersion);
209
+ }
210
+ else if (!versionAttribute.Value.Equals(newDependencyVersion, StringComparison.OrdinalIgnoreCase))
211
+ {
212
+ // need to update the version
213
+ logger.Log($" Updating version attribute of [{versionAttribute.Value}].");
214
+ var updatedVersionAttribute = versionAttribute.WithValue(newDependencyVersion);
215
+ updatedPackageVersionElement = existingPackageVersionElement.ReplaceAttribute(versionAttribute, updatedVersionAttribute);
216
+ }
217
+ else
218
+ {
219
+ logger.Log(" Existing PackageVersion element version was already correct.");
220
+ return;
221
+ }
222
+
223
+ updatedItemGroup = lastItemGroup.ReplaceChildElement(existingPackageVersionElement, updatedPackageVersionElement);
224
+ }
225
+
226
+ var updatedXml = directoryPackages.Contents.ReplaceNode(lastItemGroup.AsNode, updatedItemGroup.AsNode);
227
+ directoryPackages.Update(updatedXml);
228
+ }
229
+
230
+ private static async Task AddTransitiveDependencyAsync(string projectPath, string dependencyName, string newDependencyVersion, Logger logger)
231
+ {
232
+ logger.Log($" Adding [{dependencyName}/{newDependencyVersion}] as a top-level package reference.");
233
+
234
+ // see https://learn.microsoft.com/nuget/consume-packages/install-use-packages-dotnet-cli
235
+ var (exitCode, _, _) = await ProcessEx.RunAsync("dotnet", $"add {projectPath} package {dependencyName} --version {newDependencyVersion}");
236
+ if (exitCode != 0)
237
+ {
238
+ logger.Log($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.");
239
+ }
240
+ }
241
+
242
+ private static async Task UpdateTopLevelDepdendencyAsync(ImmutableArray<ProjectBuildFile> buildFiles, string dependencyName, string previousDependencyVersion, string newDependencyVersion, Dictionary<string, string> packagesAndVersions, Logger logger)
243
+ {
244
+ var result = TryUpdateDependencyVersion(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, logger);
245
+ if (result == UpdateResult.NotFound)
246
+ {
247
+ logger.Log($" Root package [{dependencyName}/{previousDependencyVersion}] was not updated; skipping dependencies.");
248
+ return;
249
+ }
250
+
251
+ foreach (var (packageName, packageVersion) in packagesAndVersions.Where(kvp => string.Compare(kvp.Key, dependencyName, StringComparison.OrdinalIgnoreCase) != 0))
252
+ {
253
+ TryUpdateDependencyVersion(buildFiles, packageName, previousDependencyVersion: null, newDependencyVersion: packageVersion, logger);
254
+ }
255
+ }
256
+
257
+ private static UpdateResult TryUpdateDependencyVersion(ImmutableArray<ProjectBuildFile> buildFiles, string dependencyName, string? previousDependencyVersion, string newDependencyVersion, Logger logger)
258
+ {
259
+ var foundCorrect = false;
260
+ var foundUnsupported = false;
261
+ var updateWasPerformed = false;
262
+ var propertyNames = new List<string>();
263
+
264
+ // First we locate all the PackageReference, GlobalPackageReference, or PackageVersion which set the Version
265
+ // or VersionOverride attribute. In the simplest case we can update the version attribute directly then move
266
+ // on. When property substitution is used we have to additionally search for the property containing the version.
267
+
268
+ foreach (var buildFile in buildFiles)
269
+ {
270
+ var updateNodes = new List<XmlNodeSyntax>();
271
+ var packageNodes = FindPackageNodes(buildFile, dependencyName);
272
+
273
+ var previousPackageVersion = previousDependencyVersion;
274
+
275
+ foreach (var packageNode in packageNodes)
276
+ {
277
+ var versionAttribute = packageNode.GetAttribute("Version", StringComparison.OrdinalIgnoreCase)
278
+ ?? packageNode.GetAttribute("VersionOverride", StringComparison.OrdinalIgnoreCase);
279
+ var versionElement = packageNode.Elements.FirstOrDefault(e => e.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))
280
+ ?? packageNode.Elements.FirstOrDefault(e => e.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase));
281
+ if (versionAttribute is not null)
282
+ {
283
+ // Is this the case where version is specified with property substitution?
284
+ if (MSBuildHelper.TryGetPropertyName(versionAttribute.Value, out var propertyName))
285
+ {
286
+ propertyNames.Add(propertyName);
287
+ }
288
+ // Is this the case that the version is specified directly in the package node?
289
+ else
290
+ {
291
+ var currentVersion = versionAttribute.Value.TrimStart('[', '(').TrimEnd(']', ')');
292
+ if (currentVersion.Contains(',') || currentVersion.Contains('*'))
293
+ {
294
+ logger.Log($" Found unsupported [{packageNode.Name}] version attribute value [{versionAttribute.Value}] in [{buildFile.RepoRelativePath}].");
295
+ foundUnsupported = true;
296
+ }
297
+ else if (string.Equals(currentVersion, previousDependencyVersion, StringComparison.Ordinal))
298
+ {
299
+ logger.Log($" Found incorrect [{packageNode.Name}] version attribute in [{buildFile.RepoRelativePath}].");
300
+ updateNodes.Add(versionAttribute);
301
+ }
302
+ else if (previousDependencyVersion == null && NuGetVersion.TryParse(currentVersion, out var previousVersion))
303
+ {
304
+ var newVersion = NuGetVersion.Parse(newDependencyVersion);
305
+ if (previousVersion < newVersion)
306
+ {
307
+ previousPackageVersion = currentVersion;
308
+
309
+ logger.Log($" Found incorrect peer [{packageNode.Name}] version attribute in [{buildFile.RepoRelativePath}].");
310
+ updateNodes.Add(versionAttribute);
311
+ }
312
+ }
313
+ else if (string.Equals(currentVersion, newDependencyVersion, StringComparison.Ordinal))
314
+ {
315
+ logger.Log($" Found correct [{packageNode.Name}] version attribute in [{buildFile.RepoRelativePath}].");
316
+ foundCorrect = true;
317
+ }
318
+ }
319
+ }
320
+ else if (versionElement is not null)
321
+ {
322
+ var versionValue = versionElement.GetContentValue();
323
+ if (MSBuildHelper.TryGetPropertyName(versionValue, out var propertyName))
324
+ {
325
+ propertyNames.Add(propertyName);
326
+ }
327
+ else
328
+ {
329
+ var currentVersion = versionValue.TrimStart('[', '(').TrimEnd(']', ')');
330
+ if (currentVersion.Contains(',') || currentVersion.Contains('*'))
331
+ {
332
+ logger.Log($" Found unsupported [{packageNode.Name}] version node value [{versionValue}] in [{buildFile.RepoRelativePath}].");
333
+ foundUnsupported = true;
334
+ }
335
+ else if (currentVersion == previousDependencyVersion)
336
+ {
337
+ logger.Log($" Found incorrect [{packageNode.Name}] version node in [{buildFile.RepoRelativePath}].");
338
+ if (versionElement is XmlElementSyntax elementSyntax)
339
+ {
340
+ updateNodes.Add(elementSyntax);
341
+ }
342
+ else
343
+ {
344
+ throw new InvalidDataException("A concrete type was required for updateNodes. This should not happen.");
345
+ }
346
+ }
347
+ else if (previousDependencyVersion == null && NuGetVersion.TryParse(currentVersion, out var previousVersion))
348
+ {
349
+ var newVersion = NuGetVersion.Parse(newDependencyVersion);
350
+ if (previousVersion < newVersion)
351
+ {
352
+ previousPackageVersion = currentVersion;
353
+
354
+ logger.Log($" Found incorrect peer [{packageNode.Name}] version node in [{buildFile.RepoRelativePath}].");
355
+ if (versionElement is XmlElementSyntax elementSyntax)
356
+ {
357
+ updateNodes.Add(elementSyntax);
358
+ }
359
+ else
360
+ {
361
+ // This only exists for completeness in case we ever add a new type of node we don't want to silently ignore them.
362
+ throw new InvalidDataException("A concrete type was required for updateNodes. This should not happen.");
363
+ }
364
+ }
365
+ }
366
+ else if (currentVersion == newDependencyVersion)
367
+ {
368
+ logger.Log($" Found correct [{packageNode.Name}] version node in [{buildFile.RepoRelativePath}].");
369
+ foundCorrect = true;
370
+ }
371
+ }
372
+ }
373
+ else
374
+ {
375
+ // We weren't able to find the version node. Central package management?
376
+ logger.Log($" Found package reference but was unable to locate version information.");
377
+ continue;
378
+ }
379
+ }
380
+
381
+ if (updateNodes.Count > 0)
382
+ {
383
+ var updatedXml = buildFile.Contents
384
+ .ReplaceNodes(updateNodes, (o, n) =>
385
+ {
386
+ if (n is XmlAttributeSyntax attributeSyntax)
387
+ {
388
+ return attributeSyntax.WithValue(attributeSyntax.Value.Replace(previousPackageVersion!, newDependencyVersion));
389
+ }
390
+ else if (n is XmlElementSyntax elementsSyntax)
391
+ {
392
+ var modifiedContent = elementsSyntax.GetContentValue().Replace(previousPackageVersion!, newDependencyVersion);
393
+
394
+ var textSyntax = SyntaxFactory.XmlText(SyntaxFactory.Token(null, SyntaxKind.XmlTextLiteralToken, null, modifiedContent));
395
+ return elementsSyntax.WithContent(SyntaxFactory.SingletonList(textSyntax));
396
+ }
397
+ else
398
+ {
399
+ throw new InvalidDataException($"Unsupported SyntaxType {n.GetType().Name} marked for update");
400
+ }
401
+ });
402
+ buildFile.Update(updatedXml);
403
+ updateWasPerformed = true;
404
+ }
405
+ }
406
+
407
+ // If property substitution was used to set the Version, we must search for the property containing
408
+ // the version string. Since it could also be populated by property substitution this search repeats
409
+ // with the each new property name until the version string is located.
410
+
411
+ var processedPropertyNames = new HashSet<string>();
412
+
413
+ for (int propertyNameIndex = 0; propertyNameIndex < propertyNames.Count; propertyNameIndex++)
414
+ {
415
+ var propertyName = propertyNames[propertyNameIndex];
416
+ if (processedPropertyNames.Contains(propertyName))
417
+ {
418
+ continue;
419
+ }
420
+
421
+ processedPropertyNames.Add(propertyName);
422
+
423
+ foreach (var buildFile in buildFiles)
424
+ {
425
+ var updateProperties = new List<XmlElementSyntax>();
426
+ var propertyElements = buildFile.PropertyNodes
427
+ .Where(e => e.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
428
+
429
+ var previousPackageVersion = previousDependencyVersion;
430
+
431
+ foreach (var propertyElement in propertyElements)
432
+ {
433
+ var propertyContents = propertyElement.GetContentValue();
434
+
435
+ // Is this the case where this property contains another property substitution?
436
+ if (MSBuildHelper.TryGetPropertyName(propertyContents, out var propName))
437
+ {
438
+ propertyNames.Add(propName);
439
+ }
440
+ // Is this the case that the property contains the version?
441
+ else
442
+ {
443
+ var currentVersion = propertyContents.TrimStart('[', '(').TrimEnd(']', ')');
444
+ if (currentVersion.Contains(',') || currentVersion.Contains('*'))
445
+ {
446
+ logger.Log($" Found unsupported version property [{propertyElement.Name}] value [{propertyContents}] in [{buildFile.RepoRelativePath}].");
447
+ foundUnsupported = true;
448
+ }
449
+ else if (currentVersion == previousDependencyVersion)
450
+ {
451
+ logger.Log($" Found incorrect version property [{propertyElement.Name}] in [{buildFile.RepoRelativePath}].");
452
+ updateProperties.Add((XmlElementSyntax)propertyElement.AsNode);
453
+ }
454
+ else if (previousDependencyVersion is null && NuGetVersion.TryParse(currentVersion, out var previousVersion))
455
+ {
456
+ var newVersion = NuGetVersion.Parse(newDependencyVersion);
457
+ if (previousVersion < newVersion)
458
+ {
459
+ previousPackageVersion = currentVersion;
460
+
461
+ logger.Log($" Found incorrect peer version property [{propertyElement.Name}] in [{buildFile.RepoRelativePath}].");
462
+ updateProperties.Add((XmlElementSyntax)propertyElement.AsNode);
463
+ }
464
+ }
465
+ else if (currentVersion == newDependencyVersion)
466
+ {
467
+ logger.Log($" Found correct version property [{propertyElement.Name}] in [{buildFile.RepoRelativePath}].");
468
+ foundCorrect = true;
469
+ }
470
+ }
471
+ }
472
+
473
+ if (updateProperties.Count > 0)
474
+ {
475
+ var updatedXml = buildFile.Contents
476
+ .ReplaceNodes(updateProperties, (o, n) => n.WithContent(o.GetContentValue().Replace(previousPackageVersion!, newDependencyVersion)).AsNode);
477
+ buildFile.Update(updatedXml);
478
+ updateWasPerformed = true;
479
+ }
480
+ }
481
+ }
482
+
483
+ return updateWasPerformed
484
+ ? UpdateResult.Updated
485
+ : foundCorrect
486
+ ? UpdateResult.Correct
487
+ : foundUnsupported
488
+ ? UpdateResult.NotSupported
489
+ : UpdateResult.NotFound;
490
+ }
491
+
492
+ private static IEnumerable<IXmlElementSyntax> FindPackageNodes(ProjectBuildFile buildFile, string packageName)
493
+ {
494
+ return buildFile.PackageItemNodes.Where(e =>
495
+ string.Equals(e.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase), packageName, StringComparison.OrdinalIgnoreCase) &&
496
+ (e.GetAttributeOrSubElementValue("Version", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("VersionOverride", StringComparison.OrdinalIgnoreCase)) is not null);
497
+ }
498
+ }
@@ -0,0 +1,7 @@
1
+ internal enum UpdateResult
2
+ {
3
+ NotFound,
4
+ NotSupported,
5
+ Updated,
6
+ Correct,
7
+ }
@@ -0,0 +1,105 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.IO;
4
+ using System.Threading.Tasks;
5
+
6
+ namespace NuGetUpdater.Core;
7
+
8
+ public partial class UpdaterWorker
9
+ {
10
+ private readonly Logger _logger;
11
+ private readonly HashSet<string> _processedGlobalJsonPaths = new(StringComparer.OrdinalIgnoreCase);
12
+
13
+ public UpdaterWorker(Logger logger)
14
+ {
15
+ _logger = logger;
16
+ }
17
+
18
+ public async Task RunAsync(string repoRootPath, string filePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
19
+ {
20
+ MSBuildHelper.RegisterMSBuild();
21
+
22
+ if (!Path.IsPathRooted(filePath) || !File.Exists(filePath))
23
+ {
24
+ filePath = Path.GetFullPath(Path.Join(repoRootPath, filePath));
25
+ }
26
+
27
+ if (!isTransitive)
28
+ {
29
+ await DotNetToolsJsonUpdater.UpdateDependencyAsync(repoRootPath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
30
+ }
31
+
32
+ var extension = Path.GetExtension(filePath).ToLowerInvariant();
33
+ switch (extension)
34
+ {
35
+ case ".sln":
36
+ await RunForSolutionAsync(repoRootPath, filePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
37
+ break;
38
+ case ".proj":
39
+ await RunForProjFileAsync(repoRootPath, filePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
40
+ break;
41
+ case ".csproj":
42
+ case ".fsproj":
43
+ case ".vbproj":
44
+ await RunForProjectAsync(repoRootPath, filePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
45
+ break;
46
+ default:
47
+ _logger.Log($"File extension [{extension}] is not supported.");
48
+ break;
49
+ }
50
+
51
+ _processedGlobalJsonPaths.Clear();
52
+ }
53
+
54
+ private async Task RunForSolutionAsync(string repoRootPath, string solutionPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
55
+ {
56
+ _logger.Log($"Running for solution [{Path.GetRelativePath(repoRootPath, solutionPath)}]");
57
+ var projectPaths = MSBuildHelper.GetProjectPathsFromSolution(solutionPath);
58
+ foreach (var projectPath in projectPaths)
59
+ {
60
+ await RunForProjectAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
61
+ }
62
+ }
63
+
64
+ private async Task RunForProjFileAsync(string repoRootPath, string projFilePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
65
+ {
66
+ _logger.Log($"Running for proj file [{Path.GetRelativePath(repoRootPath, projFilePath)}]");
67
+ if (!File.Exists(projFilePath))
68
+ {
69
+ _logger.Log($"File [{projFilePath}] does not exist.");
70
+ return;
71
+ }
72
+ var projectFilePaths = MSBuildHelper.GetProjectPathsFromProject(projFilePath);
73
+ foreach (var projectFullPath in projectFilePaths)
74
+ {
75
+ // If there is some MSBuild logic that needs to run to fully resolve the path skip the project
76
+ if (File.Exists(projectFullPath))
77
+ {
78
+ await RunForProjectAsync(repoRootPath, projectFullPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
79
+ }
80
+ }
81
+ }
82
+
83
+ private async Task RunForProjectAsync(string repoRootPath, string projectPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
84
+ {
85
+ _logger.Log($"Running for project [{projectPath}]");
86
+
87
+ if (!isTransitive
88
+ && MSBuildHelper.GetGlobalJsonPath(repoRootPath, projectPath) is string globalJsonPath
89
+ && !_processedGlobalJsonPaths.Contains(globalJsonPath))
90
+ {
91
+ _processedGlobalJsonPaths.Add(globalJsonPath);
92
+ await GlobalJsonUpdater.UpdateDependencyAsync(repoRootPath, globalJsonPath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
93
+ }
94
+
95
+ if (NuGetHelper.HasProjectConfigFile(projectPath))
96
+ {
97
+ await PackagesConfigUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive, _logger);
98
+ }
99
+
100
+ // Some repos use a mix of packages.config and PackageReference
101
+ await SdkPackageUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive, _logger);
102
+
103
+ _logger.Log("Update complete.");
104
+ }
105
+ }