dependabot-nuget 0.292.0 → 0.294.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.gitignore +1 -0
  3. data/helpers/lib/NuGetUpdater/Directory.Packages.props +2 -1
  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/Analyze/VersionFinder.cs +16 -5
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +2 -1
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +2 -2
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +7 -20
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +3 -3
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +99 -2
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EnsureDotNetPackageCorrelation.targets +25 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +9 -22
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +5 -1
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/MissingFileException.cs +2 -1
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NativeResult.cs +3 -3
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +3 -0
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Advisory.cs +2 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +7 -3
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotParseable.cs +15 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +24 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +33 -30
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/UnparseableFileException.cs +12 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +21 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +6 -30
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DotNetPackageCorrelationManager.cs +46 -0
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +51 -27
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +15 -4
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +70 -9
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +20 -12
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +108 -0
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +16 -12
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -1
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +15 -28
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +61 -0
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +5 -4
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +9 -1
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestBase.cs +24 -0
  64. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/ExpectedUpdateOperationResult.cs +1 -1
  65. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +11 -15
  66. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +1 -1
  67. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +14 -8
  68. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +148 -3
  69. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +12 -14
  70. data/helpers/lib/NuGetUpdater/NuGetUpdater.sln +18 -1
  71. data/lib/dependabot/nuget/native_helpers.rb +41 -16
  72. metadata +25 -6
  73. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +0 -12
@@ -113,11 +113,22 @@ internal static class VersionFinder
113
113
  ? versionRange.MinVersion
114
114
  : null;
115
115
 
116
- return version => (currentVersion is null || version > currentVersion)
117
- && versionRange.Satisfies(version)
118
- && (currentVersion is null || !currentVersion.IsPrerelease || !version.IsPrerelease || version.Version == currentVersion.Version)
119
- && !dependencyInfo.IgnoredVersions.Any(r => r.IsSatisfiedBy(version))
120
- && !dependencyInfo.Vulnerabilities.Any(v => v.IsVulnerable(version));
116
+ var safeVersions = dependencyInfo.Vulnerabilities.SelectMany(v => v.SafeVersions).ToList();
117
+ return version =>
118
+ {
119
+ var versionGreaterThanCurrent = currentVersion is null || version > currentVersion;
120
+ var rangeSatisfies = versionRange.Satisfies(version);
121
+ var prereleaseTypeMatches = currentVersion is null || !currentVersion.IsPrerelease || !version.IsPrerelease || version.Version == currentVersion.Version;
122
+ var isIgnoredVersion = dependencyInfo.IgnoredVersions.Any(i => i.IsSatisfiedBy(version));
123
+ var isVulnerableVersion = dependencyInfo.Vulnerabilities.Any(v => v.IsVulnerable(version));
124
+ var isSafeVersion = !safeVersions.Any() || safeVersions.Any(s => s.IsSatisfiedBy(version));
125
+ return versionGreaterThanCurrent
126
+ && rangeSatisfies
127
+ && prereleaseTypeMatches
128
+ && !isIgnoredVersion
129
+ && !isVulnerableVersion
130
+ && isSafeVersion;
131
+ };
121
132
  }
122
133
 
123
134
  internal static Func<NuGetVersion, bool> CreateVersionFilter(NuGetVersion currentVersion)
@@ -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>
@@ -10,4 +10,6 @@ public record Advisory
10
10
  public ImmutableArray<Requirement>? AffectedVersions { get; init; } = null;
11
11
  public ImmutableArray<Requirement>? PatchedVersions { get; init; } = null;
12
12
  public ImmutableArray<Requirement>? UnaffectedVersions { get; init; } = null;
13
+
14
+ public IEnumerable<Requirement> SafeVersions => (PatchedVersions ?? []).Concat(UnaffectedVersions ?? []);
13
15
  }
@@ -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
  }
@@ -4,6 +4,8 @@ using System.Text;
4
4
  using System.Text.Json;
5
5
  using System.Text.Json.Serialization;
6
6
 
7
+ using NuGet.Versioning;
8
+
7
9
  using NuGetUpdater.Core.Analyze;
8
10
  using NuGetUpdater.Core.Discover;
9
11
  using NuGetUpdater.Core.Run.ApiModel;
@@ -53,7 +55,7 @@ public class RunWorker
53
55
  private async Task<RunResult> RunWithErrorHandlingAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha)
54
56
  {
55
57
  JobErrorBase? error = null;
56
- string[] lastUsedPackageSourceUrls = []; // used for error reporting below
58
+ var currentDirectory = repoContentsPath.FullName; // used for error reporting below
57
59
  var runResult = new RunResult()
58
60
  {
59
61
  Base64DependencyFiles = [],
@@ -69,7 +71,7 @@ public class RunWorker
69
71
  foreach (var directory in job.GetAllDirectories())
70
72
  {
71
73
  var localPath = PathHelper.JoinPath(repoContentsPath.FullName, directory);
72
- lastUsedPackageSourceUrls = NuGetContext.GetPackageSourceUrls(localPath);
74
+ currentDirectory = localPath;
73
75
  var result = await RunForDirectory(job, repoContentsPath, directory, baseCommitSha, experimentsManager);
74
76
  foreach (var dependencyFile in result.Base64DependencyFiles)
75
77
  {
@@ -84,26 +86,9 @@ public class RunWorker
84
86
  BaseCommitSha = baseCommitSha,
85
87
  };
86
88
  }
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
89
  catch (Exception ex)
105
90
  {
106
- error = new UnknownError(ex, _jobId);
91
+ error = JobErrorBase.ErrorFromException(ex, _jobId, currentDirectory);
107
92
  }
108
93
 
109
94
  if (error is not null)
@@ -123,6 +108,8 @@ public class RunWorker
123
108
  _logger.Info("Discovery JSON content:");
124
109
  _logger.Info(JsonSerializer.Serialize(discoveryResult, DiscoveryWorker.SerializerOptions));
125
110
 
111
+ // TODO: report errors
112
+
126
113
  // report dependencies
127
114
  var discoveredUpdatedDependencies = GetUpdatedDependencyListFromDiscovery(discoveryResult, repoContentsPath.FullName);
128
115
  await _apiHandler.UpdateDependencyList(discoveredUpdatedDependencies);
@@ -179,15 +166,7 @@ public class RunWorker
179
166
  continue;
180
167
  }
181
168
 
182
- var ignoredVersions = GetIgnoredRequirementsForDependency(job, dependency.Name);
183
- var dependencyInfo = new DependencyInfo()
184
- {
185
- Name = dependency.Name,
186
- Version = dependency.Version!,
187
- IsVulnerable = false,
188
- IgnoredVersions = ignoredVersions,
189
- Vulnerabilities = [],
190
- };
169
+ var dependencyInfo = GetDependencyInfo(job, dependency);
191
170
  var analysisResult = await _analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
192
171
  // TODO: log analysisResult
193
172
  if (analysisResult.CanUpdate)
@@ -221,7 +200,7 @@ public class RunWorker
221
200
  var dependencyFilePath = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
222
201
  var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, dependencyFilePath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: false);
223
202
  // TODO: need to report if anything was actually updated
224
- if (updateResult.ErrorType is null || updateResult.ErrorType == ErrorType.None)
203
+ if (updateResult.Error is null)
225
204
  {
226
205
  if (dependencyLocation != dependencyFilePath)
227
206
  {
@@ -329,6 +308,30 @@ public class RunWorker
329
308
  return ignoredVersions;
330
309
  }
331
310
 
311
+ internal static DependencyInfo GetDependencyInfo(Job job, Dependency dependency)
312
+ {
313
+ var dependencyVersion = NuGetVersion.Parse(dependency.Version!);
314
+ var securityAdvisories = job.SecurityAdvisories.Where(s => s.DependencyName.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase)).ToArray();
315
+ var isVulnerable = securityAdvisories.Any(s => (s.AffectedVersions ?? []).Any(v => v.IsSatisfiedBy(dependencyVersion)));
316
+ var ignoredVersions = GetIgnoredRequirementsForDependency(job, dependency.Name);
317
+ var vulnerabilities = securityAdvisories.Select(s => new SecurityVulnerability()
318
+ {
319
+ DependencyName = dependency.Name,
320
+ PackageManager = "nuget",
321
+ VulnerableVersions = s.AffectedVersions ?? [],
322
+ SafeVersions = s.SafeVersions.ToImmutableArray(),
323
+ }).ToImmutableArray();
324
+ var dependencyInfo = new DependencyInfo()
325
+ {
326
+ Name = dependency.Name,
327
+ Version = dependencyVersion.ToString(),
328
+ IsVulnerable = isVulnerable,
329
+ IgnoredVersions = ignoredVersions,
330
+ Vulnerabilities = vulnerabilities,
331
+ };
332
+ return dependencyInfo;
333
+ }
334
+
332
335
  internal static UpdatedDependencyList GetUpdatedDependencyListFromDiscovery(WorkspaceDiscoveryResult discoveryResult, string pathToContents)
333
336
  {
334
337
  string GetFullRepoPath(string path)
@@ -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
+ }