dependabot-nuget 0.287.0 → 0.288.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) 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 +10 -3
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +7 -3
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +1 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +72 -51
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +1 -1
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.props +7 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +10 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +36 -19
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +6 -2
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscoveryResult.cs +2 -1
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +5 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +386 -12
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +1 -1
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +11 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +7 -2
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +23 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +0 -4
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/TargetFrameworkReporter.targets +13 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +1 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +2 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/CollectionExtensions.cs +17 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +57 -4
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathComparer.cs +31 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +30 -2
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +50 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +74 -38
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +50 -4
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Proj.cs +5 -5
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +728 -253
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +322 -78
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +2 -6
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +472 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +46 -1
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +0 -1
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +1 -1
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +33 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +3 -2
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +4 -2
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +3 -2
  43. data/lib/dependabot/nuget/file_parser.rb +7 -1
  44. data/lib/dependabot/nuget/native_discovery/native_discovery_json_reader.rb +0 -3
  45. data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +7 -10
  46. data/lib/dependabot/nuget/native_helpers.rb +13 -4
  47. metadata +12 -8
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscovery.cs +0 -69
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscoveryResult.cs +0 -11
  50. data/lib/dependabot/nuget/native_discovery/native_directory_packages_props_discovery.rb +0 -44
@@ -1,12 +1,376 @@
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
+ public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string startingProjectPath, ExperimentsManager experimentsManager, ILogger logger)
40
+ {
41
+ if (experimentsManager.UseDirectDiscovery)
42
+ {
43
+ return await DiscoverWithBinLogAsync(repoRootPath, workspacePath, startingProjectPath, logger);
44
+ }
45
+ else
46
+ {
47
+ return await DiscoverWithTempProjectAsync(repoRootPath, workspacePath, startingProjectPath, logger);
48
+ }
49
+ }
50
+
51
+ public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverWithBinLogAsync(string repoRootPath, string workspacePath, string startingProjectPath, ILogger logger)
52
+ {
53
+ // 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
54
+ // throughout until the very end when the appropriate kind of relative path is returned.
55
+
56
+ // step through the binlog one item at a time
57
+ var startingProjectDirectory = Path.GetDirectoryName(startingProjectPath)!;
58
+
59
+ // the following collection feature heavily; the shape is described as follows
60
+
61
+ Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject = new(PathComparer.Instance);
62
+ // projectPath tfm packageName, packageVersion
63
+
64
+ Dictionary<string, HashSet<string>> topLevelPackagesPerProject = new(PathComparer.Instance);
65
+ // projectPath, packageNames
66
+
67
+ Dictionary<string, Dictionary<string, string>> resolvedProperties = new(PathComparer.Instance);
68
+ // projectPath propertyName, propertyValue
69
+
70
+ Dictionary<string, HashSet<string>> importedFiles = new(PathComparer.Instance);
71
+ // projectPath, importedFiles
72
+
73
+ Dictionary<string, HashSet<string>> referencedProjects = new(PathComparer.Instance);
74
+ // projectPath, referencedProjects
75
+
76
+ var tfms = await MSBuildHelper.GetTargetFrameworkValuesFromProject(repoRootPath, startingProjectPath, logger);
77
+ foreach (var tfm in tfms)
78
+ {
79
+ // create a binlog
80
+ var binLogPath = Path.Combine(Path.GetTempPath(), $"msbuild_{Guid.NewGuid():d}.binlog");
81
+ try
82
+ {
83
+ // TODO: once the updater image has all relevant SDKs installed, we won't have to sideline global.json anymore
84
+ var (exitCode, stdOut, stdErr) = await MSBuildHelper.SidelineGlobalJsonAsync(startingProjectDirectory, repoRootPath, async () =>
85
+ {
86
+ // the built-in target `GenerateBuildDependencyFile` forces resolution of all NuGet packages, but doesn't invoke a full build
87
+ var dependencyDiscoveryTargetsPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "DependencyDiscovery.targets");
88
+ var args = new string[]
89
+ {
90
+ "build",
91
+ startingProjectPath,
92
+ "/t:_DiscoverDependencies",
93
+ $"/p:TargetFramework={tfm}",
94
+ $"/p:CustomAfterMicrosoftCommonCrossTargetingTargets={dependencyDiscoveryTargetsPath};CustomAfterMicrosoftCommonTargets={dependencyDiscoveryTargetsPath}",
95
+ $"/bl:{binLogPath}"
96
+ };
97
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", args, workingDirectory: startingProjectDirectory);
98
+ return (exitCode, stdOut, stdErr);
99
+ }, logger, retainMSBuildSdks: true);
100
+ MSBuildHelper.ThrowOnUnauthenticatedFeed(stdOut);
101
+ if (stdOut.Contains("""error MSB4057: The target "GenerateBuildDependencyFile" does not exist in the project."""))
102
+ {
103
+ // this can happen if it's a non-SDK-style project; totally normal, not worth examining the binlog
104
+ return [];
105
+ }
106
+ if (exitCode != 0)
107
+ {
108
+ // log error, but still try to resolve what we can
109
+ logger.Log($" Error determining dependencies from `{startingProjectPath}`:\nSTDOUT:\n{stdOut}\nSTDERR:\n{stdErr}");
110
+ }
111
+
112
+ var buildRoot = BinaryLog.ReadBuild(binLogPath);
113
+ buildRoot.VisitAllChildren<BaseNode>(node =>
114
+ {
115
+ switch (node)
116
+ {
117
+ case LoggerProperty property:
118
+ {
119
+ var projectEvaluation = property.GetNearestParent<ProjectEvaluation>();
120
+ if (projectEvaluation is not null)
121
+ {
122
+ var properties = resolvedProperties.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
123
+ properties[property.Name] = property.Value;
124
+ }
125
+ }
126
+ break;
127
+ case Import import:
128
+ {
129
+ var projectEvaluation = GetNearestProjectEvaluation(import);
130
+ if (projectEvaluation is not null)
131
+ {
132
+ // props and targets files might have been imported from these, but they're not to be considered as dependency files
133
+ var forbiddenDirectories = new[]
134
+ {
135
+ GetPropertyValueFromProjectEvaluation(projectEvaluation, "BaseIntermediateOutputPath"), // e.g., "obj/"
136
+ GetPropertyValueFromProjectEvaluation(projectEvaluation, "BaseOutputPath"), // e.g., "bin/"
137
+ }
138
+ .Where(p => !string.IsNullOrEmpty(p))
139
+ .Select(p => Path.Combine(Path.GetDirectoryName(projectEvaluation.ProjectFile)!, p!))
140
+ .Select(p => p.NormalizePathToUnix())
141
+ .Select(p => new DirectoryInfo(p))
142
+ .ToArray();
143
+ if (PathHelper.IsFileUnderDirectory(new DirectoryInfo(repoRootPath), new FileInfo(import.ImportedProjectFilePath)))
144
+ {
145
+ if (!forbiddenDirectories.Any(f => PathHelper.IsFileUnderDirectory(f, new FileInfo(import.ImportedProjectFilePath))))
146
+ {
147
+ var imports = importedFiles.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
148
+ imports.Add(import.ImportedProjectFilePath);
149
+ }
150
+ }
151
+ }
152
+ }
153
+ break;
154
+ case NamedNode namedNode when namedNode is AddItem or RemoveItem:
155
+ ProcessResolvedPackageReference(namedNode, packagesPerProject, topLevelPackagesPerProject);
156
+
157
+ // maintain list of project references
158
+ if (namedNode is AddItem addItem && addItem.Name == "ProjectReference")
159
+ {
160
+ var projectEvaluation = GetNearestProjectEvaluation(addItem);
161
+ if (projectEvaluation is not null)
162
+ {
163
+ foreach (var referencedProject in addItem.Children.OfType<Item>())
164
+ {
165
+ var referencedProjectPaths = referencedProjects.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
166
+ var referencedProjectPath = new FileInfo(Path.Combine(Path.GetDirectoryName(projectEvaluation.ProjectFile)!, referencedProject.Name)).FullName;
167
+ referencedProjectPaths.Add(referencedProjectPath);
168
+ }
169
+ }
170
+ }
171
+ break;
172
+ }
173
+ }, takeChildrenSnapshot: true);
174
+ }
175
+ catch (HttpRequestException)
176
+ {
177
+ // likely an unauthenticated feed; this needs to be sent further up the chain
178
+ throw;
179
+ }
180
+ finally
181
+ {
182
+ try
183
+ {
184
+ File.Delete(binLogPath);
185
+ }
186
+ catch
187
+ {
188
+ }
189
+ }
190
+ }
191
+
192
+ // and done
193
+ var projectDiscoveryResults = packagesPerProject.Keys.OrderBy(p => p).Select(projectPath =>
194
+ {
195
+ // gather some project-level information
196
+ var packagesByTfm = packagesPerProject[projectPath];
197
+ var projectFullDirectory = Path.GetDirectoryName(projectPath)!;
198
+ var doc = XDocument.Load(projectPath);
199
+ var localPropertyDefinitionElements = doc.Root!.XPathSelectElements("/Project/PropertyGroup/*");
200
+ var projectPropertyNames = localPropertyDefinitionElements.Select(e => e.Name.LocalName).ToHashSet(StringComparer.OrdinalIgnoreCase);
201
+ var projectRelativePath = Path.GetRelativePath(workspacePath, projectPath);
202
+ var topLevelPackageNames = topLevelPackagesPerProject.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase));
203
+
204
+ // create dependencies
205
+ var tfms = packagesByTfm.Keys.OrderBy(tfm => tfm).ToImmutableArray();
206
+ var dependencies = tfms.SelectMany(tfm =>
207
+ {
208
+ return packagesByTfm[tfm].Keys.OrderBy(p => p).Select(packageName =>
209
+ {
210
+ var packageVersion = packagesByTfm[tfm][packageName]!;
211
+ var isTopLevel = topLevelPackageNames.Contains(packageName);
212
+ var dependencyType = isTopLevel ? DependencyType.PackageReference : DependencyType.Unknown;
213
+ return new Dependency(packageName, packageVersion, dependencyType, TargetFrameworks: [tfm], IsDirect: isTopLevel, IsTransitive: !isTopLevel);
214
+ });
215
+ }).ToImmutableArray();
216
+
217
+ // others
218
+ var properties = resolvedProperties[projectPath]
219
+ .Where(pkvp => projectPropertyNames.Contains(pkvp.Key))
220
+ .Select(pkvp => new Property(pkvp.Key, pkvp.Value, Path.GetRelativePath(repoRootPath, projectPath).NormalizePathToUnix()))
221
+ .OrderBy(p => p.Name)
222
+ .ToImmutableArray();
223
+ var referenced = referencedProjects.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase))
224
+ .Select(p => Path.GetRelativePath(projectFullDirectory, p).NormalizePathToUnix())
225
+ .OrderBy(p => p)
226
+ .ToImmutableArray();
227
+ var imported = importedFiles.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase))
228
+ .Select(p => Path.GetRelativePath(projectFullDirectory, p))
229
+ .Select(p => p.NormalizePathToUnix())
230
+ .OrderBy(p => p)
231
+ .ToImmutableArray();
232
+
233
+ return new ProjectDiscoveryResult()
234
+ {
235
+ FilePath = projectRelativePath,
236
+ Dependencies = dependencies,
237
+ TargetFrameworks = tfms,
238
+ Properties = properties,
239
+ ReferencedProjectPaths = referenced,
240
+ ImportedFiles = imported,
241
+ };
242
+ }).ToImmutableArray();
243
+ return projectDiscoveryResults;
244
+ }
245
+
246
+ private static void ProcessResolvedPackageReference(
247
+ NamedNode node,
248
+ Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject, // projectPath -> tfm -> (packageName, packageVersion)
249
+ Dictionary<string, HashSet<string>> topLevelPackagesPerProject
250
+ )
251
+ {
252
+ var doRemoveOperation = node is RemoveItem;
253
+ var doAddOperation = node is AddItem;
254
+
255
+ if (TopLevelPackageItemNames.Contains(node.Name))
256
+ {
257
+ foreach (var child in node.Children.OfType<Item>())
258
+ {
259
+ var projectEvaluation = GetNearestProjectEvaluation(node);
260
+ if (projectEvaluation is not null)
261
+ {
262
+ var packageName = child.Name;
263
+ if (NonReportedPackgeNames.Contains(packageName))
264
+ {
265
+ continue;
266
+ }
267
+
268
+ var topLevelPackages = topLevelPackagesPerProject.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
269
+
270
+ if (doRemoveOperation)
271
+ {
272
+ topLevelPackages.Remove(packageName);
273
+ }
274
+
275
+ if (doAddOperation)
276
+ {
277
+ topLevelPackages.Add(packageName);
278
+ }
279
+ }
280
+ }
281
+ }
282
+ else if (ResolvedPackageItemNames.TryGetValue(node.Name, out var metadataNames))
283
+ {
284
+ var nameMetadata = metadataNames.NameMetadata;
285
+ var versionMetadata = metadataNames.VersionMetadata;
286
+ var projectEvaluation = GetNearestProjectEvaluation(node);
287
+ if (projectEvaluation is not null)
288
+ {
289
+ // without a tfm we can't do anything meaningful with the package reference
290
+ var tfm = GetPropertyValueFromProjectEvaluation(projectEvaluation, "TargetFramework");
291
+ if (tfm is not null)
292
+ {
293
+ foreach (var child in node.Children.OfType<Item>())
294
+ {
295
+ var packageName = GetChildMetadataValue(child, nameMetadata);
296
+ var packageVersion = GetChildMetadataValue(child, versionMetadata);
297
+ if (packageName is not null && packageVersion is not null)
298
+ {
299
+ if (NonReportedPackgeNames.Contains(packageName))
300
+ {
301
+ continue;
302
+ }
303
+
304
+ var tfmsPerProject = packagesPerProject.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
305
+ var packagesPerTfm = tfmsPerProject.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
306
+
307
+ if (doRemoveOperation)
308
+ {
309
+ packagesPerTfm.Remove(packageName);
310
+ }
311
+
312
+ if (doAddOperation)
313
+ {
314
+ packagesPerTfm[packageName] = packageVersion;
315
+ }
316
+ }
317
+ }
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ private static string? GetChildMetadataValue(TreeNode node, string metadataItemName)
324
+ {
325
+ var metadata = node.Children.OfType<Metadata>();
326
+ var metadataValue = metadata.FirstOrDefault(m => m.Name.Equals(metadataItemName, StringComparison.OrdinalIgnoreCase))?.Value;
327
+ return metadataValue;
328
+ }
329
+
330
+ private static ProjectEvaluation? GetNearestProjectEvaluation(BaseNode node)
331
+ {
332
+ // we need to find the containing project evaluation
333
+ // if this is a <PackageReference>, one of the parents is it
334
+ // otherwise, we need to find the parent `Project` and the corresponding evaluation from the build
335
+ var projectEvaluation = node.GetNearestParent<ProjectEvaluation>();
336
+ if (projectEvaluation is null)
337
+ {
338
+ var project = node.GetNearestParent<Project>();
339
+ if (project is null)
340
+ {
341
+ return null;
342
+ }
343
+
344
+ var build = project.GetNearestParent<Build>();
345
+ if (build is null)
346
+ {
347
+ return null;
348
+ }
349
+
350
+ projectEvaluation = build.FindEvaluation(project.EvaluationId);
351
+ }
352
+
353
+ return projectEvaluation;
354
+ }
355
+
356
+ private static string? GetPropertyValueFromProjectEvaluation(ProjectEvaluation projectEvaluation, string propertyName)
357
+ {
358
+ var propertiesFolder = projectEvaluation.Children.OfType<Folder>().FirstOrDefault(f => f.Name == "Properties");
359
+ if (propertiesFolder is null)
360
+ {
361
+ return null;
362
+ }
363
+
364
+ var property = propertiesFolder.Children.OfType<LoggerProperty>().FirstOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
365
+ if (property is null)
366
+ {
367
+ return null;
368
+ }
369
+
370
+ return property.Value;
371
+ }
372
+
373
+ public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverWithTempProjectAsync(string repoRootPath, string workspacePath, string projectPath, ILogger logger)
10
374
  {
11
375
  // Determine which targets and props files contribute to the build.
12
376
  var (buildFiles, projectTargetFrameworks) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(repoRootPath, projectPath);
@@ -30,6 +394,10 @@ internal static class SdkProjectDiscovery
30
394
  // The build file dependencies have the correct DependencyType and the TopLevelDependencies have the evaluated version.
31
395
  // Combine them to have the set of dependencies that are directly referenced from the build file.
32
396
  var fileDependencies = BuildFile.GetDependencies(buildFile).ToImmutableArray();
397
+
398
+ // this is new-ish behavior; don't ever report this dependency because there's no meaningful way to update it
399
+ fileDependencies = fileDependencies.Where(d => !d.Name.Equals("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase)).ToImmutableArray();
400
+
33
401
  var fileDependencyLookup = fileDependencies
34
402
  .ToLookup(d => d.Name, StringComparer.OrdinalIgnoreCase);
35
403
  var sdkDependencies = fileDependencies
@@ -58,7 +426,7 @@ internal static class SdkProjectDiscovery
58
426
  .OrderBy(p => p.Name)
59
427
  .ToImmutableArray();
60
428
  var referencedProjectPaths = MSBuildHelper.GetProjectPathsFromProject(projectPath)
61
- .Select(path => Path.GetRelativePath(workspacePath, path))
429
+ .Select(path => Path.GetRelativePath(workspacePath, path).NormalizePathToUnix())
62
430
  .OrderBy(p => p)
63
431
  .ToImmutableArray();
64
432
 
@@ -72,22 +440,28 @@ internal static class SdkProjectDiscovery
72
440
  .OrderBy(d => d.Name)
73
441
  .ToImmutableArray();
74
442
 
443
+ // for the temporary project, these directories correspond to $(OutputPath) and $(IntermediateOutputPath) and files from
444
+ // these directories should not be reported
445
+ var intermediateDirectories = new string[]
446
+ {
447
+ Path.Join(Path.GetDirectoryName(buildFile.Path), "bin"),
448
+ Path.Join(Path.GetDirectoryName(buildFile.Path), "obj"),
449
+ };
450
+ var projectDirectory = Path.GetDirectoryName(buildFile.Path)!;
75
451
  results.Add(new()
76
452
  {
77
- FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
453
+ FilePath = Path.GetRelativePath(workspacePath, buildFile.Path).NormalizePathToUnix(),
78
454
  Properties = properties,
79
455
  TargetFrameworks = tfms,
80
456
  ReferencedProjectPaths = referencedProjectPaths,
81
457
  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)
458
+ ImportedFiles = buildFiles.Where(b =>
459
+ {
460
+ var fileType = b.GetFileType();
461
+ return fileType == ProjectBuildFileType.Props || fileType == ProjectBuildFileType.Targets;
462
+ })
463
+ .Where(b => !intermediateDirectories.Any(i => PathHelper.IsFileUnderDirectory(new DirectoryInfo(i), new FileInfo(b.Path))))
464
+ .Select(b => Path.GetRelativePath(projectDirectory, b.Path).NormalizePathToUnix())
91
465
  .ToImmutableArray(),
92
466
  });
93
467
  }
@@ -7,7 +7,7 @@ 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; }
10
+ public ImmutableArray<string> ImportedFiles { get; init; } = [];
11
11
  public GlobalJsonDiscoveryResult? GlobalJson { get; init; }
12
12
  public DotNetToolsJsonDiscoveryResult? DotNetToolsJson { get; init; }
13
13
  }
@@ -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
 
@@ -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
+ }
@@ -335,10 +335,6 @@ public class RunWorker
335
335
  {
336
336
  auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.DotNetToolsJson.FilePath));
337
337
  }
338
- if (discoveryResult.DirectoryPackagesProps is not null)
339
- {
340
- auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.DirectoryPackagesProps.FilePath));
341
- }
342
338
 
343
339
  foreach (var project in discoveryResult.Projects)
344
340
  {
@@ -0,0 +1,13 @@
1
+ <Project>
2
+ <Import Project="DependencyDiscovery.props" />
3
+
4
+ <Target Name="ReportTargetFramework">
5
+ <!-- this property is for non-SDK projects, commonly with `packages.config -->
6
+ <!-- e.g., returns ".NETFramework,Version=v4.5" -->
7
+ <Message Text="ProjectData::TargetFrameworkVersion=$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)" Importance="High" Condition="'$(TargetFrameworkIdentifier)' != '' AND '' != '$(TargetFrameworkVersion)'" />
8
+
9
+ <!-- these properties are for SDK projects -->
10
+ <Message Text="ProjectData::TargetFramework=$(TargetFramework)" Importance="High" Condition="'$(TargetFramework)' != ''" />
11
+ <Message Text="ProjectData::TargetFrameworks=$(TargetFrameworks)" Importance="High" Condition="'$(TargetFrameworks)' != ''" />
12
+ </Target>
13
+ </Project>
@@ -23,6 +23,7 @@ internal static class LockFileUpdater
23
23
  {
24
24
  logger.Log($" Lock file update failed.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
25
25
  }
26
+ return (exitCode, stdout, stderr);
26
27
  }, logger, retainMSBuildSdks: true);
27
28
  }
28
29
  }
@@ -311,6 +311,8 @@ internal static class PackageReferenceUpdater
311
311
  {
312
312
  logger.Log($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
313
313
  }
314
+
315
+ return exitCode;
314
316
  }, logger, retainMSBuildSdks: true);
315
317
  }
316
318
 
@@ -0,0 +1,17 @@
1
+ using Newtonsoft.Json.Linq;
2
+
3
+ namespace NuGetUpdater.Core.Utilities;
4
+
5
+ public static class CollectionExtensions
6
+ {
7
+ public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, Func<TValue> valueFactory) where TKey : notnull
8
+ {
9
+ if (!dictionary.TryGetValue(key, out var value))
10
+ {
11
+ value = valueFactory();
12
+ dictionary[key] = value;
13
+ }
14
+
15
+ return value;
16
+ }
17
+ }