dependabot-nuget 0.245.0 → 0.246.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (22) 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 +57 -17
  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 +355 -83
  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/nuget_client.rb +8 -13
  17. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +23 -13
  18. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +75 -11
  19. data/lib/dependabot/nuget/update_checker/repository_finder.rb +4 -10
  20. data/lib/dependabot/nuget/update_checker.rb +2 -3
  21. metadata +7 -7
  22. 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: 4e67c27ae4f1d9736ba0db82edf309551b56c5337213f72a71d5dc7502e2a91d
4
- data.tar.gz: f04858bb986a722dbe26aae525f26885dbd7cc0dc514ee099ab5fed628dfbc43
3
+ metadata.gz: 3e0fef609dc45e4b8d8f1bc8bf9c4d62ee6fd83a4fea8e28ae9022a469151875
4
+ data.tar.gz: caab818c3d702d5f34c1986e7ca9dca6b7457eade3a0c5f4d4df004682193d98
5
5
  SHA512:
6
- metadata.gz: 9fc5b619a387d372e8b91007cb3a1983e93b891874e87e475286d177e3075a911c98cda226746f7e8bb792ba83da409ba9abaa29402857d3b01e82850bffbbc9
7
- data.tar.gz: '0919b3eb28f2d1c1aa5e3d137eb0ef4d443458f92aaddfca0628353d9b49d2ad6b2ccc6698102f55ada4c04517017c143d5115c1ff0d6ba701c422b6547923f2'
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.");
251
280
  }
252
281
 
253
- msbuildString = propertyInfo.TryGetValue(propertyName, out var propertyValue)
254
- ? msbuildString.Replace($"$({propertyName})", propertyValue)
255
- : throw new InvalidDataException($"Property '{propertyName}' was not found.");
282
+ if (!propertyInfo.TryGetValue(propertyName, out var propertyValue))
283
+ {
284
+ return (EvaluationResultType.PropertyNotFound, msbuildString, $"Property '{propertyName}' was not found.");
285
+ }
286
+
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)
@@ -492,4 +524,12 @@ internal static partial class MSBuildHelper
492
524
 
493
525
  [GeneratedRegex("^\\s*NuGetData::Package=(?<PackageName>[^,]+), Version=(?<PackageVersion>.+)$")]
494
526
  private static partial Regex PackagePattern();
527
+
528
+ internal enum EvaluationResultType
529
+ {
530
+ Success,
531
+ PropertyIgnored,
532
+ CircularReference,
533
+ PropertyNotFound,
534
+ }
495
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);