dependabot-nuget 0.267.0 → 0.270.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,9 +13,12 @@ using Microsoft.Build.Exceptions;
13
13
  using Microsoft.Build.Locator;
14
14
  using Microsoft.Extensions.FileSystemGlobbing;
15
15
 
16
+ using NuGet;
16
17
  using NuGet.Configuration;
18
+ using NuGet.Frameworks;
17
19
  using NuGet.Versioning;
18
20
 
21
+ using NuGetUpdater.Core.Analyze;
19
22
  using NuGetUpdater.Core.Utilities;
20
23
 
21
24
  namespace NuGetUpdater.Core;
@@ -31,29 +34,54 @@ internal static partial class MSBuildHelper
31
34
  // Ensure MSBuild types are registered before calling a method that loads the types
32
35
  if (!IsMSBuildRegistered)
33
36
  {
34
- var candidateDirectories = PathHelper.GetAllDirectoriesToRoot(currentDirectory, rootDirectory);
35
- var globalJsonPaths = candidateDirectories.Select(d => Path.Combine(d, "global.json")).Where(File.Exists).Select(p => (p, p + Guid.NewGuid().ToString())).ToArray();
36
- foreach (var (globalJsonPath, tempGlobalJsonPath) in globalJsonPaths)
37
- {
38
- Console.WriteLine($"Temporarily removing `global.json` from `{Path.GetDirectoryName(globalJsonPath)}` for MSBuild detection.");
39
- File.Move(globalJsonPath, tempGlobalJsonPath);
40
- }
41
-
42
- try
37
+ SidelineGlobalJsonAsync(currentDirectory, rootDirectory, () =>
43
38
  {
44
39
  var defaultInstance = MSBuildLocator.QueryVisualStudioInstances().First();
45
40
  MSBuildPath = defaultInstance.MSBuildPath;
46
41
  MSBuildLocator.RegisterInstance(defaultInstance);
47
- }
48
- finally
42
+ return Task.CompletedTask;
43
+ }).Wait();
44
+ }
45
+ }
46
+
47
+ public static async Task SidelineGlobalJsonAsync(string currentDirectory, string rootDirectory, Func<Task> action, bool retainMSBuildSdks = false)
48
+ {
49
+ var candidateDirectories = PathHelper.GetAllDirectoriesToRoot(currentDirectory, rootDirectory);
50
+ var globalJsonPaths = candidateDirectories.Select(d => Path.Combine(d, "global.json")).Where(File.Exists).Select(p => (p, p + Guid.NewGuid().ToString())).ToArray();
51
+ foreach (var (globalJsonPath, tempGlobalJsonPath) in globalJsonPaths)
52
+ {
53
+ Console.WriteLine($"Temporarily removing `global.json` from `{Path.GetDirectoryName(globalJsonPath)}`{(retainMSBuildSdks ? " and retaining MSBuild SDK declarations" : string.Empty)}.");
54
+ File.Move(globalJsonPath, tempGlobalJsonPath);
55
+ if (retainMSBuildSdks)
49
56
  {
50
- foreach (var (globalJsonpath, tempGlobalJsonPath) in globalJsonPaths)
57
+ // custom SDKs might need to be retained for other operations; rebuild `global.json` with only the relevant key
58
+ var originalContent = await File.ReadAllTextAsync(tempGlobalJsonPath);
59
+ var jsonNode = JsonHelper.ParseNode(originalContent);
60
+ if (jsonNode is JsonObject obj &&
61
+ obj.TryGetPropertyValue("msbuild-sdks", out var sdks) &&
62
+ sdks is not null)
51
63
  {
52
- Console.WriteLine($"Restoring `global.json` to `{Path.GetDirectoryName(globalJsonpath)}` after MSBuild discovery.");
53
- File.Move(tempGlobalJsonPath, globalJsonpath);
64
+ var newObj = new JsonObject()
65
+ {
66
+ ["msbuild-sdks"] = sdks.DeepClone(),
67
+ };
68
+ await File.WriteAllTextAsync(globalJsonPath, newObj.ToJsonString());
54
69
  }
55
70
  }
56
71
  }
72
+
73
+ try
74
+ {
75
+ await action();
76
+ }
77
+ finally
78
+ {
79
+ foreach (var (globalJsonpath, tempGlobalJsonPath) in globalJsonPaths)
80
+ {
81
+ Console.WriteLine($"Restoring `global.json` to `{Path.GetDirectoryName(globalJsonpath)}`.");
82
+ File.Move(tempGlobalJsonPath, globalJsonpath, overwrite: retainMSBuildSdks);
83
+ }
84
+ }
57
85
  }
58
86
 
59
87
  public static IEnumerable<string> GetProjectPathsFromSolution(string solutionPath)
@@ -306,7 +334,182 @@ internal static partial class MSBuildHelper
306
334
  }
307
335
  }
308
336
 
309
- internal static async Task<Dependency[]?> ResolveDependencyConflicts(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Logger logger)
337
+ internal static bool UseNewDependencySolver()
338
+ {
339
+ return Environment.GetEnvironmentVariable("UseNewNugetPackageResolver") == "true";
340
+ }
341
+
342
+ internal static async Task<Dependency[]?> ResolveDependencyConflicts(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Dependency[] update, Logger logger)
343
+ {
344
+ if (UseNewDependencySolver())
345
+ {
346
+ return await ResolveDependencyConflictsNew(repoRoot, projectPath, targetFramework, packages, update, logger);
347
+ }
348
+ else
349
+ {
350
+ return await ResolveDependencyConflictsOld(repoRoot, projectPath, targetFramework, packages, logger);
351
+ }
352
+ }
353
+
354
+ internal static async Task<Dependency[]?> ResolveDependencyConflictsNew(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Dependency[] update, Logger logger)
355
+ {
356
+ var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
357
+ PackageManager packageManager = new PackageManager(repoRoot, projectPath);
358
+
359
+ try
360
+ {
361
+ string tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
362
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"", workingDirectory: tempDirectory.FullName);
363
+
364
+ // Add Dependency[] packages to List<PackageToUpdate> existingPackages
365
+ List<PackageToUpdate> existingPackages = packages
366
+ .Select(existingPackage => new PackageToUpdate
367
+ {
368
+ PackageName = existingPackage.Name,
369
+ CurrentVersion = existingPackage.Version
370
+ })
371
+ .ToList();
372
+
373
+ // Add Dependency[] update to List<PackageToUpdate> packagesToUpdate
374
+ List<PackageToUpdate> packagesToUpdate = update
375
+ .Where(package => package.Version != null)
376
+ .Select(package => new PackageToUpdate
377
+ {
378
+ PackageName = package.Name,
379
+ NewVersion = package.Version.ToString()
380
+ })
381
+ .ToList();
382
+
383
+ foreach (PackageToUpdate existing in existingPackages)
384
+ {
385
+ var foundPackage = packagesToUpdate.Where(p => string.Equals(p.PackageName, existing.PackageName, StringComparison.OrdinalIgnoreCase));
386
+ if (!foundPackage.Any())
387
+ {
388
+ existing.NewVersion = existing.CurrentVersion;
389
+ }
390
+ }
391
+
392
+ // Create a duplicate set of existingPackages for flexible package reference addition and removal
393
+ List<PackageToUpdate> existingDuplicate = new List<PackageToUpdate>(existingPackages);
394
+
395
+ // Bool to keep track of if anything was added to the existingDuplicate list
396
+ bool added = false;
397
+
398
+ // If package 'isnt there, add it to the existingDuplicate list
399
+ foreach (PackageToUpdate package in packagesToUpdate)
400
+ {
401
+ if (!existingDuplicate.Any(p => string.Equals(p.PackageName, package.PackageName, StringComparison.OrdinalIgnoreCase)))
402
+ {
403
+ existingDuplicate.Add(package);
404
+ added = true;
405
+ }
406
+ }
407
+
408
+ // If you have to use the existingDuplicate list
409
+ if (added == true)
410
+ {
411
+ // Add existing versions to existing list
412
+ packageManager.UpdateExistingPackagesWithNewVersions(existingDuplicate, packagesToUpdate);
413
+
414
+ // Make relationships
415
+ await packageManager.PopulatePackageDependenciesAsync(existingDuplicate, targetFramework, Path.GetDirectoryName(projectPath));
416
+
417
+ // Update all to new versions
418
+ foreach (var package in existingDuplicate)
419
+ {
420
+ string updateResult = await packageManager.UpdateVersion(existingDuplicate, package, targetFramework, Path.GetDirectoryName(projectPath));
421
+ }
422
+ }
423
+
424
+ // Editing existing list because nothing was added to existingDuplicate
425
+ else
426
+ {
427
+ // Add existing versions to existing list
428
+ packageManager.UpdateExistingPackagesWithNewVersions(existingPackages, packagesToUpdate);
429
+
430
+ // Make relationships
431
+ await packageManager.PopulatePackageDependenciesAsync(existingPackages, targetFramework, Path.GetDirectoryName(projectPath));
432
+
433
+ // Update all to new versions
434
+ foreach (var package in existingPackages)
435
+ {
436
+ string updateResult = await packageManager.UpdateVersion(existingPackages, package, targetFramework, Path.GetDirectoryName(projectPath));
437
+ }
438
+ }
439
+
440
+ // Make new list to remove and differentiate between existingDuplicate and existingPackages lists
441
+ List<PackageToUpdate> packagesToRemove = existingDuplicate
442
+ .Where(existingPackageDupe => !existingPackages.Contains(existingPackageDupe) && existingPackageDupe.IsSpecific == true)
443
+ .ToList();
444
+
445
+ foreach (PackageToUpdate package in packagesToRemove)
446
+ {
447
+ existingDuplicate.Remove(package);
448
+ }
449
+
450
+ if (existingDuplicate != null)
451
+ {
452
+ existingPackages = existingDuplicate;
453
+ }
454
+
455
+ // Convert back to Dependency [], use NewVersion if available, otherwise use CurrentVersion
456
+ List<Dependency> candidatePackages = existingPackages
457
+ .Select(package => new Dependency(
458
+ package.PackageName,
459
+ package.NewVersion ?? package.CurrentVersion,
460
+ DependencyType.Unknown,
461
+ null,
462
+ null,
463
+ false,
464
+ false,
465
+ false,
466
+ false,
467
+ false
468
+ ))
469
+ .ToList();
470
+
471
+ // Return as array
472
+ Dependency[] candidatePackagesArray = candidatePackages.ToArray();
473
+
474
+ var targetFrameworks = new NuGetFramework[] { NuGetFramework.Parse(targetFramework) };
475
+
476
+ var resolveProjectPath = projectPath;
477
+
478
+ if (!Path.IsPathRooted(resolveProjectPath) || !File.Exists(resolveProjectPath))
479
+ {
480
+ resolveProjectPath = Path.GetFullPath(Path.Join(repoRoot, resolveProjectPath));
481
+ }
482
+
483
+ NuGetContext nugetContext = new NuGetContext(Path.GetDirectoryName(resolveProjectPath));
484
+
485
+ // Target framework compatibility check
486
+ foreach (var package in candidatePackages)
487
+ {
488
+ if (!NuGetVersion.TryParse(package.Version, out var nuGetVersion))
489
+ {
490
+ // If version is not valid, return original packages and revert
491
+ return packages;
492
+ }
493
+
494
+ var packageIdentity = new NuGet.Packaging.Core.PackageIdentity(package.Name, nuGetVersion);
495
+
496
+ bool isNewPackageCompatible = await CompatibilityChecker.CheckAsync(packageIdentity, targetFrameworks.ToImmutableArray(), nugetContext, logger, CancellationToken.None);
497
+ if (!isNewPackageCompatible)
498
+ {
499
+ // If the package target framework is not compatible, return original packages and revert
500
+ return packages;
501
+ }
502
+ }
503
+
504
+ return candidatePackagesArray;
505
+ }
506
+ finally
507
+ {
508
+ tempDirectory.Delete(recursive: true);
509
+ }
510
+ }
511
+
512
+ internal static async Task<Dependency[]?> ResolveDependencyConflictsOld(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Logger logger)
310
513
  {
311
514
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
312
515
  try
@@ -334,22 +537,22 @@ internal static partial class MSBuildHelper
334
537
  Dictionary<string, HashSet<NuGetVersion>> badPackagesAndCandidateVersionsDictionary = new(StringComparer.OrdinalIgnoreCase);
335
538
 
336
539
  // and for each of those packages, find all versions greater than the one that's currently installed
337
- foreach ((string packageName, NuGetVersion packageVersion) in badPackagesAndVersions)
540
+ foreach ((string PackageName, NuGetVersion packageVersion) in badPackagesAndVersions)
338
541
  {
339
542
  // this command dumps a JSON object with all versions of the specified package from all package sources
340
- (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"package search {packageName} --exact-match --format json", workingDirectory: tempDirectory.FullName);
543
+ (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"package search {PackageName} --exact-match --format json", workingDirectory: tempDirectory.FullName);
341
544
  if (exitCode != 0)
342
545
  {
343
546
  continue;
344
547
  }
345
548
 
346
549
  // ensure collection exists
347
- if (!badPackagesAndCandidateVersionsDictionary.ContainsKey(packageName))
550
+ if (!badPackagesAndCandidateVersionsDictionary.ContainsKey(PackageName))
348
551
  {
349
- badPackagesAndCandidateVersionsDictionary.Add(packageName, new HashSet<NuGetVersion>());
552
+ badPackagesAndCandidateVersionsDictionary.Add(PackageName, new HashSet<NuGetVersion>());
350
553
  }
351
554
 
352
- HashSet<NuGetVersion> foundVersions = badPackagesAndCandidateVersionsDictionary[packageName];
555
+ HashSet<NuGetVersion> foundVersions = badPackagesAndCandidateVersionsDictionary[PackageName];
353
556
 
354
557
  var json = JsonHelper.ParseNode(stdOut);
355
558
  if (json?["searchResult"] is JsonArray searchResults)
@@ -580,9 +783,9 @@ internal static partial class MSBuildHelper
580
783
  .Where(match => match.Success)
581
784
  .Select(match =>
582
785
  {
583
- var packageName = match.Groups["PackageName"].Value;
584
- var isTransitive = !topLevelPackagesNames.Contains(packageName);
585
- return new Dependency(packageName, match.Groups["PackageVersion"].Value, DependencyType.Unknown, TargetFrameworks: tfms, IsTransitive: isTransitive);
786
+ var PackageName = match.Groups["PackageName"].Value;
787
+ var isTransitive = !topLevelPackagesNames.Contains(PackageName);
788
+ return new Dependency(PackageName, match.Groups["PackageVersion"].Value, DependencyType.Unknown, TargetFrameworks: tfms, IsTransitive: isTransitive);
586
789
  })
587
790
  .ToArray();
588
791
 
@@ -264,6 +264,46 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
264
264
 
265
265
  [Fact]
266
266
  public async Task ReturnsUpToDate_ForMissingDependency()
267
+ {
268
+ await TestAnalyzeAsync(
269
+ packages:
270
+ [
271
+ // no packages listed
272
+ ],
273
+ discovery: new()
274
+ {
275
+ Path = "/",
276
+ Projects = [
277
+ new()
278
+ {
279
+ FilePath = "./project.csproj",
280
+ TargetFrameworks = ["net8.0"],
281
+ Dependencies = [
282
+ new("Some.Package", "1.0.0", DependencyType.PackageReference), // this was found in the source, but doesn't exist in any feed
283
+ ],
284
+ },
285
+ ],
286
+ },
287
+ dependencyInfo: new()
288
+ {
289
+ Name = "Some.Package",
290
+ Version = "1.0.0",
291
+ IgnoredVersions = [],
292
+ IsVulnerable = false,
293
+ Vulnerabilities = [],
294
+ },
295
+ expectedResult: new()
296
+ {
297
+ UpdatedVersion = "1.0.0",
298
+ CanUpdate = false,
299
+ VersionComesFromMultiDependencyProperty = false,
300
+ UpdatedDependencies = [],
301
+ }
302
+ );
303
+ }
304
+
305
+ [Fact]
306
+ public async Task ReturnsUpToDate_ForIgnoredRequirements()
267
307
  {
268
308
  await TestAnalyzeAsync(
269
309
  packages:
@@ -307,6 +347,50 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
307
347
  );
308
348
  }
309
349
 
350
+ [Fact]
351
+ public async Task IgnoredVersionsCanHandleWildcardSpecification()
352
+ {
353
+ await TestAnalyzeAsync(
354
+ packages:
355
+ [
356
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"), // initially this
357
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.1.0", "net8.0"), // should update to this
358
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.2.0", "net8.0"), // `IgnoredVersions` should prevent this from being selected
359
+ ],
360
+ discovery: new()
361
+ {
362
+ Path = "/",
363
+ Projects = [
364
+ new()
365
+ {
366
+ FilePath = "./project.csproj",
367
+ TargetFrameworks = ["net8.0"],
368
+ Dependencies = [
369
+ new("Some.Package", "1.0.0", DependencyType.PackageReference),
370
+ ],
371
+ },
372
+ ],
373
+ },
374
+ dependencyInfo: new()
375
+ {
376
+ Name = "Some.Package",
377
+ Version = "1.0.0",
378
+ IgnoredVersions = [Requirement.Parse("> 1.1.*")],
379
+ IsVulnerable = false,
380
+ Vulnerabilities = [],
381
+ },
382
+ expectedResult: new()
383
+ {
384
+ UpdatedVersion = "1.1.0",
385
+ CanUpdate = true,
386
+ VersionComesFromMultiDependencyProperty = false,
387
+ UpdatedDependencies = [
388
+ new("Some.Package", "1.1.0", DependencyType.Unknown, TargetFrameworks: ["net8.0"]),
389
+ ],
390
+ }
391
+ );
392
+ }
393
+
310
394
  [Fact]
311
395
  public async Task VersionFinderCanHandle404FromPackageSource_V2()
312
396
  {
@@ -33,6 +33,8 @@ public class RequirementTests
33
33
  [InlineData("1", "~> 1", true)]
34
34
  [InlineData("2", "~> 1", false)]
35
35
  [InlineData("5.3.8", "< 6, > 5.2.4", true)]
36
+ [InlineData("1.0-preview", ">= 1.x", false)] // wildcards
37
+ [InlineData("1.1-preview", ">= 1.x", true)]
36
38
  public void IsSatisfiedBy(string versionString, string requirementString, bool expected)
37
39
  {
38
40
  var version = NuGetVersion.Parse(versionString);
@@ -43,6 +45,18 @@ public class RequirementTests
43
45
  Assert.Equal(expected, actual);
44
46
  }
45
47
 
48
+ [Theory]
49
+ [InlineData("> 1.*", "> 1.0")] // standard wildcard, single digit
50
+ [InlineData("> 1.2.*", "> 1.2.0")] // standard wildcard, multiple digit
51
+ [InlineData("> 1.a", "> 1.0")] // alternate wildcard, single digit
52
+ [InlineData("> 1.2.a", "> 1.2.0")] // alternate wildcard, multiple digit
53
+ public void Parse_ConvertsWildcardInVersion(string givenRequirementString, string expectedRequirementString)
54
+ {
55
+ var parsedRequirement = Requirement.Parse(givenRequirementString);
56
+ var actualRequirementString = parsedRequirement.ToString();
57
+ Assert.Equal(expectedRequirementString, actualRequirementString);
58
+ }
59
+
46
60
  [Theory]
47
61
  [InlineData("> = 1.0.0")] // Invalid format
48
62
  [InlineData("<>= 1.0.0")] // Invalid Operator
@@ -0,0 +1,23 @@
1
+ namespace NuGetUpdater.Core.Test;
2
+
3
+ public class TemporaryEnvironment : IDisposable
4
+ {
5
+ private readonly List<(string Name, string? Value)> _originalVariables = new();
6
+
7
+ public TemporaryEnvironment((string Name, string Value)[] variables)
8
+ {
9
+ foreach (var (name, value) in variables)
10
+ {
11
+ _originalVariables.Add((name, Environment.GetEnvironmentVariable(name)));
12
+ Environment.SetEnvironmentVariable(name, value);
13
+ }
14
+ }
15
+
16
+ public void Dispose()
17
+ {
18
+ foreach (var (name, value) in _originalVariables)
19
+ {
20
+ Environment.SetEnvironmentVariable(name, value);
21
+ }
22
+ }
23
+ }