dependabot-nuget 0.292.0 → 0.293.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.gitignore +1 -0
  3. data/helpers/lib/NuGetUpdater/Directory.Packages.props +1 -0
  4. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Correlator.cs +197 -0
  5. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/DotNetPackageCorrelation.csproj +12 -0
  6. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageMapper.cs +68 -0
  7. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageSet.cs +11 -0
  8. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/Release.cs +25 -0
  9. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/ReleasesFile.cs +9 -0
  10. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/RuntimePackages.cs +11 -0
  11. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/Sdk.cs +13 -0
  12. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/SemVerComparer.cs +16 -0
  13. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/SemVersionConverter.cs +42 -0
  14. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/DotNetPackageCorrelation.Cli.csproj +16 -0
  15. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/Program.cs +32 -0
  16. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/CorrelatorTests.cs +99 -0
  17. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/DotNetPackageCorrelation.Test.csproj +18 -0
  18. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/EndToEndTests.cs +30 -0
  19. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/RuntimePackagesTests.cs +206 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +6 -4
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +8 -7
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +4 -4
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +17 -5
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +7 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +46 -6
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +8 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +8 -17
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +4 -4
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Extensions.cs +1 -1
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +2 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +2 -2
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +7 -20
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +3 -3
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +99 -2
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EnsureDotNetPackageCorrelation.targets +25 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +9 -22
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +5 -1
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/MissingFileException.cs +2 -1
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NativeResult.cs +3 -3
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +3 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +7 -3
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotParseable.cs +15 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +24 -0
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +6 -21
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/UnparseableFileException.cs +12 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +21 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +6 -30
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DotNetPackageCorrelationManager.cs +46 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +51 -27
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +15 -4
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +15 -9
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +20 -12
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +108 -0
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +16 -12
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -1
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +15 -28
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +5 -4
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +9 -1
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestBase.cs +24 -0
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/ExpectedUpdateOperationResult.cs +1 -1
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +11 -15
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +1 -1
  64. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +14 -8
  65. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +148 -3
  66. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +12 -14
  67. data/helpers/lib/NuGetUpdater/NuGetUpdater.sln +18 -1
  68. data/lib/dependabot/nuget/native_helpers.rb +41 -16
  69. metadata +25 -6
  70. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +0 -12
@@ -70,11 +70,12 @@ public class CloneWorker
70
70
  catch (HttpRequestException ex)
71
71
  when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
72
72
  {
73
+ // this is a _very_ specific case we want to handle before the common error handling kicks in
73
74
  error = new JobRepoNotFound(ex.Message);
74
75
  }
75
76
  catch (Exception ex)
76
77
  {
77
- error = new UnknownError(ex, _jobId);
78
+ error = JobErrorBase.ErrorFromException(ex, _jobId, repoContentsPath);
78
79
  }
79
80
 
80
81
  if (error is not null)
@@ -1,9 +1,9 @@
1
1
  <Project>
2
2
  <Import Project="DependencyDiscovery.props" />
3
3
 
4
- <Target Name="_DiscoverDependencies" DependsOnTargets="GenerateBuildDependencyFile;ResolvePackageAssets">
4
+ <Target Name="_DiscoverDependencies" DependsOnTargets="ResolveAssemblyReferences;GenerateBuildDependencyFile;ResolvePackageAssets">
5
5
  <!--
6
- The target GenerateBuildDependencyFile is sufficient for projects targeting .NET Standard or .NET Core.
6
+ The targets ResolveAssemblyReferences and GenerateBuildDependencyFile are sufficient for projects targeting .NET Standard or .NET Core.
7
7
  The target ResolvePackageAssets is necessary for projects targeting .NET Framework.
8
8
  -->
9
9
  </Target>
@@ -1,5 +1,4 @@
1
1
  using System.Collections.Immutable;
2
- using System.Net;
3
2
  using System.Text.Json;
4
3
  using System.Text.Json.Serialization;
5
4
 
@@ -10,7 +9,7 @@ using Microsoft.Build.Exceptions;
10
9
 
11
10
  using NuGet.Frameworks;
12
11
 
13
- using NuGetUpdater.Core.Analyze;
12
+ using NuGetUpdater.Core.Run.ApiModel;
14
13
  using NuGetUpdater.Core.Utilities;
15
14
 
16
15
  namespace NuGetUpdater.Core.Discover;
@@ -19,6 +18,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
19
18
  {
20
19
  public const string DiscoveryResultFileName = "./.dependabot/discovery.json";
21
20
 
21
+ private readonly string _jobId;
22
22
  private readonly ExperimentsManager _experimentsManager;
23
23
  private readonly ILogger _logger;
24
24
  private readonly HashSet<string> _processedProjectPaths = new(StringComparer.Ordinal); private readonly HashSet<string> _restoredMSBuildSdks = new(StringComparer.OrdinalIgnoreCase);
@@ -29,8 +29,9 @@ public partial class DiscoveryWorker : IDiscoveryWorker
29
29
  Converters = { new JsonStringEnumConverter() },
30
30
  };
31
31
 
32
- public DiscoveryWorker(ExperimentsManager experimentsManager, ILogger logger)
32
+ public DiscoveryWorker(string jobId, ExperimentsManager experimentsManager, ILogger logger)
33
33
  {
34
+ _jobId = jobId;
34
35
  _experimentsManager = experimentsManager;
35
36
  _logger = logger;
36
37
  }
@@ -48,23 +49,11 @@ public partial class DiscoveryWorker : IDiscoveryWorker
48
49
  {
49
50
  result = await RunAsync(repoRootPath, workspacePath);
50
51
  }
51
- catch (HttpRequestException ex)
52
- when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
53
- {
54
- result = new WorkspaceDiscoveryResult
55
- {
56
- ErrorType = ErrorType.AuthenticationFailure,
57
- ErrorDetails = "(" + string.Join("|", NuGetContext.GetPackageSourceUrls(PathHelper.JoinPath(repoRootPath, workspacePath))) + ")",
58
- Path = workspacePath,
59
- Projects = [],
60
- };
61
- }
62
52
  catch (Exception ex)
63
53
  {
64
54
  result = new WorkspaceDiscoveryResult
65
55
  {
66
- ErrorType = ErrorType.Unknown,
67
- ErrorDetails = ex.ToString(),
56
+ Error = JobErrorBase.ErrorFromException(ex, _jobId, PathHelper.JoinPath(repoRootPath, workspacePath)),
68
57
  Path = workspacePath,
69
58
  Projects = [],
70
59
  };
@@ -123,8 +112,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
123
112
  DotNetToolsJson = null,
124
113
  GlobalJson = null,
125
114
  Projects = projectResults.Where(p => p.IsSuccess).OrderBy(p => p.FilePath).ToImmutableArray(),
126
- ErrorType = failedProjectResult.ErrorType,
127
- ErrorDetails = failedProjectResult.FilePath,
115
+ Error = failedProjectResult.Error,
128
116
  IsSuccess = false,
129
117
  };
130
118
 
@@ -192,8 +180,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
192
180
  ImportedFiles = ImmutableArray<string>.Empty,
193
181
  AdditionalFiles = ImmutableArray<string>.Empty,
194
182
  IsSuccess = false,
195
- ErrorType = ErrorType.DependencyFileNotParseable,
196
- ErrorDetails = "Failed to parse project file found at " + invalidProjectFile,
183
+ Error = new DependencyFileNotParseable(invalidProjectFile),
197
184
  }];
198
185
  }
199
186
  if (projects.IsEmpty)
@@ -1,5 +1,6 @@
1
1
  using System.Collections.Immutable;
2
- using System.Text.Json.Serialization;
2
+
3
+ using NuGetUpdater.Core.Run.ApiModel;
3
4
 
4
5
  namespace NuGetUpdater.Core.Discover;
5
6
 
@@ -8,8 +9,7 @@ public record ProjectDiscoveryResult : IDiscoveryResultWithDependencies
8
9
  public required string FilePath { get; init; }
9
10
  public required ImmutableArray<Dependency> Dependencies { get; init; }
10
11
  public bool IsSuccess { get; init; } = true;
11
- public string? ErrorDetails { get; init; }
12
- public ErrorType? ErrorType { get; init; }
12
+ public JobErrorBase? Error { get; init; } = null;
13
13
  public ImmutableArray<Property> Properties { get; init; } = [];
14
14
  public ImmutableArray<string> TargetFrameworks { get; init; } = [];
15
15
  public ImmutableArray<string> ReferencedProjectPaths { get; init; } = [];
@@ -9,6 +9,8 @@ using NuGet.Versioning;
9
9
 
10
10
  using NuGetUpdater.Core.Utilities;
11
11
 
12
+ using Semver;
13
+
12
14
  using LoggerProperty = Microsoft.Build.Logging.StructuredLogger.Property;
13
15
 
14
16
  namespace NuGetUpdater.Core.Discover;
@@ -72,6 +74,9 @@ internal static class SdkProjectDiscovery
72
74
  Dictionary<string, HashSet<string>> topLevelPackagesPerProject = new(PathComparer.Instance);
73
75
  // projectPath, packageNames
74
76
 
77
+ Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesReplacedBySdkPerProject = new(PathComparer.Instance);
78
+ // projectPath tfm packageName, packageVersion
79
+
75
80
  Dictionary<string, Dictionary<string, string>> resolvedProperties = new(PathComparer.Instance);
76
81
  // projectPath propertyName, propertyValue
77
82
 
@@ -164,7 +169,7 @@ internal static class SdkProjectDiscovery
164
169
  }
165
170
  break;
166
171
  case NamedNode namedNode when namedNode is AddItem or RemoveItem:
167
- ProcessResolvedPackageReference(namedNode, packagesPerProject, topLevelPackagesPerProject);
172
+ ProcessResolvedPackageReference(namedNode, packagesPerProject, topLevelPackagesPerProject, experimentsManager);
168
173
 
169
174
  if (namedNode is AddItem addItem)
170
175
  {
@@ -203,6 +208,70 @@ internal static class SdkProjectDiscovery
203
208
  }
204
209
  }
205
210
  break;
211
+ case Target target when target.Name == "_HandlePackageFileConflicts":
212
+ // this only works if we've installed the exact SDK required
213
+ if (experimentsManager.InstallDotnetSdks)
214
+ {
215
+ var projectEvaluation = GetNearestProjectEvaluation(target);
216
+ if (projectEvaluation is null)
217
+ {
218
+ break;
219
+ }
220
+
221
+ var removedReferences = target.Children.OfType<RemoveItem>().FirstOrDefault(r => r.Name == "Reference");
222
+ var addedReferences = target.Children.OfType<AddItem>().FirstOrDefault(r => r.Name == "Reference");
223
+ if (removedReferences is null || addedReferences is null)
224
+ {
225
+ break;
226
+ }
227
+
228
+ foreach (var removedAssembly in removedReferences.Children.OfType<Item>())
229
+ {
230
+ var removedPackageName = GetChildMetadataValue(removedAssembly, "NuGetPackageId");
231
+ var removedFileName = Path.GetFileName(removedAssembly.Name);
232
+ if (removedPackageName is null || removedFileName is null)
233
+ {
234
+ continue;
235
+ }
236
+
237
+ var existingProjectPackagesByTfm = packagesPerProject.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
238
+ var existingProjectPackages = existingProjectPackagesByTfm.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
239
+ if (!existingProjectPackages.ContainsKey(removedPackageName))
240
+ {
241
+ continue;
242
+ }
243
+
244
+ var correspondingAddedFile = addedReferences.Children.OfType<Item>()
245
+ .FirstOrDefault(i => removedFileName.Equals(Path.GetFileName(i.Name), StringComparison.OrdinalIgnoreCase));
246
+ if (correspondingAddedFile is null)
247
+ {
248
+ continue;
249
+ }
250
+
251
+ var runtimePackageName = GetChildMetadataValue(correspondingAddedFile, "NuGetPackageId");
252
+ var runtimePackageVersion = GetChildMetadataValue(correspondingAddedFile, "NuGetPackageVersion");
253
+ if (runtimePackageName is null ||
254
+ runtimePackageVersion is null ||
255
+ !SemVersion.TryParse(runtimePackageVersion, out var parsedRuntimePackageVersion))
256
+ {
257
+ continue;
258
+ }
259
+
260
+ var packageMapper = DotNetPackageCorrelationManager.GetPackageMapper();
261
+ var replacementPackageVersion = packageMapper.GetPackageVersionThatShippedWithOtherPackage(runtimePackageName, parsedRuntimePackageVersion, removedPackageName);
262
+ if (replacementPackageVersion is null)
263
+ {
264
+ continue;
265
+ }
266
+
267
+ var packagesPerThisProject = packagesReplacedBySdkPerProject.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
268
+ var packagesPerTfm = packagesPerThisProject.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
269
+ packagesPerTfm[removedPackageName] = replacementPackageVersion.ToString();
270
+ var relativeProjectPath = Path.GetRelativePath(repoRootPath, projectEvaluation.ProjectFile).NormalizePathToUnix();
271
+ logger.Info($"Re-added SDK managed package [{removedPackageName}/{replacementPackageVersion}] to project [{relativeProjectPath}]");
272
+ }
273
+ }
274
+ break;
206
275
  }
207
276
  }, takeChildrenSnapshot: true);
208
277
  }
@@ -228,6 +297,33 @@ internal static class SdkProjectDiscovery
228
297
  {
229
298
  // gather some project-level information
230
299
  var packagesByTfm = packagesPerProject[projectPath];
300
+ if (packagesReplacedBySdkPerProject.TryGetValue(projectPath, out var packagesReplacedBySdk))
301
+ {
302
+ var consolidatedPackagesByTfm = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
303
+
304
+ // copy the first dictionary
305
+ foreach (var kvp in packagesByTfm)
306
+ {
307
+ var tfm = kvp.Key;
308
+ var packages = kvp.Value;
309
+ consolidatedPackagesByTfm[tfm] = packages;
310
+ }
311
+
312
+ // merge in the second
313
+ foreach (var kvp in packagesReplacedBySdk)
314
+ {
315
+ var tfm = kvp.Key;
316
+ var packages = kvp.Value;
317
+ var replacedPackages = consolidatedPackagesByTfm.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
318
+ foreach (var packagePair in packages)
319
+ {
320
+ replacedPackages[packagePair.Key] = packagePair.Value;
321
+ }
322
+ }
323
+
324
+ packagesByTfm = consolidatedPackagesByTfm;
325
+ }
326
+
231
327
  var projectFullDirectory = Path.GetDirectoryName(projectPath)!;
232
328
  var doc = XDocument.Load(projectPath);
233
329
  var localPropertyDefinitionElements = doc.Root!.XPathSelectElements("/Project/PropertyGroup/*");
@@ -288,7 +384,8 @@ internal static class SdkProjectDiscovery
288
384
  private static void ProcessResolvedPackageReference(
289
385
  NamedNode node,
290
386
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject, // projectPath -> tfm -> (packageName, packageVersion)
291
- Dictionary<string, HashSet<string>> topLevelPackagesPerProject
387
+ Dictionary<string, HashSet<string>> topLevelPackagesPerProject,
388
+ ExperimentsManager experimentsManager
292
389
  )
293
390
  {
294
391
  var doRemoveOperation = node is RemoveItem;
@@ -0,0 +1,25 @@
1
+ <Project>
2
+
3
+ <PropertyGroup>
4
+ <PackageCorrelationCliDirectory>$(MSBuildThisFileDirectory)..\DotNetPackageCorrelation.Cli</PackageCorrelationCliDirectory>
5
+ <DotNetCoreDirectory>$(MSBuildThisFileDirectory)..\..\dotnet-core</DotNetCoreDirectory>
6
+ <PackageCorrelationFile>$(MSBuildThisFileDirectory)dotnet-package-correlation.json</PackageCorrelationFile>
7
+ </PropertyGroup>
8
+
9
+ <ItemGroup>
10
+ <None Include="$(PackageCorrelationFile)" CopyToOutputDirectory="PreserveNewest" />
11
+ <_DotNetCoreFiles Include="$(DotNetCoreDirectory)\**\*" />
12
+ </ItemGroup>
13
+
14
+ <Target Name="BuildDotNetPackageCorrelationFile" Inputs="@(_DotNetCoreFiles)" Outputs="$(PackageCorrelationFile)" BeforeTargets="GetCopyToOutputDirectoryItems">
15
+ <Exec Command="dotnet run --core-location &quot;$(DotNetCoreDirectory)&quot; --output &quot;$(PackageCorrelationFile)&quot;" WorkingDirectory="$(PackageCorrelationCliDirectory)" />
16
+ </Target>
17
+
18
+ <Target Name="CleanDotNetPackageCorrelationFile" BeforeTargets="Clean">
19
+ <Delete Files="$(PackageCorrelationFile)" Condition="Exists('$(PackageCorrelationFile)')" />
20
+ </Target>
21
+
22
+ <Target Name="RebuildDotNetPackageCorrelationFile" BeforeTargets="Rebuild" DependsOnTargets="CleanDotNetPackageCorrelationFile">
23
+ </Target>
24
+
25
+ </Project>
@@ -1,6 +1,7 @@
1
1
  using System.Text.Json;
2
2
 
3
3
  using NuGetUpdater.Core.Run;
4
+ using NuGetUpdater.Core.Run.ApiModel;
4
5
 
5
6
  namespace NuGetUpdater.Core;
6
7
 
@@ -30,42 +31,28 @@ public record ExperimentsManager
30
31
  };
31
32
  }
32
33
 
33
- public static async Task<(ExperimentsManager ExperimentsManager, NativeResult? ErrorResult)> FromJobFileAsync(string jobFilePath)
34
+ public static async Task<(ExperimentsManager ExperimentsManager, JobErrorBase? Error)> FromJobFileAsync(string jobId, string jobFilePath)
34
35
  {
35
36
  var experimentsManager = new ExperimentsManager();
36
- NativeResult? errorResult = null;
37
+ JobErrorBase? error = null;
38
+ var jobFileContent = string.Empty;
37
39
  try
38
40
  {
39
- var jobFileContent = await File.ReadAllTextAsync(jobFilePath);
41
+ jobFileContent = await File.ReadAllTextAsync(jobFilePath);
40
42
  var jobWrapper = RunWorker.Deserialize(jobFileContent);
41
43
  experimentsManager = GetExperimentsManager(jobWrapper.Job.Experiments);
42
44
  }
43
- catch (BadRequirementException ex)
44
- {
45
- errorResult = new NativeResult
46
- {
47
- ErrorType = ErrorType.BadRequirement,
48
- ErrorDetails = ex.Message,
49
- };
50
- }
51
45
  catch (JsonException ex)
52
46
  {
53
- errorResult = new NativeResult
54
- {
55
- ErrorType = ErrorType.Unknown,
56
- ErrorDetails = $"Error deserializing job file: {ex}: {File.ReadAllText(jobFilePath)}",
57
- };
47
+ // this is a very specific case where we want to log the JSON contents for easier debugging
48
+ error = JobErrorBase.ErrorFromException(new NotSupportedException($"Error deserializing job file contents: {jobFileContent}", ex), jobId, Environment.CurrentDirectory); // TODO
58
49
  }
59
50
  catch (Exception ex)
60
51
  {
61
- errorResult = new NativeResult
62
- {
63
- ErrorType = ErrorType.Unknown,
64
- ErrorDetails = ex.ToString(),
65
- };
52
+ error = JobErrorBase.ErrorFromException(ex, jobId, Environment.CurrentDirectory); // TODO
66
53
  }
67
54
 
68
- return (experimentsManager, errorResult);
55
+ return (experimentsManager, error);
69
56
  }
70
57
 
71
58
  private static bool IsEnabled(Dictionary<string, object>? experiments, string experimentName)
@@ -13,12 +13,16 @@ internal sealed class PackagesConfigBuildFile : XmlBuildFile
13
13
  public PackagesConfigBuildFile(string basePath, string path, XmlDocumentSyntax contents)
14
14
  : base(basePath, path, contents)
15
15
  {
16
+ var invalidPackageElements = Packages.Where(p => p.GetAttribute("id") is null || p.GetAttribute("version") is null).ToList();
17
+ if (invalidPackageElements.Any())
18
+ {
19
+ throw new UnparseableFileException("`package` element missing `id` or `version` attributes", path);
20
+ }
16
21
  }
17
22
 
18
23
  public IEnumerable<IXmlElementSyntax> Packages => Contents.RootSyntax.GetElements("package", StringComparison.OrdinalIgnoreCase);
19
24
 
20
25
  public IEnumerable<Dependency> GetDependencies() => Packages
21
- .Where(p => p.GetAttribute("id") is not null && p.GetAttribute("version") is not null)
22
26
  .Select(p => new Dependency(
23
27
  p.GetAttributeValue("id", StringComparison.OrdinalIgnoreCase),
24
28
  p.GetAttributeValue("version", StringComparison.OrdinalIgnoreCase),
@@ -4,7 +4,8 @@ internal class MissingFileException : Exception
4
4
  {
5
5
  public string FilePath { get; }
6
6
 
7
- public MissingFileException(string filePath)
7
+ public MissingFileException(string filePath, string? message = null)
8
+ : base(message)
8
9
  {
9
10
  FilePath = filePath;
10
11
  }
@@ -1,8 +1,8 @@
1
+ using NuGetUpdater.Core.Run.ApiModel;
2
+
1
3
  namespace NuGetUpdater.Core;
2
4
 
3
5
  public record NativeResult
4
6
  {
5
- // TODO: nullable not required, `ErrorType.None` is the default anyway
6
- public ErrorType? ErrorType { get; init; }
7
- public object? ErrorDetails { get; init; }
7
+ public JobErrorBase? Error { get; init; } = null;
8
8
  }
@@ -14,6 +14,7 @@
14
14
  </ItemGroup>
15
15
 
16
16
  <ItemGroup>
17
+ <ProjectReference Include="..\DotNetPackageCorrelation\DotNetPackageCorrelation.csproj" />
17
18
  <ProjectReference Include="..\NuGetProjects\NuGet.CommandLine\NuGet.CommandLine.csproj" />
18
19
  </ItemGroup>
19
20
 
@@ -31,4 +32,6 @@
31
32
  <InternalsVisibleTo Include="NuGetUpdater.Core.Test" />
32
33
  </ItemGroup>
33
34
 
35
+ <Import Project="EnsureDotNetPackageCorrelation.targets" />
36
+
34
37
  </Project>
@@ -2,10 +2,14 @@ namespace NuGetUpdater.Core.Run.ApiModel;
2
2
 
3
3
  public record DependencyFileNotFound : JobErrorBase
4
4
  {
5
- public DependencyFileNotFound(string message, string filePath)
5
+ public DependencyFileNotFound(string filePath, string? message = null)
6
6
  : base("dependency_file_not_found")
7
7
  {
8
- Details["message"] = message;
9
- Details["file-path"] = filePath;
8
+ if (message is not null)
9
+ {
10
+ Details["message"] = message;
11
+ }
12
+
13
+ Details["file-path"] = filePath.NormalizePathToUnix();
10
14
  }
11
15
  }
@@ -0,0 +1,15 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record DependencyFileNotParseable : JobErrorBase
4
+ {
5
+ public DependencyFileNotParseable(string filePath, string? message = null)
6
+ : base("dependency_file_not_parseable")
7
+ {
8
+ if (message is not null)
9
+ {
10
+ Details["message"] = message;
11
+ }
12
+
13
+ Details["file-path"] = filePath.NormalizePathToUnix();
14
+ }
15
+ }
@@ -1,5 +1,10 @@
1
+ using System.Net;
1
2
  using System.Text.Json.Serialization;
2
3
 
4
+ using Microsoft.Build.Exceptions;
5
+
6
+ using NuGetUpdater.Core.Analyze;
7
+
3
8
  namespace NuGetUpdater.Core.Run.ApiModel;
4
9
 
5
10
  public abstract record JobErrorBase
@@ -14,4 +19,23 @@ public abstract record JobErrorBase
14
19
 
15
20
  [JsonPropertyName("error-details")]
16
21
  public Dictionary<string, object> Details { get; init; } = new();
22
+
23
+ public static JobErrorBase ErrorFromException(Exception ex, string jobId, string currentDirectory)
24
+ {
25
+ return ex switch
26
+ {
27
+ BadRequirementException badRequirement => new BadRequirement(badRequirement.Message),
28
+ HttpRequestException httpRequest => httpRequest.StatusCode switch
29
+ {
30
+ HttpStatusCode.Unauthorized or
31
+ HttpStatusCode.Forbidden => new PrivateSourceAuthenticationFailure(NuGetContext.GetPackageSourceUrls(currentDirectory)),
32
+ _ => new UnknownError(ex, jobId),
33
+ },
34
+ InvalidProjectFileException invalidProjectFile => new DependencyFileNotParseable(invalidProjectFile.ProjectFile),
35
+ MissingFileException missingFile => new DependencyFileNotFound(missingFile.FilePath, missingFile.Message),
36
+ UnparseableFileException unparseableFile => new DependencyFileNotParseable(unparseableFile.FilePath, unparseableFile.Message),
37
+ UpdateNotPossibleException updateNotPossible => new UpdateNotPossible(updateNotPossible.Dependencies),
38
+ _ => new UnknownError(ex, jobId),
39
+ };
40
+ }
17
41
  }
@@ -53,7 +53,7 @@ public class RunWorker
53
53
  private async Task<RunResult> RunWithErrorHandlingAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha)
54
54
  {
55
55
  JobErrorBase? error = null;
56
- string[] lastUsedPackageSourceUrls = []; // used for error reporting below
56
+ var currentDirectory = repoContentsPath.FullName; // used for error reporting below
57
57
  var runResult = new RunResult()
58
58
  {
59
59
  Base64DependencyFiles = [],
@@ -69,7 +69,7 @@ public class RunWorker
69
69
  foreach (var directory in job.GetAllDirectories())
70
70
  {
71
71
  var localPath = PathHelper.JoinPath(repoContentsPath.FullName, directory);
72
- lastUsedPackageSourceUrls = NuGetContext.GetPackageSourceUrls(localPath);
72
+ currentDirectory = localPath;
73
73
  var result = await RunForDirectory(job, repoContentsPath, directory, baseCommitSha, experimentsManager);
74
74
  foreach (var dependencyFile in result.Base64DependencyFiles)
75
75
  {
@@ -84,26 +84,9 @@ public class RunWorker
84
84
  BaseCommitSha = baseCommitSha,
85
85
  };
86
86
  }
87
- catch (HttpRequestException ex)
88
- when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
89
- {
90
- error = new PrivateSourceAuthenticationFailure(lastUsedPackageSourceUrls);
91
- }
92
- catch (BadRequirementException ex)
93
- {
94
- error = new BadRequirement(ex.Message);
95
- }
96
- catch (MissingFileException ex)
97
- {
98
- error = new DependencyFileNotFound("file not found", ex.FilePath);
99
- }
100
- catch (UpdateNotPossibleException ex)
101
- {
102
- error = new UpdateNotPossible(ex.Dependencies);
103
- }
104
87
  catch (Exception ex)
105
88
  {
106
- error = new UnknownError(ex, _jobId);
89
+ error = JobErrorBase.ErrorFromException(ex, _jobId, currentDirectory);
107
90
  }
108
91
 
109
92
  if (error is not null)
@@ -123,6 +106,8 @@ public class RunWorker
123
106
  _logger.Info("Discovery JSON content:");
124
107
  _logger.Info(JsonSerializer.Serialize(discoveryResult, DiscoveryWorker.SerializerOptions));
125
108
 
109
+ // TODO: report errors
110
+
126
111
  // report dependencies
127
112
  var discoveredUpdatedDependencies = GetUpdatedDependencyListFromDiscovery(discoveryResult, repoContentsPath.FullName);
128
113
  await _apiHandler.UpdateDependencyList(discoveredUpdatedDependencies);
@@ -221,7 +206,7 @@ public class RunWorker
221
206
  var dependencyFilePath = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
222
207
  var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, dependencyFilePath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: false);
223
208
  // TODO: need to report if anything was actually updated
224
- if (updateResult.ErrorType is null || updateResult.ErrorType == ErrorType.None)
209
+ if (updateResult.Error is null)
225
210
  {
226
211
  if (dependencyLocation != dependencyFilePath)
227
212
  {
@@ -0,0 +1,12 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ internal class UnparseableFileException : Exception
4
+ {
5
+ public string FilePath { get; }
6
+
7
+ public UnparseableFileException(string message, string filePath)
8
+ : base(message)
9
+ {
10
+ FilePath = filePath;
11
+ }
12
+ }
@@ -4,6 +4,8 @@ using Microsoft.Language.Xml;
4
4
 
5
5
  using NuGet.Versioning;
6
6
 
7
+ using NuGetUpdater.Core.Utilities;
8
+
7
9
  namespace NuGetUpdater.Core;
8
10
 
9
11
  /// <summary>
@@ -36,6 +38,25 @@ internal static class PackageReferenceUpdater
36
38
 
37
39
  // Get the set of all top-level dependencies in the current project
38
40
  var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
41
+ var isDependencyTopLevel = topLevelDependencies.Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
42
+ if (isDependencyTopLevel)
43
+ {
44
+ var packageMapper = DotNetPackageCorrelationManager.GetPackageMapper();
45
+ // TODO: this is slow
46
+ var isSdkReplacementPackage = packageMapper.RuntimePackages.Runtimes.Any(r =>
47
+ {
48
+ return r.Value.Packages.Any(p => dependencyName.Equals(p.Key, StringComparison.Ordinal));
49
+ });
50
+ if (isSdkReplacementPackage)
51
+ {
52
+ // If we're updating a top level SDK replacement package, the version listed in the project file won't
53
+ // necessarily match the resolved version that caused the update because the SDK might have replaced
54
+ // the package. To handle this scenario, we pretend the version we're searching for is the actual
55
+ // version in the file, not the resolved version. This allows us to keep a strict equality check when
56
+ // finding the file to update.
57
+ previousDependencyVersion = topLevelDependencies.First(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase)).Version!;
58
+ }
59
+ }
39
60
 
40
61
  if (!await DoesDependencyRequireUpdateAsync(repoRootPath, projectPath, tfms, topLevelDependencies, dependencyName, newDependencyVersion, experimentsManager, logger))
41
62
  {