dependabot-nuget 0.292.0 → 0.293.0

Sign up to get free protection for your applications and to get access to all the features.
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
  {