dependabot-nuget 0.321.1 → 0.321.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageMapper.cs +9 -0
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscoveryTargetingPacks.props +2 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencySolver/IDependencySolver.cs +8 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencySolver/MSBuildDependencySolver.cs +32 -0
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +1 -0
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +10 -1
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +6 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +6 -3
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/FileWriterWorker.cs +326 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/IFileWriter.cs +14 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/XmlFileWriter.cs +465 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +9 -5
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +26 -1
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/DependencySolver/MSBuildDependencySolverTests.cs +633 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +49 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs +484 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/DotNetToolsJsonUpdaterTests.cs +181 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterTestsBase.cs +61 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests.cs +917 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests_MiscellaneousTests.cs +109 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/TestFileWriterReturnsConstantResult.cs +20 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests.cs +1594 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests_CreateUpdatedVersionRangeTests.cs +25 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/GlobalJsonUpdaterTests.cs +139 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +1961 -1
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateOperationResultTests.cs +116 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +0 -1043
  30. metadata +19 -10
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +0 -375
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +0 -296
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.LockFile.cs +0 -251
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +0 -201
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +0 -3821
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +0 -2706
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3a9595c500e6254f99f1cef75b2f819b7ea3736d2dc4083c7007371203c6fd5
4
- data.tar.gz: be1476e3b56bfa67e803fb9d70eb4ec82113af256995d0e44d7fd5dd0335331b
3
+ metadata.gz: 832ab457e727f6583a33319b04bc49b5142d33aae4f95873137c580f838b5abf
4
+ data.tar.gz: 0dca08fa4a87530852652d0264c13e4bd79024561cb3e3fe397551c12fa1b921
5
5
  SHA512:
6
- metadata.gz: ee2d1cc30d23f9f141f9e3da92f23d4e241ffac6870a0c54b4cc065ca1bf9226ed553ebf7a8f371bc6b36e616c5abfc49d44c182be6538f1611594572c30ae5a
7
- data.tar.gz: 5233059c5c248a2f54b39ca1f8819f96ddc7b4d81701135a98403f7965000aeffbd8407680d582bf7449bb543df38568f21cb45d1a895f2d220177cdea1b5b0b
6
+ metadata.gz: 756d833b0d1616230f5ce980d1410ae8ea9883ab441837847a18eda7c41180032d20ae84cbc986231d293a2bf1e846b81f93fb8b40fd8419edcfd51e5bfd276e
7
+ data.tar.gz: 1acab8370fe0a8b0bd8719613e783c1c2e1d5cc7f7a22355ce1e80d10eb242c0545df65d6cbaba3964cd551345d02c0e43a7c94b7da76d6a10ccdd1c354ab3c4
@@ -46,6 +46,15 @@ public class PackageMapper
46
46
  return null;
47
47
  }
48
48
 
49
+ public bool IsSdkReplacementPackage(string packageName)
50
+ {
51
+ var isSdkReplacementPackage = RuntimePackages.Runtimes.Any(r =>
52
+ {
53
+ return r.Value.Packages.Any(p => packageName.Equals(p.Key, StringComparison.Ordinal));
54
+ });
55
+ return isSdkReplacementPackage;
56
+ }
57
+
49
58
  private SemVersion? GetRuntimeVersionFromPackage(string packageName, SemVersion packageVersion)
50
59
  {
51
60
  // TODO: linear search is slow
@@ -6,5 +6,7 @@
6
6
  because that's irrelevant to dependency discovery.
7
7
  -->
8
8
  <NoWarn>$(NoWarn);MSB3644</NoWarn>
9
+ <!-- A package downgrade warning shouldn't be lifted to an error because that would prevent discovery. -->
10
+ <NoWarn>$(NoWarn);NU1605</NoWarn>
9
11
  </PropertyGroup>
10
12
  </Project>
@@ -0,0 +1,8 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.DependencySolver;
4
+
5
+ public interface IDependencySolver
6
+ {
7
+ Task<ImmutableArray<Dependency>?> SolveAsync(ImmutableArray<Dependency> existingTopLevelDependencies, ImmutableArray<Dependency> desiredDependencies, string targetFramework);
8
+ }
@@ -0,0 +1,32 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.DependencySolver;
4
+
5
+ public class MSBuildDependencySolver : IDependencySolver
6
+ {
7
+ private readonly DirectoryInfo _repoContentsPath;
8
+ private readonly FileInfo _projectPath;
9
+ private readonly ExperimentsManager _experimentsManager;
10
+ private readonly ILogger _logger;
11
+
12
+ public MSBuildDependencySolver(DirectoryInfo repoContentsPath, FileInfo projectPath, ExperimentsManager experimentsManager, ILogger logger)
13
+ {
14
+ _repoContentsPath = repoContentsPath;
15
+ _projectPath = projectPath;
16
+ _experimentsManager = experimentsManager;
17
+ _logger = logger;
18
+ }
19
+
20
+ public async Task<ImmutableArray<Dependency>?> SolveAsync(ImmutableArray<Dependency> existingTopLevelDependencies, ImmutableArray<Dependency> desiredDependencies, string targetFramework)
21
+ {
22
+ var result = await MSBuildHelper.ResolveDependencyConflicts(
23
+ _repoContentsPath.FullName,
24
+ _projectPath.FullName,
25
+ targetFramework,
26
+ existingTopLevelDependencies,
27
+ desiredDependencies,
28
+ _experimentsManager,
29
+ _logger);
30
+ return result;
31
+ }
32
+ }
@@ -15,4 +15,5 @@ public record ProjectDiscoveryResult : IDiscoveryResultWithDependencies
15
15
  public ImmutableArray<string> ReferencedProjectPaths { get; init; } = [];
16
16
  public required ImmutableArray<string> ImportedFiles { get; init; }
17
17
  public required ImmutableArray<string> AdditionalFiles { get; init; }
18
+ public bool CentralPackageTransitivePinningEnabled { get; init; } = false;
18
19
  }
@@ -526,7 +526,8 @@ internal static class SdkProjectDiscovery
526
526
  .ToImmutableArray();
527
527
 
528
528
  // others
529
- var properties = resolvedProperties[projectPath]
529
+ var projectProperties = resolvedProperties[projectPath];
530
+ var properties = projectProperties
530
531
  .Where(pkvp => projectPropertyNames.Contains(pkvp.Key))
531
532
  .Select(pkvp => new Property(pkvp.Key, pkvp.Value, Path.GetRelativePath(repoRootPath, projectPath).NormalizePathToUnix()))
532
533
  .OrderBy(p => p.Name)
@@ -547,6 +548,13 @@ internal static class SdkProjectDiscovery
547
548
  .Select(p => p.NormalizePathToUnix())
548
549
  .OrderBy(p => p)
549
550
  .ToImmutableArray();
551
+ var useCpmTransitivePinning =
552
+ projectProperties.TryGetValue("ManagePackageVersionsCentrally", out var useCpmString) &&
553
+ bool.TryParse(useCpmString, out var useCpm) &&
554
+ useCpm &&
555
+ projectProperties.TryGetValue("CentralPackageTransitivePinningEnabled", out var useTransitivePinningString) &&
556
+ bool.TryParse(useTransitivePinningString, out var useTransitivePinning) &&
557
+ useTransitivePinning;
550
558
 
551
559
  var projectDiscoveryResult = new ProjectDiscoveryResult()
552
560
  {
@@ -557,6 +565,7 @@ internal static class SdkProjectDiscovery
557
565
  ReferencedProjectPaths = referenced,
558
566
  ImportedFiles = imported,
559
567
  AdditionalFiles = additional,
568
+ CentralPackageTransitivePinningEnabled = useCpmTransitivePinning,
560
569
  };
561
570
  projectDiscoveryResults.Add(projectDiscoveryResult);
562
571
  }
@@ -15,4 +15,10 @@ public sealed record WorkspaceDiscoveryResult : NativeResult
15
15
  var projectDiscovery = Projects.FirstOrDefault(p => System.IO.Path.Join(Path, p.FilePath).FullyNormalizedRootedPath().Equals(repoPath, StringComparison.OrdinalIgnoreCase));
16
16
  return projectDiscovery;
17
17
  }
18
+
19
+ public ProjectDiscoveryResult? GetProjectDiscoveryFromFullPath(DirectoryInfo repoContentsPath, FileInfo projectPath)
20
+ {
21
+ var projectDiscovery = Projects.FirstOrDefault(p => System.IO.Path.Join(repoContentsPath.FullName, Path, p.FilePath).FullyNormalizedRootedPath().Equals(projectPath.FullName.FullyNormalizedRootedPath(), StringComparison.OrdinalIgnoreCase));
22
+ return projectDiscovery;
23
+ }
18
24
  }
@@ -12,6 +12,7 @@ public record ExperimentsManager
12
12
  public bool UseLegacyDependencySolver { get; init; } = false;
13
13
  public bool UseLegacyUpdateHandler { get; init; } = false;
14
14
  public bool UseDirectDiscovery { get; init; } = false;
15
+ public bool UseNewFileUpdater { get; init; } = false;
15
16
 
16
17
  public Dictionary<string, object> ToDictionary()
17
18
  {
@@ -22,6 +23,7 @@ public record ExperimentsManager
22
23
  ["nuget_legacy_dependency_solver"] = UseLegacyDependencySolver,
23
24
  ["nuget_use_legacy_update_handler"] = UseLegacyUpdateHandler,
24
25
  ["nuget_use_direct_discovery"] = UseDirectDiscovery,
26
+ ["nuget_use_new_file_updater"] = UseNewFileUpdater,
25
27
  };
26
28
  }
27
29
 
@@ -34,6 +36,7 @@ public record ExperimentsManager
34
36
  UseLegacyDependencySolver = IsEnabled(experiments, "nuget_legacy_dependency_solver"),
35
37
  UseLegacyUpdateHandler = IsEnabled(experiments, "nuget_use_legacy_update_handler"),
36
38
  UseDirectDiscovery = IsEnabled(experiments, "nuget_use_direct_discovery"),
39
+ UseNewFileUpdater = IsEnabled(experiments, "nuget_use_new_file_updater"),
37
40
  };
38
41
  }
39
42
 
@@ -2,7 +2,7 @@ namespace NuGetUpdater.Core;
2
2
 
3
3
  internal static class DotNetToolsJsonUpdater
4
4
  {
5
- public static async Task UpdateDependencyAsync(
5
+ public static async Task<string?> UpdateDependencyAsync(
6
6
  string repoRootPath,
7
7
  string workspacePath,
8
8
  string dependencyName,
@@ -13,7 +13,7 @@ internal static class DotNetToolsJsonUpdater
13
13
  if (!MSBuildHelper.TryGetDotNetToolsJsonPath(repoRootPath, workspacePath, out var dotnetToolsJsonPath))
14
14
  {
15
15
  logger.Info(" No dotnet-tools.json file found.");
16
- return;
16
+ return null;
17
17
  }
18
18
 
19
19
  var dotnetToolsJsonFile = DotNetToolsJsonBuildFile.Open(repoRootPath, dotnetToolsJsonPath, logger);
@@ -24,7 +24,7 @@ internal static class DotNetToolsJsonUpdater
24
24
  if (!containsDependency)
25
25
  {
26
26
  logger.Info($" Dependency [{dependencyName}] not found.");
27
- return;
27
+ return null;
28
28
  }
29
29
 
30
30
  var tool = dotnetToolsJsonFile.Tools
@@ -40,7 +40,10 @@ internal static class DotNetToolsJsonUpdater
40
40
  if (await dotnetToolsJsonFile.SaveAsync())
41
41
  {
42
42
  logger.Info($" Saved [{dotnetToolsJsonFile.RelativePath}].");
43
+ return dotnetToolsJsonFile.Path;
43
44
  }
44
45
  }
46
+
47
+ return null;
45
48
  }
46
49
  }
@@ -0,0 +1,326 @@
1
+ using System.Collections.Immutable;
2
+
3
+ using NuGet.Versioning;
4
+
5
+ using NuGetUpdater.Core.DependencySolver;
6
+ using NuGetUpdater.Core.Discover;
7
+ using NuGetUpdater.Core.Utilities;
8
+
9
+ namespace NuGetUpdater.Core.Updater.FileWriters;
10
+
11
+ public class FileWriterWorker
12
+ {
13
+ private readonly IDiscoveryWorker _discoveryWorker;
14
+ private readonly IDependencySolver _dependencySolver;
15
+ private readonly IFileWriter _fileWriter;
16
+ private readonly ILogger _logger;
17
+
18
+ public FileWriterWorker(IDiscoveryWorker discoveryWorker, IDependencySolver dependencySolver, IFileWriter fileWriter, ILogger logger)
19
+ {
20
+ _discoveryWorker = discoveryWorker;
21
+ _dependencySolver = dependencySolver;
22
+ _fileWriter = fileWriter;
23
+ _logger = logger;
24
+ }
25
+
26
+ public async Task<ImmutableArray<UpdateOperationBase>> RunAsync(
27
+ DirectoryInfo repoContentsPath,
28
+ FileInfo projectPath,
29
+ string dependencyName,
30
+ NuGetVersion oldDependencyVersion,
31
+ NuGetVersion newDependencyVersion
32
+ )
33
+ {
34
+ var updateOperations = new List<UpdateOperationBase>();
35
+ var projectDirectory = Path.GetDirectoryName(projectPath.FullName)!;
36
+ var projectDirectoryRelativeToRepoRoot = Path.GetRelativePath(repoContentsPath.FullName, projectDirectory).FullyNormalizedRootedPath();
37
+
38
+ // first try non-project updates
39
+ var updatedDotNetToolsPath = await DotNetToolsJsonUpdater.UpdateDependencyAsync(
40
+ repoContentsPath.FullName,
41
+ projectDirectory,
42
+ dependencyName,
43
+ oldDependencyVersion.ToString(),
44
+ newDependencyVersion.ToString(),
45
+ _logger
46
+ );
47
+ if (updatedDotNetToolsPath is not null)
48
+ {
49
+ updateOperations.Add(new DirectUpdate()
50
+ {
51
+ DependencyName = dependencyName,
52
+ OldVersion = oldDependencyVersion,
53
+ NewVersion = newDependencyVersion,
54
+ UpdatedFiles = [Path.GetRelativePath(repoContentsPath.FullName, updatedDotNetToolsPath).FullyNormalizedRootedPath()]
55
+ });
56
+ }
57
+
58
+ var updatedGlobalJsonPath = await GlobalJsonUpdater.UpdateDependencyAsync(
59
+ repoContentsPath.FullName,
60
+ projectDirectory,
61
+ dependencyName,
62
+ oldDependencyVersion.ToString(),
63
+ newDependencyVersion.ToString(),
64
+ _logger
65
+ );
66
+ if (updatedGlobalJsonPath is not null)
67
+ {
68
+ updateOperations.Add(new DirectUpdate()
69
+ {
70
+ DependencyName = dependencyName,
71
+ OldVersion = oldDependencyVersion,
72
+ NewVersion = newDependencyVersion,
73
+ UpdatedFiles = [Path.GetRelativePath(repoContentsPath.FullName, updatedGlobalJsonPath).FullyNormalizedRootedPath()]
74
+ });
75
+ }
76
+
77
+ // then try packages.config updates
78
+ var additionalFiles = ProjectHelper.GetAllAdditionalFilesFromProject(projectPath.FullName, ProjectHelper.PathFormat.Full);
79
+ var packagesConfigFullPath = additionalFiles.Where(p => Path.GetFileName(p).Equals(ProjectHelper.PackagesConfigFileName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
80
+ if (packagesConfigFullPath is not null)
81
+ {
82
+ var packagesConfigOperations = await PackagesConfigUpdater.UpdateDependencyAsync(
83
+ repoContentsPath.FullName,
84
+ projectPath.FullName,
85
+ dependencyName,
86
+ oldDependencyVersion.ToString(),
87
+ newDependencyVersion.ToString(),
88
+ packagesConfigFullPath,
89
+ _logger
90
+ );
91
+ var packagesConfigOperationsWithNormalizedPaths = packagesConfigOperations
92
+ .Select(op => op with { UpdatedFiles = [.. op.UpdatedFiles.Select(f => Path.GetRelativePath(repoContentsPath.FullName, f).FullyNormalizedRootedPath())] })
93
+ .ToArray();
94
+ updateOperations.AddRange(packagesConfigOperationsWithNormalizedPaths);
95
+ }
96
+
97
+ // then try project updates
98
+ var initialDiscoveryResult = await _discoveryWorker.RunAsync(repoContentsPath.FullName, projectDirectoryRelativeToRepoRoot);
99
+ var initialProjectDiscovery = initialDiscoveryResult.GetProjectDiscoveryFromFullPath(repoContentsPath, projectPath);
100
+ if (initialProjectDiscovery is null)
101
+ {
102
+ _logger.Info($"Unable to find project discovery for project {projectPath}.");
103
+ return [.. updateOperations];
104
+ }
105
+
106
+ var initialRequestedDependency = initialProjectDiscovery.Dependencies
107
+ .FirstOrDefault(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
108
+ if (initialRequestedDependency is null || initialRequestedDependency.Version is null)
109
+ {
110
+ _logger.Info($"Dependency {dependencyName} not found in initial project discovery.");
111
+ return [.. updateOperations];
112
+ }
113
+
114
+ var initialDependencyVersion = NuGetVersion.Parse(initialRequestedDependency.Version);
115
+ if (initialDependencyVersion >= newDependencyVersion)
116
+ {
117
+ _logger.Info($"Dependency {dependencyName} is already at version {initialDependencyVersion}, no update needed.");
118
+ return [.. updateOperations];
119
+ }
120
+
121
+ var initialTopLevelDependencies = initialProjectDiscovery.Dependencies
122
+ .Where(d => !d.IsTransitive)
123
+ .ToImmutableArray();
124
+ var newDependency = new Dependency(dependencyName, newDependencyVersion.ToString(), DependencyType.Unknown);
125
+ var desiredDependencies = initialTopLevelDependencies.Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
126
+ ? initialTopLevelDependencies.Select(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase) ? newDependency : d).ToImmutableArray()
127
+ : initialTopLevelDependencies.Concat([newDependency]).ToImmutableArray();
128
+
129
+ foreach (var targetFramework in initialProjectDiscovery.TargetFrameworks)
130
+ {
131
+ var resolvedDependencies = await _dependencySolver.SolveAsync(initialTopLevelDependencies, desiredDependencies, targetFramework);
132
+ if (resolvedDependencies is null)
133
+ {
134
+ _logger.Warn($"Unable to solve dependency conflicts for target framework {targetFramework}.");
135
+ continue;
136
+ }
137
+
138
+ var resolvedRequestedDependency = resolvedDependencies.Value
139
+ .SingleOrDefault(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
140
+ if (resolvedRequestedDependency is null || resolvedRequestedDependency.Version is null)
141
+ {
142
+ _logger.Warn($"Dependency resolution failed to include {dependencyName}.");
143
+ continue;
144
+ }
145
+
146
+ var resolvedRequestedDependencyVersion = NuGetVersion.Parse(resolvedRequestedDependency.Version);
147
+ if (resolvedRequestedDependencyVersion != newDependencyVersion)
148
+ {
149
+ _logger.Warn($"Requested dependency resolution to include {dependencyName}/{newDependencyVersion} but it was instead resolved to {resolvedRequestedDependencyVersion}.");
150
+ continue;
151
+ }
152
+
153
+ // process all projects bottom up
154
+ var orderedProjectDiscovery = GetProjectDiscoveryEvaluationOrder(repoContentsPath, initialDiscoveryResult, projectPath, _logger);
155
+
156
+ // track original contents
157
+ var originalFileContents = await GetOriginalFileContentsAsync(repoContentsPath, new DirectoryInfo(projectDirectory), orderedProjectDiscovery);
158
+
159
+ var allUpdatedFiles = new List<string>();
160
+ foreach (var projectDiscovery in orderedProjectDiscovery)
161
+ {
162
+ var projectFullPath = Path.Join(repoContentsPath.FullName, initialDiscoveryResult.Path, projectDiscovery.FilePath).FullyNormalizedRootedPath();
163
+ var updatedFiles = await TryPerformFileWritesAsync(repoContentsPath, new FileInfo(projectFullPath), projectDiscovery, resolvedDependencies.Value);
164
+ allUpdatedFiles.AddRange(updatedFiles);
165
+ }
166
+
167
+ if (allUpdatedFiles.Count == 0)
168
+ {
169
+ _logger.Warn("Failed to write new dependency versions.");
170
+ await RestoreOriginalFileContentsAsync(originalFileContents);
171
+ continue;
172
+ }
173
+
174
+ // this final call to discover has the benefit of also updating the lock file if it exists
175
+ var finalDiscoveryResult = await _discoveryWorker.RunAsync(repoContentsPath.FullName, projectDirectoryRelativeToRepoRoot);
176
+ var finalProjectDiscovery = finalDiscoveryResult.GetProjectDiscoveryFromFullPath(repoContentsPath, projectPath);
177
+ if (finalProjectDiscovery is null)
178
+ {
179
+ _logger.Warn($"Unable to find final project discovery for project {projectPath}.");
180
+ await RestoreOriginalFileContentsAsync(originalFileContents);
181
+ continue;
182
+ }
183
+
184
+ var finalRequestedDependency = finalProjectDiscovery.Dependencies
185
+ .FirstOrDefault(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
186
+ if (finalRequestedDependency is null || finalRequestedDependency.Version is null)
187
+ {
188
+ _logger.Warn($"Dependency {dependencyName} not found in final project discovery.");
189
+ await RestoreOriginalFileContentsAsync(originalFileContents);
190
+ continue;
191
+ }
192
+
193
+ var resolvedVersion = NuGetVersion.Parse(finalRequestedDependency.Version);
194
+ if (resolvedVersion != newDependencyVersion)
195
+ {
196
+ _logger.Warn($"Final dependency version for {dependencyName} is {resolvedVersion}, expected {newDependencyVersion}.");
197
+ await RestoreOriginalFileContentsAsync(originalFileContents);
198
+ continue;
199
+ }
200
+
201
+ var computedUpdateOperations = await PackageReferenceUpdater.ComputeUpdateOperations(
202
+ repoContentsPath.FullName,
203
+ projectPath.FullName,
204
+ targetFramework,
205
+ initialTopLevelDependencies,
206
+ desiredDependencies,
207
+ resolvedDependencies.Value,
208
+ new ExperimentsManager() { UseDirectDiscovery = true },
209
+ _logger);
210
+ var filteredUpdateOperations = computedUpdateOperations
211
+ .Where(op =>
212
+ {
213
+ var initialDependency = initialProjectDiscovery.Dependencies.FirstOrDefault(d => d.Name.Equals(op.DependencyName, StringComparison.OrdinalIgnoreCase));
214
+ return initialDependency is not null
215
+ && initialDependency.Version is not null
216
+ && NuGetVersion.Parse(initialDependency.Version) < op.NewVersion;
217
+ })
218
+ .ToImmutableArray();
219
+ var computedOperationsWithUpdatedFiles = filteredUpdateOperations
220
+ .Select(op => op with { UpdatedFiles = [.. allUpdatedFiles] })
221
+ .ToImmutableArray();
222
+ updateOperations.AddRange(computedOperationsWithUpdatedFiles);
223
+ }
224
+
225
+ var normalizedUpdateOperations = UpdateOperationBase.NormalizeUpdateOperationCollection(repoContentsPath.FullName, updateOperations);
226
+ return normalizedUpdateOperations;
227
+ }
228
+
229
+ internal static async Task<Dictionary<string, string>> GetOriginalFileContentsAsync(DirectoryInfo repoContentsPath, DirectoryInfo initialStartingDirectory, IEnumerable<ProjectDiscoveryResult> projectDiscoveryResults)
230
+ {
231
+ var filesAndContents = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
232
+ foreach (var projectDiscoveryResult in projectDiscoveryResults)
233
+ {
234
+ var fullProjectPath = Path.Join(initialStartingDirectory.FullName, projectDiscoveryResult.FilePath).FullyNormalizedRootedPath();
235
+ var projectContents = await File.ReadAllTextAsync(fullProjectPath);
236
+ filesAndContents[fullProjectPath] = projectContents;
237
+
238
+ foreach (var file in projectDiscoveryResult.ImportedFiles.Concat(projectDiscoveryResult.AdditionalFiles))
239
+ {
240
+ var filePath = Path.Join(Path.GetDirectoryName(fullProjectPath)!, file).FullyNormalizedRootedPath();
241
+ var fileContents = await File.ReadAllTextAsync(filePath);
242
+ filesAndContents[filePath] = fileContents;
243
+ }
244
+ }
245
+
246
+ return filesAndContents;
247
+ }
248
+
249
+ internal static async Task RestoreOriginalFileContentsAsync(Dictionary<string, string> originalFilesAndContents)
250
+ {
251
+ foreach (var (path, contents) in originalFilesAndContents)
252
+ {
253
+ await File.WriteAllTextAsync(path, contents);
254
+ }
255
+ }
256
+
257
+ internal static ImmutableArray<ProjectDiscoveryResult> GetProjectDiscoveryEvaluationOrder(DirectoryInfo repoContentsPath, WorkspaceDiscoveryResult discoveryResult, FileInfo projectPath, ILogger logger)
258
+ {
259
+ var visitedProjectPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
260
+ var projectsToProcess = new Queue<ProjectDiscoveryResult>();
261
+ var startingProjectDiscovery = discoveryResult.GetProjectDiscoveryFromFullPath(repoContentsPath, projectPath);
262
+ if (startingProjectDiscovery is null)
263
+ {
264
+ logger.Warn($"Unable to find project discovery for project {projectPath.FullName} in discovery result.");
265
+ return [];
266
+ }
267
+
268
+ projectsToProcess.Enqueue(startingProjectDiscovery);
269
+
270
+ var reversedProjectDiscoveryOrder = new List<ProjectDiscoveryResult>();
271
+ while (projectsToProcess.TryDequeue(out var projectDiscovery))
272
+ {
273
+ var projectFullPath = Path.Join(repoContentsPath.FullName, discoveryResult.Path, projectDiscovery.FilePath).FullyNormalizedRootedPath();
274
+ if (visitedProjectPaths.Add(projectFullPath))
275
+ {
276
+ reversedProjectDiscoveryOrder.Add(projectDiscovery);
277
+ foreach (var referencedProjectPath in projectDiscovery.ReferencedProjectPaths)
278
+ {
279
+ var referencedProjectFullPath = Path.Join(repoContentsPath.FullName, discoveryResult.Path, referencedProjectPath).FullyNormalizedRootedPath();
280
+ var referencedProjectDiscovery = discoveryResult.GetProjectDiscoveryFromFullPath(repoContentsPath, new FileInfo(referencedProjectFullPath));
281
+ if (referencedProjectDiscovery is not null)
282
+ {
283
+ projectsToProcess.Enqueue(referencedProjectDiscovery);
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ var projectDiscoveryOrder = ((IEnumerable<ProjectDiscoveryResult>)reversedProjectDiscoveryOrder)
290
+ .Reverse()
291
+ .ToImmutableArray();
292
+ return projectDiscoveryOrder;
293
+ }
294
+
295
+ private async Task<ImmutableArray<string>> TryPerformFileWritesAsync(DirectoryInfo repoContentsPath, FileInfo projectPath, ProjectDiscoveryResult projectDiscovery, ImmutableArray<Dependency> requiredPackageVersions)
296
+ {
297
+ var originalFileContents = await GetOriginalFileContentsAsync(repoContentsPath, projectPath.Directory!, [projectDiscovery]);
298
+ var relativeFilePaths = originalFileContents.Keys
299
+ .Select(p => Path.GetRelativePath(repoContentsPath.FullName, p).FullyNormalizedRootedPath())
300
+ .ToImmutableArray();
301
+
302
+ // try update
303
+ var addPackageReferenceElementForPinnedPackages = !projectDiscovery.CentralPackageTransitivePinningEnabled;
304
+ var success = await _fileWriter.UpdatePackageVersionsAsync(repoContentsPath, [.. relativeFilePaths], projectDiscovery.Dependencies, requiredPackageVersions, addPackageReferenceElementForPinnedPackages);
305
+ var updatedFiles = new List<string>();
306
+ foreach (var (filePath, originalContents) in originalFileContents)
307
+ {
308
+ var currentContents = await File.ReadAllTextAsync(filePath);
309
+ var currentContentsNormalized = currentContents.Replace("\r", "");
310
+ var originalContentsNormalized = originalContents.Replace("\r", "");
311
+ if (currentContentsNormalized != originalContentsNormalized)
312
+ {
313
+ var relativeUpdatedPath = Path.GetRelativePath(repoContentsPath.FullName, filePath).FullyNormalizedRootedPath();
314
+ updatedFiles.Add(relativeUpdatedPath);
315
+ }
316
+ }
317
+
318
+ if (!success)
319
+ {
320
+ await RestoreOriginalFileContentsAsync(originalFileContents);
321
+ }
322
+
323
+ var sortedUpdatedFiles = updatedFiles.OrderBy(p => p, StringComparer.Ordinal);
324
+ return [.. sortedUpdatedFiles];
325
+ }
326
+ }
@@ -0,0 +1,14 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Updater.FileWriters;
4
+
5
+ public interface IFileWriter
6
+ {
7
+ Task<bool> UpdatePackageVersionsAsync(
8
+ DirectoryInfo repoContentsPath,
9
+ ImmutableArray<string> relativeFilePaths,
10
+ ImmutableArray<Dependency> originalDependencies,
11
+ ImmutableArray<Dependency> requiredPackageVersions,
12
+ bool addPackageReferenceElementForPinnedPackages
13
+ );
14
+ }