dependabot-nuget 0.296.0 → 0.296.1

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 (26) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +2 -0
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +6 -7
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +29 -4
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionResult.cs +7 -10
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.props +2 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ClosePullRequest.cs +12 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PullRequestExistsForLatestVersion.cs +11 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateNotNeeded.cs +10 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdatePullRequest.cs +28 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +5 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/IApiHandler.cs +1 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +106 -64
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/TargetFrameworkReporter.targets +1 -6
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DependencyConflictResolver.cs +5 -2
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +41 -30
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathComparer.cs +13 -6
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/VersionFinderTests.cs +33 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +8 -4
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +175 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +21 -21
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +69 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/TestApiHandler.cs +6 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +10 -10
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +83 -0
  26. metadata +9 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22109eb4de3c3d7317682ad493e6162a7af4328e07751a27c8dbb48978597ef3
4
- data.tar.gz: 9a8afdc613b3313c7e08ac3967c4abd311a6eb66a662022677bed40229e52d3b
3
+ metadata.gz: e95198bddff97b4ff66198a8f1bbdfd30e1277f38d13ff043c3ea11a4b03004d
4
+ data.tar.gz: 85d9c549fe2b678720e036d90448b0d6b4a23fef3f494ce266cfc25c837f87de
5
5
  SHA512:
6
- metadata.gz: fe8805919ec14b5bf6385d4bb4ff3a1dc00137fc08c1e002d8c3db0600c4e5c2e8e86319d3e40fe4c608d29229834c3c741fa8a262b3e8d9f9c8e21755cf9029
7
- data.tar.gz: d0b247df044695e253e08ed9cabbdea0341ff9234ca601c61fc48d2f13ac138ffce51683ebf45ed37478190750a791bacbc3308ab0a69e3cff70e61cffb8576a
6
+ metadata.gz: f6804dc6b7080b98cf7b9b1b015fcb1a2d2ea3c6e552479bdd0e198765716d6d449d0f012297931bff515b104e81f9daec49ef71f232b906799f403f110f0366
7
+ data.tar.gz: 73242eed344d61d77a40b8f9f65374fb0101d8efc2a805c9e80c853a5164d870776aad2828e5775baac792634053fe0897bb790ff67d2d927cc960feb716515d
@@ -236,6 +236,7 @@ public partial class AnalyzeWorker : IAnalyzeWorker
236
236
  CancellationToken cancellationToken)
237
237
  {
238
238
  var versionResult = await VersionFinder.GetVersionsAsync(
239
+ projectFrameworks,
239
240
  dependencyInfo,
240
241
  nugetContext,
241
242
  logger,
@@ -262,6 +263,7 @@ public partial class AnalyzeWorker : IAnalyzeWorker
262
263
  CancellationToken cancellationToken)
263
264
  {
264
265
  var versionResult = await VersionFinder.GetVersionsAsync(
266
+ projectFrameworks,
265
267
  packageIds.First(),
266
268
  version,
267
269
  nugetContext,
@@ -108,19 +108,18 @@ internal static class CompatibilityChecker
108
108
 
109
109
  foreach (var d in dependencyGroups)
110
110
  {
111
- var libItems = (await readers.ContentReader.GetLibItemsAsync(cancellationToken)).ToList();
112
-
113
- foreach (var item in libItems)
114
- {
115
- tfms.Add(item.TargetFramework);
116
- }
117
-
118
111
  if (!d.TargetFramework.IsAny)
119
112
  {
120
113
  tfms.Add(d.TargetFramework);
121
114
  }
122
115
  }
123
116
 
117
+ var refItems = await readers.ContentReader.GetReferenceItemsAsync(cancellationToken);
118
+ foreach (var refItem in refItems)
119
+ {
120
+ tfms.Add(refItem.TargetFramework);
121
+ }
122
+
124
123
  if (!tfms.Any())
125
124
  {
126
125
  tfms.Add(NuGetFramework.AnyFramework);
@@ -2,6 +2,7 @@ using System.Collections.Immutable;
2
2
 
3
3
  using NuGet.Common;
4
4
  using NuGet.Configuration;
5
+ using NuGet.Frameworks;
5
6
  using NuGet.Packaging.Core;
6
7
  using NuGet.Protocol;
7
8
  using NuGet.Protocol.Core.Types;
@@ -12,6 +13,7 @@ namespace NuGetUpdater.Core.Analyze;
12
13
  internal static class VersionFinder
13
14
  {
14
15
  public static Task<VersionResult> GetVersionsAsync(
16
+ ImmutableArray<NuGetFramework> projectTfms,
15
17
  string packageId,
16
18
  NuGetVersion currentVersion,
17
19
  NuGetContext nugetContext,
@@ -20,10 +22,11 @@ internal static class VersionFinder
20
22
  {
21
23
  var versionFilter = CreateVersionFilter(currentVersion);
22
24
 
23
- return GetVersionsAsync(packageId, currentVersion, versionFilter, nugetContext, logger, cancellationToken);
25
+ return GetVersionsAsync(projectTfms, packageId, currentVersion, versionFilter, nugetContext, logger, cancellationToken);
24
26
  }
25
27
 
26
28
  public static Task<VersionResult> GetVersionsAsync(
29
+ ImmutableArray<NuGetFramework> projectTfms,
27
30
  DependencyInfo dependencyInfo,
28
31
  NuGetContext nugetContext,
29
32
  ILogger logger,
@@ -34,10 +37,11 @@ internal static class VersionFinder
34
37
  var currentVersion = versionRange.MinVersion!;
35
38
  var versionFilter = CreateVersionFilter(dependencyInfo, versionRange);
36
39
 
37
- return GetVersionsAsync(packageId, currentVersion, versionFilter, nugetContext, logger, cancellationToken);
40
+ return GetVersionsAsync(projectTfms, packageId, currentVersion, versionFilter, nugetContext, logger, cancellationToken);
38
41
  }
39
42
 
40
43
  public static async Task<VersionResult> GetVersionsAsync(
44
+ ImmutableArray<NuGetFramework> projectTfms,
41
45
  string packageId,
42
46
  NuGetVersion currentVersion,
43
47
  Func<NuGetVersion, bool> versionFilter,
@@ -62,7 +66,14 @@ internal static class VersionFinder
62
66
  var feed = await sourceRepository.GetResourceAsync<MetadataResource>();
63
67
  if (feed is null)
64
68
  {
65
- logger.Warn($"Failed to get MetadataResource for [{source.Source}]");
69
+ logger.Warn($"Failed to get {nameof(MetadataResource)} for [{source.Source}]");
70
+ continue;
71
+ }
72
+
73
+ var packageFinder = await sourceRepository.GetResourceAsync<FindPackageByIdResource>();
74
+ if (packageFinder is null)
75
+ {
76
+ logger.Warn($"Failed to get {nameof(FindPackageByIdResource)} for [{source.Source}]");
66
77
  continue;
67
78
  }
68
79
 
@@ -100,7 +111,21 @@ internal static class VersionFinder
100
111
  result.AddCurrentVersionSource(source);
101
112
  }
102
113
 
103
- result.AddRange(source, feedVersions.Where(versionFilter));
114
+ var versions = feedVersions.Where(versionFilter).ToArray();
115
+ foreach (var version in versions)
116
+ {
117
+ var isTfmCompatible = await CompatibilityChecker.CheckAsync(
118
+ new PackageIdentity(packageId, version),
119
+ projectTfms,
120
+ nugetContext,
121
+ logger,
122
+ CancellationToken.None);
123
+ if (isTfmCompatible || projectTfms.IsEmpty)
124
+ {
125
+ // dotnet-tools.json and global.json packages won't specify a TFM, so they're always compatible
126
+ result.Add(source, version);
127
+ }
128
+ }
104
129
  }
105
130
 
106
131
  return result;
@@ -22,18 +22,15 @@ internal class VersionResult
22
22
  _currentVersionSources.Add(source);
23
23
  }
24
24
 
25
- public void AddRange(PackageSource source, IEnumerable<NuGetVersion> versions)
25
+ public void Add(PackageSource source, NuGetVersion version)
26
26
  {
27
- foreach (var version in versions)
27
+ if (_versions.ContainsKey(version))
28
28
  {
29
- if (_versions.ContainsKey(version))
30
- {
31
- _versions[version].Add(source);
32
- }
33
- else
34
- {
35
- _versions.Add(version, [source]);
36
- }
29
+ _versions[version].Add(source);
30
+ }
31
+ else
32
+ {
33
+ _versions.Add(version, [source]);
37
34
  }
38
35
  }
39
36
 
@@ -2,6 +2,7 @@
2
2
  <!-- The following properties enable target framework and dependency discovery when OS-specific workloads are required -->
3
3
  <PropertyGroup>
4
4
  <DesignTimeBuild>true</DesignTimeBuild>
5
- <TargetPlatformVersion Condition=" $(TargetFramework.Contains('-')) ">1.0</TargetPlatformVersion>
5
+ <EnableWindowsTargeting>true</EnableWindowsTargeting>
6
+ <TargetPlatformVersion Condition="$(TargetPlatformVersion) == '' AND $(TargetFramework.Contains('-'))">1.0</TargetPlatformVersion>
6
7
  </PropertyGroup>
7
8
  </Project>
@@ -0,0 +1,12 @@
1
+ using System.Collections.Immutable;
2
+ using System.Text.Json.Serialization;
3
+
4
+ namespace NuGetUpdater.Core.Run.ApiModel;
5
+
6
+ public sealed record ClosePullRequest
7
+ {
8
+ [JsonPropertyName("dependency-names")]
9
+ public required ImmutableArray<string> DependencyNames { get; init; }
10
+
11
+ public string Reason { get; init; } = "up_to_date";
12
+ }
@@ -0,0 +1,11 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record PullRequestExistsForLatestVersion : JobErrorBase
4
+ {
5
+ public PullRequestExistsForLatestVersion(string dependencyName, string dependencyVersion)
6
+ : base("pull_request_exists_for_latest_version")
7
+ {
8
+ Details["dependency-name"] = dependencyName;
9
+ Details["dependency-version"] = dependencyVersion;
10
+ }
11
+ }
@@ -0,0 +1,10 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record SecurityUpdateNotNeeded : JobErrorBase
4
+ {
5
+ public SecurityUpdateNotNeeded(string dependencyName)
6
+ : base("security_update_not_needed")
7
+ {
8
+ Details["dependency-name"] = dependencyName;
9
+ }
10
+ }
@@ -0,0 +1,28 @@
1
+ using System.Collections.Immutable;
2
+ using System.Text.Json.Serialization;
3
+
4
+ namespace NuGetUpdater.Core.Run.ApiModel;
5
+
6
+ public sealed record UpdatePullRequest
7
+ {
8
+ [JsonPropertyName("base-commit-sha")]
9
+ public required string BaseCommitSha { get; init; }
10
+
11
+ [JsonPropertyName("dependency-names")]
12
+ public required ImmutableArray<string> DependencyNames { get; init; }
13
+
14
+ [JsonPropertyName("updated-dependency-files")]
15
+ public required DependencyFile[] UpdatedDependencyFiles { get; init; }
16
+
17
+ [JsonPropertyName("pr-title")]
18
+ public required string PrTitle { get; init; }
19
+
20
+ [JsonPropertyName("pr-body")]
21
+ public required string PrBody { get; init; }
22
+
23
+ [JsonPropertyName("commit-message")]
24
+ public required string CommitMessage { get; init; }
25
+
26
+ [JsonPropertyName("dependency-group")]
27
+ public required string? DependencyGroup { get; init; }
28
+ }
@@ -45,6 +45,11 @@ public class HttpApiHandler : IApiHandler
45
45
  await PostAsJson("create_pull_request", createPullRequest);
46
46
  }
47
47
 
48
+ public async Task UpdatePullRequest(UpdatePullRequest updatePullRequest)
49
+ {
50
+ await PostAsJson("update_pull_request", updatePullRequest);
51
+ }
52
+
48
53
  public async Task MarkAsProcessed(MarkAsProcessed markAsProcessed)
49
54
  {
50
55
  await PostAsJson("mark_as_processed", markAsProcessed);
@@ -8,5 +8,6 @@ public interface IApiHandler
8
8
  Task UpdateDependencyList(UpdatedDependencyList updatedDependencyList);
9
9
  Task IncrementMetric(IncrementMetric incrementMetric);
10
10
  Task CreatePullRequest(CreatePullRequest createPullRequest);
11
+ Task UpdatePullRequest(UpdatePullRequest updatePullRequest);
11
12
  Task MarkAsProcessed(MarkAsProcessed markAsProcessed);
12
13
  }
@@ -11,6 +11,7 @@ using NuGet.Versioning;
11
11
  using NuGetUpdater.Core.Analyze;
12
12
  using NuGetUpdater.Core.Discover;
13
13
  using NuGetUpdater.Core.Run.ApiModel;
14
+ using NuGetUpdater.Core.Utilities;
14
15
 
15
16
  namespace NuGetUpdater.Core.Run;
16
17
 
@@ -116,14 +117,12 @@ public class RunWorker
116
117
  var discoveredUpdatedDependencies = GetUpdatedDependencyListFromDiscovery(discoveryResult, repoContentsPath.FullName);
117
118
  await _apiHandler.UpdateDependencyList(discoveredUpdatedDependencies);
118
119
 
120
+ var incrementMetric = GetIncrementMetric(job);
121
+ await _apiHandler.IncrementMetric(incrementMetric);
122
+
119
123
  // TODO: pull out relevant dependencies, then check each for updates and track the changes
120
124
  var originalDependencyFileContents = new Dictionary<string, string>();
121
125
  var actualUpdatedDependencies = new List<ReportedDependency>();
122
- await _apiHandler.IncrementMetric(new()
123
- {
124
- Metric = "updater.started",
125
- Tags = { ["operation"] = "group_update_all_versions" },
126
- });
127
126
 
128
127
  // track original contents for later handling
129
128
  async Task TrackOriginalContentsAsync(string directory, string fileName)
@@ -147,59 +146,51 @@ public class RunWorker
147
146
  }
148
147
 
149
148
  // do update
150
- _logger.Info($"Running update in directory {repoDirectory}");
151
- foreach (var project in discoveryResult.Projects)
149
+ var updateOperations = GetUpdateOperations(discoveryResult).ToArray();
150
+ foreach (var updateOperation in updateOperations)
152
151
  {
153
- foreach (var dependency in project.Dependencies)
152
+ var dependency = updateOperation.Dependency;
153
+ if (!IsUpdateAllowed(job, dependency))
154
154
  {
155
- if (!IsUpdateAllowed(job, dependency))
156
- {
157
- continue;
158
- }
155
+ continue;
156
+ }
157
+
158
+ _logger.Info($"Updating [{dependency.Name}] in [{updateOperation.ProjectPath}]");
159
159
 
160
- var dependencyInfo = GetDependencyInfo(job, dependency);
161
- var analysisResult = await _analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
162
- // TODO: log analysisResult
163
- if (analysisResult.CanUpdate)
160
+ var dependencyInfo = GetDependencyInfo(job, dependency);
161
+ var analysisResult = await _analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
162
+ // TODO: log analysisResult
163
+ if (analysisResult.CanUpdate)
164
+ {
165
+ // TODO: this is inefficient, but not likely causing a bottleneck
166
+ var previousDependency = discoveredUpdatedDependencies.Dependencies
167
+ .Single(d => d.Name == dependency.Name && d.Requirements.Single().File == updateOperation.ProjectPath);
168
+ var updatedDependency = new ReportedDependency()
164
169
  {
165
- var dependencyLocation = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
166
-
167
- // TODO: this is inefficient, but not likely causing a bottleneck
168
- var previousDependency = discoveredUpdatedDependencies.Dependencies
169
- .Single(d => d.Name == dependency.Name && d.Requirements.Single().File == dependencyLocation);
170
- var updatedDependency = new ReportedDependency()
171
- {
172
- Name = dependency.Name,
173
- Version = analysisResult.UpdatedVersion,
174
- Requirements =
175
- [
176
- new ReportedRequirement()
177
- {
178
- File = dependencyLocation,
179
- Requirement = analysisResult.UpdatedVersion,
180
- Groups = previousDependency.Requirements.Single().Groups,
181
- Source = new RequirementSource()
182
- {
183
- SourceUrl = analysisResult.UpdatedDependencies.FirstOrDefault(d => d.Name == dependency.Name)?.InfoUrl,
184
- },
185
- }
186
- ],
187
- PreviousVersion = dependency.Version,
188
- PreviousRequirements = previousDependency.Requirements,
189
- };
190
-
191
- var dependencyFilePath = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
192
- var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, dependencyFilePath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: dependency.IsTransitive);
193
- // TODO: need to report if anything was actually updated
194
- if (updateResult.Error is null)
195
- {
196
- if (dependencyLocation != dependencyFilePath)
170
+ Name = dependency.Name,
171
+ Version = analysisResult.UpdatedVersion,
172
+ Requirements =
173
+ [
174
+ new ReportedRequirement()
197
175
  {
198
- updatedDependency.Requirements.All(r => r.File == dependencyFilePath);
176
+ File = updateOperation.ProjectPath,
177
+ Requirement = analysisResult.UpdatedVersion,
178
+ Groups = previousDependency.Requirements.Single().Groups,
179
+ Source = new RequirementSource()
180
+ {
181
+ SourceUrl = analysisResult.UpdatedDependencies.FirstOrDefault(d => d.Name == dependency.Name)?.InfoUrl,
182
+ },
199
183
  }
184
+ ],
185
+ PreviousVersion = dependency.Version,
186
+ PreviousRequirements = previousDependency.Requirements,
187
+ };
200
188
 
201
- actualUpdatedDependencies.Add(updatedDependency);
202
- }
189
+ var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, updateOperation.ProjectPath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: dependency.IsTransitive);
190
+ // TODO: need to report if anything was actually updated
191
+ if (updateResult.Error is null)
192
+ {
193
+ actualUpdatedDependencies.Add(updatedDependency);
203
194
  }
204
195
  }
205
196
  }
@@ -275,6 +266,52 @@ public class RunWorker
275
266
  return result;
276
267
  }
277
268
 
269
+ internal static IEnumerable<(string ProjectPath, Dependency Dependency)> GetUpdateOperations(WorkspaceDiscoveryResult discovery)
270
+ {
271
+ // discovery is grouped by project then dependency, but we want to pivot and return a list of update operations sorted by dependency name then project path
272
+
273
+ var updateOrder = new Dictionary<string, Dictionary<string, Dictionary<string, Dependency>>>(StringComparer.OrdinalIgnoreCase);
274
+ // <dependency name, <project path, specific dependencies>>
275
+
276
+ // collect
277
+ foreach (var project in discovery.Projects)
278
+ {
279
+ var projectPath = Path.Join(discovery.Path, project.FilePath).FullyNormalizedRootedPath();
280
+ foreach (var dependency in project.Dependencies)
281
+ {
282
+ var dependencyGroup = updateOrder.GetOrAdd(dependency.Name, () => new Dictionary<string, Dictionary<string, Dependency>>(PathComparer.Instance));
283
+ var dependenciesForProject = dependencyGroup.GetOrAdd(projectPath, () => new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase));
284
+ dependenciesForProject[dependency.Name] = dependency;
285
+ }
286
+ }
287
+
288
+ // return
289
+ foreach (var dependencyName in updateOrder.Keys.OrderBy(k => k, StringComparer.OrdinalIgnoreCase))
290
+ {
291
+ var projectDependencies = updateOrder[dependencyName];
292
+ foreach (var projectPath in projectDependencies.Keys.OrderBy(p => p, PathComparer.Instance))
293
+ {
294
+ var dependencies = projectDependencies[projectPath];
295
+ var dependency = dependencies[dependencyName];
296
+ yield return (projectPath, dependency);
297
+ }
298
+ }
299
+ }
300
+
301
+ internal static IncrementMetric GetIncrementMetric(Job job)
302
+ {
303
+ var isSecurityUpdate = job.AllowedUpdates.Any(a => a.UpdateType == UpdateType.Security) || job.SecurityUpdatesOnly;
304
+ var metricOperation = isSecurityUpdate ?
305
+ (job.UpdatingAPullRequest ? "update_security_pr" : "create_security_pr")
306
+ : (job.UpdatingAPullRequest ? "update_version_pr" : "group_update_all_versions");
307
+ var increment = new IncrementMetric()
308
+ {
309
+ Metric = "updater.started",
310
+ Tags = { ["operation"] = metricOperation },
311
+ };
312
+ return increment;
313
+ }
314
+
278
315
  internal static bool IsUpdateAllowed(Job job, Dependency dependency)
279
316
  {
280
317
  if (dependency.Name.Equals("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase))
@@ -409,23 +446,28 @@ public class RunWorker
409
446
  .Distinct()
410
447
  .OrderBy(p => p)
411
448
  .ToArray();
449
+ var orderedProjects = discoveryResult.Projects
450
+ .OrderBy(p => Path.Join(discoveryResult.Path, p.FilePath).FullyNormalizedRootedPath(), PathComparer.Instance)
451
+ .ToArray();
412
452
  var updatedDependencyList = new UpdatedDependencyList()
413
453
  {
414
- Dependencies = discoveryResult.Projects.SelectMany(p =>
454
+ Dependencies = orderedProjects.SelectMany(p =>
415
455
  {
416
- return p.Dependencies.Where(d => d.Version is not null).Select(d =>
417
- new ReportedDependency()
418
- {
419
- Name = d.Name,
420
- Requirements = [new ReportedRequirement()
456
+ return p.Dependencies
457
+ .Where(d => d.Version is not null)
458
+ .OrderBy(d => d.Name, StringComparer.OrdinalIgnoreCase)
459
+ .Select(d =>
460
+ new ReportedDependency()
421
461
  {
422
- File = GetFullRepoPath(p.FilePath),
423
- Requirement = d.Version!,
424
- Groups = ["dependencies"],
425
- }],
426
- Version = d.Version,
427
- }
428
- );
462
+ Name = d.Name,
463
+ Requirements = [new ReportedRequirement()
464
+ {
465
+ File = GetFullRepoPath(p.FilePath),
466
+ Requirement = d.Version!,
467
+ Groups = ["dependencies"],
468
+ }],
469
+ Version = d.Version,
470
+ });
429
471
  }).ToArray(),
430
472
  DependencyFiles = dependencyFiles,
431
473
  };
@@ -2,12 +2,7 @@
2
2
  <Import Project="DependencyDiscovery.props" />
3
3
 
4
4
  <Target Name="ReportTargetFramework">
5
- <!-- this property is for non-SDK projects, commonly with `packages.config -->
6
- <!-- e.g., returns ".NETFramework,Version=v4.5" -->
7
- <Message Text="ProjectData::TargetFrameworkVersion=$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)" Importance="High" Condition="'$(TargetFrameworkIdentifier)' != '' AND '' != '$(TargetFrameworkVersion)'" />
8
-
9
- <!-- these properties are for SDK projects -->
10
- <Message Text="ProjectData::TargetFramework=$(TargetFramework)" Importance="High" Condition="'$(TargetFramework)' != ''" />
5
+ <Message Text="ProjectData::TargetFrameworkMoniker=$(TargetFrameworkMoniker);ProjectData::TargetPlatformMoniker=$(TargetPlatformMoniker)" Importance="High" Condition="'$(TargetFrameworkMoniker)' != ''" />
11
6
  <Message Text="ProjectData::TargetFrameworks=$(TargetFrameworks)" Importance="High" Condition="'$(TargetFrameworks)' != ''" />
12
7
  </Target>
13
8
  </Project>
@@ -420,6 +420,8 @@ public class PackageManager
420
420
  }
421
421
  }
422
422
 
423
+ var projectFramework = NuGetFramework.Parse(targetFramework);
424
+
423
425
  // Get the parent packages of the package and check the compatibility between its family
424
426
  HashSet<PackageToUpdate> parentPackages = GetParentPackages(package);
425
427
 
@@ -458,7 +460,7 @@ public class PackageManager
458
460
  string currentVersionString = parent.CurrentVersion;
459
461
  NuGetVersion currentVersionParent = NuGetVersion.Parse(currentVersionString);
460
462
 
461
- var result = await VersionFinder.GetVersionsAsync(parent.PackageName, currentVersionParent, nugetContext, logger, CancellationToken.None);
463
+ var result = await VersionFinder.GetVersionsAsync([projectFramework], parent.PackageName, currentVersionParent, nugetContext, logger, CancellationToken.None);
462
464
  var versions = result.GetVersions();
463
465
  NuGetVersion latestVersion = versions.Where(v => !v.IsPrerelease).Max();
464
466
 
@@ -565,8 +567,9 @@ public class PackageManager
565
567
 
566
568
  // Create a NugetContext instance to get the latest versions of the parent
567
569
  NuGetContext nugetContext = new NuGetContext(Path.GetDirectoryName(projectPath));
570
+ var projectFramework = NuGetFramework.Parse(targetFramework);
568
571
 
569
- var result = await VersionFinder.GetVersionsAsync(possibleParent.PackageName, CurrentVersion, nugetContext, logger, CancellationToken.None);
572
+ var result = await VersionFinder.GetVersionsAsync([projectFramework], possibleParent.PackageName, CurrentVersion, nugetContext, logger, CancellationToken.None);
570
573
  var versions = result.GetVersions();
571
574
 
572
575
  // If there are no versions
@@ -801,40 +801,51 @@ internal static partial class MSBuildHelper
801
801
  logger.Warn($"Error determining target frameworks.\nSTDOUT:\n{stdOut}\nSTDERR:\n{stdErr}");
802
802
  }
803
803
 
804
- // There are 3 return values, all uses slightly differently. Only one will be set, the others will be blank
805
- // ProjectData::TargetFrameworkVersion=.NETFramework,Version=v4.5 // non-SDK projects, commonly with `packages.config`
806
- // ProjectData::TargetFramework=net7.0 // SDK-style projects
807
- // ProjectData::TargetFrameworks=net7.0;net8.0
808
- var tfmPatterns = new Regex[]
804
+ // There are 2 possible return values:
805
+ // 1. For SDK-style projects with a single TFM and legacy projects the output will look like:
806
+ // ProjectData::TargetFrameworkMoniker=.NETCoreApp,Version=8.0;ProjectData::TargetPlatformMoniker=Windows,Version=7.0
807
+ // 2. For SDK-style projects with multiple TFMs the output will look like:
808
+ // ProjectData::TargetFrameworks=net8.0;net9.0
809
+ var listedTargetFrameworks = new List<ValueTuple<string, string>>();
810
+ var listedTfmMatch = Regex.Match(stdOut, "ProjectData::TargetFrameworks=(?<TargetFrameworks>.*)$", RegexOptions.Multiline);
811
+ if (listedTfmMatch.Success)
809
812
  {
810
- new Regex("ProjectData::TargetFrameworkVersion=(?<Value>.*)$", RegexOptions.Multiline),
811
- new Regex("ProjectData::TargetFramework=(?<Value>.*)$", RegexOptions.Multiline),
812
- new Regex("ProjectData::TargetFrameworks=(?<Value>.*)$", RegexOptions.Multiline),
813
- };
814
- var tfms = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
815
- foreach (var tfmPattern in tfmPatterns)
813
+ var value = listedTfmMatch.Groups["TargetFrameworks"].Value;
814
+ var foundTfms = value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
815
+ .Select(tfm => ValueTuple.Create(tfm, string.Empty))
816
+ .ToArray();
817
+ listedTargetFrameworks.AddRange(foundTfms);
818
+ }
819
+
820
+ var individualTfmMatch = Regex.Match(stdOut, "ProjectData::TargetFrameworkMoniker=(?<TargetFrameworkMoniker>[^;]*);ProjectData::TargetPlatformMoniker=(?<TargetPlatformMoniker>.*)$", RegexOptions.Multiline);
821
+ if (individualTfmMatch.Success)
816
822
  {
817
- var candidateTfms = tfmPattern.Matches(stdOut)
818
- .Select(m => m.Groups["Value"].Value)
819
- .SelectMany(v => v.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
820
- .Where(v => !string.IsNullOrWhiteSpace(v))
821
- .Select(v =>
822
- {
823
- try
824
- {
825
- var framework = NuGetFramework.Parse(v);
826
- return framework.GetShortFolderName();
827
- }
828
- catch
829
- {
830
- return string.Empty;
831
- }
832
- })
833
- .Where(tfm => !string.IsNullOrEmpty(tfm));
834
- tfms.AddRange(candidateTfms);
823
+ var tfm = individualTfmMatch.Groups["TargetFrameworkMoniker"].Value;
824
+ var tpm = individualTfmMatch.Groups["TargetPlatformMoniker"].Value;
825
+ listedTargetFrameworks.Add(ValueTuple.Create(tfm, tpm));
835
826
  }
836
827
 
837
- return tfms.ToImmutableArray();
828
+ var tfms = listedTargetFrameworks.Select(tfpm =>
829
+ {
830
+ try
831
+ {
832
+ // Item2 is an optional component that looks like: "Windows,Version=7.0"
833
+ var framework = string.IsNullOrWhiteSpace(tfpm.Item2)
834
+ ? NuGetFramework.Parse(tfpm.Item1)
835
+ : NuGetFramework.ParseComponents(tfpm.Item1, tfpm.Item2);
836
+ return framework;
837
+ }
838
+ catch
839
+ {
840
+ return null;
841
+ }
842
+ })
843
+ .Where(tfm => tfm is not null)
844
+ .Select(tfm => tfm!.GetShortFolderName())
845
+ .OrderBy(tfm => tfm)
846
+ .ToImmutableArray();
847
+
848
+ return tfms;
838
849
  }
839
850
 
840
851
  internal static async Task<Dependency[]> GetAllPackageDependenciesAsync(
@@ -2,28 +2,35 @@ using System.Diagnostics.CodeAnalysis;
2
2
 
3
3
  namespace NuGetUpdater.Core.Utilities;
4
4
 
5
- public class PathComparer : IEqualityComparer<string>
5
+ public class PathComparer : IComparer<string>, IEqualityComparer<string>
6
6
  {
7
7
  public static PathComparer Instance { get; } = new PathComparer();
8
8
 
9
- public bool Equals(string? x, string? y)
9
+ public int Compare(string? x, string? y)
10
10
  {
11
11
  x = x?.NormalizePathToUnix();
12
12
  y = y?.NormalizePathToUnix();
13
13
 
14
14
  if (x is null && y is null)
15
15
  {
16
- return true;
16
+ return 0;
17
17
  }
18
18
 
19
- if (x is null || y is null)
19
+ if (x is null)
20
20
  {
21
- return false;
21
+ return -1;
22
22
  }
23
23
 
24
- return x.Equals(y, StringComparison.OrdinalIgnoreCase);
24
+ if (y is null)
25
+ {
26
+ return 1;
27
+ }
28
+
29
+ return x.CompareTo(y);
25
30
  }
26
31
 
32
+ public bool Equals(string? x, string? y) => Compare(x, y) == 0;
33
+
27
34
  public int GetHashCode([DisallowNull] string obj)
28
35
  {
29
36
  return obj.NormalizePathToUnix().GetHashCode();