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
@@ -1,12 +1,417 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Reflection;
3
+ using System.Xml.Linq;
4
+ using System.Xml.XPath;
5
+
6
+ using Microsoft.Build.Logging.StructuredLogger;
2
7
 
3
8
  using NuGet.Versioning;
4
9
 
10
+ using NuGetUpdater.Core.Utilities;
11
+
12
+ using LoggerProperty = Microsoft.Build.Logging.StructuredLogger.Property;
13
+
5
14
  namespace NuGetUpdater.Core.Discover;
6
15
 
7
16
  internal static class SdkProjectDiscovery
8
17
  {
9
- public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string projectPath, ILogger logger)
18
+ private static readonly HashSet<string> TopLevelPackageItemNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
19
+ {
20
+ "PackageReference"
21
+ };
22
+
23
+ // the items listed below represent collection names that NuGet will resolve a package into, along with the metadata value names to get the package name and version
24
+ private static readonly Dictionary<string, (string NameMetadata, string VersionMetadata)> ResolvedPackageItemNames = new Dictionary<string, (string, string)>(StringComparer.OrdinalIgnoreCase)
25
+ {
26
+ ["NativeCopyLocalItems"] = ("NuGetPackageId", "NuGetPackageVersion"),
27
+ ["ResourceCopyLocalItems"] = ("NuGetPackageId", "NuGetPackageVersion"),
28
+ ["RuntimeCopyLocalItems"] = ("NuGetPackageId", "NuGetPackageVersion"),
29
+ ["ResolvedAnalyzers"] = ("NuGetPackageId", "NuGetPackageVersion"),
30
+ ["_PackageDependenciesDesignTime"] = ("Name", "Version"),
31
+ };
32
+
33
+ // these packages are resolved during restore, but aren't really updatable and shouldn't be reported as dependencies
34
+ private static readonly HashSet<string> NonReportedPackgeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
35
+ {
36
+ "NETStandard.Library"
37
+ };
38
+
39
+ // these are additional files that are relevant to the project and need to be reported
40
+ private static readonly HashSet<string> AdditionalFileNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
41
+ {
42
+ "packages.config",
43
+ "app.config",
44
+ "web.config",
45
+ };
46
+
47
+ public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string startingProjectPath, ExperimentsManager experimentsManager, ILogger logger)
48
+ {
49
+ if (experimentsManager.UseDirectDiscovery)
50
+ {
51
+ return await DiscoverWithBinLogAsync(repoRootPath, workspacePath, startingProjectPath, logger);
52
+ }
53
+ else
54
+ {
55
+ return await DiscoverWithTempProjectAsync(repoRootPath, workspacePath, startingProjectPath, logger);
56
+ }
57
+ }
58
+
59
+ public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverWithBinLogAsync(string repoRootPath, string workspacePath, string startingProjectPath, ILogger logger)
60
+ {
61
+ // N.b., there are many paths used in this function. The MSBuild binary log always reports fully qualified paths, so that's what will be used
62
+ // throughout until the very end when the appropriate kind of relative path is returned.
63
+
64
+ // step through the binlog one item at a time
65
+ var startingProjectDirectory = Path.GetDirectoryName(startingProjectPath)!;
66
+
67
+ // the following collection feature heavily; the shape is described as follows
68
+
69
+ Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject = new(PathComparer.Instance);
70
+ // projectPath tfm packageName, packageVersion
71
+
72
+ Dictionary<string, HashSet<string>> topLevelPackagesPerProject = new(PathComparer.Instance);
73
+ // projectPath, packageNames
74
+
75
+ Dictionary<string, Dictionary<string, string>> resolvedProperties = new(PathComparer.Instance);
76
+ // projectPath propertyName, propertyValue
77
+
78
+ Dictionary<string, HashSet<string>> importedFiles = new(PathComparer.Instance);
79
+ // projectPath, importedFiles
80
+
81
+ Dictionary<string, HashSet<string>> referencedProjects = new(PathComparer.Instance);
82
+ // projectPath, referencedProjects
83
+
84
+ Dictionary<string, HashSet<string>> additionalFiles = new(PathComparer.Instance);
85
+ // projectPath, additionalFiles
86
+
87
+ var tfms = await MSBuildHelper.GetTargetFrameworkValuesFromProject(repoRootPath, startingProjectPath, logger);
88
+ foreach (var tfm in tfms)
89
+ {
90
+ // create a binlog
91
+ var binLogPath = Path.Combine(Path.GetTempPath(), $"msbuild_{Guid.NewGuid():d}.binlog");
92
+ try
93
+ {
94
+ // TODO: once the updater image has all relevant SDKs installed, we won't have to sideline global.json anymore
95
+ var (exitCode, stdOut, stdErr) = await MSBuildHelper.SidelineGlobalJsonAsync(startingProjectDirectory, repoRootPath, async () =>
96
+ {
97
+ // the built-in target `GenerateBuildDependencyFile` forces resolution of all NuGet packages, but doesn't invoke a full build
98
+ var dependencyDiscoveryTargetsPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "DependencyDiscovery.targets");
99
+ var args = new string[]
100
+ {
101
+ "build",
102
+ startingProjectPath,
103
+ "/t:_DiscoverDependencies",
104
+ $"/p:TargetFramework={tfm}",
105
+ $"/p:CustomAfterMicrosoftCommonCrossTargetingTargets={dependencyDiscoveryTargetsPath};CustomAfterMicrosoftCommonTargets={dependencyDiscoveryTargetsPath}",
106
+ $"/bl:{binLogPath}"
107
+ };
108
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", args, workingDirectory: startingProjectDirectory);
109
+ return (exitCode, stdOut, stdErr);
110
+ }, logger, retainMSBuildSdks: true);
111
+ MSBuildHelper.ThrowOnUnauthenticatedFeed(stdOut);
112
+ if (stdOut.Contains("""error MSB4057: The target "GenerateBuildDependencyFile" does not exist in the project."""))
113
+ {
114
+ // this can happen if it's a non-SDK-style project; totally normal, not worth examining the binlog
115
+ return [];
116
+ }
117
+ if (exitCode != 0)
118
+ {
119
+ // log error, but still try to resolve what we can
120
+ logger.Warn($" Error determining dependencies from `{startingProjectPath}`:\nSTDOUT:\n{stdOut}\nSTDERR:\n{stdErr}");
121
+ }
122
+
123
+ var buildRoot = BinaryLog.ReadBuild(binLogPath);
124
+ buildRoot.VisitAllChildren<BaseNode>(node =>
125
+ {
126
+ switch (node)
127
+ {
128
+ case LoggerProperty property:
129
+ {
130
+ var projectEvaluation = property.GetNearestParent<ProjectEvaluation>();
131
+ if (projectEvaluation is not null)
132
+ {
133
+ var properties = resolvedProperties.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
134
+ properties[property.Name] = property.Value;
135
+ }
136
+ }
137
+ break;
138
+ case Import import:
139
+ {
140
+ var projectEvaluation = GetNearestProjectEvaluation(import);
141
+ if (projectEvaluation is not null)
142
+ {
143
+ // props and targets files might have been imported from these, but they're not to be considered as dependency files
144
+ var forbiddenDirectories = new[]
145
+ {
146
+ GetPropertyValueFromProjectEvaluation(projectEvaluation, "BaseIntermediateOutputPath"), // e.g., "obj/"
147
+ GetPropertyValueFromProjectEvaluation(projectEvaluation, "BaseOutputPath"), // e.g., "bin/"
148
+ }
149
+ .Where(p => !string.IsNullOrEmpty(p))
150
+ .Select(p => Path.Combine(Path.GetDirectoryName(projectEvaluation.ProjectFile)!, p!))
151
+ .Select(p => p.NormalizePathToUnix())
152
+ .Select(p => new DirectoryInfo(p))
153
+ .ToArray();
154
+ if (PathHelper.IsFileUnderDirectory(new DirectoryInfo(repoRootPath), new FileInfo(import.ImportedProjectFilePath)))
155
+ {
156
+ if (!forbiddenDirectories.Any(f => PathHelper.IsFileUnderDirectory(f, new FileInfo(import.ImportedProjectFilePath))))
157
+ {
158
+ var imports = importedFiles.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
159
+ imports.Add(import.ImportedProjectFilePath);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ break;
165
+ case NamedNode namedNode when namedNode is AddItem or RemoveItem:
166
+ ProcessResolvedPackageReference(namedNode, packagesPerProject, topLevelPackagesPerProject);
167
+
168
+ if (namedNode is AddItem addItem)
169
+ {
170
+ // maintain list of project references
171
+ if (addItem.Name.Equals("ProjectReference", StringComparison.OrdinalIgnoreCase))
172
+ {
173
+ var projectEvaluation = GetNearestProjectEvaluation(addItem);
174
+ if (projectEvaluation is not null)
175
+ {
176
+ foreach (var referencedProject in addItem.Children.OfType<Item>())
177
+ {
178
+ var referencedProjectPaths = referencedProjects.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
179
+ var referencedProjectPath = new FileInfo(Path.Combine(Path.GetDirectoryName(projectEvaluation.ProjectFile)!, referencedProject.Name)).FullName;
180
+ referencedProjectPaths.Add(referencedProjectPath);
181
+ }
182
+ }
183
+ }
184
+
185
+ // maintain list of additional files
186
+ if (addItem.Name.Equals("None", StringComparison.OrdinalIgnoreCase) ||
187
+ addItem.Name.Equals("Content", StringComparison.OrdinalIgnoreCase))
188
+ {
189
+ var projectEvaluation = GetNearestProjectEvaluation(addItem);
190
+ if (projectEvaluation is not null)
191
+ {
192
+ foreach (var additionalItem in addItem.Children.OfType<Item>())
193
+ {
194
+ if (AdditionalFileNames.Contains(additionalItem.Name))
195
+ {
196
+ var additionalFilesForProject = additionalFiles.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
197
+ var additionalFilePath = new FileInfo(Path.Combine(Path.GetDirectoryName(projectEvaluation.ProjectFile)!, additionalItem.Name)).FullName;
198
+ additionalFilesForProject.Add(additionalFilePath);
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ break;
205
+ }
206
+ }, takeChildrenSnapshot: true);
207
+ }
208
+ catch (HttpRequestException)
209
+ {
210
+ // likely an unauthenticated feed; this needs to be sent further up the chain
211
+ throw;
212
+ }
213
+ finally
214
+ {
215
+ try
216
+ {
217
+ File.Delete(binLogPath);
218
+ }
219
+ catch
220
+ {
221
+ }
222
+ }
223
+ }
224
+
225
+ // and done
226
+ var projectDiscoveryResults = packagesPerProject.Keys.OrderBy(p => p).Select(projectPath =>
227
+ {
228
+ // gather some project-level information
229
+ var packagesByTfm = packagesPerProject[projectPath];
230
+ var projectFullDirectory = Path.GetDirectoryName(projectPath)!;
231
+ var doc = XDocument.Load(projectPath);
232
+ var localPropertyDefinitionElements = doc.Root!.XPathSelectElements("/Project/PropertyGroup/*");
233
+ var projectPropertyNames = localPropertyDefinitionElements.Select(e => e.Name.LocalName).ToHashSet(StringComparer.OrdinalIgnoreCase);
234
+ var projectRelativePath = Path.GetRelativePath(workspacePath, projectPath);
235
+ var topLevelPackageNames = topLevelPackagesPerProject.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase));
236
+
237
+ // create dependencies
238
+ var tfms = packagesByTfm.Keys.OrderBy(tfm => tfm).ToImmutableArray();
239
+ var dependencies = tfms.SelectMany(tfm =>
240
+ {
241
+ return packagesByTfm[tfm].Keys.OrderBy(p => p).Select(packageName =>
242
+ {
243
+ var packageVersion = packagesByTfm[tfm][packageName]!;
244
+ var isTopLevel = topLevelPackageNames.Contains(packageName);
245
+ var dependencyType = isTopLevel ? DependencyType.PackageReference : DependencyType.Unknown;
246
+ return new Dependency(packageName, packageVersion, dependencyType, TargetFrameworks: [tfm], IsDirect: isTopLevel, IsTransitive: !isTopLevel);
247
+ });
248
+ }).ToImmutableArray();
249
+
250
+ // others
251
+ var properties = resolvedProperties[projectPath]
252
+ .Where(pkvp => projectPropertyNames.Contains(pkvp.Key))
253
+ .Select(pkvp => new Property(pkvp.Key, pkvp.Value, Path.GetRelativePath(repoRootPath, projectPath).NormalizePathToUnix()))
254
+ .OrderBy(p => p.Name)
255
+ .ToImmutableArray();
256
+ var referenced = referencedProjects.GetOrAdd(projectPath, () => new(PathComparer.Instance))
257
+ .Select(p => Path.GetRelativePath(projectFullDirectory, p).NormalizePathToUnix())
258
+ .OrderBy(p => p)
259
+ .ToImmutableArray();
260
+ var imported = importedFiles.GetOrAdd(projectPath, () => new(PathComparer.Instance))
261
+ .Select(p => Path.GetRelativePath(projectFullDirectory, p))
262
+ .Select(p => p.NormalizePathToUnix())
263
+ .OrderBy(p => p)
264
+ .ToImmutableArray();
265
+ var additionalFromLocation = ProjectHelper.GetAdditionalFilesFromProjectLocation(projectPath, ProjectHelper.PathFormat.Full);
266
+ var additional = additionalFiles.GetOrAdd(projectPath, () => new(PathComparer.Instance))
267
+ .Concat(additionalFromLocation)
268
+ .Select(p => Path.GetRelativePath(projectFullDirectory, p))
269
+ .Select(p => p.NormalizePathToUnix())
270
+ .OrderBy(p => p)
271
+ .ToImmutableArray();
272
+
273
+ return new ProjectDiscoveryResult()
274
+ {
275
+ FilePath = projectRelativePath,
276
+ Dependencies = dependencies,
277
+ TargetFrameworks = tfms,
278
+ Properties = properties,
279
+ ReferencedProjectPaths = referenced,
280
+ ImportedFiles = imported,
281
+ AdditionalFiles = additional,
282
+ };
283
+ }).ToImmutableArray();
284
+ return projectDiscoveryResults;
285
+ }
286
+
287
+ private static void ProcessResolvedPackageReference(
288
+ NamedNode node,
289
+ Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject, // projectPath -> tfm -> (packageName, packageVersion)
290
+ Dictionary<string, HashSet<string>> topLevelPackagesPerProject
291
+ )
292
+ {
293
+ var doRemoveOperation = node is RemoveItem;
294
+ var doAddOperation = node is AddItem;
295
+
296
+ if (TopLevelPackageItemNames.Contains(node.Name))
297
+ {
298
+ foreach (var child in node.Children.OfType<Item>())
299
+ {
300
+ var projectEvaluation = GetNearestProjectEvaluation(node);
301
+ if (projectEvaluation is not null)
302
+ {
303
+ var packageName = child.Name;
304
+ if (NonReportedPackgeNames.Contains(packageName))
305
+ {
306
+ continue;
307
+ }
308
+
309
+ var topLevelPackages = topLevelPackagesPerProject.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
310
+
311
+ if (doRemoveOperation)
312
+ {
313
+ topLevelPackages.Remove(packageName);
314
+ }
315
+
316
+ if (doAddOperation)
317
+ {
318
+ topLevelPackages.Add(packageName);
319
+ }
320
+ }
321
+ }
322
+ }
323
+ else if (ResolvedPackageItemNames.TryGetValue(node.Name, out var metadataNames))
324
+ {
325
+ var nameMetadata = metadataNames.NameMetadata;
326
+ var versionMetadata = metadataNames.VersionMetadata;
327
+ var projectEvaluation = GetNearestProjectEvaluation(node);
328
+ if (projectEvaluation is not null)
329
+ {
330
+ // without a tfm we can't do anything meaningful with the package reference
331
+ var tfm = GetPropertyValueFromProjectEvaluation(projectEvaluation, "TargetFramework");
332
+ if (tfm is not null)
333
+ {
334
+ foreach (var child in node.Children.OfType<Item>())
335
+ {
336
+ var packageName = GetChildMetadataValue(child, nameMetadata);
337
+ var packageVersion = GetChildMetadataValue(child, versionMetadata);
338
+ if (packageName is not null && packageVersion is not null)
339
+ {
340
+ if (NonReportedPackgeNames.Contains(packageName))
341
+ {
342
+ continue;
343
+ }
344
+
345
+ var tfmsPerProject = packagesPerProject.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
346
+ var packagesPerTfm = tfmsPerProject.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
347
+
348
+ if (doRemoveOperation)
349
+ {
350
+ packagesPerTfm.Remove(packageName);
351
+ }
352
+
353
+ if (doAddOperation)
354
+ {
355
+ packagesPerTfm[packageName] = packageVersion;
356
+ }
357
+ }
358
+ }
359
+ }
360
+ }
361
+ }
362
+ }
363
+
364
+ private static string? GetChildMetadataValue(TreeNode node, string metadataItemName)
365
+ {
366
+ var metadata = node.Children.OfType<Metadata>();
367
+ var metadataValue = metadata.FirstOrDefault(m => m.Name.Equals(metadataItemName, StringComparison.OrdinalIgnoreCase))?.Value;
368
+ return metadataValue;
369
+ }
370
+
371
+ private static ProjectEvaluation? GetNearestProjectEvaluation(BaseNode node)
372
+ {
373
+ // we need to find the containing project evaluation
374
+ // if this is a <PackageReference>, one of the parents is it
375
+ // otherwise, we need to find the parent `Project` and the corresponding evaluation from the build
376
+ var projectEvaluation = node.GetNearestParent<ProjectEvaluation>();
377
+ if (projectEvaluation is null)
378
+ {
379
+ var project = node.GetNearestParent<Project>();
380
+ if (project is null)
381
+ {
382
+ return null;
383
+ }
384
+
385
+ var build = project.GetNearestParent<Build>();
386
+ if (build is null)
387
+ {
388
+ return null;
389
+ }
390
+
391
+ projectEvaluation = build.FindEvaluation(project.EvaluationId);
392
+ }
393
+
394
+ return projectEvaluation;
395
+ }
396
+
397
+ private static string? GetPropertyValueFromProjectEvaluation(ProjectEvaluation projectEvaluation, string propertyName)
398
+ {
399
+ var propertiesFolder = projectEvaluation.Children.OfType<Folder>().FirstOrDefault(f => f.Name == "Properties");
400
+ if (propertiesFolder is null)
401
+ {
402
+ return null;
403
+ }
404
+
405
+ var property = propertiesFolder.Children.OfType<LoggerProperty>().FirstOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
406
+ if (property is null)
407
+ {
408
+ return null;
409
+ }
410
+
411
+ return property.Value;
412
+ }
413
+
414
+ public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverWithTempProjectAsync(string repoRootPath, string workspacePath, string projectPath, ILogger logger)
10
415
  {
11
416
  // Determine which targets and props files contribute to the build.
12
417
  var (buildFiles, projectTargetFrameworks) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(repoRootPath, projectPath);
@@ -30,6 +435,10 @@ internal static class SdkProjectDiscovery
30
435
  // The build file dependencies have the correct DependencyType and the TopLevelDependencies have the evaluated version.
31
436
  // Combine them to have the set of dependencies that are directly referenced from the build file.
32
437
  var fileDependencies = BuildFile.GetDependencies(buildFile).ToImmutableArray();
438
+
439
+ // this is new-ish behavior; don't ever report this dependency because there's no meaningful way to update it
440
+ fileDependencies = fileDependencies.Where(d => !d.Name.Equals("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase)).ToImmutableArray();
441
+
33
442
  var fileDependencyLookup = fileDependencies
34
443
  .ToLookup(d => d.Name, StringComparer.OrdinalIgnoreCase);
35
444
  var sdkDependencies = fileDependencies
@@ -58,7 +467,7 @@ internal static class SdkProjectDiscovery
58
467
  .OrderBy(p => p.Name)
59
468
  .ToImmutableArray();
60
469
  var referencedProjectPaths = MSBuildHelper.GetProjectPathsFromProject(projectPath)
61
- .Select(path => Path.GetRelativePath(workspacePath, path))
470
+ .Select(path => Path.GetRelativePath(workspacePath, path).NormalizePathToUnix())
62
471
  .OrderBy(p => p)
63
472
  .ToImmutableArray();
64
473
 
@@ -72,23 +481,31 @@ internal static class SdkProjectDiscovery
72
481
  .OrderBy(d => d.Name)
73
482
  .ToImmutableArray();
74
483
 
484
+ // for the temporary project, these directories correspond to $(OutputPath) and $(IntermediateOutputPath) and files from
485
+ // these directories should not be reported
486
+ var intermediateDirectories = new string[]
487
+ {
488
+ Path.Join(Path.GetDirectoryName(buildFile.Path), "bin"),
489
+ Path.Join(Path.GetDirectoryName(buildFile.Path), "obj"),
490
+ };
491
+ var projectDirectory = Path.GetDirectoryName(buildFile.Path)!;
492
+ var additionalFiles = ProjectHelper.GetAllAdditionalFilesFromProject(buildFile.Path, ProjectHelper.PathFormat.Relative);
75
493
  results.Add(new()
76
494
  {
77
- FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
495
+ FilePath = Path.GetRelativePath(workspacePath, buildFile.Path).NormalizePathToUnix(),
78
496
  Properties = properties,
79
497
  TargetFrameworks = tfms,
80
498
  ReferencedProjectPaths = referencedProjectPaths,
81
499
  Dependencies = allDependencies,
82
- });
83
- }
84
- else
85
- {
86
- results.Add(new()
87
- {
88
- FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
89
- Dependencies = directDependencies.Concat(sdkDependencies)
90
- .OrderBy(d => d.Name)
500
+ ImportedFiles = buildFiles.Where(b =>
501
+ {
502
+ var fileType = b.GetFileType();
503
+ return fileType == ProjectBuildFileType.Props || fileType == ProjectBuildFileType.Targets;
504
+ })
505
+ .Where(b => !intermediateDirectories.Any(i => PathHelper.IsFileUnderDirectory(new DirectoryInfo(i), new FileInfo(b.Path))))
506
+ .Select(b => Path.GetRelativePath(projectDirectory, b.Path).NormalizePathToUnix())
91
507
  .ToImmutableArray(),
508
+ AdditionalFiles = additionalFiles,
92
509
  });
93
510
  }
94
511
  }
@@ -7,7 +7,6 @@ public sealed record WorkspaceDiscoveryResult : NativeResult
7
7
  public required string Path { get; init; }
8
8
  public bool IsSuccess { get; init; } = true;
9
9
  public ImmutableArray<ProjectDiscoveryResult> Projects { get; init; }
10
- public DirectoryPackagesPropsDiscoveryResult? DirectoryPackagesProps { get; init; }
11
10
  public GlobalJsonDiscoveryResult? GlobalJson { get; init; }
12
11
  public DotNetToolsJsonDiscoveryResult? DotNetToolsJson { get; init; }
13
12
  }
@@ -7,12 +7,23 @@ namespace NuGetUpdater.Core;
7
7
  public record ExperimentsManager
8
8
  {
9
9
  public bool UseLegacyDependencySolver { get; init; } = false;
10
+ public bool UseDirectDiscovery { get; init; } = false;
11
+
12
+ public Dictionary<string, object> ToDictionary()
13
+ {
14
+ return new()
15
+ {
16
+ ["nuget_legacy_dependency_solver"] = UseLegacyDependencySolver,
17
+ ["nuget_use_direct_discovery"] = UseDirectDiscovery,
18
+ };
19
+ }
10
20
 
11
21
  public static ExperimentsManager GetExperimentsManager(Dictionary<string, object>? experiments)
12
22
  {
13
23
  return new ExperimentsManager()
14
24
  {
15
25
  UseLegacyDependencySolver = IsEnabled(experiments, "nuget_legacy_dependency_solver"),
26
+ UseDirectDiscovery = IsEnabled(experiments, "nuget_use_direct_discovery"),
16
27
  };
17
28
  }
18
29
 
@@ -26,8 +37,7 @@ public record ExperimentsManager
26
37
  }
27
38
  catch (JsonException ex)
28
39
  {
29
- // the following message has been specifically designed to match the format of `Dependabot.logger.info(...)` from Ruby
30
- logger.Log($"{DateTime.UtcNow:yyyy/MM/dd HH:mm:ss} INFO Error deserializing job file: {ex.ToString()}: {jobFileContent}");
40
+ logger.Info($"Error deserializing job file: {ex.ToString()}: {jobFileContent}");
31
41
  return new ExperimentsManager();
32
42
  }
33
43
  }
@@ -39,7 +39,7 @@ internal abstract class JsonBuildFile : BuildFile<string>
39
39
  {
40
40
  // We can't police that people have legal JSON files.
41
41
  // If they don't, we just return null.
42
- logger.Log($"Failed to parse JSON file: {RelativePath}, got {ex}");
42
+ logger.Warn($"Failed to parse JSON file: {RelativePath}, got {ex}");
43
43
  FailedToParse = true;
44
44
  return null;
45
45
  }
@@ -15,11 +15,11 @@ public class CompatibilityChecker
15
15
  var incompatibleFrameworks = projectFrameworks.Where(f => !compatibleFrameworks.Contains(f)).ToArray();
16
16
  if (incompatibleFrameworks.Length > 0)
17
17
  {
18
- logger.Log($"The package is not compatible. Incompatible project frameworks: {string.Join(", ", incompatibleFrameworks.Select(f => f.GetShortFolderName()))}");
18
+ logger.Warn($"The package is not compatible. Incompatible project frameworks: {string.Join(", ", incompatibleFrameworks.Select(f => f.GetShortFolderName()))}");
19
19
  return false;
20
20
  }
21
21
 
22
- logger.Log("The package is compatible.");
22
+ logger.Info("The package is compatible.");
23
23
  return true;
24
24
 
25
25
  static NuGetFramework ParseFramework(string tfm)
@@ -7,6 +7,12 @@
7
7
  <GeneratePathProperty>true</GeneratePathProperty>
8
8
  </PropertyGroup>
9
9
 
10
+ <ItemGroup>
11
+ <None Include="DependencyDiscovery.props" CopyToOutputDirectory="PreserveNewest" />
12
+ <None Include="DependencyDiscovery.targets" CopyToOutputDirectory="PreserveNewest" />
13
+ <None Include="TargetFrameworkReporter.targets" CopyToOutputDirectory="PreserveNewest" />
14
+ </ItemGroup>
15
+
10
16
  <ItemGroup>
11
17
  <ProjectReference Include="..\NuGetProjects\NuGet.CommandLine\NuGet.CommandLine.csproj" />
12
18
  </ItemGroup>
@@ -15,8 +21,7 @@
15
21
  <PackageReference Include="GuiLabs.Language.Xml" />
16
22
  <PackageReference Include="DiffPlex" />
17
23
  <PackageReference Include="Microsoft.Build.Locator" />
18
- <PackageReference Include="Microsoft.Build" ExcludeAssets="Runtime" PrivateAssets="All" />
19
- <PackageReference Include="Microsoft.Build.Utilities.Core" ExcludeAssets="Runtime" PrivateAssets="All" />
24
+ <PackageReference Include="MSBuild.StructuredLogger" />
20
25
  <PackageReference Include="NuGet.Core" Aliases="CoreV2" />
21
26
  </ItemGroup>
22
27
 
@@ -1,9 +1,14 @@
1
+ using System.Text.Json;
2
+ using System.Text.Json.Serialization;
3
+
1
4
  namespace NuGetUpdater.Core.Run.ApiModel;
2
5
 
3
6
  public sealed record Job
4
7
  {
5
8
  public string PackageManager { get; init; } = "nuget";
6
9
  public AllowedUpdate[]? AllowedUpdates { get; init; } = null;
10
+
11
+ [JsonConverter(typeof(NullAsBoolConverter))]
7
12
  public bool Debug { get; init; } = false;
8
13
  public object[]? DependencyGroups { get; init; } = null;
9
14
  public object[]? Dependencies { get; init; } = null;
@@ -47,3 +52,21 @@ public sealed record Job
47
52
  }
48
53
  }
49
54
  }
55
+
56
+ public class NullAsBoolConverter : JsonConverter<bool>
57
+ {
58
+ public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
59
+ {
60
+ if (reader.TokenType == JsonTokenType.Null)
61
+ {
62
+ return false;
63
+ }
64
+
65
+ return reader.GetBoolean();
66
+ }
67
+
68
+ public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
69
+ {
70
+ writer.WriteBooleanValue(value);
71
+ }
72
+ }