dependabot-nuget 0.322.0 → 0.322.1

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +1 -1
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +10 -23
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +9 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +15 -232
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +1 -154
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -12
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/AzurePackageDetailFinder.cs +30 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/DetailedPullRequestBodyGenerator.cs +237 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/GitHubPackageDetailFinder.cs +101 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/GitLabPackageDetailFinder.cs +107 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/HttpFetcher.cs +32 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/IHttpFetcher.cs +30 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/IPackageDetailFinder.cs +47 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/IPullRequestBodyGenerator.cs +11 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/SimplePullRequestBodyGenerator.cs +15 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestTextGenerator.cs +7 -3
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +3 -525
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandler.cs +1 -1
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/GroupUpdateAllVersionsHandler.cs +2 -2
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandler.cs +1 -1
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandler.cs +1 -1
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandler.cs +1 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/FileWriterWorker.cs +1 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/XmlFileWriter.cs +10 -3
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +1 -856
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +16 -200
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +6 -556
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +9 -73
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +2 -2
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/DependencySolver/MSBuildDependencySolverTests.cs +1 -1
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +1 -20
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +3 -62
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +13 -563
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +20 -267
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +2 -2
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs +131 -131
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +0 -203
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestBodyGenerator/DetailedPullRequestBodyGeneratorTests.cs +871 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestBodyGenerator/IPackageDetailFinderTests.cs +28 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestBodyGenerator/TestHttpFetcher.cs +23 -0
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestTextTests.cs +3 -2
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +6 -12
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandlerTests.cs +6 -6
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/GroupUpdateAllVersionsHandlerTests.cs +18 -18
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandlerTests.cs +15 -15
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandlerTests.cs +21 -21
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandlerTests.cs +15 -15
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlersTestsBase.cs +1 -8
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests.cs +1 -1
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests.cs +85 -0
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackageReferenceUpdaterTests.cs +1 -159
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +10 -660
  54. metadata +16 -10
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunResult.cs +0 -13
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestMessageTests.cs +0 -296
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +0 -3592
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatePermittedAndMessageTests.cs +0 -457
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +0 -378
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterHelperTests.cs +0 -175
@@ -1,19 +1,11 @@
1
1
  using System.Collections.Immutable;
2
2
  using System.Diagnostics.CodeAnalysis;
3
3
  using System.Reflection;
4
- using System.Text;
5
- using System.Text.Json;
6
4
  using System.Text.Json.Nodes;
7
5
  using System.Text.RegularExpressions;
8
- using System.Xml;
9
6
  using System.Xml.Linq;
10
7
 
11
- using Microsoft.Build.Construction;
12
- using Microsoft.Build.Definition;
13
- using Microsoft.Build.Evaluation;
14
- using Microsoft.Build.Exceptions;
15
8
  using Microsoft.Build.Locator;
16
- using Microsoft.Extensions.FileSystemGlobbing;
17
9
 
18
10
  using NuGet.Configuration;
19
11
  using NuGet.Frameworks;
@@ -104,260 +96,6 @@ internal static partial class MSBuildHelper
104
96
  }
105
97
  }
106
98
 
107
- public static IEnumerable<string> GetProjectPathsFromSolution(string solutionPath)
108
- {
109
- var solution = SolutionFile.Parse(solutionPath);
110
- return solution.ProjectsInOrder.Select(p => p.AbsolutePath);
111
- }
112
-
113
- public static IEnumerable<string> GetProjectPathsFromProject(string projFilePath)
114
- {
115
- var projectStack = new Stack<(string folderPath, ProjectRootElement)>();
116
- var processedProjectFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
117
- using var projectCollection = new ProjectCollection();
118
-
119
- try
120
- {
121
- var projectRootElement = ProjectRootElement.Open(projFilePath, projectCollection);
122
- projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(projFilePath)!), projectRootElement));
123
- }
124
- catch (InvalidProjectFileException)
125
- {
126
- yield break; // Skip invalid project files
127
- }
128
-
129
- while (projectStack.Count > 0)
130
- {
131
- var (folderPath, tmpProject) = projectStack.Pop();
132
- foreach (var projectReference in tmpProject.Items.Where(static x => x.ItemType == "ProjectReference" || x.ItemType == "ProjectFile"))
133
- {
134
- if (projectReference.Include is not { } projectPath)
135
- {
136
- continue;
137
- }
138
-
139
- Matcher matcher = new Matcher();
140
- matcher.AddInclude(PathHelper.NormalizePathToUnix(projectReference.Include));
141
-
142
- string searchDirectory = PathHelper.NormalizePathToUnix(folderPath);
143
-
144
- IEnumerable<string> files = matcher.GetResultsInFullPath(searchDirectory);
145
-
146
- foreach (var file in files)
147
- {
148
- // Check that we haven't already processed this file
149
- if (processedProjectFiles.Contains(file))
150
- {
151
- continue;
152
- }
153
-
154
- var projectExtension = Path.GetExtension(file).ToLowerInvariant();
155
- if (projectExtension == ".proj")
156
- {
157
- // If there is some MSBuild logic that needs to run to fully resolve the path skip the project
158
- if (File.Exists(file))
159
- {
160
- var additionalProjectRootElement = ProjectRootElement.Open(file, projectCollection);
161
- projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(file)!), additionalProjectRootElement));
162
- processedProjectFiles.Add(file);
163
- }
164
- }
165
- else if (projectExtension == ".csproj" || projectExtension == ".vbproj" || projectExtension == ".fsproj")
166
- {
167
- yield return file;
168
- }
169
- }
170
- }
171
- }
172
- }
173
-
174
- public static IReadOnlyDictionary<string, Property> GetProperties(ImmutableArray<ProjectBuildFile> buildFiles)
175
- {
176
- Dictionary<string, Property> properties = new(StringComparer.OrdinalIgnoreCase);
177
-
178
- foreach (var buildFile in buildFiles)
179
- {
180
- var projectRoot = CreateProjectRootElement(buildFile);
181
-
182
- foreach (var property in projectRoot.Properties)
183
- {
184
- // Short of evaluating the entire project, there's no way to _really_ know what package version is
185
- // going to be used, and even then we might not be able to update it. As a best guess, we'll simply
186
- // skip any property that has a condition _or_ where the condition is checking for an empty string.
187
- var hasEmptyCondition = string.IsNullOrEmpty(property.Condition);
188
- var conditionIsCheckingForEmptyString = string.Equals(property.Condition, $"$({property.Name}) == ''", StringComparison.OrdinalIgnoreCase) ||
189
- string.Equals(property.Condition, $"'$({property.Name})' == ''", StringComparison.OrdinalIgnoreCase);
190
- if (hasEmptyCondition || conditionIsCheckingForEmptyString)
191
- {
192
- properties[property.Name] = new(property.Name, property.Value, PathHelper.NormalizePathToUnix(buildFile.RelativePath));
193
- }
194
- }
195
- }
196
-
197
- return properties;
198
- }
199
-
200
- public static IEnumerable<Dependency> GetTopLevelPackageDependencyInfos(ImmutableArray<ProjectBuildFile> buildFiles)
201
- {
202
- Dictionary<string, (string, bool, DependencyType)> packageInfo = new(StringComparer.OrdinalIgnoreCase);
203
- Dictionary<string, string> packageVersionInfo = new(StringComparer.OrdinalIgnoreCase);
204
- Dictionary<string, Property> propertyInfo = new(StringComparer.OrdinalIgnoreCase);
205
-
206
- foreach (var buildFile in buildFiles)
207
- {
208
- var projectRoot = CreateProjectRootElement(buildFile);
209
-
210
- foreach (var property in projectRoot.Properties)
211
- {
212
- // Short of evaluating the entire project, there's no way to _really_ know what package version is
213
- // going to be used, and even then we might not be able to update it. As a best guess, we'll simply
214
- // skip any property that has a condition _or_ where the condition is checking for an empty string.
215
- var hasEmptyCondition = string.IsNullOrEmpty(property.Condition);
216
- var conditionIsCheckingForEmptyString = string.Equals(property.Condition, $"$({property.Name}) == ''", StringComparison.OrdinalIgnoreCase) ||
217
- string.Equals(property.Condition, $"'$({property.Name})' == ''", StringComparison.OrdinalIgnoreCase);
218
- if (hasEmptyCondition || conditionIsCheckingForEmptyString)
219
- {
220
- propertyInfo[property.Name] = new(property.Name, property.Value, buildFile.RelativePath);
221
- }
222
- }
223
-
224
- if (buildFile.IsOutsideBasePath)
225
- {
226
- continue;
227
- }
228
-
229
- foreach (var packageItem in projectRoot.Items
230
- .Where(i => (i.ItemType == "PackageReference" || i.ItemType == "GlobalPackageReference")))
231
- {
232
- var dependencyType = packageItem.ItemType == "PackageReference" ? DependencyType.PackageReference : DependencyType.GlobalPackageReference;
233
- var versionSpecification = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value
234
- ?? packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase))?.Value
235
- ?? string.Empty;
236
- foreach (var rawAttributeValue in new[] { packageItem.Include, packageItem.Update })
237
- {
238
- var attributeValue = rawAttributeValue?.Trim();
239
- if (!string.IsNullOrWhiteSpace(attributeValue))
240
- {
241
- if (packageInfo.TryGetValue(attributeValue, out var existingInfo))
242
- {
243
- var existingVersion = existingInfo.Item1;
244
- var existingUpdate = existingInfo.Item2;
245
- // Retain the version from the Update reference since the intention
246
- // would be to override the version of the Include reference.
247
- var vSpec = string.IsNullOrEmpty(versionSpecification) || existingUpdate ? existingVersion : versionSpecification;
248
-
249
- var isUpdate = existingUpdate && string.IsNullOrEmpty(packageItem.Include);
250
- packageInfo[attributeValue] = (vSpec, isUpdate, dependencyType);
251
- }
252
- else
253
- {
254
- var isUpdate = !string.IsNullOrEmpty(packageItem.Update);
255
- packageInfo[attributeValue] = (versionSpecification, isUpdate, dependencyType);
256
- }
257
- }
258
- }
259
- }
260
-
261
- foreach (var packageItem in projectRoot.Items
262
- .Where(i => i.ItemType == "PackageVersion" && !string.IsNullOrEmpty(i.Include)))
263
- {
264
- packageVersionInfo[packageItem.Include] = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value
265
- ?? string.Empty;
266
- }
267
- }
268
-
269
- foreach (var (name, info) in packageInfo)
270
- {
271
- var (version, isUpdate, dependencyType) = info;
272
- if (version.Length != 0 || !packageVersionInfo.TryGetValue(name, out var packageVersion))
273
- {
274
- packageVersion = version;
275
- }
276
-
277
- // Walk the property replacements until we don't find another one.
278
- var evaluationResult = GetEvaluatedValue(packageVersion, propertyInfo);
279
- packageVersion = evaluationResult.ResultType == EvaluationResultType.Success
280
- ? evaluationResult.EvaluatedValue.TrimStart('[', '(').TrimEnd(']', ')')
281
- : evaluationResult.EvaluatedValue;
282
-
283
- // If at this point we have a semicolon in the name then split it and yield multiple dependencies.
284
- foreach (var splitName in name.Split(';', StringSplitOptions.RemoveEmptyEntries))
285
- {
286
- yield return new Dependency(splitName.Trim(), packageVersion, dependencyType, EvaluationResult: evaluationResult, IsUpdate: isUpdate);
287
- }
288
- }
289
- }
290
-
291
- /// <summary>
292
- /// Given an MSBuild string and a set of properties, returns our best guess at the final value MSBuild will evaluate to.
293
- /// </summary>
294
- public static EvaluationResult GetEvaluatedValue(string msbuildString, IReadOnlyDictionary<string, Property> propertyInfo, params string[] propertiesToIgnore)
295
- {
296
- var ignoredProperties = new HashSet<string>(propertiesToIgnore, StringComparer.OrdinalIgnoreCase);
297
- var seenProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
298
-
299
- string originalValue = msbuildString;
300
- string? rootPropertyName = null;
301
- while (TryGetPropertyName(msbuildString, out var propertyName))
302
- {
303
- rootPropertyName = propertyName;
304
-
305
- if (ignoredProperties.Contains(propertyName))
306
- {
307
- return new(EvaluationResultType.PropertyIgnored, originalValue, msbuildString, rootPropertyName, $"Property '{propertyName}' is ignored.");
308
- }
309
-
310
- if (!seenProperties.Add(propertyName))
311
- {
312
- return new(EvaluationResultType.CircularReference, originalValue, msbuildString, rootPropertyName, $"Property '{propertyName}' has a circular reference.");
313
- }
314
-
315
- if (!propertyInfo.TryGetValue(propertyName, out var property))
316
- {
317
- return new(EvaluationResultType.PropertyNotFound, originalValue, msbuildString, rootPropertyName, $"Property '{propertyName}' was not found.");
318
- }
319
-
320
- msbuildString = msbuildString.Replace($"$({propertyName})", property.Value);
321
- }
322
-
323
- return new(EvaluationResultType.Success, originalValue, msbuildString, rootPropertyName, null);
324
- }
325
-
326
- public static bool TryGetPropertyName(string versionContent, [NotNullWhen(true)] out string? propertyName)
327
- {
328
- var startIndex = versionContent.IndexOf("$(", StringComparison.Ordinal);
329
- if (startIndex != -1)
330
- {
331
- var endIndex = versionContent.IndexOf(')', startIndex);
332
- if (endIndex != -1)
333
- {
334
- propertyName = versionContent.Substring(startIndex + 2, endIndex - startIndex - 2);
335
- return true;
336
- }
337
- }
338
-
339
- propertyName = null;
340
- return false;
341
- }
342
-
343
- internal static async Task<bool> DependenciesAreCoherentAsync(string repoRoot, string projectPath, string targetFramework, ImmutableArray<Dependency> packages, ExperimentsManager experimentsManager, ILogger logger)
344
- {
345
- var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
346
- try
347
- {
348
- var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, experimentsManager, logger);
349
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
350
-
351
- // NU1608: Detected package version outside of dependency constraint
352
-
353
- return exitCode == 0 && !stdOut.Contains("NU1608");
354
- }
355
- finally
356
- {
357
- tempDirectory.Delete(recursive: true);
358
- }
359
- }
360
-
361
99
  internal static async Task<ImmutableArray<Dependency>?> ResolveDependencyConflicts(string repoRoot, string projectPath, string targetFramework, ImmutableArray<Dependency> packages, ImmutableArray<Dependency> update, ExperimentsManager experimentsManager, ILogger logger)
362
100
  {
363
101
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
@@ -524,136 +262,6 @@ internal static partial class MSBuildHelper
524
262
  }
525
263
  }
526
264
 
527
- internal static async Task<ImmutableArray<Dependency>?> ResolveDependencyConflictsWithBruteForce(string repoRoot, string projectPath, string targetFramework, ImmutableArray<Dependency> packages, ExperimentsManager experimentsManager, ILogger logger)
528
- {
529
- var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
530
- try
531
- {
532
- var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, experimentsManager, logger);
533
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", tempProjectPath], tempDirectory.FullName, experimentsManager);
534
- ThrowOnUnauthenticatedFeed(stdOut);
535
-
536
- // simple cases first
537
- // if restore failed, nothing we can do
538
- if (exitCode != 0)
539
- {
540
- return null;
541
- }
542
-
543
- // if no problems found, just return the current set
544
- if (!stdOut.Contains("NU1608"))
545
- {
546
- return packages;
547
- }
548
-
549
- // now it gets complicated; look for the packages with issues
550
- MatchCollection matches = PackageIncompatibilityWarningPattern().Matches(stdOut);
551
- (string, NuGetVersion)[] badPackagesAndVersions = matches.Select(m => (m.Groups["PackageName"].Value, NuGetVersion.Parse(m.Groups["PackageVersion"].Value))).ToArray();
552
- Dictionary<string, HashSet<NuGetVersion>> badPackagesAndCandidateVersionsDictionary = new(StringComparer.OrdinalIgnoreCase);
553
-
554
- // and for each of those packages, find all versions greater than the one that's currently installed
555
- foreach ((string PackageName, NuGetVersion packageVersion) in badPackagesAndVersions)
556
- {
557
- // this command dumps a JSON object with all versions of the specified package from all package sources
558
- // not using the `dotnet` execution method because we want to force the latest MSBuild and SDK to be used
559
- (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["package", "search", PackageName, "--exact-match", "--format", "json"], workingDirectory: tempDirectory.FullName);
560
- if (exitCode != 0)
561
- {
562
- continue;
563
- }
564
-
565
- // ensure collection exists
566
- if (!badPackagesAndCandidateVersionsDictionary.ContainsKey(PackageName))
567
- {
568
- badPackagesAndCandidateVersionsDictionary.Add(PackageName, new HashSet<NuGetVersion>());
569
- }
570
-
571
- HashSet<NuGetVersion> foundVersions = badPackagesAndCandidateVersionsDictionary[PackageName];
572
-
573
- var json = JsonHelper.ParseNode(stdOut);
574
- if (json?["searchResult"] is JsonArray searchResults)
575
- {
576
- foreach (var searchResult in searchResults)
577
- {
578
- if (searchResult?["packages"] is JsonArray packagesArray)
579
- {
580
- foreach (var package in packagesArray)
581
- {
582
- // in 8.0.xxx SDKs, the package version is in the `latestVersion` property, but in 9.0.xxx, it's `version`
583
- var packageVersionProperty = package?["version"] ?? package?["latestVersion"];
584
- if (packageVersionProperty is JsonValue latestVersion &&
585
- latestVersion.GetValueKind() == JsonValueKind.String &&
586
- NuGetVersion.TryParse(latestVersion.ToString(), out var nugetVersion) &&
587
- nugetVersion > packageVersion)
588
- {
589
- foundVersions.Add(nugetVersion);
590
- }
591
- }
592
- }
593
- }
594
- }
595
- }
596
-
597
- // generate all possible combinations
598
- (string Key, NuGetVersion v)[][] expandedLists = badPackagesAndCandidateVersionsDictionary.Select(kvp => kvp.Value.Order().Select(v => (kvp.Key, v)).ToArray()).ToArray();
599
- IEnumerable<(string PackageName, NuGetVersion PackageVersion)>[] product = expandedLists.CartesianProduct().ToArray();
600
-
601
- // FUTURE WORK: pre-filter individual known package incompatibilities to reduce the number of combinations, e.g., if Package.A v1.0.0
602
- // is incompatible with Package.B v2.0.0, then remove _all_ combinations with that pair
603
-
604
- // this is the slow part
605
- foreach (IEnumerable<(string PackageName, NuGetVersion PackageVersion)> candidateSet in product)
606
- {
607
- // rebuild candidate dependency list with the relevant versions
608
- Dictionary<string, NuGetVersion> packageVersions = candidateSet.ToDictionary(candidateSet => candidateSet.PackageName, candidateSet => candidateSet.PackageVersion);
609
- var candidatePackages = packages.Select(p =>
610
- {
611
- if (packageVersions.TryGetValue(p.Name, out var version))
612
- {
613
- // create a new dependency with the updated version
614
- return new Dependency(p.Name, version.ToString(), p.Type, IsDevDependency: p.IsDevDependency, IsOverride: p.IsOverride, IsUpdate: p.IsUpdate);
615
- }
616
-
617
- // not the dependency we're looking for, use whatever it already was in this set
618
- return p;
619
- }).ToImmutableArray();
620
-
621
- if (await DependenciesAreCoherentAsync(repoRoot, projectPath, targetFramework, candidatePackages, experimentsManager, logger))
622
- {
623
- // return as soon as we find a coherent set
624
- return candidatePackages;
625
- }
626
- }
627
-
628
- // no package resolution set found
629
- return null;
630
- }
631
- finally
632
- {
633
- tempDirectory.Delete(recursive: true);
634
- }
635
- }
636
-
637
- // fully expand all possible combinations using the algorithm from here:
638
- // https://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
639
- private static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
640
- {
641
- IEnumerable<IEnumerable<T>> emptyProduct = [[]];
642
- return sequences.Aggregate(emptyProduct, (accumulator, sequence) => from accseq in accumulator
643
- from item in sequence
644
- select accseq.Concat([item]));
645
- }
646
-
647
- private static ProjectRootElement CreateProjectRootElement(ProjectBuildFile buildFile)
648
- {
649
- var xmlString = buildFile.Contents.ToFullString();
650
- using var xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlString));
651
- using var xmlReader = XmlReader.Create(xmlStream);
652
- var projectRoot = ProjectRootElement.Create(xmlReader);
653
-
654
- return projectRoot;
655
- }
656
-
657
265
  private static IEnumerable<PackageSource>? LoadPackageSources(string nugetConfigPath, ILogger logger)
658
266
  {
659
267
  try
@@ -900,44 +508,13 @@ internal static partial class MSBuildHelper
900
508
  try
901
509
  {
902
510
  var topLevelPackagesNames = packages.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
903
- var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, experimentsManager, logger, importDependencyTargets: !experimentsManager.UseDirectDiscovery);
511
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, experimentsManager, logger, importDependencyTargets: false);
904
512
 
905
- ImmutableArray<Dependency> allDependencies;
906
- if (experimentsManager.UseDirectDiscovery)
907
- {
908
- var projectDiscovery = await SdkProjectDiscovery.DiscoverAsync(repoRoot, tempDirectory.FullName, tempProjectPath, experimentsManager, logger);
909
- allDependencies = projectDiscovery
910
- .Where(p => p.FilePath == Path.GetFileName(tempProjectPath))
911
- .FirstOrDefault()
912
- ?.Dependencies.ToImmutableArray() ?? [];
913
- }
914
- else
915
- {
916
- var (exitCode, stdout, stderr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["build", tempProjectPath, "/t:_ReportDependencies"], tempDirectory.FullName, experimentsManager);
917
- ThrowOnUnauthenticatedFeed(stdout);
918
-
919
- if (exitCode == 0)
920
- {
921
- ImmutableArray<string> tfms = [targetFramework];
922
- var lines = stdout.Split('\n').Select(line => line.Trim());
923
- var pattern = PackagePattern();
924
- allDependencies = lines
925
- .Select(line => pattern.Match(line))
926
- .Where(match => match.Success)
927
- .Select(match =>
928
- {
929
- var PackageName = match.Groups["PackageName"].Value;
930
- var isTransitive = !topLevelPackagesNames.Contains(PackageName);
931
- return new Dependency(PackageName, match.Groups["PackageVersion"].Value, DependencyType.Unknown, TargetFrameworks: tfms, IsTransitive: isTransitive);
932
- })
933
- .ToImmutableArray();
934
- }
935
- else
936
- {
937
- logger?.Warn($"dotnet build in {nameof(GetAllPackageDependenciesAsync)} failed. STDOUT: {stdout} STDERR: {stderr}");
938
- allDependencies = [];
939
- }
940
- }
513
+ var projectDiscovery = await SdkProjectDiscovery.DiscoverAsync(repoRoot, tempDirectory.FullName, tempProjectPath, experimentsManager, logger);
514
+ var allDependencies = projectDiscovery
515
+ .Where(p => p.FilePath == Path.GetFileName(tempProjectPath))
516
+ .FirstOrDefault()
517
+ ?.Dependencies.ToImmutableArray() ?? [];
941
518
 
942
519
  return allDependencies;
943
520
  }
@@ -1123,131 +700,4 @@ internal static partial class MSBuildHelper
1123
700
  dotnetToolsJsonJsonPath = PathHelper.GetFileInDirectoryOrParent(workspacePath, repoRootPath, "./.config/dotnet-tools.json", caseSensitive: false);
1124
701
  return dotnetToolsJsonJsonPath is not null;
1125
702
  }
1126
-
1127
- internal static bool TryGetDirectoryPackagesPropsPath(string repoRootPath, string workspacePath, [NotNullWhen(returnValue: true)] out string? directoryPackagesPropsPath)
1128
- {
1129
- directoryPackagesPropsPath = PathHelper.GetFileInDirectoryOrParent(workspacePath, repoRootPath, "./Directory.Packages.props", caseSensitive: false);
1130
- return directoryPackagesPropsPath is not null;
1131
- }
1132
-
1133
- internal static async Task<(ImmutableArray<ProjectBuildFile> ProjectBuildFiles, string[] TargetFrameworks)> LoadBuildFilesAndTargetFrameworksAsync(string repoRootPath, string projectPath)
1134
- {
1135
- var buildFileList = new List<string>
1136
- {
1137
- projectPath.NormalizePathToUnix() // always include the starting project
1138
- };
1139
-
1140
- // a global.json file might cause problems with the dotnet msbuild command; create a safe version temporarily
1141
- TryGetGlobalJsonPath(repoRootPath, projectPath, out var globalJsonPath);
1142
- var safeGlobalJsonName = $"{globalJsonPath}{Guid.NewGuid()}";
1143
- HashSet<string> targetFrameworks = new(StringComparer.OrdinalIgnoreCase);
1144
- var repoRootDirectoryInfo = new DirectoryInfo(repoRootPath);
1145
-
1146
- try
1147
- {
1148
- // move the original
1149
- if (globalJsonPath is not null)
1150
- {
1151
- File.Move(globalJsonPath, safeGlobalJsonName);
1152
-
1153
- // create a safe version with only certain top-level keys
1154
- var globalJsonContent = await File.ReadAllTextAsync(safeGlobalJsonName);
1155
- var json = JsonHelper.ParseNode(globalJsonContent);
1156
- var sdks = json?["msbuild-sdks"];
1157
- if (sdks is not null)
1158
- {
1159
- var newObject = new Dictionary<string, object>()
1160
- {
1161
- ["msbuild-sdks"] = sdks,
1162
- };
1163
- var newContent = JsonSerializer.Serialize(newObject);
1164
- await File.WriteAllTextAsync(globalJsonPath, newContent);
1165
- }
1166
- }
1167
-
1168
- // This is equivalent to running the command `dotnet msbuild <projectPath> /pp` to preprocess the file.
1169
- // The only difference is that we're specifying the `IgnoreMissingImports` flag which will allow us to
1170
- // load the project even if it imports a file that doesn't exist (e.g. a file that's generated at restore
1171
- // or build time).
1172
- using var projectCollection = new ProjectCollection(); // do this in a one-off instance and don't pollute the global collection
1173
- var project = Project.FromFile(projectPath, new ProjectOptions
1174
- {
1175
- LoadSettings = ProjectLoadSettings.IgnoreMissingImports,
1176
- ProjectCollection = projectCollection,
1177
- });
1178
- var allImportedPaths = project.Imports.Select(i => i.ImportedProject.FullPath.NormalizePathToUnix()).ToArray();
1179
- var importedPathsInRepo = allImportedPaths.Where(p => PathHelper.IsFileUnderDirectory(repoRootDirectoryInfo, new FileInfo(p))).ToArray();
1180
- var projectDir = Path.GetDirectoryName(projectPath)!;
1181
- var intermediateDir = new DirectoryInfo(Path.Combine(projectDir, project.GetPropertyValue("BaseIntermediateOutputPath")));
1182
- var outputDir = new DirectoryInfo(Path.Combine(projectDir, project.GetPropertyValue("BaseOutputPath")));
1183
- var nonTransitivePathsInRepo = importedPathsInRepo.Where(p =>
1184
- {
1185
- var fi = new FileInfo(p);
1186
- return !PathHelper.IsFileUnderDirectory(intermediateDir, fi)
1187
- && !PathHelper.IsFileUnderDirectory(outputDir, fi);
1188
- }).ToArray();
1189
- buildFileList.AddRange(nonTransitivePathsInRepo.Select(p => p.NormalizePathToUnix()));
1190
-
1191
- // use the MSBuild-evaluated value so we don't have to try to manually parse XML
1192
- IEnumerable<ProjectProperty> targetFrameworkProperties = project.Properties.Where(p => p.Name.Equals("TargetFramework", StringComparison.OrdinalIgnoreCase)).ToList();
1193
- IEnumerable<ProjectProperty> targetFrameworksProperties = project.Properties.Where(p => p.Name.Equals("TargetFrameworks", StringComparison.OrdinalIgnoreCase)).ToList();
1194
- IEnumerable<ProjectProperty> targetFrameworkVersionProperties = project.Properties.Where(p => p.Name.Equals("TargetFrameworkVersion", StringComparison.OrdinalIgnoreCase)).ToList();
1195
- foreach (ProjectProperty tfm in targetFrameworkProperties)
1196
- {
1197
- if (!string.IsNullOrWhiteSpace(tfm.EvaluatedValue))
1198
- {
1199
- targetFrameworks.Add(tfm.EvaluatedValue);
1200
- }
1201
- }
1202
-
1203
- foreach (ProjectProperty tfms in targetFrameworksProperties)
1204
- {
1205
- foreach (string tfmValue in tfms.EvaluatedValue.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
1206
- {
1207
- targetFrameworks.Add(tfmValue);
1208
- }
1209
- }
1210
-
1211
- if (targetFrameworks.Count == 0)
1212
- {
1213
- // Only try this if we haven't been able to resolve anything yet. This is because deep in the SDK, a
1214
- // `TargetFramework` of `netstandard2.0` (eventually) gets turned into `v2.0` and we don't want to
1215
- // interpret that as a .NET Framework 2.0 project.
1216
- foreach (ProjectProperty tfvm in targetFrameworkVersionProperties)
1217
- {
1218
- // `v0.0` is an error case where no TFM could be evaluated
1219
- if (tfvm.EvaluatedValue != "v0.0")
1220
- {
1221
- targetFrameworks.Add($"net{tfvm.EvaluatedValue.TrimStart('v').Replace(".", "")}");
1222
- }
1223
- }
1224
- }
1225
- }
1226
- catch (InvalidProjectFileException)
1227
- {
1228
- return ([], []);
1229
- }
1230
- finally
1231
- {
1232
- if (globalJsonPath is not null)
1233
- {
1234
- File.Move(safeGlobalJsonName, globalJsonPath, overwrite: true);
1235
- }
1236
- }
1237
-
1238
- var result = buildFileList
1239
- .Where(File.Exists)
1240
- .Select(path => ProjectBuildFile.Open(repoRootPath, path))
1241
- .ToImmutableArray();
1242
- return (result, targetFrameworks.ToArray());
1243
- }
1244
-
1245
- [GeneratedRegex("^\\s*NuGetData::Package=(?<PackageName>[^,]+), Version=(?<PackageVersion>.+)$")]
1246
- private static partial Regex PackagePattern();
1247
-
1248
- // Example output:
1249
- // NU1608: Detected package version outside of dependency constraint: SpecFlow.Tools.MsBuild.Generation 3.3.30 requires SpecFlow(= 3.3.30) but version SpecFlow 3.9.74 was resolved.
1250
- // PackageName-|+++++++++++++++++++++++++++++++| |++++|-PackageVersion
1251
- [GeneratedRegex("NU1608: [^:]+: (?<PackageName>[^ ]+) (?<PackageVersion>[^ ]+)")]
1252
- private static partial Regex PackageIncompatibilityWarningPattern();
1253
703
  }