dependabot-nuget 0.290.0 → 0.291.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 (27) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +1 -1
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +3 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +2 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +44 -5
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +2 -2
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +2 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +19 -11
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +1 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +3 -2
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +43 -18
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +1 -1
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +40 -14
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +2 -2
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +45 -7
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +41 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +1 -1
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +2 -1
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +246 -60
  22. data/lib/dependabot/nuget/file_fetcher.rb +1 -0
  23. data/lib/dependabot/nuget/file_parser.rb +90 -0
  24. data/lib/dependabot/nuget/language.rb +82 -0
  25. data/lib/dependabot/nuget/native_helpers.rb +23 -0
  26. data/lib/dependabot/nuget/package_manager.rb +51 -0
  27. metadata +7 -5
@@ -34,7 +34,8 @@ internal static partial class MSBuildHelper
34
34
  // Ensure MSBuild types are registered before calling a method that loads the types
35
35
  if (!IsMSBuildRegistered)
36
36
  {
37
- SidelineGlobalJsonAsync(currentDirectory, rootDirectory, () =>
37
+ var experimentsManager = new ExperimentsManager() { InstallDotnetSdks = false }; // `global.json` definitely needs to be moved for this operation
38
+ HandleGlobalJsonAsync(currentDirectory, rootDirectory, experimentsManager, () =>
38
39
  {
39
40
  var defaultInstance = MSBuildLocator.QueryVisualStudioInstances().First();
40
41
  MSBuildPath = defaultInstance.MSBuildPath;
@@ -44,9 +45,23 @@ internal static partial class MSBuildHelper
44
45
  }
45
46
  }
46
47
 
47
- public static async Task<T> SidelineGlobalJsonAsync<T>(string currentDirectory, string rootDirectory, Func<Task<T>> action, ILogger? logger = null, bool retainMSBuildSdks = false)
48
+ public static async Task<T> HandleGlobalJsonAsync<T>(
49
+ string currentDirectory,
50
+ string rootDirectory,
51
+ ExperimentsManager experimentsManager,
52
+ Func<Task<T>> action,
53
+ ILogger? logger = null,
54
+ bool retainMSBuildSdks = false
55
+ )
48
56
  {
49
57
  logger ??= new ConsoleLogger();
58
+ if (experimentsManager.InstallDotnetSdks)
59
+ {
60
+ logger.Info($"{nameof(ExperimentsManager.InstallDotnetSdks)} == true; retaining `global.json` contents.");
61
+ var result = await action();
62
+ return result;
63
+ }
64
+
50
65
  var candidateDirectories = PathHelper.GetAllDirectoriesToRoot(currentDirectory, rootDirectory);
51
66
  var globalJsonPaths = candidateDirectories.Select(d => Path.Combine(d, "global.json")).Where(File.Exists).Select(p => (p, p + Guid.NewGuid().ToString())).ToArray();
52
67
  foreach (var (globalJsonPath, tempGlobalJsonPath) in globalJsonPaths)
@@ -322,13 +337,13 @@ internal static partial class MSBuildHelper
322
337
  return false;
323
338
  }
324
339
 
325
- internal static async Task<bool> DependenciesAreCoherentAsync(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, ILogger logger)
340
+ internal static async Task<bool> DependenciesAreCoherentAsync(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, ExperimentsManager experimentsManager, ILogger logger)
326
341
  {
327
342
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
328
343
  try
329
344
  {
330
345
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
331
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
346
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
332
347
 
333
348
  // NU1608: Detected package version outside of dependency constraint
334
349
 
@@ -340,7 +355,7 @@ internal static partial class MSBuildHelper
340
355
  }
341
356
  }
342
357
 
343
- internal static async Task<Dependency[]?> ResolveDependencyConflicts(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Dependency[] update, ILogger logger)
358
+ internal static async Task<Dependency[]?> ResolveDependencyConflicts(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Dependency[] update, ExperimentsManager experimentsManager, ILogger logger)
344
359
  {
345
360
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
346
361
  PackageManager packageManager = new PackageManager(repoRoot, projectPath);
@@ -348,7 +363,7 @@ internal static partial class MSBuildHelper
348
363
  try
349
364
  {
350
365
  string tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
351
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
366
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
352
367
 
353
368
  // Add Dependency[] packages to List<PackageToUpdate> existingPackages
354
369
  List<PackageToUpdate> existingPackages = packages
@@ -498,13 +513,13 @@ internal static partial class MSBuildHelper
498
513
  }
499
514
  }
500
515
 
501
- internal static async Task<Dependency[]?> ResolveDependencyConflictsWithBruteForce(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, ILogger logger)
516
+ internal static async Task<Dependency[]?> ResolveDependencyConflictsWithBruteForce(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, ExperimentsManager experimentsManager, ILogger logger)
502
517
  {
503
518
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
504
519
  try
505
520
  {
506
521
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
507
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
522
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
508
523
  ThrowOnUnauthenticatedFeed(stdOut);
509
524
 
510
525
  // simple cases first
@@ -529,6 +544,7 @@ internal static partial class MSBuildHelper
529
544
  foreach ((string PackageName, NuGetVersion packageVersion) in badPackagesAndVersions)
530
545
  {
531
546
  // this command dumps a JSON object with all versions of the specified package from all package sources
547
+ // not using the `dotnet` execution method because we want to force the latest MSBuild and SDK to be used
532
548
  (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["package", "search", PackageName, "--exact-match", "--format", "json"], workingDirectory: tempDirectory.FullName);
533
549
  if (exitCode != 0)
534
550
  {
@@ -591,7 +607,7 @@ internal static partial class MSBuildHelper
591
607
  return p;
592
608
  }).ToArray();
593
609
 
594
- if (await DependenciesAreCoherentAsync(repoRoot, projectPath, targetFramework, candidatePackages, logger))
610
+ if (await DependenciesAreCoherentAsync(repoRoot, projectPath, targetFramework, candidatePackages, experimentsManager, logger))
595
611
  {
596
612
  // return as soon as we find a coherent set
597
613
  return candidatePackages;
@@ -749,14 +765,23 @@ internal static partial class MSBuildHelper
749
765
  return tempProjectPath;
750
766
  }
751
767
 
752
- internal static async Task<ImmutableArray<string>> GetTargetFrameworkValuesFromProject(string repoRoot, string projectPath, ILogger logger)
768
+ internal static async Task<ImmutableArray<string>> GetTargetFrameworkValuesFromProject(string repoRoot, string projectPath, ExperimentsManager experimentsManager, ILogger logger)
753
769
  {
754
- // TODO: once the updater image has all relevant SDKs installed, we won't have to sideline global.json anymore
755
770
  var projectDirectory = Path.GetDirectoryName(projectPath)!;
756
- var (exitCode, stdOut, stdErr) = await SidelineGlobalJsonAsync(projectDirectory, repoRoot, async () =>
771
+ var (exitCode, stdOut, stdErr) = await HandleGlobalJsonAsync(projectDirectory, repoRoot, experimentsManager, async () =>
757
772
  {
758
773
  var targetsHelperPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "TargetFrameworkReporter.targets");
759
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["build", projectPath, "/t:ReportTargetFramework", $"/p:CustomAfterMicrosoftCommonCrossTargetingTargets={targetsHelperPath};CustomAfterMicrosoftCommonTargets={targetsHelperPath}"], workingDirectory: Path.GetDirectoryName(projectPath));
774
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(
775
+ [
776
+ "build",
777
+ projectPath,
778
+ "/t:ReportTargetFramework",
779
+ $"/p:CustomAfterMicrosoftCommonCrossTargetingTargets={targetsHelperPath}",
780
+ $"/p:CustomAfterMicrosoftCommonTargets={targetsHelperPath}"
781
+ ],
782
+ projectDirectory,
783
+ experimentsManager
784
+ );
760
785
  return (exitCode, stdOut, stdErr);
761
786
  });
762
787
  ThrowOnUnauthenticatedFeed(stdOut);
@@ -806,6 +831,7 @@ internal static partial class MSBuildHelper
806
831
  string projectPath,
807
832
  string targetFramework,
808
833
  IReadOnlyCollection<Dependency> packages,
834
+ ExperimentsManager experimentsManager,
809
835
  ILogger logger)
810
836
  {
811
837
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-resolution_");
@@ -814,7 +840,7 @@ internal static partial class MSBuildHelper
814
840
  var topLevelPackagesNames = packages.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
815
841
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
816
842
 
817
- var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", ["build", tempProjectPath, "/t:_ReportDependencies"], workingDirectory: tempDirectory.FullName);
843
+ var (exitCode, stdout, stderr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["build", tempProjectPath, "/t:_ReportDependencies"], tempDirectory.FullName, experimentsManager);
818
844
  ThrowOnUnauthenticatedFeed(stdout);
819
845
 
820
846
  if (exitCode == 0)
@@ -4,13 +4,13 @@ namespace NuGetUpdater.Core;
4
4
 
5
5
  internal static class NuGetHelper
6
6
  {
7
- internal static async Task<bool> DownloadNuGetPackagesAsync(string repoRoot, string projectPath, IReadOnlyCollection<Dependency> packages, ILogger logger)
7
+ internal static async Task<bool> DownloadNuGetPackagesAsync(string repoRoot, string projectPath, IReadOnlyCollection<Dependency> packages, ExperimentsManager experimentsManager, ILogger logger)
8
8
  {
9
9
  var tempDirectory = Directory.CreateTempSubdirectory("msbuild_sdk_restore_");
10
10
  try
11
11
  {
12
12
  var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, "netstandard2.0", packages, logger, usePackageDownload: true);
13
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath]);
13
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
14
14
 
15
15
  return exitCode == 0;
16
16
  }
@@ -5,19 +5,57 @@ namespace NuGetUpdater.Core;
5
5
 
6
6
  public static class ProcessEx
7
7
  {
8
- public static Task<(int ExitCode, string Output, string Error)> RunAsync(string fileName, IEnumerable<string>? arguments = null, string? workingDirectory = null)
8
+ /// <summary>
9
+ /// Run the `dotnet` command with the given values. This will exclude all `MSBuild*` environment variables from the execution.
10
+ /// </summary>
11
+ public static Task<(int ExitCode, string Output, string Error)> RunDotnetWithoutMSBuildEnvironmentVariablesAsync(IEnumerable<string> arguments, string workingDirectory, ExperimentsManager experimentsManager)
12
+ {
13
+ var environmentVariablesToUnset = new List<string>();
14
+ if (experimentsManager.InstallDotnetSdks)
15
+ {
16
+ // If using the SDK specified by a `global.json` file, these environment variables need to be unset to
17
+ // allow the new process to discover the correct MSBuild binaries to load, and not load the ones that
18
+ // this process is using.
19
+ environmentVariablesToUnset.Add("MSBuildExtensionsPath");
20
+ environmentVariablesToUnset.Add("MSBuildLoadMicrosoftTargetsReadOnly");
21
+ environmentVariablesToUnset.Add("MSBUILDLOGIMPORTS");
22
+ environmentVariablesToUnset.Add("MSBuildSDKsPath");
23
+ environmentVariablesToUnset.Add("MSBUILDTARGETOUTPUTLOGGING");
24
+ environmentVariablesToUnset.Add("MSBUILD_EXE_PATH");
25
+ }
26
+
27
+ var environmentVariableOverrides = environmentVariablesToUnset.Select(name => (name, (string?)null));
28
+ return RunAsync("dotnet",
29
+ arguments,
30
+ workingDirectory,
31
+ environmentVariableOverrides
32
+ );
33
+ }
34
+
35
+ public static Task<(int ExitCode, string Output, string Error)> RunAsync(
36
+ string fileName,
37
+ IEnumerable<string>? arguments = null,
38
+ string? workingDirectory = null,
39
+ IEnumerable<(string Name, string? Value)>? environmentVariableOverrides = null
40
+ )
9
41
  {
10
42
  var tcs = new TaskCompletionSource<(int, string, string)>();
11
43
 
12
44
  var redirectInitiated = new ManualResetEventSlim();
45
+ var psi = new ProcessStartInfo(fileName, arguments ?? [])
46
+ {
47
+ UseShellExecute = false, // required to redirect output and set environment variables
48
+ RedirectStandardOutput = true,
49
+ RedirectStandardError = true,
50
+ };
51
+ foreach (var (name, value) in environmentVariableOverrides ?? [])
52
+ {
53
+ psi.EnvironmentVariables[name] = value;
54
+ }
55
+
13
56
  var process = new Process
14
57
  {
15
- StartInfo = new ProcessStartInfo(fileName, arguments ?? [])
16
- {
17
- UseShellExecute = false, // required to redirect output
18
- RedirectStandardOutput = true,
19
- RedirectStandardError = true,
20
- },
58
+ StartInfo = psi,
21
59
  EnableRaisingEvents = true
22
60
  };
23
61
 
@@ -1094,6 +1094,47 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
1094
1094
  );
1095
1095
  }
1096
1096
 
1097
+ [Theory]
1098
+ [InlineData(true)]
1099
+ [InlineData(false)]
1100
+ public async Task DiscoveryReportsDependencyFileNotParseable(bool useDirectDiscovery)
1101
+ {
1102
+ var experimentsManager = new ExperimentsManager() { UseDirectDiscovery = useDirectDiscovery };
1103
+ await TestDiscoveryAsync(
1104
+ experimentsManager: experimentsManager,
1105
+ workspacePath: "",
1106
+ files:
1107
+ [
1108
+ ("project.csproj", """
1109
+ <Project Sdk="Microsoft.NET.Sdk">
1110
+ <PropertyGroup>
1111
+ <TargetFramework>net8.0</TargetFramework>
1112
+ </PropertyGroup>
1113
+ <ItemGroup>
1114
+ <PackageReference Include="Some.Package" Version="1.2.3" />
1115
+ </ItemGroup>
1116
+ </Project>
1117
+ """),
1118
+ ("project2.csproj", """
1119
+ <Project Sdk="Microsoft.NET.Sdk">
1120
+ <PropertyGroup>
1121
+ <TargetFramework>net8.0</TargetFramework>
1122
+ </PropertyGroup>
1123
+ <ItemGroup>
1124
+ <PackageReference: Include="Some.Package2" Version="1.2.3" />
1125
+ </ItemGroup>
1126
+ </Project>
1127
+ """),
1128
+ ],
1129
+ expectedResult: new()
1130
+ {
1131
+ Path = "",
1132
+ Projects = [],
1133
+ ErrorType = ErrorType.DependencyFileNotParseable,
1134
+ ErrorDetails = "project2.csproj",
1135
+ });
1136
+ }
1137
+
1097
1138
  [Fact]
1098
1139
  public async Task ResultFileHasCorrectShapeForAuthenticationFailure()
1099
1140
  {
@@ -21,6 +21,7 @@ public record ExpectedSdkProjectDiscoveryResult : ExpectedDependencyDiscoveryRes
21
21
  public required ImmutableArray<string> ReferencedProjectPaths { get; init; }
22
22
  public required ImmutableArray<string> ImportedFiles { get; init; }
23
23
  public required ImmutableArray<string> AdditionalFiles { get; init; }
24
+ public string? ErrorDetails { get; init; }
24
25
  }
25
26
 
26
27
  public record ExpectedDependencyDiscoveryResult : IDiscoveryResultWithDependencies
@@ -488,7 +488,7 @@ public class SdkProjectDiscoveryTests : DiscoveryWorkerTestBase
488
488
  var logger = new TestLogger();
489
489
  var fullProjectPath = Path.Combine(testDirectory.DirectoryPath, projectPath);
490
490
  var experimentsManager = new ExperimentsManager() { UseDirectDiscovery = true }; // the following method is direct discovery; this just makes the call to Validate... happy
491
- var projectDiscovery = await SdkProjectDiscovery.DiscoverWithBinLogAsync(testDirectory.DirectoryPath, Path.GetDirectoryName(fullProjectPath)!, fullProjectPath, logger);
491
+ var projectDiscovery = await SdkProjectDiscovery.DiscoverWithBinLogAsync(testDirectory.DirectoryPath, Path.GetDirectoryName(fullProjectPath)!, fullProjectPath, experimentsManager, logger);
492
492
  ValidateProjectResults(expectedProjects, projectDiscovery, experimentsManager);
493
493
  }
494
494
  }
@@ -318,7 +318,8 @@ namespace NuGetUpdater.Core.Test
318
318
  </Project>
319
319
  """
320
320
  );
321
- var (exitCode, stdout, stderr) = ProcessEx.RunAsync("dotnet", ["msbuild", projectPath, "/t:_ReportCurrentSdkVersion"]).Result;
321
+ var experimentsManager = new ExperimentsManager();
322
+ var (exitCode, stdout, stderr) = ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["msbuild", projectPath, "/t:_ReportCurrentSdkVersion"], projectDir.FullName, experimentsManager).Result;
322
323
  if (exitCode != 0)
323
324
  {
324
325
  throw new Exception($"Failed to report the current SDK version:\n{stdout}\n{stderr}");