dependabot-nuget 0.287.0 → 0.289.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.gitignore +1 -0
  3. data/helpers/lib/NuGetUpdater/Directory.Build.targets +17 -0
  4. data/helpers/lib/NuGetUpdater/Directory.Packages.props +26 -17
  5. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Packaging/NuGet.Packaging.csproj +0 -1
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +7 -3
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +1 -1
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +3 -1
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +88 -47
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +31 -16
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +1 -1
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/RequirementArrayConverter.cs +39 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +1 -1
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/ShellGitCommandHandler.cs +1 -1
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.props +7 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +10 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +64 -53
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DotNetToolsJsonDiscovery.cs +2 -2
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/GlobalJsonDiscovery.cs +2 -2
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +17 -5
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscoveryResult.cs +3 -1
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +3 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +429 -12
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +0 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +12 -2
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +1 -1
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs +2 -2
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +7 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +23 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +43 -58
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/TargetFrameworkReporter.targets +13 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +13 -43
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +4 -4
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +5 -5
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +3 -10
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +40 -33
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +12 -11
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +16 -12
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/CollectionExtensions.cs +17 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ConsoleLogger.cs +1 -1
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DependencyConflictResolver.cs +19 -19
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ILogger.cs +11 -1
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +74 -20
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -17
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathComparer.cs +31 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +46 -10
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProjectHelper.cs +96 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +135 -3
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +71 -38
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +66 -4
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Proj.cs +11 -5
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +808 -222
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +477 -97
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +5 -9
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +494 -0
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +46 -1
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +0 -1
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +401 -77
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +35 -2
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +60 -2
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +3 -2
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestLogger.cs +1 -1
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/BindingRedirectsTests.cs +1 -1
  64. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +8 -4
  65. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +40 -0
  66. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +1 -1
  67. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/AssertEx.cs +1 -1
  68. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/LinuxOnlyAttribute.cs +12 -0
  69. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +8 -5
  70. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +49 -3
  71. data/lib/dependabot/nuget/analysis/analysis_json_reader.rb +3 -1
  72. data/lib/dependabot/nuget/file_fetcher.rb +12 -393
  73. data/lib/dependabot/nuget/file_parser.rb +23 -54
  74. data/lib/dependabot/nuget/file_updater.rb +21 -16
  75. data/lib/dependabot/nuget/native_discovery/native_dependency_file_discovery.rb +2 -9
  76. data/lib/dependabot/nuget/native_discovery/native_discovery_json_reader.rb +183 -80
  77. data/lib/dependabot/nuget/native_discovery/native_project_discovery.rb +25 -3
  78. data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +1 -11
  79. data/lib/dependabot/nuget/native_helpers.rb +13 -4
  80. data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +17 -4
  81. metadata +15 -12
  82. data/helpers/lib/NuGetUpdater/NuGetProjects/Directory.Packages.props +0 -29
  83. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/RequirementConverter.cs +0 -17
  84. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscovery.cs +0 -69
  85. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscoveryResult.cs +0 -11
  86. data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +0 -73
  87. data/lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb +0 -60
  88. data/lib/dependabot/nuget/native_discovery/native_directory_packages_props_discovery.rb +0 -44
@@ -2,5 +2,15 @@ namespace NuGetUpdater.Core;
2
2
 
3
3
  public interface ILogger
4
4
  {
5
- void Log(string message);
5
+ void LogRaw(string message);
6
+ }
7
+
8
+ public static class LoggerExtensions
9
+ {
10
+ public static void Info(this ILogger logger, string message) => logger.LogWithLevel("INFO", message);
11
+ public static void Warn(this ILogger logger, string message) => logger.LogWithLevel("WARN", message);
12
+ public static void Error(this ILogger logger, string message) => logger.LogWithLevel("ERROR", message);
13
+
14
+ private static void LogWithLevel(this ILogger logger, string level, string message) => logger.LogRaw($"{GetCurrentTimestamp()} {level} {message}");
15
+ private static string GetCurrentTimestamp() => DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss");
6
16
  }
@@ -1,5 +1,6 @@
1
1
  using System.Collections.Immutable;
2
2
  using System.Diagnostics.CodeAnalysis;
3
+ using System.Reflection;
3
4
  using System.Text;
4
5
  using System.Text.Json;
5
6
  using System.Text.Json.Nodes;
@@ -13,7 +14,6 @@ using Microsoft.Build.Exceptions;
13
14
  using Microsoft.Build.Locator;
14
15
  using Microsoft.Extensions.FileSystemGlobbing;
15
16
 
16
- using NuGet;
17
17
  using NuGet.Configuration;
18
18
  using NuGet.Frameworks;
19
19
  using NuGet.Versioning;
@@ -39,19 +39,19 @@ internal static partial class MSBuildHelper
39
39
  var defaultInstance = MSBuildLocator.QueryVisualStudioInstances().First();
40
40
  MSBuildPath = defaultInstance.MSBuildPath;
41
41
  MSBuildLocator.RegisterInstance(defaultInstance);
42
- return Task.CompletedTask;
42
+ return Task.FromResult(0);
43
43
  }).Wait();
44
44
  }
45
45
  }
46
46
 
47
- public static async Task SidelineGlobalJsonAsync(string currentDirectory, string rootDirectory, Func<Task> action, ILogger? logger = null, bool retainMSBuildSdks = false)
47
+ public static async Task<T> SidelineGlobalJsonAsync<T>(string currentDirectory, string rootDirectory, Func<Task<T>> action, ILogger? logger = null, bool retainMSBuildSdks = false)
48
48
  {
49
49
  logger ??= new ConsoleLogger();
50
50
  var candidateDirectories = PathHelper.GetAllDirectoriesToRoot(currentDirectory, rootDirectory);
51
51
  var globalJsonPaths = candidateDirectories.Select(d => Path.Combine(d, "global.json")).Where(File.Exists).Select(p => (p, p + Guid.NewGuid().ToString())).ToArray();
52
52
  foreach (var (globalJsonPath, tempGlobalJsonPath) in globalJsonPaths)
53
53
  {
54
- logger.Log($"Temporarily removing `global.json` from `{Path.GetDirectoryName(globalJsonPath)}`{(retainMSBuildSdks ? " and retaining MSBuild SDK declarations" : string.Empty)}.");
54
+ logger.Info($"Temporarily removing `global.json` from `{Path.GetDirectoryName(globalJsonPath)}`{(retainMSBuildSdks ? " and retaining MSBuild SDK declarations" : string.Empty)}.");
55
55
  File.Move(globalJsonPath, tempGlobalJsonPath);
56
56
  if (retainMSBuildSdks)
57
57
  {
@@ -73,13 +73,14 @@ internal static partial class MSBuildHelper
73
73
 
74
74
  try
75
75
  {
76
- await action();
76
+ var result = await action();
77
+ return result;
77
78
  }
78
79
  finally
79
80
  {
80
81
  foreach (var (globalJsonpath, tempGlobalJsonPath) in globalJsonPaths)
81
82
  {
82
- logger.Log($"Restoring `global.json` to `{Path.GetDirectoryName(globalJsonpath)}`.");
83
+ logger.Info($"Restoring `global.json` to `{Path.GetDirectoryName(globalJsonpath)}`.");
83
84
  File.Move(tempGlobalJsonPath, globalJsonpath, overwrite: retainMSBuildSdks);
84
85
  }
85
86
  }
@@ -326,7 +327,7 @@ internal static partial class MSBuildHelper
326
327
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
327
328
  try
328
329
  {
329
- var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
330
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
330
331
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
331
332
 
332
333
  // NU1608: Detected package version outside of dependency constraint
@@ -346,7 +347,7 @@ internal static partial class MSBuildHelper
346
347
 
347
348
  try
348
349
  {
349
- string tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
350
+ string tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
350
351
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
351
352
 
352
353
  // Add Dependency[] packages to List<PackageToUpdate> existingPackages
@@ -397,10 +398,10 @@ internal static partial class MSBuildHelper
397
398
  if (added == true)
398
399
  {
399
400
  // Add existing versions to existing list
400
- packageManager.UpdateExistingPackagesWithNewVersions(existingDuplicate, packagesToUpdate);
401
+ packageManager.UpdateExistingPackagesWithNewVersions(existingDuplicate, packagesToUpdate, logger);
401
402
 
402
403
  // Make relationships
403
- await packageManager.PopulatePackageDependenciesAsync(existingDuplicate, targetFramework, Path.GetDirectoryName(projectPath));
404
+ await packageManager.PopulatePackageDependenciesAsync(existingDuplicate, targetFramework, Path.GetDirectoryName(projectPath), logger);
404
405
 
405
406
  // Update all to new versions
406
407
  foreach (var package in existingDuplicate)
@@ -413,10 +414,10 @@ internal static partial class MSBuildHelper
413
414
  else
414
415
  {
415
416
  // Add existing versions to existing list
416
- packageManager.UpdateExistingPackagesWithNewVersions(existingPackages, packagesToUpdate);
417
+ packageManager.UpdateExistingPackagesWithNewVersions(existingPackages, packagesToUpdate, logger);
417
418
 
418
419
  // Make relationships
419
- await packageManager.PopulatePackageDependenciesAsync(existingPackages, targetFramework, Path.GetDirectoryName(projectPath));
420
+ await packageManager.PopulatePackageDependenciesAsync(existingPackages, targetFramework, Path.GetDirectoryName(projectPath), logger);
420
421
 
421
422
  // Update all to new versions
422
423
  foreach (var package in existingPackages)
@@ -502,7 +503,7 @@ internal static partial class MSBuildHelper
502
503
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
503
504
  try
504
505
  {
505
- var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
506
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
506
507
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
507
508
  ThrowOnUnauthenticatedFeed(stdOut);
508
509
 
@@ -626,7 +627,7 @@ internal static partial class MSBuildHelper
626
627
  return projectRoot;
627
628
  }
628
629
 
629
- private static IEnumerable<PackageSource>? LoadPackageSources(string nugetConfigPath)
630
+ private static IEnumerable<PackageSource>? LoadPackageSources(string nugetConfigPath, ILogger logger)
630
631
  {
631
632
  try
632
633
  {
@@ -637,8 +638,8 @@ internal static partial class MSBuildHelper
637
638
  }
638
639
  catch (NuGetConfigurationException ex)
639
640
  {
640
- Console.WriteLine("Error while parsing NuGet.config");
641
- Console.WriteLine(ex.Message);
641
+ logger.Warn("Error while parsing NuGet.config");
642
+ logger.Warn(ex.Message);
642
643
 
643
644
  // Nuget.config is invalid. Won't be able to do anything with specific sources.
644
645
  return null;
@@ -651,6 +652,7 @@ internal static partial class MSBuildHelper
651
652
  string projectPath,
652
653
  string targetFramework,
653
654
  IReadOnlyCollection<Dependency> packages,
655
+ ILogger logger,
654
656
  bool usePackageDownload = false)
655
657
  {
656
658
  var projectDirectory = Path.GetDirectoryName(projectPath);
@@ -663,7 +665,7 @@ internal static partial class MSBuildHelper
663
665
  File.Copy(nugetConfigPath, Path.Combine(tempDir.FullName, "NuGet.Config"));
664
666
  var nugetConfigDir = Path.GetDirectoryName(nugetConfigPath);
665
667
 
666
- var packageSources = LoadPackageSources(nugetConfigPath);
668
+ var packageSources = LoadPackageSources(nugetConfigPath, logger);
667
669
  if (packageSources is not null)
668
670
  {
669
671
  // We need to copy local package sources from the NuGet.Config file to the temp directory
@@ -747,18 +749,70 @@ internal static partial class MSBuildHelper
747
749
  return tempProjectPath;
748
750
  }
749
751
 
752
+ internal static async Task<ImmutableArray<string>> GetTargetFrameworkValuesFromProject(string repoRoot, string projectPath, ILogger logger)
753
+ {
754
+ // TODO: once the updater image has all relevant SDKs installed, we won't have to sideline global.json anymore
755
+ var projectDirectory = Path.GetDirectoryName(projectPath)!;
756
+ var (exitCode, stdOut, stdErr) = await SidelineGlobalJsonAsync(projectDirectory, repoRoot, async () =>
757
+ {
758
+ 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));
760
+ return (exitCode, stdOut, stdErr);
761
+ });
762
+ ThrowOnUnauthenticatedFeed(stdOut);
763
+ if (exitCode != 0)
764
+ {
765
+ logger.Warn($"Error determining target frameworks.\nSTDOUT:\n{stdOut}\nSTDERR:\n{stdErr}");
766
+ }
767
+
768
+ // There are 3 return values, all uses slightly differently. Only one will be set, the others will be blank
769
+ // ProjectData::TargetFrameworkVersion=.NETFramework,Version=v4.5 // non-SDK projects, commonly with `packages.config`
770
+ // ProjectData::TargetFramework=net7.0 // SDK-style projects
771
+ // ProjectData::TargetFrameworks=net7.0;net8.0
772
+ var tfmPatterns = new Regex[]
773
+ {
774
+ new Regex("ProjectData::TargetFrameworkVersion=(?<Value>.*)$", RegexOptions.Multiline),
775
+ new Regex("ProjectData::TargetFramework=(?<Value>.*)$", RegexOptions.Multiline),
776
+ new Regex("ProjectData::TargetFrameworks=(?<Value>.*)$", RegexOptions.Multiline),
777
+ };
778
+ var tfms = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
779
+ foreach (var tfmPattern in tfmPatterns)
780
+ {
781
+ var candidateTfms = tfmPattern.Matches(stdOut)
782
+ .Select(m => m.Groups["Value"].Value)
783
+ .SelectMany(v => v.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
784
+ .Where(v => !string.IsNullOrWhiteSpace(v))
785
+ .Select(v =>
786
+ {
787
+ try
788
+ {
789
+ var framework = NuGetFramework.Parse(v);
790
+ return framework.GetShortFolderName();
791
+ }
792
+ catch
793
+ {
794
+ return string.Empty;
795
+ }
796
+ })
797
+ .Where(tfm => !string.IsNullOrEmpty(tfm));
798
+ tfms.AddRange(candidateTfms);
799
+ }
800
+
801
+ return tfms.ToImmutableArray();
802
+ }
803
+
750
804
  internal static async Task<Dependency[]> GetAllPackageDependenciesAsync(
751
805
  string repoRoot,
752
806
  string projectPath,
753
807
  string targetFramework,
754
808
  IReadOnlyCollection<Dependency> packages,
755
- ILogger? logger = null)
809
+ ILogger logger)
756
810
  {
757
811
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-resolution_");
758
812
  try
759
813
  {
760
814
  var topLevelPackagesNames = packages.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
761
- var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
815
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger);
762
816
 
763
817
  var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", ["build", tempProjectPath, "/t:_ReportDependencies"], workingDirectory: tempDirectory.FullName);
764
818
  ThrowOnUnauthenticatedFeed(stdout);
@@ -783,7 +837,7 @@ internal static partial class MSBuildHelper
783
837
  }
784
838
  else
785
839
  {
786
- logger?.Log($"dotnet build in {nameof(GetAllPackageDependenciesAsync)} failed. STDOUT: {stdout} STDERR: {stderr}");
840
+ logger?.Warn($"dotnet build in {nameof(GetAllPackageDependenciesAsync)} failed. STDOUT: {stdout} STDERR: {stderr}");
787
841
  return [];
788
842
  }
789
843
  }
@@ -4,28 +4,12 @@ namespace NuGetUpdater.Core;
4
4
 
5
5
  internal static class NuGetHelper
6
6
  {
7
- internal const string PackagesConfigFileName = "packages.config";
8
-
9
- public static bool TryGetPackagesConfigFile(string projectPath, [NotNullWhen(returnValue: true)] out string? packagesConfigPath)
10
- {
11
- var projectDirectory = Path.GetDirectoryName(projectPath);
12
-
13
- packagesConfigPath = PathHelper.JoinPath(projectDirectory, PackagesConfigFileName);
14
- if (File.Exists(packagesConfigPath))
15
- {
16
- return true;
17
- }
18
-
19
- packagesConfigPath = null;
20
- return false;
21
- }
22
-
23
7
  internal static async Task<bool> DownloadNuGetPackagesAsync(string repoRoot, string projectPath, IReadOnlyCollection<Dependency> packages, ILogger logger)
24
8
  {
25
9
  var tempDirectory = Directory.CreateTempSubdirectory("msbuild_sdk_restore_");
26
10
  try
27
11
  {
28
- var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, "netstandard2.0", packages, usePackageDownload: true);
12
+ var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, "netstandard2.0", packages, logger, usePackageDownload: true);
29
13
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath]);
30
14
 
31
15
  return exitCode == 0;
@@ -0,0 +1,31 @@
1
+ using System.Diagnostics.CodeAnalysis;
2
+
3
+ namespace NuGetUpdater.Core.Utilities;
4
+
5
+ public class PathComparer : IEqualityComparer<string>
6
+ {
7
+ public static PathComparer Instance { get; } = new PathComparer();
8
+
9
+ public bool Equals(string? x, string? y)
10
+ {
11
+ x = x?.NormalizePathToUnix();
12
+ y = y?.NormalizePathToUnix();
13
+
14
+ if (x is null && y is null)
15
+ {
16
+ return true;
17
+ }
18
+
19
+ if (x is null || y is null)
20
+ {
21
+ return false;
22
+ }
23
+
24
+ return x.Equals(y, StringComparison.OrdinalIgnoreCase);
25
+ }
26
+
27
+ public int GetHashCode([DisallowNull] string obj)
28
+ {
29
+ return obj.NormalizePathToUnix().GetHashCode();
30
+ }
31
+ }
@@ -1,3 +1,6 @@
1
+ using System.Runtime.InteropServices;
2
+ using System.Text.RegularExpressions;
3
+
1
4
  namespace NuGetUpdater.Core;
2
5
 
3
6
  internal static class PathHelper
@@ -63,7 +66,17 @@ internal static class PathHelper
63
66
  return result;
64
67
  }
65
68
 
66
- public static string FullyNormalizedRootedPath(this string path) => path.NormalizePathToUnix().NormalizeUnixPathParts().EnsurePrefix("/");
69
+ public static string FullyNormalizedRootedPath(this string path)
70
+ {
71
+ var normalizedPath = path.NormalizePathToUnix().NormalizeUnixPathParts();
72
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Regex.IsMatch(normalizedPath, @"^[a-z]:", RegexOptions.IgnoreCase))
73
+ {
74
+ // Windows path is ready to go
75
+ return normalizedPath;
76
+ }
77
+
78
+ return normalizedPath.EnsurePrefix("/");
79
+ }
67
80
 
68
81
  public static string GetFullPathFromRelative(string rootPath, string relativePath)
69
82
  => Path.GetFullPath(JoinPath(rootPath, relativePath.NormalizePathToUnix()));
@@ -92,7 +105,7 @@ internal static class PathHelper
92
105
  /// </summary>
93
106
  /// <param name="filePath">The file path to resolve.</param>
94
107
  /// <param name="repoRootPath">The root path of the repository.</param>
95
- public static string? ResolveCaseInsensitivePathInsideRepoRoot(string filePath, string repoRootPath)
108
+ public static List<string>? ResolveCaseInsensitivePathsInsideRepoRoot(string filePath, string repoRootPath)
96
109
  {
97
110
  if (string.IsNullOrEmpty(filePath) || string.IsNullOrEmpty(repoRootPath))
98
111
  {
@@ -110,7 +123,7 @@ internal static class PathHelper
110
123
  }
111
124
 
112
125
  // Start resolving from the root path
113
- var currentPath = normalizedRepoRoot;
126
+ var currentPaths = new List<string> { normalizedRepoRoot };
114
127
  var relativePath = normalizedFilePath.Substring(normalizedRepoRoot.Length).TrimStart('/');
115
128
 
116
129
  foreach (var part in relativePath.Split('/'))
@@ -120,20 +133,28 @@ internal static class PathHelper
120
133
  continue;
121
134
  }
122
135
 
123
- // Enumerate the current directory to find a case-insensitive match
124
- var nextPath = Directory
125
- .EnumerateFileSystemEntries(currentPath)
126
- .FirstOrDefault(entry => string.Equals(Path.GetFileName(entry), part, StringComparison.OrdinalIgnoreCase));
136
+ var nextPaths = new List<string>();
137
+
138
+ // Iterate through all current paths to find matches for the current part
139
+ foreach (var currentPath in currentPaths)
140
+ {
141
+ var matches = Directory
142
+ .EnumerateFileSystemEntries(currentPath)
143
+ .Where(entry => string.Equals(Path.GetFileName(entry), part, StringComparison.OrdinalIgnoreCase));
144
+
145
+ nextPaths.AddRange(matches);
146
+ }
127
147
 
128
- if (nextPath == null)
148
+ if (!nextPaths.Any())
129
149
  {
130
150
  return null; // Part of the path does not exist
131
151
  }
132
152
 
133
- currentPath = nextPath;
153
+ currentPaths = nextPaths;
134
154
  }
135
155
 
136
- return currentPath; // Fully resolved path with correct casing
156
+ // Normalize all resulting paths to Unix format
157
+ return currentPaths.Select(path => path.NormalizePathToUnix()).ToList();
137
158
  }
138
159
 
139
160
  /// <summary>
@@ -212,4 +233,19 @@ internal static class PathHelper
212
233
 
213
234
  return false;
214
235
  }
236
+
237
+ public static bool IsFileUnderDirectory(DirectoryInfo directory, FileInfo candidateFile)
238
+ {
239
+ // n.b., using `DirectoryInfo` and `FileInfo` here to ensure that the callsite doesn't get confused with just strings
240
+ // the paths are then normalized to make the comparison easier.
241
+ var directoryPath = directory.FullName.NormalizePathToUnix();
242
+ if (!directoryPath.EndsWith("/"))
243
+ {
244
+ // ensuring a trailing slash means we can do a simple string check later on
245
+ directoryPath += "/";
246
+ }
247
+ var candidateFilePath = candidateFile.FullName.NormalizePathToUnix();
248
+
249
+ return candidateFilePath.StartsWith(directoryPath);
250
+ }
215
251
  }
@@ -0,0 +1,96 @@
1
+ using System.Collections.Immutable;
2
+
3
+ using Microsoft.Build.Construction;
4
+
5
+ namespace NuGetUpdater.Core.Utilities;
6
+
7
+ internal static class ProjectHelper
8
+ {
9
+ public const string PackagesConfigFileName = "packages.config";
10
+ public const string AppConfigFileName = "app.config";
11
+ public const string WebConfigFileName = "web.config";
12
+ public const string PackagesLockJsonFileName = "packages.lock.json";
13
+
14
+ public enum PathFormat
15
+ {
16
+ Relative,
17
+ Full,
18
+ }
19
+
20
+ public static ImmutableArray<string> GetAllAdditionalFilesFromProject(string fullProjectPath, PathFormat pathFormat)
21
+ {
22
+ return GetAdditionalFilesFromProjectContent(fullProjectPath, pathFormat)
23
+ .AddRange(GetAdditionalFilesFromProjectLocation(fullProjectPath, pathFormat));
24
+ }
25
+
26
+ public static ImmutableArray<string> GetAdditionalFilesFromProjectContent(string fullProjectPath, PathFormat pathFormat)
27
+ {
28
+ var projectRootElement = ProjectRootElement.Open(fullProjectPath);
29
+ var additionalFilesWithFullPaths = new[]
30
+ {
31
+ projectRootElement.GetItemPathWithFileName(PackagesConfigFileName),
32
+ projectRootElement.GetItemPathWithFileName(AppConfigFileName),
33
+ projectRootElement.GetItemPathWithFileName(WebConfigFileName),
34
+ }.Where(p => p is not null).Cast<string>().ToImmutableArray();
35
+
36
+ var additionalFiles = additionalFilesWithFullPaths
37
+ .Select(p => MakePathAppropriateFormat(fullProjectPath, p, pathFormat))
38
+ .ToImmutableArray();
39
+ return additionalFiles;
40
+ }
41
+
42
+ public static ImmutableArray<string> GetAdditionalFilesFromProjectLocation(string fullProjectPath, PathFormat pathFormat)
43
+ {
44
+ var additionalFilesWithFullPaths = new[]
45
+ {
46
+ GetPathWithRegardsToProjectFile(fullProjectPath, PackagesLockJsonFileName),
47
+ }.Where(p => p is not null).Cast<string>().ToImmutableArray();
48
+
49
+ var additionalFiles = additionalFilesWithFullPaths
50
+ .Select(p => MakePathAppropriateFormat(fullProjectPath, p, pathFormat))
51
+ .ToImmutableArray();
52
+ return additionalFiles;
53
+ }
54
+
55
+ public static string? GetPackagesConfigPathFromProject(string fullProjectPath, PathFormat pathFormat)
56
+ {
57
+ var additionalFiles = GetAdditionalFilesFromProjectContent(fullProjectPath, pathFormat);
58
+ var packagesConfigFile = additionalFiles.FirstOrDefault(p => Path.GetFileName(p).Equals(PackagesConfigFileName, StringComparison.Ordinal));
59
+ return packagesConfigFile;
60
+ }
61
+
62
+ private static string MakePathAppropriateFormat(string fullProjectPath, string fullFilePath, PathFormat pathFormat)
63
+ {
64
+ var projectDirectory = Path.GetDirectoryName(fullProjectPath)!;
65
+ var updatedPath = pathFormat switch
66
+ {
67
+ PathFormat.Full => fullFilePath,
68
+ PathFormat.Relative => Path.GetRelativePath(projectDirectory, fullFilePath),
69
+ _ => throw new NotSupportedException(),
70
+ };
71
+ return updatedPath.NormalizePathToUnix();
72
+ }
73
+
74
+ private static string? GetItemPathWithFileName(this ProjectRootElement projectRootElement, string itemFileName)
75
+ {
76
+ var projectDirectory = Path.GetDirectoryName(projectRootElement.FullPath)!;
77
+ var packagesConfigPath = projectRootElement.Items
78
+ .Where(i => i.ElementName.Equals("None", StringComparison.OrdinalIgnoreCase) ||
79
+ i.ElementName.Equals("Content", StringComparison.OrdinalIgnoreCase))
80
+ .Where(i => Path.GetFileName(i.Include).Equals(itemFileName, StringComparison.OrdinalIgnoreCase))
81
+ .Select(i => Path.GetFullPath(Path.Combine(projectDirectory, i.Include)))
82
+ .Where(File.Exists)
83
+ .FirstOrDefault()
84
+ ?.NormalizePathToUnix();
85
+ return packagesConfigPath;
86
+ }
87
+
88
+ private static string? GetPathWithRegardsToProjectFile(string fullProjectPath, string fileName)
89
+ {
90
+ var projectDirectory = Path.GetDirectoryName(fullProjectPath)!;
91
+ var filePath = Directory.EnumerateFiles(projectDirectory)
92
+ .Where(p => Path.GetFileName(p).Equals(fileName, StringComparison.Ordinal))
93
+ .FirstOrDefault();
94
+ return filePath;
95
+ }
96
+ }