dependabot-nuget 0.244.0 → 0.246.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +42 -7
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +164 -90
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +38 -2
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +68 -18
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +115 -14
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/{UpdateWorker.DirsProj.cs → UpdateWorkerTests.DirsProj.cs} +22 -24
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +66 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +385 -81
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +7 -4
  12. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +0 -4
  13. data/lib/dependabot/nuget/file_parser.rb +15 -1
  14. data/lib/dependabot/nuget/http_response_helpers.rb +14 -0
  15. data/lib/dependabot/nuget/metadata_finder.rb +6 -2
  16. data/lib/dependabot/nuget/native_helpers.rb +21 -12
  17. data/lib/dependabot/nuget/nuget_client.rb +8 -13
  18. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +23 -13
  19. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +75 -11
  20. data/lib/dependabot/nuget/update_checker/repository_finder.rb +4 -10
  21. data/lib/dependabot/nuget/update_checker.rb +2 -3
  22. metadata +7 -7
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +0 -317
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be3a65448fe495f267cc054563bd29fd5a2142bb07613e2ed70a1cd21490bae5
4
- data.tar.gz: 1fa876f1715e1ab11ccd5e0c323e58cd60b68d08560efe63a4c563755d36b1ca
3
+ metadata.gz: 3e0fef609dc45e4b8d8f1bc8bf9c4d62ee6fd83a4fea8e28ae9022a469151875
4
+ data.tar.gz: caab818c3d702d5f34c1986e7ca9dca6b7457eade3a0c5f4d4df004682193d98
5
5
  SHA512:
6
- metadata.gz: b22d903bfd1bab554a9513aac135e7f4ea8e055ce8e6a5f163c9d8331e87c258805d333043953c5b7beeaeba6404792694150e7e9c035de29cd1568a09b2fc22
7
- data.tar.gz: 00abcf17c29b5f98242b8ca97ab28c79f88e7c2425430f08d549364b16b160d640841ccd2e2b4c01978e96d7b0b4d51826795247f8087d1af6a4962abfba7809
6
+ metadata.gz: ce37d23d0440b68f8fce93364bcda68d61fb093b2435e853f956549c6c44e1bb68940ad06365e54c70ec18d667faa251455dc28f201b1b93b9410bfc1861681c
7
+ data.tar.gz: '0396865c4813a9c0b357826545eb6dcbee4c350d62fceed1d97469a9de4c0f2ec751974d07862d7667b0824a24bff12453f8f5309789e6a10a1ca7c25cb0a4f6'
@@ -48,7 +48,7 @@ internal static class PackagesConfigUpdater
48
48
  var packagesDirectory = PathHelper.JoinPath(projectDirectory, packagesSubDirectory);
49
49
  Directory.CreateDirectory(packagesDirectory);
50
50
 
51
- var args = new List<string>
51
+ var updateArgs = new List<string>
52
52
  {
53
53
  "update",
54
54
  packagesConfigPath,
@@ -61,17 +61,29 @@ internal static class PackagesConfigUpdater
61
61
  "-NonInteractive",
62
62
  };
63
63
 
64
+ var restoreArgs = new List<string>
65
+ {
66
+ "restore",
67
+ projectPath,
68
+ "-PackagesDirectory",
69
+ packagesDirectory,
70
+ "-NonInteractive",
71
+ };
72
+
64
73
  logger.Log(" Finding MSBuild...");
65
74
  var msbuildDirectory = MSBuildHelper.MSBuildPath;
66
75
  if (msbuildDirectory is not null)
67
76
  {
68
- args.Add("-MSBuildPath");
69
- args.Add(msbuildDirectory); // e.g., /usr/share/dotnet/sdk/7.0.203
77
+ foreach (var args in new[] { updateArgs, restoreArgs })
78
+ {
79
+ args.Add("-MSBuildPath");
80
+ args.Add(msbuildDirectory); // e.g., /usr/share/dotnet/sdk/7.0.203
81
+ }
70
82
  }
71
83
 
72
84
  using (new WebApplicationTargetsConditionPatcher(projectPath))
73
85
  {
74
- RunNuget(args, packagesDirectory, logger);
86
+ RunNuget(updateArgs, restoreArgs, packagesDirectory, logger);
75
87
  }
76
88
 
77
89
  projectBuildFile = ProjectBuildFile.Open(repoRootPath, projectPath);
@@ -84,7 +96,7 @@ internal static class PackagesConfigUpdater
84
96
  await projectBuildFile.SaveAsync();
85
97
  }
86
98
 
87
- private static void RunNuget(List<string> args, string packagesDirectory, Logger logger)
99
+ private static void RunNuget(List<string> updateArgs, List<string> restoreArgs, string packagesDirectory, Logger logger)
88
100
  {
89
101
  var outputBuilder = new StringBuilder();
90
102
  var writer = new StringWriter(outputBuilder);
@@ -97,15 +109,38 @@ internal static class PackagesConfigUpdater
97
109
  var currentDir = Environment.CurrentDirectory;
98
110
  try
99
111
  {
100
- logger.Log($" Running NuGet.exe with args: {string.Join(" ", args)}");
101
112
 
102
113
  Environment.CurrentDirectory = packagesDirectory;
103
- var result = Program.Main(args.ToArray());
114
+ var retryingAfterRestore = false;
115
+
116
+ doRestore:
117
+ logger.Log($" Running NuGet.exe with args: {string.Join(" ", updateArgs)}");
118
+ outputBuilder.Clear();
119
+ var result = Program.Main(updateArgs.ToArray());
104
120
  var fullOutput = outputBuilder.ToString();
105
121
  logger.Log($" Result: {result}");
106
122
  logger.Log($" Output:\n{fullOutput}");
107
123
  if (result != 0)
108
124
  {
125
+ // If the `packages.config` file contains a delisted package, the initial `update` operation will fail
126
+ // with the message listed below. The solution is to run `nuget.exe restore ...` and retry.
127
+ if (!retryingAfterRestore &&
128
+ fullOutput.Contains("Existing packages must be restored before performing an install or update."))
129
+ {
130
+ logger.Log($" Running NuGet.exe with args: {string.Join(" ", restoreArgs)}");
131
+ retryingAfterRestore = true;
132
+ outputBuilder.Clear();
133
+ var exitCodeAgain = Program.Main(restoreArgs.ToArray());
134
+ var restoreOutput = outputBuilder.ToString();
135
+
136
+ if (exitCodeAgain != 0)
137
+ {
138
+ throw new Exception($"Unable to restore.\nOutput:\n${restoreOutput}\n");
139
+ }
140
+
141
+ goto doRestore;
142
+ }
143
+
109
144
  throw new Exception(fullOutput);
110
145
  }
111
146
  }
@@ -20,132 +20,124 @@ internal static class SdkPackageUpdater
20
20
  string previousDependencyVersion,
21
21
  string newDependencyVersion,
22
22
  bool isTransitive,
23
- Logger logger
24
- )
23
+ Logger logger)
25
24
  {
26
25
  // SDK-style project, modify the XML directly
27
26
  logger.Log(" Running for SDK-style project");
28
- var buildFiles = await MSBuildHelper.LoadBuildFiles(repoRootPath, projectPath);
29
-
30
- var newDependencyNuGetVersion = NuGetVersion.Parse(newDependencyVersion);
31
27
 
32
- // update all dependencies, including transitive
28
+ var buildFiles = await MSBuildHelper.LoadBuildFiles(repoRootPath, projectPath);
33
29
  var tfms = MSBuildHelper.GetTargetFrameworkMonikers(buildFiles);
34
30
 
35
31
  // Get the set of all top-level dependencies in the current project
36
32
  var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
33
+ if (!await DoesDependencyRequireUpdateAsync(repoRootPath, projectPath, tfms, topLevelDependencies, dependencyName, newDependencyVersion, logger))
34
+ {
35
+ return;
36
+ }
37
37
 
38
- var packageFoundInDependencies = false;
39
- var packageNeedsUpdating = false;
40
-
41
- foreach (var tfm in tfms)
38
+ if (isTransitive)
42
39
  {
43
- var dependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, topLevelDependencies, logger);
44
- foreach (var (packageName, packageVersion, _, _, _, _) in dependencies)
40
+ await UpdateTransitiveDependencyAsnyc(projectPath, dependencyName, newDependencyVersion, buildFiles, logger);
41
+ }
42
+ else
43
+ {
44
+ var peerDependencies = await GetUpdatedPeerDependenciesAsync(repoRootPath, projectPath, tfms, dependencyName, newDependencyVersion, logger);
45
+ if (peerDependencies is null)
45
46
  {
46
- if (packageName.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
47
- {
48
- packageFoundInDependencies = true;
49
-
50
- var nugetVersion = NuGetVersion.Parse(packageVersion);
51
- if (nugetVersion < newDependencyNuGetVersion)
52
- {
53
- packageNeedsUpdating = true;
54
- }
55
- }
47
+ return;
56
48
  }
57
- }
58
49
 
59
- // Skip updating the project if the dependency does not exist in the graph
60
- if (!packageFoundInDependencies)
61
- {
62
- logger.Log($" Package [{dependencyName}] Does not exist as a dependency in [{projectPath}].");
63
- return;
50
+ UpdateTopLevelDepdendency(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, peerDependencies, logger);
64
51
  }
65
52
 
66
- // Skip updating the project if the dependency version meets or exceeds the newDependencyVersion
67
- if (!packageNeedsUpdating)
53
+ if (!await AreDependenciesCoherentAsync(repoRootPath, projectPath, dependencyName, logger, buildFiles, tfms))
68
54
  {
69
- logger.Log($" Package [{dependencyName}] already meets the requested dependency version in [{projectPath}].");
70
55
  return;
71
56
  }
72
57
 
73
- var newDependency = new[] { new Dependency(dependencyName, newDependencyVersion, DependencyType.Unknown) };
74
- var tfmsAndDependencies = new Dictionary<string, Dependency[]>();
75
- foreach (var tfm in tfms)
76
- {
77
- var dependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, newDependency, logger);
78
- tfmsAndDependencies[tfm] = dependencies;
79
- }
58
+ await SaveBuildFilesAsync(buildFiles, logger);
59
+ }
80
60
 
81
- // stop update process if we find conflicting package versions
82
- var conflictingPackageVersionsFound = false;
83
- var packagesAndVersions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
84
- foreach (var (_, dependencies) in tfmsAndDependencies)
61
+ /// <summary>
62
+ /// Verifies that the package does not already satisfy the requested dependency version.
63
+ /// </summary>
64
+ /// <returns>Returns false if the package is not found or does not need to be updated.</returns>
65
+ private static async Task<bool> DoesDependencyRequireUpdateAsync(
66
+ string repoRootPath,
67
+ string projectPath,
68
+ string[] tfms,
69
+ Dependency[] topLevelDependencies,
70
+ string dependencyName,
71
+ string newDependencyVersion,
72
+ Logger logger)
73
+ {
74
+ var newDependencyNuGetVersion = NuGetVersion.Parse(newDependencyVersion);
75
+
76
+ bool packageFound = false;
77
+ bool needsUpdate = false;
78
+
79
+ foreach (var tfm in tfms)
85
80
  {
81
+ var dependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(
82
+ repoRootPath,
83
+ projectPath,
84
+ tfm,
85
+ topLevelDependencies,
86
+ logger);
86
87
  foreach (var (packageName, packageVersion, _, _, _, _) in dependencies)
87
88
  {
88
- if (packagesAndVersions.TryGetValue(packageName, out var existingVersion) &&
89
- existingVersion != packageVersion)
89
+ if (packageVersion is null)
90
90
  {
91
- logger.Log($" Package [{packageName}] tried to update to version [{packageVersion}], but found conflicting package version of [{existingVersion}].");
92
- conflictingPackageVersionsFound = true;
91
+ continue;
93
92
  }
94
- else
93
+
94
+ if (packageName.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
95
95
  {
96
- packagesAndVersions[packageName] = packageVersion!;
96
+ packageFound = true;
97
+
98
+ var nugetVersion = NuGetVersion.Parse(packageVersion);
99
+ if (nugetVersion < newDependencyNuGetVersion)
100
+ {
101
+ needsUpdate = true;
102
+ break;
103
+ }
97
104
  }
98
105
  }
99
- }
100
106
 
101
- if (conflictingPackageVersionsFound)
102
- {
103
- return;
107
+ if (packageFound && needsUpdate)
108
+ {
109
+ break;
110
+ }
104
111
  }
105
112
 
106
- var unupgradableTfms = tfmsAndDependencies.Where(kvp => !kvp.Value.Any()).Select(kvp => kvp.Key);
107
- if (unupgradableTfms.Any())
113
+ // Skip updating the project if the dependency does not exist in the graph
114
+ if (!packageFound)
108
115
  {
109
- logger.Log($" The following target frameworks could not find packages to upgrade: {string.Join(", ", unupgradableTfms)}");
110
- return;
116
+ logger.Log($" Package [{dependencyName}] Does not exist as a dependency in [{projectPath}].");
117
+ return false;
111
118
  }
112
119
 
113
- if (isTransitive)
114
- {
115
- var directoryPackagesWithPinning = buildFiles.OfType<ProjectBuildFile>()
116
- .FirstOrDefault(bf => IsCpmTransitivePinningEnabled(bf));
117
- if (directoryPackagesWithPinning is not null)
118
- {
119
- PinTransitiveDependency(directoryPackagesWithPinning, dependencyName, newDependencyVersion, logger);
120
- }
121
- else
122
- {
123
- await AddTransitiveDependencyAsync(projectPath, dependencyName, newDependencyVersion, logger);
124
- }
125
- }
126
- else
120
+ // Skip updating the project if the dependency version meets or exceeds the newDependencyVersion
121
+ if (!needsUpdate)
127
122
  {
128
- UpdateTopLevelDepdendency(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, packagesAndVersions, logger);
123
+ logger.Log($" Package [{dependencyName}] already meets the requested dependency version in [{projectPath}].");
124
+ return false;
129
125
  }
130
126
 
131
- var updatedTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles);
132
- foreach (var tfm in tfms)
127
+ return true;
128
+ }
129
+
130
+ private static async Task UpdateTransitiveDependencyAsnyc(string projectPath, string dependencyName, string newDependencyVersion, ImmutableArray<ProjectBuildFile> buildFiles, Logger logger)
131
+ {
132
+ var directoryPackagesWithPinning = buildFiles.OfType<ProjectBuildFile>()
133
+ .FirstOrDefault(bf => IsCpmTransitivePinningEnabled(bf));
134
+ if (directoryPackagesWithPinning is not null)
133
135
  {
134
- var updatedPackages = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, updatedTopLevelDependencies.ToArray(), logger);
135
- var dependenciesAreCoherent = await MSBuildHelper.DependenciesAreCoherentAsync(repoRootPath, projectPath, tfm, updatedPackages, logger);
136
- if (!dependenciesAreCoherent)
137
- {
138
- logger.Log($" Package [{dependencyName}] could not be updated in [{projectPath}] because it would cause a dependency conflict.");
139
- return;
140
- }
136
+ PinTransitiveDependency(directoryPackagesWithPinning, dependencyName, newDependencyVersion, logger);
141
137
  }
142
-
143
- foreach (var buildFile in buildFiles)
138
+ else
144
139
  {
145
- if (await buildFile.SaveAsync())
146
- {
147
- logger.Log($" Saved [{buildFile.RepoRelativePath}].");
148
- }
140
+ await AddTransitiveDependencyAsync(projectPath, dependencyName, newDependencyVersion, logger);
149
141
  }
150
142
  }
151
143
 
@@ -246,14 +238,68 @@ internal static class SdkPackageUpdater
246
238
  }
247
239
  }
248
240
 
241
+ /// <summary>
242
+ /// Gets the set of peer dependencies that need to be updated.
243
+ /// </summary>
244
+ /// <returns>Returns null if there are conflicting versions.</returns>
245
+ private static async Task<Dictionary<string, string>?> GetUpdatedPeerDependenciesAsync(
246
+ string repoRootPath,
247
+ string projectPath,
248
+ string[] tfms,
249
+ string dependencyName,
250
+ string newDependencyVersion,
251
+ Logger logger)
252
+ {
253
+ var newDependency = new[] { new Dependency(dependencyName, newDependencyVersion, DependencyType.Unknown) };
254
+ var tfmsAndDependencies = new Dictionary<string, Dependency[]>();
255
+ foreach (var tfm in tfms)
256
+ {
257
+ var dependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, newDependency, logger);
258
+ tfmsAndDependencies[tfm] = dependencies;
259
+ }
260
+
261
+ var unupgradableTfms = tfmsAndDependencies.Where(kvp => !kvp.Value.Any()).Select(kvp => kvp.Key);
262
+ if (unupgradableTfms.Any())
263
+ {
264
+ logger.Log($" The following target frameworks could not find packages to upgrade: {string.Join(", ", unupgradableTfms)}");
265
+ return null;
266
+ }
267
+
268
+ var conflictingPackageVersionsFound = false;
269
+ var packagesAndVersions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
270
+ foreach (var (_, dependencies) in tfmsAndDependencies)
271
+ {
272
+ foreach (var (packageName, packageVersion, _, _, _, _) in dependencies)
273
+ {
274
+ if (packagesAndVersions.TryGetValue(packageName, out var existingVersion) &&
275
+ existingVersion != packageVersion)
276
+ {
277
+ logger.Log($" Package [{packageName}] tried to update to version [{packageVersion}], but found conflicting package version of [{existingVersion}].");
278
+ conflictingPackageVersionsFound = true;
279
+ }
280
+ else
281
+ {
282
+ packagesAndVersions[packageName] = packageVersion!;
283
+ }
284
+ }
285
+ }
286
+
287
+ // stop update process if we find conflicting package versions
288
+ if (conflictingPackageVersionsFound)
289
+ {
290
+ return null;
291
+ }
292
+
293
+ return packagesAndVersions;
294
+ }
295
+
249
296
  private static void UpdateTopLevelDepdendency(
250
297
  ImmutableArray<ProjectBuildFile> buildFiles,
251
298
  string dependencyName,
252
299
  string previousDependencyVersion,
253
300
  string newDependencyVersion,
254
- IDictionary<string, string> packagesAndVersions,
255
- Logger logger
256
- )
301
+ IDictionary<string, string> peerDependencies,
302
+ Logger logger)
257
303
  {
258
304
  var result = TryUpdateDependencyVersion(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, logger);
259
305
  if (result == UpdateResult.NotFound)
@@ -262,7 +308,7 @@ internal static class SdkPackageUpdater
262
308
  return;
263
309
  }
264
310
 
265
- foreach (var (packageName, packageVersion) in packagesAndVersions.Where(kvp => string.Compare(kvp.Key, dependencyName, StringComparison.OrdinalIgnoreCase) != 0))
311
+ foreach (var (packageName, packageVersion) in peerDependencies.Where(kvp => string.Compare(kvp.Key, dependencyName, StringComparison.OrdinalIgnoreCase) != 0))
266
312
  {
267
313
  TryUpdateDependencyVersion(buildFiles, packageName, previousDependencyVersion: null, newDependencyVersion: packageVersion, logger);
268
314
  }
@@ -515,4 +561,32 @@ internal static class SdkPackageUpdater
515
561
  packageName,
516
562
  StringComparison.OrdinalIgnoreCase) &&
517
563
  (e.GetAttributeOrSubElementValue("Version", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("VersionOverride", StringComparison.OrdinalIgnoreCase)) is not null);
564
+
565
+ private static async Task<bool> AreDependenciesCoherentAsync(string repoRootPath, string projectPath, string dependencyName, Logger logger, ImmutableArray<ProjectBuildFile> buildFiles, string[] tfms)
566
+ {
567
+ var updatedTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
568
+ foreach (var tfm in tfms)
569
+ {
570
+ var updatedPackages = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, updatedTopLevelDependencies, logger);
571
+ var dependenciesAreCoherent = await MSBuildHelper.DependenciesAreCoherentAsync(repoRootPath, projectPath, tfm, updatedPackages, logger);
572
+ if (!dependenciesAreCoherent)
573
+ {
574
+ logger.Log($" Package [{dependencyName}] could not be updated in [{projectPath}] because it would cause a dependency conflict.");
575
+ return false;
576
+ }
577
+ }
578
+
579
+ return true;
580
+ }
581
+
582
+ private static async Task SaveBuildFilesAsync(ImmutableArray<ProjectBuildFile> buildFiles, Logger logger)
583
+ {
584
+ foreach (var buildFile in buildFiles)
585
+ {
586
+ if (await buildFile.SaveAsync())
587
+ {
588
+ logger.Log($" Saved [{buildFile.RepoRelativePath}].");
589
+ }
590
+ }
591
+ }
518
592
  }
@@ -1,6 +1,7 @@
1
1
  using System;
2
2
  using System.Collections.Generic;
3
3
  using System.IO;
4
+ using System.Linq;
4
5
  using System.Threading.Tasks;
5
6
 
6
7
  namespace NuGetUpdater.Core;
@@ -9,6 +10,7 @@ public class UpdaterWorker
9
10
  {
10
11
  private readonly Logger _logger;
11
12
  private readonly HashSet<string> _processedGlobalJsonPaths = new(StringComparer.OrdinalIgnoreCase);
13
+ private readonly HashSet<string> _processedProjectPaths = new(StringComparer.OrdinalIgnoreCase);
12
14
 
13
15
  public UpdaterWorker(Logger logger)
14
16
  {
@@ -49,6 +51,7 @@ public class UpdaterWorker
49
51
  }
50
52
 
51
53
  _processedGlobalJsonPaths.Clear();
54
+ _processedProjectPaths.Clear();
52
55
  }
53
56
 
54
57
  private async Task RunForSolutionAsync(
@@ -101,7 +104,40 @@ public class UpdaterWorker
101
104
  string newDependencyVersion,
102
105
  bool isTransitive)
103
106
  {
104
- _logger.Log($"Running for project [{projectPath}]");
107
+ _logger.Log($"Running for project file [{Path.GetRelativePath(repoRootPath, projectPath)}]");
108
+ if (!File.Exists(projectPath))
109
+ {
110
+ _logger.Log($"File [{projectPath}] does not exist.");
111
+ return;
112
+ }
113
+
114
+ var projectFilePaths = MSBuildHelper.GetProjectPathsFromProject(projectPath);
115
+ foreach (var projectFullPath in projectFilePaths.Concat([projectPath]))
116
+ {
117
+ // If there is some MSBuild logic that needs to run to fully resolve the path skip the project
118
+ if (File.Exists(projectFullPath))
119
+ {
120
+ await RunUpdaterAsync(repoRootPath, projectFullPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
121
+ }
122
+ }
123
+ }
124
+
125
+ private async Task RunUpdaterAsync(
126
+ string repoRootPath,
127
+ string projectPath,
128
+ string dependencyName,
129
+ string previousDependencyVersion,
130
+ string newDependencyVersion,
131
+ bool isTransitive)
132
+ {
133
+ if (_processedProjectPaths.Contains(projectPath))
134
+ {
135
+ return;
136
+ }
137
+
138
+ _processedProjectPaths.Add(projectPath);
139
+
140
+ _logger.Log($"Updating project [{projectPath}]");
105
141
 
106
142
  if (!isTransitive
107
143
  && MSBuildHelper.GetGlobalJsonPath(repoRootPath, projectPath) is { } globalJsonPath
@@ -111,7 +147,7 @@ public class UpdaterWorker
111
147
  await GlobalJsonUpdater.UpdateDependencyAsync(repoRootPath, globalJsonPath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
112
148
  }
113
149
 
114
- if (NuGetHelper.HasProjectConfigFile(projectPath))
150
+ if (NuGetHelper.HasPackagesConfigFile(projectPath))
115
151
  {
116
152
  await PackagesConfigUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive, _logger);
117
153
  }
@@ -21,6 +21,8 @@ using NuGetUpdater.Core.Utilities;
21
21
 
22
22
  namespace NuGetUpdater.Core;
23
23
 
24
+ using EvaluationResult = (MSBuildHelper.EvaluationResultType ResultType, string EvaluatedValue, string? ErrorMessage);
25
+
24
26
  internal static partial class MSBuildHelper
25
27
  {
26
28
  public static string MSBuildPath { get; private set; } = string.Empty;
@@ -57,7 +59,10 @@ internal static partial class MSBuildHelper
57
59
  if (property.Name.Equals("TargetFramework", StringComparison.OrdinalIgnoreCase) ||
58
60
  property.Name.Equals("TargetFrameworks", StringComparison.OrdinalIgnoreCase))
59
61
  {
60
- targetFrameworkValues.Add(property.Value);
62
+ foreach (var tfm in property.Value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
63
+ {
64
+ targetFrameworkValues.Add(tfm);
65
+ }
61
66
  }
62
67
  else if (property.Name.Equals("TargetFrameworkVersion", StringComparison.OrdinalIgnoreCase))
63
68
  {
@@ -75,8 +80,16 @@ internal static partial class MSBuildHelper
75
80
 
76
81
  foreach (var targetFrameworkValue in targetFrameworkValues)
77
82
  {
78
- var tfms = targetFrameworkValue;
79
- tfms = GetRootedValue(tfms, propertyInfo);
83
+ var (resultType, tfms, errorMessage) =
84
+ GetEvaluatedValue(targetFrameworkValue, propertyInfo, propertiesToIgnore: ["TargetFramework", "TargetFrameworks"]);
85
+ if (resultType == EvaluationResultType.PropertyIgnored)
86
+ {
87
+ continue;
88
+ }
89
+ else if (resultType != EvaluationResultType.Success)
90
+ {
91
+ throw new InvalidDataException(errorMessage);
92
+ }
80
93
 
81
94
  if (string.IsNullOrEmpty(tfms))
82
95
  {
@@ -101,10 +114,18 @@ internal static partial class MSBuildHelper
101
114
  public static IEnumerable<string> GetProjectPathsFromProject(string projFilePath)
102
115
  {
103
116
  var projectStack = new Stack<(string folderPath, ProjectRootElement)>();
104
- var projectRootElement = ProjectRootElement.Open(projFilePath);
105
117
  var processedProjectFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
118
+ using var projectCollection = new ProjectCollection();
106
119
 
107
- projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(projFilePath)!), projectRootElement));
120
+ try
121
+ {
122
+ var projectRootElement = ProjectRootElement.Open(projFilePath, projectCollection);
123
+ projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(projFilePath)!), projectRootElement));
124
+ }
125
+ catch (InvalidProjectFileException)
126
+ {
127
+ yield break; // Skip invalid project files
128
+ }
108
129
 
109
130
  while (projectStack.Count > 0)
110
131
  {
@@ -137,7 +158,7 @@ internal static partial class MSBuildHelper
137
158
  // If there is some MSBuild logic that needs to run to fully resolve the path skip the project
138
159
  if (File.Exists(file))
139
160
  {
140
- var additionalProjectRootElement = ProjectRootElement.Open(file);
161
+ var additionalProjectRootElement = ProjectRootElement.Open(file, projectCollection);
141
162
  projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(file)!), additionalProjectRootElement));
142
163
  processedProjectFiles.Add(file);
143
164
  }
@@ -222,9 +243,13 @@ internal static partial class MSBuildHelper
222
243
  }
223
244
 
224
245
  // Walk the property replacements until we don't find another one.
225
- packageVersion = GetRootedValue(packageVersion, propertyInfo);
246
+ var evaluationResult = GetEvaluatedValue(packageVersion, propertyInfo);
247
+ if (evaluationResult.ResultType != EvaluationResultType.Success)
248
+ {
249
+ throw new InvalidDataException(evaluationResult.ErrorMessage);
250
+ }
226
251
 
227
- packageVersion = packageVersion.TrimStart('[', '(').TrimEnd(']', ')');
252
+ packageVersion = evaluationResult.EvaluatedValue.TrimStart('[', '(').TrimEnd(']', ')');
228
253
 
229
254
  // We don't know the version for range requirements or wildcard
230
255
  // requirements, so return "" for these.
@@ -237,25 +262,32 @@ internal static partial class MSBuildHelper
237
262
  /// <summary>
238
263
  /// Given an MSBuild string and a set of properties, returns our best guess at the final value MSBuild will evaluate to.
239
264
  /// </summary>
240
- /// <param name="msbuildString"></param>
241
- /// <param name="propertyInfo"></param>
242
- /// <returns></returns>
243
- public static string GetRootedValue(string msbuildString, Dictionary<string, string> propertyInfo)
265
+ public static EvaluationResult GetEvaluatedValue(string msbuildString, Dictionary<string, string> propertyInfo, params string[] propertiesToIgnore)
244
266
  {
267
+ var ignoredProperties = new HashSet<string>(propertiesToIgnore, StringComparer.OrdinalIgnoreCase);
245
268
  var seenProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
269
+
246
270
  while (TryGetPropertyName(msbuildString, out var propertyName))
247
271
  {
272
+ if (ignoredProperties.Contains(propertyName))
273
+ {
274
+ return (EvaluationResultType.PropertyIgnored, msbuildString, $"Property '{propertyName}' is ignored.");
275
+ }
276
+
248
277
  if (!seenProperties.Add(propertyName))
249
278
  {
250
- throw new InvalidDataException($"Property '{propertyName}' has a circular reference.");
279
+ return (EvaluationResultType.CircularReference, msbuildString, $"Property '{propertyName}' has a circular reference.");
280
+ }
281
+
282
+ if (!propertyInfo.TryGetValue(propertyName, out var propertyValue))
283
+ {
284
+ return (EvaluationResultType.PropertyNotFound, msbuildString, $"Property '{propertyName}' was not found.");
251
285
  }
252
286
 
253
- msbuildString = propertyInfo.TryGetValue(propertyName, out var propertyValue)
254
- ? msbuildString.Replace($"$({propertyName})", propertyValue)
255
- : throw new InvalidDataException($"Property '{propertyName}' was not found.");
287
+ msbuildString = msbuildString.Replace($"$({propertyName})", propertyValue);
256
288
  }
257
289
 
258
- return msbuildString;
290
+ return (EvaluationResultType.Success, msbuildString, null);
259
291
  }
260
292
 
261
293
  public static bool TryGetPropertyName(string versionContent, [NotNullWhen(true)] out string? propertyName)
@@ -360,7 +392,17 @@ internal static partial class MSBuildHelper
360
392
  await File.WriteAllTextAsync(tempProjectPath, projectContents);
361
393
 
362
394
  // prevent directory crawling
363
- await File.WriteAllTextAsync(Path.Combine(tempDir.FullName, "Directory.Build.props"), "<Project />");
395
+ await File.WriteAllTextAsync(
396
+ Path.Combine(tempDir.FullName, "Directory.Build.props"),
397
+ """
398
+ <Project>
399
+ <PropertyGroup>
400
+ <!-- For Windows-specific apps -->
401
+ <EnableWindowsTargeting>true</EnableWindowsTargeting>
402
+ </PropertyGroup>
403
+ </Project>
404
+ """);
405
+
364
406
  await File.WriteAllTextAsync(Path.Combine(tempDir.FullName, "Directory.Build.targets"), "<Project />");
365
407
  await File.WriteAllTextAsync(Path.Combine(tempDir.FullName, "Directory.Packages.props"), "<Project />");
366
408
 
@@ -482,4 +524,12 @@ internal static partial class MSBuildHelper
482
524
 
483
525
  [GeneratedRegex("^\\s*NuGetData::Package=(?<PackageName>[^,]+), Version=(?<PackageVersion>.+)$")]
484
526
  private static partial Regex PackagePattern();
527
+
528
+ internal enum EvaluationResultType
529
+ {
530
+ Success,
531
+ PropertyIgnored,
532
+ CircularReference,
533
+ PropertyNotFound,
534
+ }
485
535
  }
@@ -6,7 +6,7 @@ internal static class NuGetHelper
6
6
  {
7
7
  internal const string PackagesConfigFileName = "packages.config";
8
8
 
9
- public static bool HasProjectConfigFile(string projectPath)
9
+ public static bool HasPackagesConfigFile(string projectPath)
10
10
  {
11
11
  var projectDirectory = Path.GetDirectoryName(projectPath);
12
12
  var packagesConfigPath = PathHelper.JoinPath(projectDirectory, PackagesConfigFileName);