dependabot-nuget 0.251.0 → 0.252.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Common.props +1 -0
  3. data/helpers/lib/NuGetUpdater/Directory.Packages.props +26 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +35 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/NuGetUpdater.Cli.csproj +1 -1
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +4 -7
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +251 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/NuGetUpdater.Cli.Test.csproj +3 -3
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Dependency.cs +56 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyType.cs +1 -1
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscovery.cs +69 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscoveryResult.cs +11 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +217 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DotNetToolsJsonDiscovery.cs +30 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DotNetToolsJsonDiscoveryResult.cs +10 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/GlobalJsonDiscovery.cs +30 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/GlobalJsonDiscoveryResult.cs +10 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/IDiscoveryResult.cs +14 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +29 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscoveryResult.cs +10 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +13 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +127 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +13 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EvaluationResult.cs +8 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EvaluationResultType.cs +9 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/BuildFile.cs +6 -8
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/DotNetToolsJsonBuildFile.cs +4 -7
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/GlobalJsonBuildFile.cs +24 -17
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +2 -2
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +8 -13
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +100 -19
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/XmlBuildFile.cs +2 -2
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +6 -6
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Property.cs +6 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +23 -36
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +5 -10
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +16 -21
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +4 -19
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/HashSetExtensions.cs +14 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ImmutableArrayExtensions.cs +18 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +0 -1
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +121 -68
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +27 -4
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +117 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.DotNetToolsJson.cs +91 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +71 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +59 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +380 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +306 -0
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +36 -0
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/DotNetToolsJsonBuildFileTests.cs +1 -2
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/GlobalJsonBuildFileTests.cs +2 -3
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/PackagesConfigBuildFileTests.cs +4 -6
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs +6 -5
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +4 -3
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +38 -6
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +12 -40
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/AssertEx.cs +272 -0
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/DiffUtil.cs +266 -0
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +195 -152
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterHelperTests.cs +7 -11
  62. data/lib/dependabot/nuget/discovery/dependency_details.rb +95 -0
  63. data/lib/dependabot/nuget/discovery/dependency_file_discovery.rb +126 -0
  64. data/lib/dependabot/nuget/discovery/directory_packages_props_discovery.rb +43 -0
  65. data/lib/dependabot/nuget/discovery/discovery_json_reader.rb +83 -0
  66. data/lib/dependabot/nuget/discovery/evaluation_details.rb +63 -0
  67. data/lib/dependabot/nuget/discovery/project_discovery.rb +71 -0
  68. data/lib/dependabot/nuget/discovery/property_details.rb +43 -0
  69. data/lib/dependabot/nuget/discovery/workspace_discovery.rb +66 -0
  70. data/lib/dependabot/nuget/file_parser.rb +19 -128
  71. data/lib/dependabot/nuget/file_updater.rb +28 -60
  72. data/lib/dependabot/nuget/native_helpers.rb +55 -0
  73. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +3 -8
  74. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +1 -0
  75. data/lib/dependabot/nuget/update_checker/property_updater.rb +1 -0
  76. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +17 -152
  77. data/lib/dependabot/nuget/update_checker/version_finder.rb +1 -6
  78. data/lib/dependabot/nuget/update_checker.rb +4 -1
  79. metadata +43 -11
  80. data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +0 -71
  81. data/lib/dependabot/nuget/file_parser/global_json_parser.rb +0 -68
  82. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +0 -92
  83. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +0 -620
  84. data/lib/dependabot/nuget/file_parser/property_value_finder.rb +0 -225
  85. data/lib/dependabot/nuget/file_updater/property_value_updater.rb +0 -81
@@ -0,0 +1,217 @@
1
+ using System.Collections.Immutable;
2
+ using System.Text.Json;
3
+ using System.Text.Json.Serialization;
4
+
5
+ using NuGetUpdater.Core.Utilities;
6
+
7
+ namespace NuGetUpdater.Core.Discover;
8
+
9
+ public partial class DiscoveryWorker
10
+ {
11
+ public const string DiscoveryResultFileName = "./.dependabot/discovery.json";
12
+
13
+ private readonly Logger _logger;
14
+ private readonly HashSet<string> _processedProjectPaths = new(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _restoredMSBuildSdks = new(StringComparer.OrdinalIgnoreCase);
15
+
16
+ internal static readonly JsonSerializerOptions SerializerOptions = new()
17
+ {
18
+ WriteIndented = true,
19
+ Converters = { new JsonStringEnumConverter() },
20
+ };
21
+
22
+ public DiscoveryWorker(Logger logger)
23
+ {
24
+ _logger = logger;
25
+ }
26
+
27
+ public async Task RunAsync(string repoRootPath, string workspacePath, string outputPath)
28
+ {
29
+ MSBuildHelper.RegisterMSBuild();
30
+
31
+ // When running under unit tests, the workspace path may not be rooted.
32
+ if (!Path.IsPathRooted(workspacePath) || !Directory.Exists(workspacePath))
33
+ {
34
+ workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
35
+ }
36
+ else if (workspacePath == "/")
37
+ {
38
+ workspacePath = repoRootPath;
39
+ }
40
+
41
+ DotNetToolsJsonDiscoveryResult? dotNetToolsJsonDiscovery = null;
42
+ GlobalJsonDiscoveryResult? globalJsonDiscovery = null;
43
+ DirectoryPackagesPropsDiscoveryResult? directoryPackagesPropsDiscovery = null;
44
+
45
+ ImmutableArray<ProjectDiscoveryResult> projectResults = [];
46
+
47
+ if (Directory.Exists(workspacePath))
48
+ {
49
+ _logger.Log($"Discovering build files in workspace [{workspacePath}].");
50
+
51
+ dotNetToolsJsonDiscovery = DotNetToolsJsonDiscovery.Discover(repoRootPath, workspacePath, _logger);
52
+ globalJsonDiscovery = GlobalJsonDiscovery.Discover(repoRootPath, workspacePath, _logger);
53
+
54
+ if (globalJsonDiscovery is not null)
55
+ {
56
+ await TryRestoreMSBuildSdksAsync(repoRootPath, workspacePath, globalJsonDiscovery.Dependencies, _logger);
57
+ }
58
+
59
+ projectResults = await RunForDirectoryAsnyc(repoRootPath, workspacePath);
60
+
61
+ directoryPackagesPropsDiscovery = DirectoryPackagesPropsDiscovery.Discover(repoRootPath, workspacePath, projectResults, _logger);
62
+
63
+ if (directoryPackagesPropsDiscovery is not null)
64
+ {
65
+ projectResults = projectResults.Remove(projectResults.First(p => p.FilePath.Equals(directoryPackagesPropsDiscovery.FilePath, StringComparison.OrdinalIgnoreCase)));
66
+ }
67
+ }
68
+ else
69
+ {
70
+ _logger.Log($"Workspace path [{workspacePath}] does not exist.");
71
+ }
72
+
73
+ var result = new WorkspaceDiscoveryResult
74
+ {
75
+ FilePath = repoRootPath != workspacePath ? Path.GetRelativePath(repoRootPath, workspacePath) : string.Empty,
76
+ DotNetToolsJson = dotNetToolsJsonDiscovery,
77
+ GlobalJson = globalJsonDiscovery,
78
+ DirectoryPackagesProps = directoryPackagesPropsDiscovery,
79
+ Projects = projectResults.OrderBy(p => p.FilePath).ToImmutableArray(),
80
+ };
81
+
82
+ await WriteResults(repoRootPath, outputPath, result);
83
+
84
+ _logger.Log("Discovery complete.");
85
+
86
+ _processedProjectPaths.Clear();
87
+ }
88
+
89
+ /// <summary>
90
+ /// Restores MSBuild SDKs from the given dependencies.
91
+ /// </summary>
92
+ /// <returns>Returns `true` when SDKs were restored successfully.</returns>
93
+ private async Task<bool> TryRestoreMSBuildSdksAsync(string repoRootPath, string workspacePath, ImmutableArray<Dependency> dependencies, Logger logger)
94
+ {
95
+ var msbuildSdks = dependencies
96
+ .Where(d => d.Type == DependencyType.MSBuildSdk && !string.IsNullOrEmpty(d.Version))
97
+ .Where(d => !d.Name.Equals("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase))
98
+ .Where(d => !_restoredMSBuildSdks.Contains($"{d.Name}/{d.Version}"))
99
+ .ToImmutableArray();
100
+
101
+ if (msbuildSdks.Length == 0)
102
+ {
103
+ return false;
104
+ }
105
+
106
+ var keys = msbuildSdks.Select(d => $"{d.Name}/{d.Version}");
107
+
108
+ _restoredMSBuildSdks.AddRange(keys);
109
+
110
+ _logger.Log($" Restoring MSBuild SDKs: {string.Join(", ", keys)}");
111
+
112
+ return await NuGetHelper.DownloadNuGetPackagesAsync(repoRootPath, workspacePath, msbuildSdks, logger);
113
+ }
114
+
115
+ private async Task<ImmutableArray<ProjectDiscoveryResult>> RunForDirectoryAsnyc(string repoRootPath, string workspacePath)
116
+ {
117
+ _logger.Log($" Discovering projects beneath [{Path.GetRelativePath(repoRootPath, workspacePath)}].");
118
+ var projectPaths = FindProjectFiles(workspacePath);
119
+ if (projectPaths.IsEmpty)
120
+ {
121
+ _logger.Log(" No project files found.");
122
+ return [];
123
+ }
124
+
125
+ return await RunForProjectPathsAsync(repoRootPath, workspacePath, projectPaths);
126
+ }
127
+
128
+ private static ImmutableArray<string> FindProjectFiles(string workspacePath)
129
+ {
130
+ return Directory.EnumerateFiles(workspacePath, "*.*proj", SearchOption.AllDirectories)
131
+ .Where(path =>
132
+ {
133
+ var extension = Path.GetExtension(path).ToLowerInvariant();
134
+ return extension == ".proj" || extension == ".csproj" || extension == ".fsproj" || extension == ".vbproj";
135
+ })
136
+ .ToImmutableArray();
137
+ }
138
+
139
+ private async Task<ImmutableArray<ProjectDiscoveryResult>> RunForProjectPathsAsync(string repoRootPath, string workspacePath, IEnumerable<string> projectPaths)
140
+ {
141
+ var results = new Dictionary<string, ProjectDiscoveryResult>(StringComparer.OrdinalIgnoreCase);
142
+ foreach (var projectPath in projectPaths)
143
+ {
144
+ // If there is some MSBuild logic that needs to run to fully resolve the path skip the project
145
+ if (!File.Exists(projectPath))
146
+ {
147
+ continue;
148
+ }
149
+
150
+ if (_processedProjectPaths.Contains(projectPath))
151
+ {
152
+ continue;
153
+ }
154
+ _processedProjectPaths.Add(projectPath);
155
+
156
+ var relativeProjectPath = Path.GetRelativePath(workspacePath, projectPath);
157
+ var packagesConfigDependencies = PackagesConfigDiscovery.Discover(workspacePath, projectPath, _logger)
158
+ ?.Dependencies;
159
+
160
+ var projectResults = await SdkProjectDiscovery.DiscoverAsync(repoRootPath, workspacePath, projectPath, _logger);
161
+
162
+ // Determine if there were unrestored MSBuildSdks
163
+ var msbuildSdks = projectResults.SelectMany(p => p.Dependencies.Where(d => d.Type == DependencyType.MSBuildSdk)).ToImmutableArray();
164
+ if (msbuildSdks.Length > 0)
165
+ {
166
+ // If new SDKs were restored, then we need to rerun SdkProjectDiscovery.
167
+ if (await TryRestoreMSBuildSdksAsync(repoRootPath, workspacePath, msbuildSdks, _logger))
168
+ {
169
+ projectResults = await SdkProjectDiscovery.DiscoverAsync(repoRootPath, workspacePath, projectPath, _logger);
170
+ }
171
+ }
172
+
173
+ foreach (var projectResult in projectResults)
174
+ {
175
+ if (results.ContainsKey(projectResult.FilePath))
176
+ {
177
+ continue;
178
+ }
179
+
180
+ // If we had packages.config dependencies, merge them with the project dependencies
181
+ if (projectResult.FilePath == relativeProjectPath && packagesConfigDependencies is not null)
182
+ {
183
+ packagesConfigDependencies = packagesConfigDependencies.Value
184
+ .Select(d => d with { TargetFrameworks = projectResult.TargetFrameworks })
185
+ .ToImmutableArray();
186
+
187
+ results[projectResult.FilePath] = projectResult with
188
+ {
189
+ Dependencies = [.. projectResult.Dependencies, .. packagesConfigDependencies],
190
+ };
191
+ }
192
+ else
193
+ {
194
+ results[projectResult.FilePath] = projectResult;
195
+ }
196
+ }
197
+ }
198
+
199
+ return [.. results.Values];
200
+ }
201
+
202
+ private static async Task WriteResults(string repoRootPath, string outputPath, WorkspaceDiscoveryResult result)
203
+ {
204
+ var resultPath = Path.IsPathRooted(outputPath)
205
+ ? outputPath
206
+ : Path.GetFullPath(outputPath, repoRootPath);
207
+
208
+ var resultDirectory = Path.GetDirectoryName(resultPath)!;
209
+ if (!Directory.Exists(resultDirectory))
210
+ {
211
+ Directory.CreateDirectory(resultDirectory);
212
+ }
213
+
214
+ var resultJson = JsonSerializer.Serialize(result, SerializerOptions);
215
+ await File.WriteAllTextAsync(path: resultPath, resultJson);
216
+ }
217
+ }
@@ -0,0 +1,30 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Discover;
4
+
5
+ internal static class DotNetToolsJsonDiscovery
6
+ {
7
+ public static DotNetToolsJsonDiscoveryResult? Discover(string repoRootPath, string workspacePath, Logger logger)
8
+ {
9
+ if (!MSBuildHelper.TryGetDotNetToolsJsonPath(repoRootPath, workspacePath, out var dotnetToolsJsonPath))
10
+ {
11
+ logger.Log(" No dotnet-tools.json file found.");
12
+ return null;
13
+ }
14
+
15
+ var dotnetToolsJsonFile = DotNetToolsJsonBuildFile.Open(workspacePath, dotnetToolsJsonPath, logger);
16
+
17
+ logger.Log($" Discovered [{dotnetToolsJsonFile.RelativePath}] file.");
18
+
19
+ var dependencies = BuildFile.GetDependencies(dotnetToolsJsonFile)
20
+ .OrderBy(d => d.Name)
21
+ .ToImmutableArray();
22
+
23
+ return new()
24
+ {
25
+ FilePath = dotnetToolsJsonFile.RelativePath,
26
+ IsSuccess = !dotnetToolsJsonFile.FailedToParse,
27
+ Dependencies = dependencies,
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,10 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Discover;
4
+
5
+ public sealed record DotNetToolsJsonDiscoveryResult : IDiscoveryResultWithDependencies
6
+ {
7
+ public required string FilePath { get; init; }
8
+ public bool IsSuccess { get; init; } = true;
9
+ public ImmutableArray<Dependency> Dependencies { get; init; }
10
+ }
@@ -0,0 +1,30 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Discover;
4
+
5
+ internal static class GlobalJsonDiscovery
6
+ {
7
+ public static GlobalJsonDiscoveryResult? Discover(string repoRootPath, string workspacePath, Logger logger)
8
+ {
9
+ if (!MSBuildHelper.TryGetGlobalJsonPath(repoRootPath, workspacePath, out var globalJsonPath))
10
+ {
11
+ logger.Log(" No global.json file found.");
12
+ return null;
13
+ }
14
+
15
+ var globalJsonFile = GlobalJsonBuildFile.Open(workspacePath, globalJsonPath, logger);
16
+
17
+ logger.Log($" Discovered [{globalJsonFile.RelativePath}] file.");
18
+
19
+ var dependencies = BuildFile.GetDependencies(globalJsonFile)
20
+ .OrderBy(d => d.Name)
21
+ .ToImmutableArray();
22
+
23
+ return new()
24
+ {
25
+ FilePath = globalJsonFile.RelativePath,
26
+ IsSuccess = !globalJsonFile.FailedToParse,
27
+ Dependencies = dependencies,
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,10 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Discover;
4
+
5
+ public sealed record GlobalJsonDiscoveryResult : IDiscoveryResultWithDependencies
6
+ {
7
+ public required string FilePath { get; init; }
8
+ public bool IsSuccess { get; init; } = true;
9
+ public ImmutableArray<Dependency> Dependencies { get; init; }
10
+ }
@@ -0,0 +1,14 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Discover;
4
+
5
+ public interface IDiscoveryResult
6
+ {
7
+ string FilePath { get; }
8
+ bool IsSuccess { get; }
9
+ }
10
+
11
+ public interface IDiscoveryResultWithDependencies : IDiscoveryResult
12
+ {
13
+ ImmutableArray<Dependency> Dependencies { get; }
14
+ }
@@ -0,0 +1,29 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Discover;
4
+
5
+ internal static class PackagesConfigDiscovery
6
+ {
7
+ public static PackagesConfigDiscoveryResult? Discover(string workspacePath, string projectPath, Logger logger)
8
+ {
9
+ if (!NuGetHelper.TryGetPackagesConfigFile(projectPath, out var packagesConfigPath))
10
+ {
11
+ logger.Log(" No packages.config file found.");
12
+ return null;
13
+ }
14
+
15
+ var packagesConfigFile = PackagesConfigBuildFile.Open(workspacePath, packagesConfigPath);
16
+
17
+ logger.Log($" Discovered [{packagesConfigFile.RelativePath}] file.");
18
+
19
+ var dependencies = BuildFile.GetDependencies(packagesConfigFile)
20
+ .OrderBy(d => d.Name)
21
+ .ToImmutableArray();
22
+
23
+ return new()
24
+ {
25
+ FilePath = packagesConfigFile.RelativePath,
26
+ Dependencies = dependencies,
27
+ };
28
+ }
29
+ }
@@ -0,0 +1,10 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Discover;
4
+
5
+ public sealed record PackagesConfigDiscoveryResult : IDiscoveryResultWithDependencies
6
+ {
7
+ public required string FilePath { get; init; }
8
+ public bool IsSuccess { get; init; } = true;
9
+ public ImmutableArray<Dependency> Dependencies { get; init; }
10
+ }
@@ -0,0 +1,13 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Discover;
4
+
5
+ public record ProjectDiscoveryResult : IDiscoveryResultWithDependencies
6
+ {
7
+ public required string FilePath { get; init; }
8
+ public required ImmutableArray<Dependency> Dependencies { get; init; }
9
+ public bool IsSuccess { get; init; } = true;
10
+ public ImmutableArray<Property> Properties { get; init; } = [];
11
+ public ImmutableArray<string> TargetFrameworks { get; init; } = [];
12
+ public ImmutableArray<string> ReferencedProjectPaths { get; init; } = [];
13
+ }
@@ -0,0 +1,127 @@
1
+ using System.Collections.Immutable;
2
+
3
+ using NuGet.Versioning;
4
+
5
+ namespace NuGetUpdater.Core.Discover;
6
+
7
+ internal static class SdkProjectDiscovery
8
+ {
9
+ public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string projectPath, Logger logger)
10
+ {
11
+ // Determine which targets and props files contribute to the build.
12
+ var buildFiles = await MSBuildHelper.LoadBuildFilesAsync(repoRootPath, projectPath, includeSdkPropsAndTargets: true);
13
+
14
+ // Get all the dependencies which are directly referenced from the project file or indirectly referenced from
15
+ // targets and props files.
16
+ var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles);
17
+
18
+ var results = ImmutableArray.CreateBuilder<ProjectDiscoveryResult>();
19
+ foreach (var buildFile in buildFiles)
20
+ {
21
+ // Only include build files that exist beneath the RepoRootPath.
22
+ if (buildFile.IsOutsideBasePath)
23
+ {
24
+ continue;
25
+ }
26
+
27
+ // The build file dependencies have the correct DependencyType and the TopLevelDependencies have the evaluated version.
28
+ // Combine them to have the set of dependencies that are directly referenced from the build file.
29
+ var fileDependencies = BuildFile.GetDependencies(buildFile)
30
+ .ToDictionary(d => d.Name, StringComparer.OrdinalIgnoreCase);
31
+ var sdkDependencies = fileDependencies.Values
32
+ .Where(d => d.Type == DependencyType.MSBuildSdk)
33
+ .ToImmutableArray();
34
+ var indirectDependencies = topLevelDependencies
35
+ .Where(d => !fileDependencies.ContainsKey(d.Name))
36
+ .ToImmutableArray();
37
+ var directDependencies = topLevelDependencies
38
+ .Where(d => fileDependencies.ContainsKey(d.Name))
39
+ .Select(d =>
40
+ {
41
+ var dependency = fileDependencies[d.Name];
42
+ return d with
43
+ {
44
+ Type = dependency.Type,
45
+ IsDirect = true
46
+ };
47
+ }).ToImmutableArray();
48
+
49
+ if (buildFile.GetFileType() == ProjectBuildFileType.Project)
50
+ {
51
+ // Collect information that is specific to the project file.
52
+ var tfms = MSBuildHelper.GetTargetFrameworkMonikers(buildFiles)
53
+ .OrderBy(tfm => tfm)
54
+ .ToImmutableArray();
55
+ var properties = MSBuildHelper.GetProperties(buildFiles).Values
56
+ .Where(p => !p.SourceFilePath.StartsWith(".."))
57
+ .OrderBy(p => p.Name)
58
+ .ToImmutableArray();
59
+ var referencedProjectPaths = MSBuildHelper.GetProjectPathsFromProject(projectPath)
60
+ .Select(path => Path.GetRelativePath(workspacePath, path))
61
+ .OrderBy(p => p)
62
+ .ToImmutableArray();
63
+
64
+ // Get the complete set of dependencies including transitive dependencies.
65
+ var dependencies = indirectDependencies.Concat(directDependencies).ToImmutableArray();
66
+ dependencies = dependencies
67
+ .Select(d => d with { TargetFrameworks = tfms })
68
+ .ToImmutableArray();
69
+ var transitiveDependencies = await GetTransitiveDependencies(repoRootPath, projectPath, tfms, dependencies, logger);
70
+ ImmutableArray<Dependency> allDependencies = dependencies.Concat(transitiveDependencies).Concat(sdkDependencies)
71
+ .OrderBy(d => d.Name)
72
+ .ToImmutableArray();
73
+
74
+ results.Add(new()
75
+ {
76
+ FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
77
+ Properties = properties,
78
+ TargetFrameworks = tfms,
79
+ ReferencedProjectPaths = referencedProjectPaths,
80
+ Dependencies = allDependencies,
81
+ });
82
+ }
83
+ else
84
+ {
85
+ results.Add(new()
86
+ {
87
+ FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
88
+ Dependencies = directDependencies.Concat(sdkDependencies)
89
+ .OrderBy(d => d.Name)
90
+ .ToImmutableArray(),
91
+ });
92
+ }
93
+ }
94
+
95
+ return results.ToImmutable();
96
+ }
97
+
98
+ private static async Task<ImmutableArray<Dependency>> GetTransitiveDependencies(string repoRootPath, string projectPath, ImmutableArray<string> tfms, ImmutableArray<Dependency> directDependencies, Logger logger)
99
+ {
100
+ Dictionary<string, Dependency> transitiveDependencies = new(StringComparer.OrdinalIgnoreCase);
101
+ foreach (var tfm in tfms)
102
+ {
103
+ var tfmDependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, directDependencies, logger);
104
+ foreach (var dependency in tfmDependencies.Where(d => d.IsTransitive))
105
+ {
106
+ if (!transitiveDependencies.TryGetValue(dependency.Name, out var existingDependency))
107
+ {
108
+ transitiveDependencies[dependency.Name] = dependency;
109
+ continue;
110
+ }
111
+
112
+ transitiveDependencies[dependency.Name] = existingDependency with
113
+ {
114
+ // Revisit this logic. We may want to return each dependency instead of merging them.
115
+ Version = SemanticVersion.Parse(existingDependency.Version!) > SemanticVersion.Parse(dependency.Version!)
116
+ ? existingDependency.Version
117
+ : dependency.Version,
118
+ TargetFrameworks = existingDependency.TargetFrameworks is not null && dependency.TargetFrameworks is not null
119
+ ? existingDependency.TargetFrameworks.Value.AddRange(dependency.TargetFrameworks)
120
+ : existingDependency.TargetFrameworks ?? dependency.TargetFrameworks,
121
+ };
122
+ }
123
+ }
124
+
125
+ return [.. transitiveDependencies.Values];
126
+ }
127
+ }
@@ -0,0 +1,13 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Discover;
4
+
5
+ public sealed record WorkspaceDiscoveryResult : IDiscoveryResult
6
+ {
7
+ public required string FilePath { get; init; }
8
+ public bool IsSuccess { get; init; } = true;
9
+ public ImmutableArray<ProjectDiscoveryResult> Projects { get; init; }
10
+ public DirectoryPackagesPropsDiscoveryResult? DirectoryPackagesProps { get; init; }
11
+ public GlobalJsonDiscoveryResult? GlobalJson { get; init; }
12
+ public DotNetToolsJsonDiscoveryResult? DotNetToolsJson { get; init; }
13
+ }
@@ -0,0 +1,8 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ public record EvaluationResult(
4
+ EvaluationResultType ResultType,
5
+ string OriginalValue,
6
+ string EvaluatedValue,
7
+ string? RootPropertyName,
8
+ string? ErrorMessage);
@@ -0,0 +1,9 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ public enum EvaluationResultType
4
+ {
5
+ Success,
6
+ PropertyIgnored,
7
+ CircularReference,
8
+ PropertyNotFound,
9
+ }
@@ -1,8 +1,4 @@
1
- using System;
2
- using System.Collections.Generic;
3
- using System.IO;
4
1
  using System.Text.RegularExpressions;
5
- using System.Threading.Tasks;
6
2
 
7
3
  using DiffPlex;
8
4
  using DiffPlex.DiffBuilder;
@@ -12,13 +8,15 @@ namespace NuGetUpdater.Core;
12
8
 
13
9
  internal abstract class BuildFile
14
10
  {
15
- public string RepoRootPath { get; }
11
+ public string BasePath { get; }
16
12
  public string Path { get; }
17
- public string RepoRelativePath => System.IO.Path.GetRelativePath(RepoRootPath, Path);
13
+ public string RelativePath => System.IO.Path.GetRelativePath(BasePath, Path);
14
+ public bool IsOutsideBasePath => RelativePath.StartsWith("..");
15
+ public bool FailedToParse { get; protected set; }
18
16
 
19
- public BuildFile(string repoRootPath, string path)
17
+ public BuildFile(string basePath, string path)
20
18
  {
21
- RepoRootPath = repoRootPath;
19
+ BasePath = basePath;
22
20
  Path = path;
23
21
  }
24
22
 
@@ -1,17 +1,14 @@
1
- using System.Collections.Generic;
2
- using System.IO;
3
- using System.Linq;
4
1
  using System.Text.Json.Nodes;
5
2
 
6
3
  namespace NuGetUpdater.Core;
7
4
 
8
5
  internal sealed class DotNetToolsJsonBuildFile : JsonBuildFile
9
6
  {
10
- public static DotNetToolsJsonBuildFile Open(string repoRootPath, string path, Logger logger)
11
- => new(repoRootPath, path, File.ReadAllText(path), logger);
7
+ public static DotNetToolsJsonBuildFile Open(string basePath, string path, Logger logger)
8
+ => new(basePath, path, File.ReadAllText(path), logger);
12
9
 
13
- public DotNetToolsJsonBuildFile(string repoRootPath, string path, string contents, Logger logger)
14
- : base(repoRootPath, path, contents, logger)
10
+ public DotNetToolsJsonBuildFile(string basePath, string path, string contents, Logger logger)
11
+ : base(basePath, path, contents, logger)
15
12
  {
16
13
  }
17
14
 
@@ -1,36 +1,43 @@
1
- using System.Collections.Generic;
2
- using System.IO;
3
- using System.Linq;
4
1
  using System.Text.Json.Nodes;
5
2
 
6
3
  namespace NuGetUpdater.Core;
7
4
 
8
5
  internal sealed class GlobalJsonBuildFile : JsonBuildFile
9
6
  {
10
- public static GlobalJsonBuildFile Open(string repoRootPath, string path, Logger logger)
11
- => new(repoRootPath, path, File.ReadAllText(path), logger);
7
+ public static GlobalJsonBuildFile Open(string basePath, string path, Logger logger)
8
+ => new(basePath, path, File.ReadAllText(path), logger);
12
9
 
13
- public GlobalJsonBuildFile(string repoRootPath, string path, string contents, Logger logger)
14
- : base(repoRootPath, path, contents, logger)
10
+ public GlobalJsonBuildFile(string basePath, string path, string contents, Logger logger)
11
+ : base(basePath, path, contents, logger)
15
12
  {
16
13
  }
17
14
 
18
- public JsonObject? Sdk
15
+ public JsonObject? Sdk => Node.Value is JsonObject root ? root["sdk"]?.AsObject() : null;
16
+
17
+ public JsonObject? MSBuildSdks => Node.Value is JsonObject root ? root["msbuild-sdks"]?.AsObject() : null;
18
+
19
+ public IEnumerable<Dependency> GetDependencies()
19
20
  {
20
- get
21
+ List<Dependency> dependencies = [];
22
+ if (Sdk is not null
23
+ && Sdk.TryGetPropertyValue("version", out var version))
21
24
  {
22
- return Node.Value is JsonObject root ? root["sdk"]?.AsObject() : null;
25
+ dependencies.Add(GetSdkDependency("Microsoft.NET.Sdk", version));
23
26
  }
24
- }
25
27
 
26
- public JsonObject? MSBuildSdks
27
- {
28
- get
28
+ if (MSBuildSdks is null)
29
29
  {
30
- return Node.Value is JsonObject root ? root["msbuild-sdks"]?.AsObject() : null;
30
+ return dependencies;
31
31
  }
32
+
33
+ var msBuildDependencies = MSBuildSdks
34
+ .Select(t => GetSdkDependency(t.Key, t.Value));
35
+ dependencies.AddRange(msBuildDependencies);
36
+ return dependencies;
32
37
  }
33
38
 
34
- public IEnumerable<Dependency> GetDependencies() => MSBuildSdks?.AsObject().Select(
35
- t => new Dependency(t.Key, t.Value?.GetValue<string>() ?? string.Empty, DependencyType.MSBuildSdk)) ?? Enumerable.Empty<Dependency>();
39
+ private Dependency GetSdkDependency(string name, JsonNode? version)
40
+ {
41
+ return new Dependency(name, version?.GetValue<string>(), DependencyType.MSBuildSdk);
42
+ }
36
43
  }
@@ -1,4 +1,3 @@
1
- using System;
2
1
  using System.Text.Json;
3
2
  using System.Text.Json.Nodes;
4
3
 
@@ -40,7 +39,8 @@ internal abstract class JsonBuildFile : BuildFile<string>
40
39
  {
41
40
  // We can't police that people have legal JSON files.
42
41
  // If they don't, we just return null.
43
- logger.Log($"Failed to parse JSON file: {RepoRelativePath}, got {ex}");
42
+ logger.Log($"Failed to parse JSON file: {RelativePath}, got {ex}");
43
+ FailedToParse = true;
44
44
  return null;
45
45
  }
46
46
  });