dependabot-nuget 0.280.0 → 0.282.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Common.props +1 -1
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +4 -6
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +4 -6
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/FrameworkCheckCommand.cs +4 -7
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +4 -6
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +3 -5
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +0 -3
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.FrameworkCheck.cs +0 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +1 -2
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +3 -6
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +9 -9
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +2 -2
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +1 -1
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/NuGetContext.cs +2 -2
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +5 -5
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscovery.cs +1 -1
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +3 -3
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DotNetToolsJsonDiscovery.cs +1 -1
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/GlobalJsonDiscovery.cs +1 -1
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +1 -1
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +2 -2
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +1 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/DotNetToolsJsonBuildFile.cs +2 -2
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/GlobalJsonBuildFile.cs +2 -2
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +2 -2
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs +1 -1
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +2 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +16 -6
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +1 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +1 -1
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +2 -2
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/{SdkPackageUpdater.cs → PackageReferenceUpdater.cs} +97 -37
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +26 -10
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +12 -4
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ConsoleLogger.cs +9 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DependencyConflictResolver.cs +14 -16
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ILogger.cs +6 -0
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +25 -23
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +1 -1
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +1 -1
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/CompatibilityCheckerTests.cs +7 -7
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/DependencySolverEnvironment.cs +12 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +1 -1
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/DotNetToolsJsonBuildFileTests.cs +1 -1
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/GlobalJsonBuildFileTests.cs +1 -1
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs +4 -4
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +1 -1
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestLogger.cs +11 -0
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/ExpectedUpdateOperationResult.cs +8 -0
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +3 -3
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +14 -6
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +1 -1
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +1 -1
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/{UpdateWorkerTests.Sdk.cs → UpdateWorkerTests.PackageReference.cs} +200 -23
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +221 -8
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +56 -18
  59. data/lib/dependabot/nuget/file_fetcher.rb +58 -21
  60. data/lib/dependabot/nuget/file_updater.rb +1 -0
  61. data/lib/dependabot/nuget/native_helpers.rb +10 -16
  62. metadata +19 -15
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/Logger.cs +0 -21
@@ -18,6 +18,16 @@ internal static class BindingRedirectManager
18
18
  private static readonly XName DependentAssemblyName = AssemblyBinding.GetQualifiedName("dependentAssembly");
19
19
  private static readonly XName BindingRedirectName = AssemblyBinding.GetQualifiedName("bindingRedirect");
20
20
 
21
+ /// <summary>
22
+ /// Updates assembly binding redirects for a project build file.
23
+ /// </summary>
24
+ /// <remarks>
25
+ /// Assembly binding redirects are only applicable to projects targeting .NET Framework.
26
+ /// .NET Framework targets can appear in SDK-style OR non-SDK-style project files, using either packages.config OR `<PackageReference>` MSBuild items.
27
+ /// See: https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/redirect-assembly-versions
28
+ /// https://learn.microsoft.com/en-us/nuget/resources/check-project-format
29
+ /// </remarks>
30
+ /// <param name="projectBuildFile">The project build file (*.xproj) to be updated</param>
21
31
  public static async ValueTask UpdateBindingRedirectsAsync(ProjectBuildFile projectBuildFile)
22
32
  {
23
33
  var configFile = await TryGetRuntimeConfigurationFile(projectBuildFile);
@@ -33,7 +43,7 @@ internal static class BindingRedirectManager
33
43
  var bindings = BindingRedirectResolver.GetBindingRedirects(projectBuildFile.Path, references.Select(static x => x.Include));
34
44
  if (!bindings.Any())
35
45
  {
36
- // no bindings to update
46
+ // no bindings found in the project file, nothing to update
37
47
  return;
38
48
  }
39
49
 
@@ -116,19 +126,19 @@ internal static class BindingRedirectManager
116
126
  return null;
117
127
  }
118
128
 
119
- var configFilePath = Path.GetFullPath(Path.Combine(directoryPath, GetContent(configFile)));
129
+ var configFilePath = Path.GetFullPath(Path.Combine(directoryPath, GetValue(configFile)));
120
130
  var configFileContents = await File.ReadAllTextAsync(configFilePath);
121
131
  return new ConfigurationFile(configFilePath, configFileContents, false);
122
132
 
123
- static string GetContent(IXmlElementSyntax element)
133
+ static string GetValue(IXmlElementSyntax element)
124
134
  {
125
- var content = element.GetContentValue();
135
+ var content = element.GetAttributeValue("Include");
126
136
  if (!string.IsNullOrEmpty(content))
127
137
  {
128
138
  return content;
129
139
  }
130
140
 
131
- content = element.GetAttributeValue("Include");
141
+ content = element.GetContentValue();
132
142
  if (!string.IsNullOrEmpty(content))
133
143
  {
134
144
  return content;
@@ -139,7 +149,7 @@ internal static class BindingRedirectManager
139
149
 
140
150
  static bool IsConfigFile(IXmlElementSyntax element)
141
151
  {
142
- var content = GetContent(element);
152
+ var content = GetValue(element);
143
153
  if (content is null)
144
154
  {
145
155
  return false;
@@ -8,7 +8,7 @@ internal static class DotNetToolsJsonUpdater
8
8
  string dependencyName,
9
9
  string previousDependencyVersion,
10
10
  string newDependencyVersion,
11
- Logger logger)
11
+ ILogger logger)
12
12
  {
13
13
  if (!MSBuildHelper.TryGetDotNetToolsJsonPath(repoRootPath, workspacePath, out var dotnetToolsJsonPath))
14
14
  {
@@ -8,7 +8,7 @@ internal static class GlobalJsonUpdater
8
8
  string dependencyName,
9
9
  string previousDependencyVersion,
10
10
  string newDependencyVersion,
11
- Logger logger)
11
+ ILogger logger)
12
12
  {
13
13
  if (!MSBuildHelper.TryGetGlobalJsonPath(repoRootPath, workspacePath, out var globalJsonPath))
14
14
  {
@@ -5,7 +5,7 @@ internal static class LockFileUpdater
5
5
  public static async Task UpdateLockFileAsync(
6
6
  string repoRootPath,
7
7
  string projectPath,
8
- Logger logger)
8
+ ILogger logger)
9
9
  {
10
10
  var projectDirectory = Path.GetDirectoryName(projectPath);
11
11
  var lockPath = Path.Combine(projectDirectory, "packages.lock.json");
@@ -23,6 +23,6 @@ internal static class LockFileUpdater
23
23
  {
24
24
  logger.Log($" Lock file update failed.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
25
25
  }
26
- }, retainMSBuildSdks: true);
26
+ }, logger, retainMSBuildSdks: true);
27
27
  }
28
28
  }
@@ -6,7 +6,18 @@ using NuGet.Versioning;
6
6
 
7
7
  namespace NuGetUpdater.Core;
8
8
 
9
- internal static class SdkPackageUpdater
9
+ /// <summary>
10
+ /// Handles package updates for projects containing `<PackageReference>` MSBuild items.
11
+ /// </summary>
12
+ /// <remarks>
13
+ /// PackageReference items can appear in both SDK-style AND non-SDK-style project files.
14
+ /// By default, PackageReference is used by [SDK-style] projects targeting .NET Core, .NET Standard, and UWP.
15
+ /// By default, packages.config is used by [non-SDK-style] projects targeting .NET Framework; However, they can be migrated to PackageReference too.
16
+ /// See: https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#project-type-support
17
+ /// https://learn.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference
18
+ /// https://learn.microsoft.com/en-us/nuget/resources/check-project-format
19
+ /// </remarks>
20
+ internal static class PackageReferenceUpdater
10
21
  {
11
22
  public static async Task UpdateDependencyAsync(
12
23
  string repoRootPath,
@@ -15,10 +26,10 @@ internal static class SdkPackageUpdater
15
26
  string previousDependencyVersion,
16
27
  string newDependencyVersion,
17
28
  bool isTransitive,
18
- Logger logger)
29
+ ILogger logger)
19
30
  {
20
- // SDK-style project, modify the XML directly
21
- logger.Log(" Running for SDK-style project");
31
+ // PackageReference project; modify the XML directly
32
+ logger.Log(" Running 'PackageReference' project direct XML update");
22
33
 
23
34
  (ImmutableArray<ProjectBuildFile> buildFiles, string[] tfms) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(repoRootPath, projectPath);
24
35
 
@@ -30,19 +41,26 @@ internal static class SdkPackageUpdater
30
41
  return;
31
42
  }
32
43
 
33
- if (isTransitive)
44
+ var peerDependencies = await GetUpdatedPeerDependenciesAsync(repoRootPath, projectPath, tfms, dependencyName, newDependencyVersion, logger);
45
+ if (MSBuildHelper.UseNewDependencySolver())
34
46
  {
35
- await UpdateTransitiveDependencyAsync(repoRootPath, projectPath, dependencyName, newDependencyVersion, buildFiles, logger);
47
+ await UpdateDependencyWithConflictResolution(repoRootPath, buildFiles, tfms, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive, peerDependencies, logger);
36
48
  }
37
49
  else
38
50
  {
39
- var peerDependencies = await GetUpdatedPeerDependenciesAsync(repoRootPath, projectPath, tfms, dependencyName, newDependencyVersion, logger);
40
- if (peerDependencies is null)
51
+ if (isTransitive)
41
52
  {
42
- return;
53
+ await UpdateTransitiveDependencyAsync(repoRootPath, projectPath, dependencyName, newDependencyVersion, buildFiles, logger);
43
54
  }
55
+ else
56
+ {
57
+ if (peerDependencies is null)
58
+ {
59
+ return;
60
+ }
44
61
 
45
- await UpdateTopLevelDepdendency(repoRootPath, buildFiles, tfms, dependencyName, previousDependencyVersion, newDependencyVersion, peerDependencies, logger);
62
+ await UpdateTopLevelDepdendency(repoRootPath, buildFiles, tfms, dependencyName, previousDependencyVersion, newDependencyVersion, peerDependencies, logger);
63
+ }
46
64
  }
47
65
 
48
66
  if (!await AreDependenciesCoherentAsync(repoRootPath, projectPath, dependencyName, logger, buildFiles, tfms))
@@ -53,6 +71,61 @@ internal static class SdkPackageUpdater
53
71
  await SaveBuildFilesAsync(buildFiles, logger);
54
72
  }
55
73
 
74
+ public static async Task UpdateDependencyWithConflictResolution(
75
+ string repoRootPath,
76
+ ImmutableArray<ProjectBuildFile> buildFiles,
77
+ string[] targetFrameworks,
78
+ string projectPath,
79
+ string dependencyName,
80
+ string previousDependencyVersion,
81
+ string newDependencyVersion,
82
+ bool isTransitive,
83
+ IDictionary<string, string> peerDependencies,
84
+ ILogger logger)
85
+ {
86
+ var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
87
+ var isDependencyTopLevel = topLevelDependencies.Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
88
+ var dependenciesToUpdate = new[] { new Dependency(dependencyName, newDependencyVersion, DependencyType.PackageReference) };
89
+
90
+ // update the initial dependency...
91
+ TryUpdateDependencyVersion(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, logger);
92
+
93
+ // ...and the peer dependencies...
94
+ foreach (var (packageName, packageVersion) in peerDependencies.Where(kvp => string.Compare(kvp.Key, dependencyName, StringComparison.OrdinalIgnoreCase) != 0))
95
+ {
96
+ TryUpdateDependencyVersion(buildFiles, packageName, previousDependencyVersion: null, newDependencyVersion: packageVersion, logger);
97
+ }
98
+
99
+ // ...and everything else
100
+ foreach (var projectFile in buildFiles)
101
+ {
102
+ foreach (var tfm in targetFrameworks)
103
+ {
104
+ var resolvedDependencies = await MSBuildHelper.ResolveDependencyConflicts(repoRootPath, projectFile.Path, tfm, topLevelDependencies, dependenciesToUpdate, logger);
105
+ if (resolvedDependencies is null)
106
+ {
107
+ logger.Log($" Unable to resolve dependency conflicts for {projectFile.Path}.");
108
+ continue;
109
+ }
110
+
111
+ var isDependencyInResolutionSet = resolvedDependencies.Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
112
+ if (isTransitive && !isDependencyTopLevel && isDependencyInResolutionSet)
113
+ {
114
+ // a transitive dependency had to be pinned; add it here
115
+ await UpdateTransitiveDependencyAsync(repoRootPath, projectPath, dependencyName, newDependencyVersion, buildFiles, logger);
116
+ }
117
+
118
+ // update all resolved dependencies that aren't the initial dependency
119
+ foreach (var resolvedDependency in resolvedDependencies
120
+ .Where(d => !d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
121
+ .Where(d => d.Version is not null))
122
+ {
123
+ TryUpdateDependencyVersion(buildFiles, resolvedDependency.Name, previousDependencyVersion: null, newDependencyVersion: resolvedDependency.Version!, logger);
124
+ }
125
+ }
126
+ }
127
+ }
128
+
56
129
  /// <summary>
57
130
  /// Verifies that the package does not already satisfy the requested dependency version.
58
131
  /// </summary>
@@ -64,7 +137,7 @@ internal static class SdkPackageUpdater
64
137
  Dependency[] topLevelDependencies,
65
138
  string dependencyName,
66
139
  string newDependencyVersion,
67
- Logger logger)
140
+ ILogger logger)
68
141
  {
69
142
  var newDependencyNuGetVersion = NuGetVersion.Parse(newDependencyVersion);
70
143
 
@@ -124,7 +197,7 @@ internal static class SdkPackageUpdater
124
197
  return true;
125
198
  }
126
199
 
127
- private static async Task UpdateTransitiveDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string newDependencyVersion, ImmutableArray<ProjectBuildFile> buildFiles, Logger logger)
200
+ private static async Task UpdateTransitiveDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string newDependencyVersion, ImmutableArray<ProjectBuildFile> buildFiles, ILogger logger)
128
201
  {
129
202
  var directoryPackagesWithPinning = buildFiles.OfType<ProjectBuildFile>()
130
203
  .FirstOrDefault(bf => IsCpmTransitivePinningEnabled(bf));
@@ -160,7 +233,7 @@ internal static class SdkPackageUpdater
160
233
  return isTransitivePinningEnabled is not null && string.Equals(isTransitivePinningEnabled, "true", StringComparison.OrdinalIgnoreCase);
161
234
  }
162
235
 
163
- private static void PinTransitiveDependency(ProjectBuildFile directoryPackages, string dependencyName, string newDependencyVersion, Logger logger)
236
+ private static void PinTransitiveDependency(ProjectBuildFile directoryPackages, string dependencyName, string newDependencyVersion, ILogger logger)
164
237
  {
165
238
  var existingPackageVersionElement = directoryPackages.ItemNodes
166
239
  .Where(e => e.Name.Equals("PackageVersion", StringComparison.OrdinalIgnoreCase) &&
@@ -223,7 +296,7 @@ internal static class SdkPackageUpdater
223
296
  directoryPackages.Update(updatedXml);
224
297
  }
225
298
 
226
- private static async Task AddTransitiveDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string newDependencyVersion, Logger logger)
299
+ private static async Task AddTransitiveDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string newDependencyVersion, ILogger logger)
227
300
  {
228
301
  var projectDirectory = Path.GetDirectoryName(projectPath)!;
229
302
  await MSBuildHelper.SidelineGlobalJsonAsync(projectDirectory, repoRootPath, async () =>
@@ -237,7 +310,7 @@ internal static class SdkPackageUpdater
237
310
  {
238
311
  logger.Log($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
239
312
  }
240
- }, retainMSBuildSdks: true);
313
+ }, logger, retainMSBuildSdks: true);
241
314
  }
242
315
 
243
316
  /// <summary>
@@ -250,7 +323,7 @@ internal static class SdkPackageUpdater
250
323
  string[] tfms,
251
324
  string dependencyName,
252
325
  string newDependencyVersion,
253
- Logger logger)
326
+ ILogger logger)
254
327
  {
255
328
  var newDependency = new[] { new Dependency(dependencyName, newDependencyVersion, DependencyType.Unknown) };
256
329
  var tfmsAndDependencies = new Dictionary<string, Dependency[]>();
@@ -305,9 +378,9 @@ internal static class SdkPackageUpdater
305
378
  string previousDependencyVersion,
306
379
  string newDependencyVersion,
307
380
  IDictionary<string, string> peerDependencies,
308
- Logger logger)
381
+ ILogger logger)
309
382
  {
310
-
383
+ // update dependencies...
311
384
  var result = TryUpdateDependencyVersion(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, logger);
312
385
  if (result == UpdateResult.NotFound)
313
386
  {
@@ -320,26 +393,13 @@ internal static class SdkPackageUpdater
320
393
  TryUpdateDependencyVersion(buildFiles, packageName, previousDependencyVersion: null, newDependencyVersion: packageVersion, logger);
321
394
  }
322
395
 
323
- // now make all dependency requirements coherent
396
+ // ...and make them all coherent
324
397
  Dependency[] updatedTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
325
398
  foreach (ProjectBuildFile projectFile in buildFiles)
326
399
  {
327
400
  foreach (string tfm in targetFrameworks)
328
401
  {
329
- if (MSBuildHelper.UseNewDependencySolver())
330
- {
331
- // Find the index of the dependency we are updating and revert it to the previous version
332
- int dependencyIndex = Array.FindIndex(updatedTopLevelDependencies, d => string.Equals(d.Name, dependencyName, StringComparison.OrdinalIgnoreCase));
333
- if (dependencyIndex != -1)
334
- {
335
- var originalDependency = updatedTopLevelDependencies[dependencyIndex];
336
- updatedTopLevelDependencies[dependencyIndex] = originalDependency with { Version = previousDependencyVersion };
337
- }
338
-
339
- }
340
- Dependency[] update = [new Dependency(dependencyName, newDependencyVersion, DependencyType.PackageReference)];
341
- Dependency[]? resolvedDependencies = await MSBuildHelper.ResolveDependencyConflicts(repoRootPath, projectFile.Path, tfm, updatedTopLevelDependencies, update, logger);
342
-
402
+ var resolvedDependencies = await MSBuildHelper.ResolveDependencyConflictsWithBruteForce(repoRootPath, projectFile.Path, tfm, updatedTopLevelDependencies, logger);
343
403
  if (resolvedDependencies is null)
344
404
  {
345
405
  logger.Log($" Unable to resolve dependency conflicts for {projectFile.Path}.");
@@ -360,7 +420,7 @@ internal static class SdkPackageUpdater
360
420
  continue;
361
421
  }
362
422
 
363
- // update all dependencies
423
+ // update all versions
364
424
  foreach (Dependency resolvedDependency in resolvedDependencies
365
425
  .Where(d => !d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
366
426
  .Where(d => d.Version is not null))
@@ -376,7 +436,7 @@ internal static class SdkPackageUpdater
376
436
  string dependencyName,
377
437
  string? previousDependencyVersion,
378
438
  string newDependencyVersion,
379
- Logger logger)
439
+ ILogger logger)
380
440
  {
381
441
  var foundCorrect = false;
382
442
  var foundUnsupported = false;
@@ -619,7 +679,7 @@ internal static class SdkPackageUpdater
619
679
  StringComparison.OrdinalIgnoreCase) &&
620
680
  (e.GetAttributeOrSubElementValue("Version", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("VersionOverride", StringComparison.OrdinalIgnoreCase)) is not null);
621
681
 
622
- private static async Task<bool> AreDependenciesCoherentAsync(string repoRootPath, string projectPath, string dependencyName, Logger logger, ImmutableArray<ProjectBuildFile> buildFiles, string[] tfms)
682
+ private static async Task<bool> AreDependenciesCoherentAsync(string repoRootPath, string projectPath, string dependencyName, ILogger logger, ImmutableArray<ProjectBuildFile> buildFiles, string[] tfms)
623
683
  {
624
684
  var updatedTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
625
685
  foreach (var tfm in tfms)
@@ -636,7 +696,7 @@ internal static class SdkPackageUpdater
636
696
  return true;
637
697
  }
638
698
 
639
- private static async Task SaveBuildFilesAsync(ImmutableArray<ProjectBuildFile> buildFiles, Logger logger)
699
+ private static async Task SaveBuildFilesAsync(ImmutableArray<ProjectBuildFile> buildFiles, ILogger logger)
640
700
  {
641
701
  foreach (var buildFile in buildFiles)
642
702
  {
@@ -13,6 +13,14 @@ using Console = System.Console;
13
13
 
14
14
  namespace NuGetUpdater.Core;
15
15
 
16
+ /// <summary>
17
+ /// Handles package updates for projects that use packages.config.
18
+ /// </summary>
19
+ /// <remarks>
20
+ /// packages.config can appear in non-SDK-style projects, but not in SDK-style projects.
21
+ /// See: https://learn.microsoft.com/en-us/nuget/reference/packages-config
22
+ /// https://learn.microsoft.com/en-us/nuget/resources/check-project-format
23
+ /// <remarks>
16
24
  internal static class PackagesConfigUpdater
17
25
  {
18
26
  public static async Task UpdateDependencyAsync(
@@ -22,12 +30,11 @@ internal static class PackagesConfigUpdater
22
30
  string previousDependencyVersion,
23
31
  string newDependencyVersion,
24
32
  string packagesConfigPath,
25
- Logger logger
33
+ ILogger logger
26
34
  )
27
35
  {
28
- logger.Log($" Found {NuGetHelper.PackagesConfigFileName}; running with NuGet.exe");
29
-
30
- // use NuGet.exe to perform update
36
+ // packages.config project; use NuGet.exe to perform update
37
+ logger.Log($" Found '{NuGetHelper.PackagesConfigFileName}' project; running NuGet.exe update");
31
38
 
32
39
  // ensure local packages directory exists
33
40
  var projectBuildFile = ProjectBuildFile.Open(repoRootPath, projectPath);
@@ -92,7 +99,7 @@ internal static class PackagesConfigUpdater
92
99
  await projectBuildFile.SaveAsync();
93
100
  }
94
101
 
95
- private static void RunNugetUpdate(List<string> updateArgs, List<string> restoreArgs, string projectDirectory, Logger logger)
102
+ private static void RunNugetUpdate(List<string> updateArgs, List<string> restoreArgs, string projectDirectory, ILogger logger)
96
103
  {
97
104
  var outputBuilder = new StringBuilder();
98
105
  var writer = new StringWriter(outputBuilder);
@@ -126,10 +133,10 @@ internal static class PackagesConfigUpdater
126
133
  // and doesn't appear in the cache. The message in this case will be "Could not install package
127
134
  // '<name> <version>'...the package does not contain any assembly references or content files that
128
135
  // are compatible with that framework.".
136
+ // 3. Yet another possibility is that the project explicitly imports a targets file without a condition
137
+ // of `Exists(...)`.
129
138
  // The solution in all cases is to run `restore` then try the update again.
130
- if (!retryingAfterRestore &&
131
- (fullOutput.Contains("Existing packages must be restored before performing an install or update.") ||
132
- fullOutput.Contains("the package does not contain any assembly references or content files that are compatible with that framework.")))
139
+ if (!retryingAfterRestore && OutputIndicatesRestoreIsRequired(fullOutput))
133
140
  {
134
141
  retryingAfterRestore = true;
135
142
  logger.Log($" Running NuGet.exe with args: {string.Join(" ", restoreArgs)}");
@@ -139,6 +146,8 @@ internal static class PackagesConfigUpdater
139
146
 
140
147
  if (exitCodeAgain != 0)
141
148
  {
149
+ MSBuildHelper.ThrowOnMissingFile(fullOutput);
150
+ MSBuildHelper.ThrowOnMissingFile(restoreOutput);
142
151
  MSBuildHelper.ThrowOnMissingPackages(restoreOutput);
143
152
  throw new Exception($"Unable to restore.\nOutput:\n${restoreOutput}\n");
144
153
  }
@@ -174,6 +183,13 @@ internal static class PackagesConfigUpdater
174
183
  }
175
184
  }
176
185
 
186
+ private static bool OutputIndicatesRestoreIsRequired(string output)
187
+ {
188
+ return output.Contains("Existing packages must be restored before performing an install or update.")
189
+ || output.Contains("the package does not contain any assembly references or content files that are compatible with that framework.")
190
+ || MSBuildHelper.GetMissingFile(output) is not null;
191
+ }
192
+
177
193
  private static Process[] GetLikelyNuGetSpawnedProcesses()
178
194
  {
179
195
  var processes = Process.GetProcesses().Where(p => p.ProcessName.StartsWith("CredentialProvider", StringComparison.OrdinalIgnoreCase) == true).ToArray();
@@ -206,7 +222,7 @@ internal static class PackagesConfigUpdater
206
222
  {
207
223
  // exact match was found, use it
208
224
  var subpath = GetUpToIndexWithoutTrailingDirectorySeparator(hintPath, hintPathSubStringLocation);
209
- return subpath;
225
+ return subpath.NormalizePathToUnix();
210
226
  }
211
227
 
212
228
  if (partialPathMatch is null)
@@ -244,7 +260,7 @@ internal static class PackagesConfigUpdater
244
260
  }
245
261
  }
246
262
 
247
- return partialPathMatch;
263
+ return partialPathMatch?.NormalizePathToUnix();
248
264
  }
249
265
 
250
266
  private static bool IsHintPathNodeForDependency(this IXmlElementSyntax element, string dependencyName)
@@ -9,7 +9,7 @@ namespace NuGetUpdater.Core;
9
9
 
10
10
  public class UpdaterWorker
11
11
  {
12
- private readonly Logger _logger;
12
+ private readonly ILogger _logger;
13
13
  private readonly HashSet<string> _processedProjectPaths = new(StringComparer.OrdinalIgnoreCase);
14
14
 
15
15
  internal static readonly JsonSerializerOptions SerializerOptions = new()
@@ -18,7 +18,7 @@ public class UpdaterWorker
18
18
  Converters = { new JsonStringEnumConverter() },
19
19
  };
20
20
 
21
- public UpdaterWorker(Logger logger)
21
+ public UpdaterWorker(ILogger logger)
22
22
  {
23
23
  _logger = logger;
24
24
  }
@@ -70,6 +70,14 @@ public class UpdaterWorker
70
70
  ErrorDetails = ex.Dependencies,
71
71
  };
72
72
  }
73
+ catch (Exception ex)
74
+ {
75
+ result = new()
76
+ {
77
+ ErrorType = ErrorType.Unknown,
78
+ ErrorDetails = ex.ToString(),
79
+ };
80
+ }
73
81
 
74
82
  return result;
75
83
  }
@@ -114,7 +122,7 @@ public class UpdaterWorker
114
122
  return new UpdateOperationResult();
115
123
  }
116
124
 
117
- internal static async Task WriteResultFile(UpdateOperationResult result, string resultOutputPath, Logger logger)
125
+ internal static async Task WriteResultFile(UpdateOperationResult result, string resultOutputPath, ILogger logger)
118
126
  {
119
127
  logger.Log($" Writing update result to [{resultOutputPath}].");
120
128
 
@@ -213,7 +221,7 @@ public class UpdaterWorker
213
221
  }
214
222
 
215
223
  // Some repos use a mix of packages.config and PackageReference
216
- await SdkPackageUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive, _logger);
224
+ await PackageReferenceUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive, _logger);
217
225
 
218
226
  // Update lock file if exists
219
227
  if (File.Exists(Path.Combine(Path.GetDirectoryName(projectPath), "packages.lock.json")))
@@ -0,0 +1,9 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ public sealed class ConsoleLogger : ILogger
4
+ {
5
+ public void Log(string message)
6
+ {
7
+ Console.WriteLine(message);
8
+ }
9
+ }
@@ -55,7 +55,7 @@ public class PackageManager
55
55
  string CurrentDirectory = currentDirectory ?? Environment.CurrentDirectory;
56
56
  SourceCacheContext SourceCacheContext = new SourceCacheContext();
57
57
  PackageDownloadContext PackageDownloadContext = new PackageDownloadContext(SourceCacheContext);
58
- ILogger Logger = NullLogger.Instance;
58
+ var Logger = NullLogger.Instance;
59
59
 
60
60
  IMachineWideSettings MachineWideSettings = new NuGet.CommandLine.CommandLineMachineWideSettings();
61
61
  ISettings Settings = NuGet.Configuration.Settings.LoadDefaultSettings(
@@ -328,7 +328,7 @@ public class PackageManager
328
328
  }
329
329
 
330
330
  // Method to update the version of a desired package based off framework
331
- public async Task<string> UpdateVersion(List<PackageToUpdate> existingPackages, PackageToUpdate package, string targetFramework, string projectDirectory)
331
+ public async Task<string> UpdateVersion(List<PackageToUpdate> existingPackages, PackageToUpdate package, string targetFramework, string projectDirectory, ILogger logger)
332
332
  {
333
333
  // Bool to track if the package was in the original existing list
334
334
  bool inExisting = true;
@@ -392,12 +392,12 @@ public class PackageManager
392
392
  existingPackage.CurrentVersion = dependency.CurrentVersion;
393
393
 
394
394
  // If the family is compatible with the dependency's version, update with the dependency version
395
- if (await AreAllParentsCompatibleAsync(existingPackages, existingPackage, targetFramework, projectDirectory) == true)
395
+ if (await AreAllParentsCompatibleAsync(existingPackages, existingPackage, targetFramework, projectDirectory, logger) == true)
396
396
  {
397
397
  existingPackage.CurrentVersion = dependencyOldVersion;
398
398
  string NewVersion = dependency.CurrentVersion;
399
399
  existingPackage.NewVersion = dependency.CurrentVersion;
400
- await UpdateVersion(existingPackages, existingPackage, targetFramework, projectDirectory);
400
+ await UpdateVersion(existingPackages, existingPackage, targetFramework, projectDirectory, logger);
401
401
  }
402
402
  // If not, resort to putting version back to normal and remove new version
403
403
  else
@@ -417,7 +417,7 @@ public class PackageManager
417
417
  dependency.IsSpecific = true;
418
418
  }
419
419
 
420
- await UpdateVersion(existingPackages, dependency, targetFramework, projectDirectory);
420
+ await UpdateVersion(existingPackages, dependency, targetFramework, projectDirectory, logger);
421
421
  }
422
422
  }
423
423
 
@@ -432,7 +432,7 @@ public class PackageManager
432
432
  if (!isCompatible)
433
433
  {
434
434
  // Attempt to find and update to a compatible version between the two
435
- NuGetVersion compatibleVersion = await FindCompatibleVersionAsync(existingPackages, parent, package, targetFramework);
435
+ NuGetVersion compatibleVersion = await FindCompatibleVersionAsync(existingPackages, parent, package, targetFramework, logger);
436
436
  if (compatibleVersion == null)
437
437
  {
438
438
  return "Failed to update";
@@ -440,7 +440,7 @@ public class PackageManager
440
440
 
441
441
  // If a version is found, update to that version
442
442
  parent.NewVersion = compatibleVersion.ToString();
443
- await UpdateVersion(existingPackages, parent, targetFramework, projectDirectory);
443
+ await UpdateVersion(existingPackages, parent, targetFramework, projectDirectory, logger);
444
444
  }
445
445
 
446
446
  // If it's compatible and the package you updated wasn't in the existing package, check if the parent's dependencies version is the same as the current version
@@ -455,7 +455,6 @@ public class PackageManager
455
455
  {
456
456
  // Create a NugetContext instance to get the latest versions of the parent
457
457
  NuGetContext nugetContext = new NuGetContext(Path.GetDirectoryName(projectPath));
458
- Logger logger = null;
459
458
 
460
459
  string currentVersionString = parent.CurrentVersion;
461
460
  NuGetVersion currentVersionParent = NuGetVersion.Parse(currentVersionString);
@@ -487,7 +486,7 @@ public class PackageManager
487
486
  {
488
487
  parent.NewVersion = parentVersion;
489
488
  parent.CurrentVersion = null;
490
- await UpdateVersion(existingPackages, parent, targetFramework, projectDirectory);
489
+ await UpdateVersion(existingPackages, parent, targetFramework, projectDirectory, logger);
491
490
  package.IsSpecific = true;
492
491
  return "Success";
493
492
  }
@@ -557,7 +556,7 @@ public class PackageManager
557
556
  }
558
557
 
559
558
  // Method to find a compatible version with the child for the parent to update to
560
- public async Task<NuGetVersion> FindCompatibleVersionAsync(List<PackageToUpdate> existingPackages, PackageToUpdate possibleParent, PackageToUpdate possibleDependency, string targetFramework)
559
+ public async Task<NuGetVersion> FindCompatibleVersionAsync(List<PackageToUpdate> existingPackages, PackageToUpdate possibleParent, PackageToUpdate possibleDependency, string targetFramework, ILogger logger)
561
560
  {
562
561
  string packageId = possibleParent.PackageName;
563
562
  string currentVersionString = possibleParent.CurrentVersion;
@@ -567,7 +566,6 @@ public class PackageManager
567
566
 
568
567
  // Create a NugetContext instance to get the latest versions of the parent
569
568
  NuGetContext nugetContext = new NuGetContext(Path.GetDirectoryName(projectPath));
570
- Logger logger = null;
571
569
 
572
570
  var result = await VersionFinder.GetVersionsAsync(possibleParent.PackageName, CurrentVersion, nugetContext, logger, CancellationToken.None);
573
571
  var versions = result.GetVersions();
@@ -618,7 +616,7 @@ public class PackageManager
618
616
  if (await IsCompatibleAsync(possibleParent, possibleDependency, targetFramework, nugetContext.CurrentDirectory))
619
617
  {
620
618
  // Check if parents are compatible, recursively
621
- if (await AreAllParentsCompatibleAsync(existingPackages, possibleParent, targetFramework, nugetContext.CurrentDirectory))
619
+ if (await AreAllParentsCompatibleAsync(existingPackages, possibleParent, targetFramework, nugetContext.CurrentDirectory, logger))
622
620
  {
623
621
  // If compatible, return the new version
624
622
  if (Regex.IsMatch(possibleParent.NewVersion, @"[a-zA-Z]"))
@@ -635,7 +633,7 @@ public class PackageManager
635
633
  }
636
634
 
637
635
  // Method to determine if all the parents of a given package are compatible with the parent's desired version
638
- public async Task<bool> AreAllParentsCompatibleAsync(List<PackageToUpdate> existingPackages, PackageToUpdate possibleParent, string targetFramework, string projectDirectory)
636
+ public async Task<bool> AreAllParentsCompatibleAsync(List<PackageToUpdate> existingPackages, PackageToUpdate possibleParent, string targetFramework, string projectDirectory, ILogger logger)
639
637
  {
640
638
  // Get the possibleParent parentPackages
641
639
  HashSet<PackageToUpdate> parentPackages = GetParentPackages(possibleParent);
@@ -649,18 +647,18 @@ public class PackageManager
649
647
  if (!isCompatible)
650
648
  {
651
649
  // Find a compatible version if possible
652
- NuGetVersion compatibleVersion = await FindCompatibleVersionAsync(existingPackages, parent, possibleParent, targetFramework);
650
+ NuGetVersion compatibleVersion = await FindCompatibleVersionAsync(existingPackages, parent, possibleParent, targetFramework, logger);
653
651
  if (compatibleVersion == null)
654
652
  {
655
653
  return false;
656
654
  }
657
655
 
658
656
  parent.NewVersion = compatibleVersion.ToString();
659
- await UpdateVersion(existingPackages, parent, targetFramework, projectDirectory);
657
+ await UpdateVersion(existingPackages, parent, targetFramework, projectDirectory, logger);
660
658
  }
661
659
 
662
660
  // Recursively check if all ancestors are compatible
663
- if (!await AreAllParentsCompatibleAsync(existingPackages, parent, targetFramework, projectDirectory))
661
+ if (!await AreAllParentsCompatibleAsync(existingPackages, parent, targetFramework, projectDirectory, logger))
664
662
  {
665
663
  return false;
666
664
  }
@@ -0,0 +1,6 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ public interface ILogger
4
+ {
5
+ void Log(string message);
6
+ }