dependabot-nuget 0.239.0 → 0.241.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) 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/cache_manager.rb +2 -0
  89. data/lib/dependabot/nuget/file_fetcher.rb +51 -40
  90. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +0 -6
  91. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +7 -22
  92. data/lib/dependabot/nuget/file_parser.rb +1 -1
  93. data/lib/dependabot/nuget/file_updater.rb +6 -2
  94. data/lib/dependabot/nuget/metadata_finder.rb +4 -4
  95. data/lib/dependabot/nuget/native_helpers.rb +7 -4
  96. data/lib/dependabot/nuget/nuget_client.rb +99 -0
  97. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +71 -0
  98. data/lib/dependabot/nuget/requirement.rb +1 -1
  99. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +2 -2
  100. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +2 -2
  101. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +1 -29
  102. data/lib/dependabot/nuget/update_checker/property_updater.rb +2 -2
  103. data/lib/dependabot/nuget/update_checker/repository_finder.rb +39 -8
  104. data/lib/dependabot/nuget/update_checker/requirements_updater.rb +2 -2
  105. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +2 -2
  106. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +2 -2
  107. data/lib/dependabot/nuget/update_checker/version_finder.rb +4 -42
  108. metadata +107 -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
+ }