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,316 @@
1
+ extern alias CoreV2;
2
+
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.IO;
6
+ using System.Linq;
7
+ using System.Threading.Tasks;
8
+ using System.Xml.Linq;
9
+
10
+ using Microsoft.Language.Xml;
11
+
12
+ using NuGet.ProjectManagement;
13
+
14
+ using AssemblyBinding = CoreV2::NuGet.Runtime.AssemblyBinding;
15
+
16
+ namespace NuGetUpdater.Core;
17
+
18
+ internal static class BindingRedirectManager
19
+ {
20
+ private static readonly XName AssemblyBindingName = AssemblyBinding.GetQualifiedName("assemblyBinding");
21
+ private static readonly XName DependentAssemblyName = AssemblyBinding.GetQualifiedName("dependentAssembly");
22
+ private static readonly XName BindingRedirectName = AssemblyBinding.GetQualifiedName("bindingRedirect");
23
+
24
+ public static async ValueTask UpdateBindingRedirectsAsync(ProjectBuildFile projectBuildFile)
25
+ {
26
+ var configFile = await TryGetRuntimeConfigurationFile(projectBuildFile);
27
+ if (configFile is null)
28
+ {
29
+ // no runtime config file so no need to add binding redirects
30
+ return;
31
+ }
32
+
33
+ var references = ExtractReferenceElements(projectBuildFile);
34
+ references = ToAbsolutePaths(references, projectBuildFile.Path);
35
+
36
+ var bindings = BindingRedirectResolver.GetBindingRedirects(projectBuildFile.Path, references.Select(static x => x.Include));
37
+ if (!bindings.Any())
38
+ {
39
+ // no bindings to update
40
+ return;
41
+ }
42
+
43
+ var fileContent = AddBindingRedirects(configFile, bindings);
44
+ configFile = configFile with { Content = fileContent };
45
+
46
+ await File.WriteAllTextAsync(configFile.Path, configFile.Content);
47
+
48
+ if (configFile.ShouldAddToProject)
49
+ {
50
+ AddConfigFileToProject(projectBuildFile, configFile);
51
+ }
52
+
53
+ return;
54
+
55
+ static List<(string Include, string HintPath)> ExtractReferenceElements(ProjectBuildFile projectBuildFile)
56
+ {
57
+ var document = projectBuildFile.Contents;
58
+ var hintPaths = new List<(string Include, string HintPath)>();
59
+
60
+ foreach (var element in document.Descendants().Where(static x => x.Name == "Reference"))
61
+ {
62
+ // Extract Include attribute
63
+ var includeAttribute = element.GetAttribute("Include");
64
+ if (includeAttribute == null) continue;
65
+
66
+ // Check for HintPath as a child element
67
+ var hintPathElement = element.Elements.FirstOrDefault(static x => x.Name == "HintPath");
68
+ if (hintPathElement != null)
69
+ {
70
+ hintPaths.Add((includeAttribute.Value, hintPathElement.GetContentValue()));
71
+ }
72
+
73
+ // Check for HintPath as an attribute
74
+ var hintPathAttribute = element.GetAttribute("HintPath");
75
+ if (hintPathAttribute != null)
76
+ {
77
+ hintPaths.Add((includeAttribute.Value, hintPathAttribute.Value));
78
+ }
79
+ }
80
+
81
+ return hintPaths;
82
+ }
83
+
84
+ static void AddConfigFileToProject(ProjectBuildFile projectBuildFile, ConfigurationFile configFile)
85
+ {
86
+ var projectNode = projectBuildFile.Contents.RootSyntax;
87
+ var itemGroup = XmlExtensions.CreateOpenCloseXmlElementSyntax("ItemGroup")
88
+ .AddChild(
89
+ XmlExtensions.CreateSingleLineXmlElementSyntax("None")
90
+ .WithAttribute("Include", Path.GetRelativePath(Path.GetDirectoryName(projectBuildFile.Path)!, configFile.Path)));
91
+
92
+ var updatedProjectNode = projectNode.AddChild(itemGroup);
93
+ var updatedXml = projectBuildFile.Contents.ReplaceNode(projectNode.AsNode, updatedProjectNode.AsNode);
94
+ projectBuildFile.Update(updatedXml);
95
+ }
96
+
97
+ static List<(string Include, string HintPath)> ToAbsolutePaths(List<(string Include, string HintPath)> references, string projectPath)
98
+ {
99
+ var directoryPath = Path.GetDirectoryName(projectPath);
100
+ ArgumentNullException.ThrowIfNull(directoryPath, nameof(directoryPath));
101
+ return references.Select(t => (t.Include, Path.GetFullPath(Path.Combine(directoryPath, t.HintPath)))).ToList();
102
+ }
103
+ }
104
+
105
+ private static async ValueTask<ConfigurationFile?> TryGetRuntimeConfigurationFile(ProjectBuildFile projectBuildFile)
106
+ {
107
+ var directoryPath = Path.GetDirectoryName(projectBuildFile.Path);
108
+ if (directoryPath is null)
109
+ {
110
+ return null;
111
+ }
112
+
113
+ var configFile = projectBuildFile.ItemNodes
114
+ .Where(IsConfigFile)
115
+ .FirstOrDefault();
116
+
117
+ if (configFile is null)
118
+ {
119
+ return null;
120
+ }
121
+
122
+ var configFilePath = Path.GetFullPath(Path.Combine(directoryPath, GetContent(configFile)));
123
+ var configFileContents = await File.ReadAllTextAsync(configFilePath);
124
+ return new ConfigurationFile(configFilePath, configFileContents, false);
125
+
126
+ static string GetContent(IXmlElementSyntax element)
127
+ {
128
+ var content = element.GetContentValue();
129
+ if (!string.IsNullOrEmpty(content))
130
+ {
131
+ return content;
132
+ }
133
+
134
+ content = element.GetAttributeValue("Include");
135
+ if (!string.IsNullOrEmpty(content))
136
+ {
137
+ return content;
138
+ }
139
+
140
+ return string.Empty;
141
+ }
142
+
143
+ static bool IsConfigFile(IXmlElementSyntax element)
144
+ {
145
+ var content = GetContent(element);
146
+ if (content is null)
147
+ {
148
+ return false;
149
+ }
150
+
151
+ var path = Path.GetFileName(content);
152
+ return (element.Name == "None" && string.Equals(path, "app.config", StringComparison.OrdinalIgnoreCase))
153
+ || (element.Name == "Content" && string.Equals(path, "web.config", StringComparison.OrdinalIgnoreCase));
154
+ }
155
+
156
+ static string GetConfigFileName(XmlDocumentSyntax document)
157
+ {
158
+ var guidValue = document.Descendants()
159
+ .Where(static x => x.Name == "PropertyGroup")
160
+ .SelectMany(static x => x.Elements.Where(static x => x.Name == "ProjectGuid"))
161
+ .FirstOrDefault()
162
+ ?.GetContentValue();
163
+ return guidValue switch
164
+ {
165
+ "{E24C65DC-7377-472B-9ABA-BC803B73C61A}" or "{349C5851-65DF-11DA-9384-00065B846F21}" => "Web.config",
166
+ _ => "App.config"
167
+ };
168
+ }
169
+
170
+ static string GenerateDefaultAppConfig(XmlDocumentSyntax document)
171
+ {
172
+ var frameworkVersion = GetFrameworkVersion(document);
173
+ return $"""
174
+ <?xml version="1.0" encoding="utf-8" ?>
175
+ <configuration>
176
+ <startup>
177
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version={frameworkVersion}" />
178
+ </startup>
179
+ </configuration>
180
+ """;
181
+ }
182
+
183
+ static string? GetFrameworkVersion(XmlDocumentSyntax document)
184
+ {
185
+ return document.Descendants()
186
+ .Where(static x => x.Name == "PropertyGroup")
187
+ .SelectMany(static x => x.Elements.Where(static x => x.Name == "TargetFrameworkVersion"))
188
+ .FirstOrDefault()
189
+ ?.GetContentValue();
190
+ }
191
+ }
192
+
193
+ private static string AddBindingRedirects(ConfigurationFile configFile, IEnumerable<AssemblyBinding> bindingRedirects)
194
+ {
195
+ // Do nothing if there are no binding redirects to add, bail out
196
+ if (!bindingRedirects.Any())
197
+ {
198
+ return configFile.Content;
199
+ }
200
+
201
+ // Get the configuration file
202
+ var document = GetConfiguration(configFile.Content);
203
+
204
+ // Get the runtime element
205
+ var runtime = document.Root?.Element("runtime");
206
+
207
+ if (runtime == null)
208
+ {
209
+ // Add the runtime element to the configuration document
210
+ runtime = new XElement("runtime");
211
+ document.Root.AddIndented(runtime);
212
+ }
213
+
214
+ // Get all of the current bindings in config
215
+ var currentBindings = GetAssemblyBindings(runtime);
216
+
217
+ foreach (var bindingRedirect in bindingRedirects)
218
+ {
219
+ // Look to see if we already have this in the list of bindings already in config.
220
+ if (currentBindings.TryGetValue((bindingRedirect.Name, bindingRedirect.PublicKeyToken), out var existingBinding))
221
+ {
222
+ UpdateBindingRedirectElement(existingBinding, bindingRedirect);
223
+ }
224
+ else
225
+ {
226
+ // Get an assembly binding element to use
227
+ var assemblyBindingElement = GetAssemblyBindingElement(runtime);
228
+
229
+ // Add the binding to that element
230
+ assemblyBindingElement.AddIndented(bindingRedirect.ToXElement());
231
+ }
232
+ }
233
+
234
+ return document.ToString();
235
+
236
+ static XDocument GetConfiguration(string configFileContent)
237
+ {
238
+ try
239
+ {
240
+ return XDocument.Parse(configFileContent, LoadOptions.PreserveWhitespace);
241
+ }
242
+ catch (Exception ex)
243
+ {
244
+ throw new InvalidOperationException("Error loading binging redirect configuration", ex);
245
+ }
246
+ }
247
+
248
+ static void RemoveElement(XElement element)
249
+ {
250
+ // Hold onto the parent element before removing the element
251
+ var parentElement = element.Parent;
252
+
253
+ // Remove the element from the document if we find a match
254
+ element.RemoveIndented();
255
+
256
+ if (parentElement?.HasElements != true)
257
+ {
258
+ parentElement.RemoveIndented();
259
+ }
260
+ }
261
+
262
+ static void UpdateBindingRedirectElement(
263
+ XElement existingDependentAssemblyElement,
264
+ AssemblyBinding newBindingRedirect)
265
+ {
266
+ var existingBindingRedirectElement = existingDependentAssemblyElement.Element(BindingRedirectName);
267
+ // Since we've successfully parsed this node, it has to be valid and this child must exist.
268
+ if (existingBindingRedirectElement != null)
269
+ {
270
+ existingBindingRedirectElement.SetAttributeValue(XName.Get("oldVersion"), newBindingRedirect.OldVersion);
271
+ existingBindingRedirectElement.SetAttributeValue(XName.Get("newVersion"), newBindingRedirect.NewVersion);
272
+ }
273
+ else
274
+ {
275
+ // At this point, <dependentAssemblyElement> already exists, but <bindingRedirectElement> does not.
276
+ // So, extract the <bindingRedirectElement> from the newDependencyAssemblyElement, and add it
277
+ // to the existingDependentAssemblyElement
278
+ var newDependentAssemblyElement = newBindingRedirect.ToXElement();
279
+ var newBindingRedirectElement = newDependentAssemblyElement.Element(BindingRedirectName);
280
+ existingDependentAssemblyElement.AddIndented(newBindingRedirectElement);
281
+ }
282
+ }
283
+
284
+ static Dictionary<(string Name, string PublicKeyToken), XElement> GetAssemblyBindings(XElement runtime)
285
+ {
286
+ var dependencyAssemblyElements = runtime.Elements(AssemblyBindingName)
287
+ .Elements(DependentAssemblyName);
288
+
289
+ // We're going to need to know which element is associated with what binding for removal
290
+ var assemblyElementPairs = from dependentAssemblyElement in dependencyAssemblyElements
291
+ select new
292
+ {
293
+ Binding = AssemblyBinding.Parse(dependentAssemblyElement),
294
+ Element = dependentAssemblyElement
295
+ };
296
+
297
+ // Return a mapping from binding to element
298
+ return assemblyElementPairs.ToDictionary(p => (p.Binding.Name, p.Binding.PublicKeyToken), p => p.Element);
299
+ }
300
+
301
+ static XElement GetAssemblyBindingElement(XElement runtime)
302
+ {
303
+ // Pick the first assembly binding element or create one if there aren't any
304
+ var assemblyBinding = runtime.Elements(AssemblyBindingName).FirstOrDefault();
305
+ if (assemblyBinding is not null)
306
+ {
307
+ return assemblyBinding;
308
+ }
309
+
310
+ assemblyBinding = new XElement(AssemblyBindingName);
311
+ runtime.AddIndented(assemblyBinding);
312
+
313
+ return assemblyBinding;
314
+ }
315
+ }
316
+ }
@@ -0,0 +1,87 @@
1
+ extern alias CoreV2;
2
+
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.Diagnostics.CodeAnalysis;
6
+ using System.IO;
7
+ using System.Linq;
8
+ using System.Reflection;
9
+ using System.Text.RegularExpressions;
10
+
11
+ using AssemblyBinding = CoreV2::NuGet.Runtime.AssemblyBinding;
12
+ using IAssembly = CoreV2::NuGet.Runtime.IAssembly;
13
+
14
+ namespace NuGetUpdater.Core;
15
+
16
+ public static partial class BindingRedirectResolver
17
+ {
18
+ public static IEnumerable<AssemblyBinding> GetBindingRedirects(string projectPath, IEnumerable<string> includes)
19
+ {
20
+ var directoryPath = Path.GetDirectoryName(projectPath);
21
+ if (directoryPath is null)
22
+ {
23
+ yield break;
24
+ }
25
+
26
+ foreach (var include in includes)
27
+ {
28
+ if (TryParseIncludesString(include, out var assemblyInfo))
29
+ {
30
+ yield return new AssemblyBinding(assemblyInfo);
31
+ }
32
+ }
33
+
34
+ yield break;
35
+
36
+ static bool TryParseIncludesString(string include, [NotNullWhen(true)] out AssemblyWrapper? assemblyInfo)
37
+ {
38
+ assemblyInfo = null;
39
+ var name = include.Split(',').FirstOrDefault();
40
+ if (name is null)
41
+ {
42
+ return false;
43
+ }
44
+
45
+ var dict = IncludesRegex
46
+ .Matches(include)
47
+ .ToDictionary(static x => x.Groups["key"].Value, static x => x.Groups["value"].Value);
48
+
49
+ if (!dict.TryGetValue("Version", out var versionString) ||
50
+ !Version.TryParse(versionString, out var version))
51
+ {
52
+ return false;
53
+ }
54
+
55
+ dict.TryGetValue("PublicKeyToken", out var publicKeyToken);
56
+ dict.TryGetValue("Culture", out var culture);
57
+
58
+ assemblyInfo = new AssemblyWrapper(name, version, publicKeyToken, culture);
59
+ return true;
60
+ }
61
+ }
62
+
63
+ private static readonly Regex IncludesRegex = IncludesPattern();
64
+
65
+ /// <summary>
66
+ /// Wraps systme<see cref="Assembly"/> type in the nuget interface <see cref="IAssembly"/> to interop with nuget apis
67
+ /// </summary>
68
+ private class AssemblyWrapper : IAssembly
69
+ {
70
+ public AssemblyWrapper(string name, Version version, string? publicKeyToken = null, string? culture = null)
71
+ {
72
+ Name = name;
73
+ Version = version;
74
+ PublicKeyToken = publicKeyToken;
75
+ Culture = culture;
76
+ }
77
+
78
+ public string Name { get; }
79
+ public Version Version { get; }
80
+ public string? PublicKeyToken { get; }
81
+ public string? Culture { get; }
82
+ public IEnumerable<IAssembly> ReferencedAssemblies { get; } = Enumerable.Empty<AssemblyWrapper>();
83
+ }
84
+
85
+ [GeneratedRegex("(?<key>\\w+)=(?<value>[^,]+)")]
86
+ private static partial Regex IncludesPattern();
87
+ }
@@ -0,0 +1,3 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ public record ConfigurationFile(string Path, string Content, bool ShouldAddToProject);
@@ -0,0 +1,66 @@
1
+ using System;
2
+ using System.Collections.Immutable;
3
+ using System.IO;
4
+ using System.Linq;
5
+ using System.Threading.Tasks;
6
+
7
+ namespace NuGetUpdater.Core;
8
+
9
+ internal static partial class DotNetToolsJsonUpdater
10
+ {
11
+ public static async Task UpdateDependencyAsync(string repoRootPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, Logger logger)
12
+ {
13
+ var buildFiles = LoadBuildFiles(repoRootPath);
14
+ if (buildFiles.Length == 0)
15
+ {
16
+ logger.Log($" No dotnet-tools.json files found.");
17
+ return;
18
+ }
19
+
20
+ logger.Log($" Updating dotnet-tools.json files.");
21
+
22
+
23
+ var filesToUpdate = buildFiles.Where(f =>
24
+ f.GetDependencies().Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase)))
25
+ .ToImmutableArray();
26
+ if (filesToUpdate.Length == 0)
27
+ {
28
+ logger.Log($" Dependency [{dependencyName}] not found in any dotnet-tools.json files.");
29
+ return;
30
+ }
31
+
32
+ foreach (var buildFile in filesToUpdate)
33
+ {
34
+ var tool = buildFile.Tools
35
+ .Single(kvp => kvp.Key.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
36
+
37
+ var toolObject = tool.Value?.AsObject();
38
+
39
+ if (toolObject is not null &&
40
+ toolObject["version"]?.GetValue<string>() == previousDependencyVersion)
41
+ {
42
+ buildFile.UpdateProperty(new[] { "tools", dependencyName, "version" }, newDependencyVersion);
43
+
44
+ if (await buildFile.SaveAsync())
45
+ {
46
+ logger.Log($" Saved [{buildFile.RepoRelativePath}].");
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ private static ImmutableArray<DotNetToolsJsonBuildFile> LoadBuildFiles(string repoRootPath)
53
+ {
54
+ var options = new EnumerationOptions()
55
+ {
56
+ RecurseSubdirectories = true,
57
+ MatchType = MatchType.Win32,
58
+ AttributesToSkip = 0,
59
+ IgnoreInaccessible = false,
60
+ MatchCasing = MatchCasing.CaseInsensitive,
61
+ };
62
+ return Directory.EnumerateFiles(repoRootPath, "dotnet-tools.json", options)
63
+ .Select(path => DotNetToolsJsonBuildFile.Open(repoRootPath, path))
64
+ .ToImmutableArray();
65
+ }
66
+ }
@@ -0,0 +1,48 @@
1
+ using System;
2
+ using System.IO;
3
+ using System.Linq;
4
+ using System.Threading.Tasks;
5
+
6
+ namespace NuGetUpdater.Core;
7
+
8
+ internal static partial class GlobalJsonUpdater
9
+ {
10
+ public static async Task UpdateDependencyAsync(string repoRootPath, string globalJsonPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, Logger logger)
11
+ {
12
+ if (!File.Exists(globalJsonPath))
13
+ {
14
+ logger.Log($" No global.json file found at [{globalJsonPath}].");
15
+ return;
16
+ }
17
+
18
+ var globalJsonFile = GlobalJsonBuildFile.Open(repoRootPath, globalJsonPath);
19
+
20
+ logger.Log($" Updating [{globalJsonFile.RepoRelativePath}] file.");
21
+
22
+ var containsDependency = globalJsonFile.GetDependencies().Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
23
+ if (!containsDependency)
24
+ {
25
+ logger.Log($" Dependency [{dependencyName}] not found.");
26
+ return;
27
+ }
28
+
29
+ if (globalJsonFile.MSBuildSdks?.TryGetPropertyValue(dependencyName, out var version) != true
30
+ || version?.GetValue<string>() is not string versionString)
31
+ {
32
+ logger.Log($" Unable to determine dependency version.");
33
+ return;
34
+ }
35
+
36
+ if (versionString != previousDependencyVersion)
37
+ {
38
+ return;
39
+ }
40
+
41
+ globalJsonFile.UpdateProperty(["msbuild-sdks", dependencyName], newDependencyVersion);
42
+
43
+ if (await globalJsonFile.SaveAsync())
44
+ {
45
+ logger.Log($" Saved [{globalJsonFile.RepoRelativePath}].");
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,172 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.IO;
4
+ using System.Linq;
5
+ using System.Text;
6
+ using System.Threading.Tasks;
7
+ using System.Xml.Linq;
8
+
9
+ using Microsoft.Language.Xml;
10
+
11
+ namespace NuGetUpdater.Core;
12
+
13
+ internal static class PackagesConfigUpdater
14
+ {
15
+ public static async Task UpdateDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive, Logger logger)
16
+ {
17
+ logger.Log($" Found {NuGetHelper.PackagesConfigFileName}; running with NuGet.exe");
18
+
19
+ // use NuGet.exe to perform update
20
+
21
+ // ensure local packages directory exists
22
+ var projectBuildFile = ProjectBuildFile.Open(repoRootPath, projectPath);
23
+ var packagesSubDirectory = GetPathToPackagesDirectory(projectBuildFile, dependencyName, previousDependencyVersion);
24
+ if (packagesSubDirectory is null)
25
+ {
26
+ logger.Log($" Project [{projectPath}] does not reference this dependency.");
27
+ return;
28
+ }
29
+
30
+ logger.Log($" Using packages directory [{packagesSubDirectory}] for project [{projectPath}].");
31
+
32
+ var projectDirectory = Path.GetDirectoryName(projectPath);
33
+ var packagesConfigPath = PathHelper.JoinPath(projectDirectory, NuGetHelper.PackagesConfigFileName);
34
+
35
+ var packagesDirectory = PathHelper.JoinPath(projectDirectory, packagesSubDirectory);
36
+ Directory.CreateDirectory(packagesDirectory);
37
+
38
+ var args = new List<string>()
39
+ {
40
+ "update",
41
+ packagesConfigPath,
42
+ "-Id",
43
+ dependencyName,
44
+ "-Version",
45
+ newDependencyVersion,
46
+ "-RepositoryPath",
47
+ packagesDirectory,
48
+ "-NonInteractive",
49
+ };
50
+
51
+ logger.Log(" Finding MSBuild...");
52
+ var msbuildDirectory = MSBuildHelper.MSBuildPath;
53
+ if (msbuildDirectory is not null)
54
+ {
55
+ args.Add("-MSBuildPath");
56
+ args.Add(msbuildDirectory); // e.g., /usr/share/dotnet/sdk/7.0.203
57
+ }
58
+
59
+ RunNuget(args, packagesDirectory, logger);
60
+
61
+ projectBuildFile = ProjectBuildFile.Open(repoRootPath, projectPath);
62
+ projectBuildFile.NormalizeDirectorySeparatorsInProject();
63
+
64
+ // Update binding redirects
65
+ await BindingRedirectManager.UpdateBindingRedirectsAsync(projectBuildFile);
66
+
67
+ logger.Log(" Writing project file back to disk");
68
+ await projectBuildFile.SaveAsync();
69
+ }
70
+
71
+ private static void RunNuget(List<string> args, string packagesDirectory, Logger logger)
72
+ {
73
+ var outputBuilder = new StringBuilder();
74
+ var writer = new StringWriter(outputBuilder);
75
+
76
+ var originalOut = Console.Out;
77
+ var originalError = Console.Error;
78
+ Console.SetOut(writer);
79
+ Console.SetError(writer);
80
+
81
+ var currentDir = Environment.CurrentDirectory;
82
+ try
83
+ {
84
+ logger.Log($" Running NuGet.exe with args: {string.Join(" ", args)}");
85
+
86
+ Environment.CurrentDirectory = packagesDirectory;
87
+ var result = NuGet.CommandLine.Program.Main(args.ToArray());
88
+ var fullOutput = outputBuilder.ToString();
89
+ logger.Log($" Result: {result}");
90
+ logger.Log($" Output:\n{fullOutput}");
91
+ if (result != 0)
92
+ {
93
+ throw new Exception(fullOutput);
94
+ }
95
+ }
96
+ catch (Exception e)
97
+ {
98
+ logger.Log($"Error: {e}");
99
+ throw;
100
+ }
101
+ finally
102
+ {
103
+ Environment.CurrentDirectory = currentDir;
104
+ Console.SetOut(originalOut);
105
+ Console.SetError(originalError);
106
+ }
107
+ }
108
+
109
+ internal static string? GetPathToPackagesDirectory(ProjectBuildFile projectBuildFile, string dependencyName, string dependencyVersion)
110
+ {
111
+ // the packages directory can be found from the hint path of the matching dependency, e.g., when given "Newtonsoft.Json", "7.0.1", and a project like this:
112
+ // <Project>
113
+ // <ItemGroup>
114
+ // <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
115
+ // <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
116
+ // </Reference>
117
+ // <ItemGroup>
118
+ // </Project>
119
+ //
120
+ // the result should be "..\packages"
121
+ //
122
+ // first try to do an exact match with the provided version number, but optionally fall back to just matching the package name and _any_ version
123
+ var hintPathSubString = $"{dependencyName}.{dependencyVersion}";
124
+
125
+ string? partialPathMatch = null;
126
+ var hintPathNodes = projectBuildFile.Contents.Descendants()
127
+ .Where(e =>
128
+ e.Name.Equals("HintPath", StringComparison.OrdinalIgnoreCase) &&
129
+ e.Parent.Name.Equals("Reference", StringComparison.OrdinalIgnoreCase) &&
130
+ e.Parent.GetAttributeValue("Include", StringComparison.OrdinalIgnoreCase)?.StartsWith($"{dependencyName},", StringComparison.OrdinalIgnoreCase) == true);
131
+ foreach (var hintPathNode in hintPathNodes)
132
+ {
133
+ var hintPath = hintPathNode.GetContentValue();
134
+ var hintPathSubStringLocation = hintPath.IndexOf(hintPathSubString, StringComparison.OrdinalIgnoreCase);
135
+ if (hintPathSubStringLocation >= 0)
136
+ {
137
+ // exact match was found, use it
138
+ var subpath = GetUpToIndexWithoutTrailingDirectorySeparator(hintPath, hintPathSubStringLocation);
139
+ return subpath;
140
+ }
141
+
142
+ if (partialPathMatch is null)
143
+ {
144
+ var partialHintPathSubStringLocation = hintPath.IndexOf($"{dependencyName}.", StringComparison.OrdinalIgnoreCase);
145
+ if (partialHintPathSubStringLocation >= 0)
146
+ {
147
+ // look instead for, e.g., "Newtonsoft.Json.<digit>"
148
+ var candidateVersionLocation = partialHintPathSubStringLocation + dependencyName.Length + 1; // 1 is the dot
149
+ if (hintPath.Length > candidateVersionLocation && char.IsDigit(hintPath[candidateVersionLocation]))
150
+ {
151
+ // partial match was found, save it in case we don't find anything better
152
+ var subpath = GetUpToIndexWithoutTrailingDirectorySeparator(hintPath, partialHintPathSubStringLocation);
153
+ partialPathMatch = subpath;
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ return partialPathMatch;
160
+ }
161
+
162
+ private static string GetUpToIndexWithoutTrailingDirectorySeparator(string path, int index)
163
+ {
164
+ var subpath = path[..index];
165
+ if (subpath.EndsWith('/') || subpath.EndsWith('\\'))
166
+ {
167
+ subpath = subpath[..^1];
168
+ }
169
+
170
+ return subpath;
171
+ }
172
+ }