dependabot-nuget 0.349.0 → 0.350.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3531f3d69297e8b0afd369fd0757932875c0674b2f0d942f92f9295f868264eb
4
- data.tar.gz: 8f62b4d3b6a2bade7ad801141bf4c720dbde9b1af7255ff7ba61f255d3fb746e
3
+ metadata.gz: 784c940d61ac0b10f0d1b6516c7b33bbaa6e8de03da294ee43245746303e24bb
4
+ data.tar.gz: 6fddc772c732c7a21c56121fdf90f35d668cc6ce3c5662ae134d6060ae55f291
5
5
  SHA512:
6
- metadata.gz: 9e28bc682a07a3959675d2b831a78be6dabb1065af1b7db0f1b78dc34b2c68d7b42e6b3eda8ec41e268ed9d0facf578b6c54b21658f2dc5857a62f9f2080b2a4
7
- data.tar.gz: 2236ac46be0f2606d395e6e90194329c49da81ec3feba63cf0560ff7e077ee2f64ae072fbe73cf2848781831a760608f5f9eabbb77ba47d48434b82f23b952f1
6
+ metadata.gz: 82b32710529925d9392c82d7403ad91eea8445f3ef734190a1175ed56a60bd9a2f338f7066956c5074f1478e4f57ff36b94a1670fabc77a068a8362aee442bf7
7
+ data.tar.gz: 6b5c2ed6e27c43f1c9f264a6dd749f42fc71018e308a66484aa6aa734dfa232e845be87c75797cb0f8cff8a54e71f164f56fb07394faf7ac0fdcfc776713f007
@@ -174,7 +174,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
174
174
  ImmutableArray<string> projects;
175
175
  try
176
176
  {
177
- projects = await ExpandEntryPointsIntoProjectsAsync(entryPoints);
177
+ projects = await ExpandEntryPointsIntoProjectsAsync(entryPoints, _experimentsManager);
178
178
  }
179
179
  catch (InvalidProjectFileException e)
180
180
  {
@@ -222,10 +222,10 @@ public partial class DiscoveryWorker : IDiscoveryWorker
222
222
  .ToImmutableArray();
223
223
  }
224
224
 
225
- private async static Task<ImmutableArray<string>> ExpandEntryPointsIntoProjectsAsync(IEnumerable<string> entryPoints)
225
+ private async static Task<ImmutableArray<string>> ExpandEntryPointsIntoProjectsAsync(IEnumerable<string> entryPoints, ExperimentsManager experimentsManager)
226
226
  {
227
- HashSet<string> expandedProjects = new();
228
- HashSet<string> seenProjects = new();
227
+ HashSet<string> expandedProjects = new(PathComparer.Instance);
228
+ HashSet<string> seenProjects = new(PathComparer.Instance);
229
229
  Stack<string> filesToExpand = new(entryPoints);
230
230
  while (filesToExpand.Count > 0)
231
231
  {
@@ -252,31 +252,51 @@ public partial class DiscoveryWorker : IDiscoveryWorker
252
252
  filesToExpand.Push(projectPath);
253
253
  }
254
254
  }
255
- else if (extension == ".proj")
256
- {
257
- IEnumerable<string> foundProjects = ExpandItemGroupFilesFromProject(candidateEntryPoint, "ProjectFile", "ProjectReference");
258
- foreach (string foundProject in foundProjects)
259
- {
260
- filesToExpand.Push(foundProject);
261
- }
262
- }
263
- else
255
+
256
+ if (experimentsManager.UseSingleRestore)
264
257
  {
258
+ // projects get shunted directly to the result because regular discovery handles it from there
265
259
  switch (extension)
266
260
  {
261
+ case ".proj":
267
262
  case ".csproj":
263
+ case ".fbproj":
268
264
  case ".fsproj":
269
- case ".vbproj":
270
- // keep this project and check for references
271
265
  expandedProjects.Add(candidateEntryPoint);
272
- IEnumerable<string> referencedProjects = ExpandItemGroupFilesFromProject(candidateEntryPoint, "ProjectReference");
273
- foreach (string referencedProject in referencedProjects)
274
- {
275
- filesToExpand.Push(referencedProject);
276
- }
277
266
  break;
278
267
  default:
279
- continue;
268
+ // unsupported project
269
+ break;
270
+ }
271
+ }
272
+ else
273
+ {
274
+ if (extension == ".proj")
275
+ {
276
+ IEnumerable<string> foundProjects = ExpandItemGroupFilesFromProject(candidateEntryPoint, "ProjectFile", "ProjectReference");
277
+ foreach (string foundProject in foundProjects)
278
+ {
279
+ filesToExpand.Push(foundProject);
280
+ }
281
+ }
282
+ else
283
+ {
284
+ switch (extension)
285
+ {
286
+ case ".csproj":
287
+ case ".fsproj":
288
+ case ".vbproj":
289
+ // keep this project and check for references
290
+ expandedProjects.Add(candidateEntryPoint);
291
+ IEnumerable<string> referencedProjects = ExpandItemGroupFilesFromProject(candidateEntryPoint, "ProjectReference");
292
+ foreach (string referencedProject in referencedProjects)
293
+ {
294
+ filesToExpand.Push(referencedProject);
295
+ }
296
+ break;
297
+ default:
298
+ continue;
299
+ }
280
300
  }
281
301
  }
282
302
  }
@@ -329,6 +349,26 @@ public partial class DiscoveryWorker : IDiscoveryWorker
329
349
 
330
350
  try
331
351
  {
352
+ // get all packages.config results first
353
+ var expandedProjects = await ExpandEntryPointsIntoProjectsAsync(normalizedProjectPaths, _experimentsManager);
354
+ foreach (var expandedProject in expandedProjects)
355
+ {
356
+ var packagesConfigResult = await PackagesConfigDiscovery.Discover(repoRootPath, workspacePath, expandedProject, _logger);
357
+ if (packagesConfigResult is not null)
358
+ {
359
+ var relativeProjectPath = Path.GetRelativePath(workspacePath, expandedProject).NormalizePathToUnix();
360
+ results[relativeProjectPath] = new ProjectDiscoveryResult()
361
+ {
362
+ FilePath = relativeProjectPath,
363
+ Dependencies = packagesConfigResult.Dependencies,
364
+ TargetFrameworks = packagesConfigResult.TargetFrameworks,
365
+ ImportedFiles = [], // no imported files resolved for packages.config scenarios
366
+ AdditionalFiles = packagesConfigResult.AdditionalFiles,
367
+ };
368
+ }
369
+ }
370
+
371
+ // now check all sdk projects
332
372
  foreach (var projectPath in normalizedProjectPaths)
333
373
  {
334
374
  if (_processedProjectPaths.Contains(projectPath))
@@ -339,8 +379,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
339
379
  _processedProjectPaths.Add(projectPath);
340
380
 
341
381
  var relativeProjectPath = Path.GetRelativePath(workspacePath, projectPath).NormalizePathToUnix();
342
- var packagesConfigResult = await PackagesConfigDiscovery.Discover(repoRootPath, workspacePath, projectPath, _logger);
343
- var projectResults = await SdkProjectDiscovery.DiscoverAsync(repoRootPath, workspacePath, projectPath, _logger);
382
+ var projectResults = await SdkProjectDiscovery.DiscoverAsync(repoRootPath, workspacePath, projectPath, _experimentsManager, _logger);
344
383
 
345
384
  // Determine if there were unrestored MSBuildSdks
346
385
  var msbuildSdks = projectResults.SelectMany(p => p.Dependencies.Where(d => d.Type == DependencyType.MSBuildSdk)).ToImmutableArray();
@@ -349,46 +388,25 @@ public partial class DiscoveryWorker : IDiscoveryWorker
349
388
  // If new SDKs were restored, then we need to rerun SdkProjectDiscovery.
350
389
  if (await TryRestoreMSBuildSdksAsync(repoRootPath, workspacePath, msbuildSdks, _logger))
351
390
  {
352
- projectResults = await SdkProjectDiscovery.DiscoverAsync(repoRootPath, workspacePath, projectPath, _logger);
391
+ projectResults = await SdkProjectDiscovery.DiscoverAsync(repoRootPath, workspacePath, projectPath, _experimentsManager, _logger);
353
392
  }
354
393
  }
355
394
 
356
395
  foreach (var projectResult in projectResults)
357
396
  {
358
- if (results.ContainsKey(projectResult.FilePath))
359
- {
360
- continue;
361
- }
362
-
363
397
  // If we had packages.config dependencies, merge them with the project dependencies
364
- if (projectResult.FilePath == relativeProjectPath && packagesConfigResult is not null)
398
+ if (results.TryGetValue(projectResult.FilePath, out var packagesConfigResult))
365
399
  {
366
400
  var packagesConfigDependencies = packagesConfigResult.Dependencies
367
401
  .Select(d => d with { TargetFrameworks = projectResult.TargetFrameworks })
368
402
  .ToImmutableArray();
369
403
 
370
- results[projectResult.FilePath] = projectResult with
371
- {
372
- Dependencies = [.. projectResult.Dependencies, .. packagesConfigDependencies],
373
- };
374
- }
375
- else
376
- {
377
- results[projectResult.FilePath] = projectResult;
378
- }
379
- }
380
-
381
- if (packagesConfigResult is not null)
382
- {
383
- // we might have to merge this dependency with some others
384
- if (results.TryGetValue(relativeProjectPath, out var existingProjectDiscovery))
385
- {
386
404
  // merge SDK and packages.config results
387
- var mergedDependencies = existingProjectDiscovery.Dependencies.Concat(packagesConfigResult.Dependencies)
405
+ var mergedDependencies = projectResult.Dependencies.Concat(packagesConfigResult.Dependencies)
388
406
  .DistinctBy(d => d.Name, StringComparer.OrdinalIgnoreCase)
389
407
  .OrderBy(d => d.Name)
390
408
  .ToImmutableArray();
391
- var mergedTargetFrameworks = existingProjectDiscovery.TargetFrameworks.Concat(packagesConfigResult.TargetFrameworks)
409
+ var mergedTargetFrameworks = projectResult.TargetFrameworks.Concat(packagesConfigResult.TargetFrameworks)
392
410
  .Select(t =>
393
411
  {
394
412
  try
@@ -405,34 +423,27 @@ public partial class DiscoveryWorker : IDiscoveryWorker
405
423
  .Distinct()
406
424
  .OrderBy(tfm => tfm)
407
425
  .ToImmutableArray();
408
- var mergedProperties = existingProjectDiscovery.Properties; // packages.config discovery doesn't produce properties
409
- var mergedImportedFiles = existingProjectDiscovery.ImportedFiles; // packages.config discovery doesn't produce imported files
410
- var mergedAdditionalFiles = existingProjectDiscovery.AdditionalFiles.Concat(packagesConfigResult.AdditionalFiles)
426
+ var mergedProperties = projectResult.Properties; // packages.config discovery doesn't produce properties
427
+ var mergedImportedFiles = projectResult.ImportedFiles; // packages.config discovery doesn't produce imported files
428
+ var mergedAdditionalFiles = projectResult.AdditionalFiles.Concat(packagesConfigResult.AdditionalFiles)
411
429
  .Distinct(StringComparer.OrdinalIgnoreCase)
412
430
  .OrderBy(f => f)
413
431
  .ToImmutableArray();
414
432
  var mergedResult = new ProjectDiscoveryResult()
415
433
  {
416
- FilePath = existingProjectDiscovery.FilePath,
434
+ FilePath = projectResult.FilePath,
417
435
  Dependencies = mergedDependencies,
418
436
  TargetFrameworks = mergedTargetFrameworks,
419
437
  Properties = mergedProperties,
420
438
  ImportedFiles = mergedImportedFiles,
421
439
  AdditionalFiles = mergedAdditionalFiles,
422
440
  };
423
- results[relativeProjectPath] = mergedResult;
441
+ results[projectResult.FilePath] = mergedResult;
424
442
  }
425
443
  else
426
444
  {
427
- // add packages.config results
428
- results[relativeProjectPath] = new ProjectDiscoveryResult()
429
- {
430
- FilePath = relativeProjectPath,
431
- Dependencies = packagesConfigResult.Dependencies,
432
- TargetFrameworks = packagesConfigResult.TargetFrameworks,
433
- ImportedFiles = [], // no imported files resolved for packages.config scenarios
434
- AdditionalFiles = packagesConfigResult.AdditionalFiles,
435
- };
445
+ // nothing to merge, just set it
446
+ results[projectResult.FilePath] = projectResult;
436
447
  }
437
448
  }
438
449
  }
@@ -53,8 +53,24 @@ internal static class SdkProjectDiscovery
53
53
  "web.config",
54
54
  };
55
55
 
56
- public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string startingProjectPath, ILogger logger)
56
+ // these are the targets that are necessary to evaluate for a single restore operation
57
+ private static readonly ImmutableArray<string> SingleRestoreTargetNames =
58
+ [
59
+ "Restore",
60
+ "ResolveProjectReferences",
61
+ "ResolvePackageAssets"
62
+ ];
63
+
64
+ public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string startingProjectPath, ExperimentsManager experimentsManager, ILogger logger)
57
65
  {
66
+ var extension = Path.GetExtension(startingProjectPath)?.ToLowerInvariant();
67
+ switch (extension)
68
+ {
69
+ case ".sln":
70
+ case ".slnx":
71
+ throw new NotSupportedException("SDK discovery can't be directly called on a solution file.");
72
+ }
73
+
58
74
  // 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
59
75
  // throughout until the very end when the appropriate kind of relative path is returned.
60
76
 
@@ -91,8 +107,10 @@ internal static class SdkProjectDiscovery
91
107
  // projectPath additionalFiles
92
108
 
93
109
  var requiresManualPackageResolution = false;
94
- var tfms = await MSBuildHelper.GetTargetFrameworkValuesFromProject(repoRootPath, startingProjectPath, logger);
95
- foreach (var tfm in tfms)
110
+ var discoveredTfms = experimentsManager.UseSingleRestore
111
+ ? ["unused-for-single-restore"] // we need to ensure we do the following just once because it'll restore all relevant projects
112
+ : await MSBuildHelper.GetTargetFrameworkValuesFromProject(repoRootPath, startingProjectPath, logger);
113
+ foreach (var discoveredTfm in discoveredTfms)
96
114
  {
97
115
  // create a binlog
98
116
  var binLogPath = Path.Combine(Path.GetTempPath(), $"msbuild_{Guid.NewGuid():d}.binlog");
@@ -106,14 +124,36 @@ internal static class SdkProjectDiscovery
106
124
  "build",
107
125
  startingProjectPath,
108
126
  "/t:_DiscoverDependencies",
109
- $"/p:TargetFramework={tfm}",
127
+ $"/p:TargetFramework={discoveredTfm}",
110
128
  $"/p:CustomBeforeMicrosoftCommonProps={dependencyDiscoveryTargetingPacksPropsPath}",
111
129
  $"/p:CustomAfterMicrosoftCommonCrossTargetingTargets={dependencyDiscoveryTargetsPath}",
112
130
  $"/p:CustomAfterMicrosoftCommonTargets={dependencyDiscoveryTargetsPath}",
113
- "/p:TreatWarningsAsErrors=false", // if using CPM and a project also sets TreatWarningsAsErrors to true, this can cause discovery to fail; explicitly don't allow that
114
- "/p:MSBuildTreatWarningsAsErrors=false",
115
- $"/bl:{binLogPath}"
116
131
  };
132
+
133
+ if (experimentsManager.UseSingleRestore)
134
+ {
135
+ // when using single restore, we can directly invoke the relevant targets
136
+ args = ["msbuild", startingProjectPath];
137
+ var targets = await MSBuildHelper.GetProjectTargetsAsync(startingProjectPath, logger);
138
+ var useDirectRestore = SingleRestoreTargetNames.All(targets.Contains);
139
+ if (useDirectRestore)
140
+ {
141
+ // directly call the required targets
142
+ args.Add($"/t:{string.Join(",", SingleRestoreTargetNames)}");
143
+ }
144
+ else
145
+ {
146
+ // delegate to the inner build and call those targets
147
+ args.Add("/t:Build");
148
+ args.Add($"/p:InnerTargets=\"{string.Join(";", SingleRestoreTargetNames)}\"");
149
+ }
150
+ }
151
+
152
+ // if using CPM and a project also sets TreatWarningsAsErrors to true, this can cause discovery to fail; explicitly don't allow that
153
+ args.Add("/p:TreatWarningsAsErrors=false");
154
+ args.Add("/p:MSBuildTreatWarningsAsErrors=false");
155
+ args.Add($"/bl:{binLogPath}");
156
+
117
157
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(args, startingProjectDirectory);
118
158
  if (exitCode != 0 && stdOut.Contains("error : Object reference not set to an instance of an object."))
119
159
  {
@@ -249,6 +289,12 @@ internal static class SdkProjectDiscovery
249
289
  break;
250
290
  }
251
291
 
292
+ var evaluatedTfm = GetPropertyValueFromProjectEvaluation(projectEvaluation, "TargetFramework");
293
+ if (evaluatedTfm is null)
294
+ {
295
+ break;
296
+ }
297
+
252
298
  var removedReferences = target.Children.OfType<RemoveItem>().FirstOrDefault(r => r.Name == "Reference");
253
299
  var addedReferences = target.Children.OfType<AddItem>().FirstOrDefault(r => r.Name == "Reference");
254
300
  if (removedReferences is null || addedReferences is null)
@@ -266,7 +312,7 @@ internal static class SdkProjectDiscovery
266
312
  }
267
313
 
268
314
  var existingProjectPackagesByTfm = packagesPerProject.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
269
- var existingProjectPackages = existingProjectPackagesByTfm.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
315
+ var existingProjectPackages = existingProjectPackagesByTfm.GetOrAdd(evaluatedTfm, () => new(StringComparer.OrdinalIgnoreCase));
270
316
  if (!existingProjectPackages.ContainsKey(removedPackageName))
271
317
  {
272
318
  continue;
@@ -296,7 +342,7 @@ internal static class SdkProjectDiscovery
296
342
  }
297
343
 
298
344
  var packagesPerThisProject = packagesReplacedBySdkPerProject.GetOrAdd(projectEvaluation.ProjectFile, () => new(PathComparer.Instance));
299
- var packagesPerTfm = packagesPerThisProject.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
345
+ var packagesPerTfm = packagesPerThisProject.GetOrAdd(evaluatedTfm, () => new(StringComparer.OrdinalIgnoreCase));
300
346
  packagesPerTfm[removedPackageName] = replacementPackageVersion.ToString();
301
347
  var relativeProjectPath = Path.GetRelativePath(repoRootPath, projectEvaluation.ProjectFile).NormalizePathToUnix();
302
348
  logger.Info($"Re-added SDK managed package [{removedPackageName}/{replacementPackageVersion}] to project [{relativeProjectPath}]");
@@ -330,9 +376,10 @@ internal static class SdkProjectDiscovery
330
376
  packagesPerProject = await RebuildPackagesPerProject(
331
377
  repoRootPath,
332
378
  startingProjectPath,
333
- tfms,
379
+ discoveredTfms,
334
380
  packagesPerProject,
335
381
  explicitPackageVersionsPerProject,
382
+ experimentsManager,
336
383
  logger
337
384
  );
338
385
  }
@@ -557,6 +604,7 @@ internal static class SdkProjectDiscovery
557
604
  ImmutableArray<string> targetFrameworks,
558
605
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject,
559
606
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> explicitPackageVersionsPerProject,
607
+ ExperimentsManager experimentsManager,
560
608
  ILogger logger
561
609
  )
562
610
  {
@@ -573,7 +621,7 @@ internal static class SdkProjectDiscovery
573
621
 
574
622
  var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRootPath, projectPath, targetFrameworks, topLevelDependencies, logger);
575
623
  var tempProjectDirectory = Path.GetDirectoryName(tempProjectPath)!;
576
- var rediscoveredDependencies = await DiscoverAsync(tempProjectDirectory, tempProjectDirectory, tempProjectPath, logger);
624
+ var rediscoveredDependencies = await DiscoverAsync(tempProjectDirectory, tempProjectDirectory, tempProjectPath, experimentsManager, logger);
577
625
  var rediscoveredDependenciesForThisProject = rediscoveredDependencies.Single(); // we started with a single temp project, this will be the only result
578
626
 
579
627
  // re-build packagesPerProject
@@ -8,12 +8,14 @@ namespace NuGetUpdater.Core;
8
8
  public record ExperimentsManager
9
9
  {
10
10
  public bool GenerateSimplePrBody { get; init; } = false;
11
+ public bool UseSingleRestore { get; init; } = false;
11
12
 
12
13
  public Dictionary<string, object> ToDictionary()
13
14
  {
14
15
  return new()
15
16
  {
16
17
  ["nuget_generate_simple_pr_body"] = GenerateSimplePrBody,
18
+ ["nuget_use_single_restore"] = UseSingleRestore,
17
19
  };
18
20
  }
19
21
 
@@ -22,6 +24,7 @@ public record ExperimentsManager
22
24
  return new ExperimentsManager()
23
25
  {
24
26
  GenerateSimplePrBody = IsEnabled(experiments, "nuget_generate_simple_pr_body"),
27
+ UseSingleRestore = IsEnabled(experiments, "nuget_use_single_restore"),
25
28
  };
26
29
  }
27
30
 
@@ -493,7 +493,8 @@ internal static partial class MSBuildHelper
493
493
  var topLevelPackagesNames = packages.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
494
494
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, logger, importDependencyTargets: false);
495
495
 
496
- var projectDiscovery = await SdkProjectDiscovery.DiscoverAsync(repoRoot, tempDirectory.FullName, tempProjectPath, logger);
496
+ var experimentsManager = new ExperimentsManager() { UseSingleRestore = false }; // single restore is meaningless here
497
+ var projectDiscovery = await SdkProjectDiscovery.DiscoverAsync(repoRoot, tempDirectory.FullName, tempProjectPath, experimentsManager, logger);
497
498
  var allDependencies = projectDiscovery
498
499
  .Where(p => p.FilePath == Path.GetFileName(tempProjectPath))
499
500
  .FirstOrDefault()
@@ -513,6 +514,37 @@ internal static partial class MSBuildHelper
513
514
  }
514
515
  }
515
516
 
517
+ public static async Task<HashSet<string>> GetProjectTargetsAsync(string projectPath, ILogger logger)
518
+ {
519
+ var extension = Path.GetExtension(projectPath)?.ToLowerInvariant();
520
+ if (extension == ".sln" || extension == ".slnx")
521
+ {
522
+ // solution files don't specify targets, so we can skip the process invocation
523
+ return [];
524
+ }
525
+
526
+ var projectDirectory = Path.GetDirectoryName(projectPath)!;
527
+ var args = new[]
528
+ {
529
+ "msbuild",
530
+ projectPath,
531
+ "-targets"
532
+ };
533
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(args, projectDirectory);
534
+ if (exitCode != 0)
535
+ {
536
+ logger.Warn($"Unable to determine targets for project [{projectPath}]:\nSTDOUT:\n{stdOut}\nSTDERR:\n{stdErr}\n");
537
+ return [];
538
+ }
539
+
540
+ var targets = stdOut.Split('\n')
541
+ .Skip(1) // first line is msbuild info
542
+ .Select(l => l.Trim())
543
+ .Where(l => !string.IsNullOrEmpty(l))
544
+ .ToHashSet(StringComparer.OrdinalIgnoreCase);
545
+ return targets;
546
+ }
547
+
516
548
  internal static string? GetMissingFile(string output)
517
549
  {
518
550
  var missingFilePatterns = new[]
@@ -8,7 +8,11 @@ public static class ProcessEx
8
8
  /// <summary>
9
9
  /// Run the `dotnet` command with the given values. This will exclude all `MSBuild*` environment variables from the execution.
10
10
  /// </summary>
11
- public static Task<(int ExitCode, string Output, string Error)> RunDotnetWithoutMSBuildEnvironmentVariablesAsync(IEnumerable<string> arguments, string workingDirectory)
11
+ public static Task<(int ExitCode, string Output, string Error)> RunDotnetWithoutMSBuildEnvironmentVariablesAsync(
12
+ IEnumerable<string> arguments,
13
+ string workingDirectory,
14
+ IEnumerable<(string Name, string? Value)>? extraEnvironmentVariables = null
15
+ )
12
16
  {
13
17
  // When using the SDK specified by a `global.json` file, these environment variables need to be unset to
14
18
  // allow the new process to discover the correct MSBuild binaries to load, and not load the ones that
@@ -24,6 +28,11 @@ public static class ProcessEx
24
28
  };
25
29
 
26
30
  var environmentVariableOverrides = environmentVariablesToUnset.Select(name => (name, (string?)null));
31
+ if (extraEnvironmentVariables is not null)
32
+ {
33
+ environmentVariableOverrides = environmentVariableOverrides.Concat(extraEnvironmentVariables);
34
+ }
35
+
27
36
  return RunAsync("dotnet",
28
37
  arguments,
29
38
  workingDirectory,
@@ -61,10 +61,13 @@ public partial class DiscoveryWorkerTests
61
61
  );
62
62
  }
63
63
 
64
- [Fact]
65
- public async Task DiscoveryIsMergedWithPackageReferences()
64
+ [Theory]
65
+ [InlineData(true)]
66
+ [InlineData(false)]
67
+ public async Task DiscoveryIsMergedWithPackageReferences(bool useSingleRestore)
66
68
  {
67
69
  await TestDiscoveryAsync(
70
+ experimentsManager: new ExperimentsManager() { UseSingleRestore = useSingleRestore },
68
71
  packages:
69
72
  [
70
73
  MockNuGetPackage.CreateSimplePackage("Package.A", "1.0.0", "net46"),
@@ -163,10 +163,13 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
163
163
  );
164
164
  }
165
165
 
166
- [Fact]
167
- public async Task TestDependenciesSeparatedBySemicolon()
166
+ [Theory]
167
+ [InlineData(true)]
168
+ [InlineData(false)]
169
+ public async Task TestDependenciesSeparatedBySemicolon(bool useSingleRestore)
168
170
  {
169
171
  await TestDiscoveryAsync(
172
+ experimentsManager: new ExperimentsManager() { UseSingleRestore = useSingleRestore },
170
173
  packages:
171
174
  [
172
175
  MockNuGetPackage.CreateSimplePackage("Some.Package", "9.0.1", "net8.0"),
@@ -368,9 +371,10 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
368
371
  [Theory]
369
372
  [InlineData(true)]
370
373
  [InlineData(false)]
371
- public async Task TestPackageConfig(bool useDirectDiscovery)
374
+ public async Task TestPackageConfig(bool useSingleRestore)
372
375
  {
373
376
  await TestDiscoveryAsync(
377
+ experimentsManager: new ExperimentsManager() { UseSingleRestore = useSingleRestore },
374
378
  packages:
375
379
  [
376
380
  MockNuGetPackage.CreateSimplePackage("Some.Package", "7.0.1", "net45"),
@@ -487,11 +491,14 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
487
491
  );
488
492
  }
489
493
 
490
- [Fact]
491
- public async Task TestRepo()
494
+ [Theory]
495
+ [InlineData(true)]
496
+ [InlineData(false)]
497
+ public async Task TestRepo(bool useSingleRestore)
492
498
  {
493
499
  var solutionPath = "solution.sln";
494
500
  await TestDiscoveryAsync(
501
+ experimentsManager: new ExperimentsManager() { UseSingleRestore = useSingleRestore },
495
502
  packages:
496
503
  [
497
504
  MockNuGetPackage.CreateSimplePackage("Some.Package", "9.0.1", "net7.0"),
@@ -617,11 +624,110 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
617
624
  );
618
625
  }
619
626
 
620
- [Fact]
621
- public async Task TestRepo_DirectDiscovery_Slnx()
627
+ [Theory]
628
+ [InlineData(true)]
629
+ [InlineData(false)]
630
+ public async Task TestRepo_Sln(bool useSingleRestore)
631
+ {
632
+ await TestDiscoveryAsync(
633
+ experimentsManager: new ExperimentsManager() { UseSingleRestore = useSingleRestore },
634
+ packages: [
635
+ MockNuGetPackage.CreateSimplePackage("Package.A", "1.2.3", "net8.0"),
636
+ MockNuGetPackage.CreateSimplePackage("Package.B", "4.5.6", "net8.0"),
637
+ ],
638
+ workspacePath: "src",
639
+ files: [
640
+ ("src/solution.sln", """
641
+ Microsoft Visual Studio Solution File, Format Version 12.00
642
+ # Visual Studio 14
643
+ VisualStudioVersion = 14.0.22705.0
644
+ MinimumVisualStudioVersion = 10.0.40219.1
645
+ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Project.A", "client\client.csproj", "{782E0C0A-10D3-444D-9640-263D03D2B20C}"
646
+ EndProject
647
+ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Project.B", "server\server.csproj", "{782E0C0A-10D3-444D-9640-263D03D2B20D}"
648
+ EndProject
649
+ Global
650
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
651
+ Debug|Any CPU = Debug|Any CPU
652
+ Release|Any CPU = Release|Any CPU
653
+ EndGlobalSection
654
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
655
+ {782E0C0A-10D3-444D-9640-263D03D2B20C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
656
+ {782E0C0A-10D3-444D-9640-263D03D2B20C}.Debug|Any CPU.Build.0 = Debug|Any CPU
657
+ {782E0C0A-10D3-444D-9640-263D03D2B20C}.Release|Any CPU.ActiveCfg = Release|Any CPU
658
+ {782E0C0A-10D3-444D-9640-263D03D2B20C}.Release|Any CPU.Build.0 = Release|Any CPU
659
+ {782E0C0A-10D3-444D-9640-263D03D2B20D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
660
+ {782E0C0A-10D3-444D-9640-263D03D2B20D}.Debug|Any CPU.Build.0 = Debug|Any CPU
661
+ {782E0C0A-10D3-444D-9640-263D03D2B20D}.Release|Any CPU.ActiveCfg = Release|Any CPU
662
+ {782E0C0A-10D3-444D-9640-263D03D2B20D}.Release|Any CPU.Build.0 = Release|Any CPU
663
+ EndGlobalSection
664
+ GlobalSection(SolutionProperties) = preSolution
665
+ HideSolutionNode = FALSE
666
+ EndGlobalSection
667
+ EndGlobal
668
+ """),
669
+ ("src/client/client.csproj", """
670
+ <Project Sdk="Microsoft.NET.Sdk">
671
+ <PropertyGroup>
672
+ <TargetFramework>net8.0</TargetFramework>
673
+ </PropertyGroup>
674
+ <ItemGroup>
675
+ <PackageReference Include="Package.A" Version="1.2.3" />
676
+ </ItemGroup>
677
+ </Project>
678
+ """),
679
+ ("src/server/server.csproj", """
680
+ <Project Sdk="Microsoft.NET.Sdk">
681
+ <PropertyGroup>
682
+ <TargetFramework>net8.0</TargetFramework>
683
+ </PropertyGroup>
684
+ <ItemGroup>
685
+ <PackageReference Include="Package.B" Version="4.5.6" />
686
+ </ItemGroup>
687
+ </Project>
688
+ """)
689
+ ],
690
+ expectedResult: new()
691
+ {
692
+ Path = "src",
693
+ Projects = [
694
+ new()
695
+ {
696
+ FilePath = "client/client.csproj",
697
+ TargetFrameworks = ["net8.0"],
698
+ Dependencies = [new("Package.A", "1.2.3", DependencyType.PackageReference, IsDirect: true, TargetFrameworks: ["net8.0"])],
699
+ Properties = [
700
+ new("TargetFramework", "net8.0", "src/client/client.csproj"),
701
+ ],
702
+ ReferencedProjectPaths = [],
703
+ ImportedFiles = [],
704
+ AdditionalFiles = [],
705
+ },
706
+ new()
707
+ {
708
+ FilePath = "server/server.csproj",
709
+ TargetFrameworks = ["net8.0"],
710
+ Dependencies = [new("Package.B", "4.5.6", DependencyType.PackageReference, IsDirect: true, TargetFrameworks: ["net8.0"])],
711
+ Properties = [
712
+ new("TargetFramework", "net8.0", "src/server/server.csproj"),
713
+ ],
714
+ ReferencedProjectPaths = [],
715
+ ImportedFiles = [],
716
+ AdditionalFiles = [],
717
+ }
718
+ ]
719
+ }
720
+ );
721
+ }
722
+
723
+ [Theory]
724
+ [InlineData(true)]
725
+ [InlineData(false)]
726
+ public async Task TestRepo_Slnx(bool useSingleRestore)
622
727
  {
623
728
  var solutionPath = "solution.slnx";
624
729
  await TestDiscoveryAsync(
730
+ experimentsManager: new ExperimentsManager() { UseSingleRestore = useSingleRestore },
625
731
  packages:
626
732
  [
627
733
  MockNuGetPackage.CreateSimplePackage("Some.Package", "9.0.1", "net7.0"),
@@ -587,6 +587,7 @@ public class SdkProjectDiscoveryTests : DiscoveryWorkerTestBase
587
587
  // Normally this would cause a restore failure which means discovery would also fail
588
588
  // This test ensures we can still run discovery
589
589
  await TestDiscoverAsync(
590
+ useSingleRestore: false,
590
591
  packages: [
591
592
  MockNuGetPackage.CreateSimplePackage("Package.A", "1.0.0", "net8.0", [(null, [("Transitive.Package", "2.0.0")])]),
592
593
  MockNuGetPackage.CreateSimplePackage("Transitive.Package", "1.0.0", "net8.0"),
@@ -629,7 +630,136 @@ public class SdkProjectDiscoveryTests : DiscoveryWorkerTestBase
629
630
  );
630
631
  }
631
632
 
632
- private static async Task TestDiscoverAsync(string startingDirectory, string projectPath, TestFile[] files, ImmutableArray<ExpectedSdkProjectDiscoveryResult> expectedProjects, MockNuGetPackage[]? packages = null)
633
+ [Fact]
634
+ public async Task DependenciesCanBeDiscoveredWithoutCompiling_FromProjectWithSingleTfm()
635
+ {
636
+ using var tempDir = new TemporaryDirectory();
637
+ var errorSentinelPath = Path.Combine(tempDir.DirectoryPath, "error-sentinel.txt");
638
+ await TestDiscoverAsync(
639
+ useSingleRestore: true,
640
+ startingDirectory: "src",
641
+ packages: [
642
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
643
+ ],
644
+ projectPath: "src/project.csproj",
645
+ files: [
646
+ ("src/project.csproj", """
647
+ <Project Sdk="Microsoft.NET.Sdk">
648
+ <PropertyGroup>
649
+ <TargetFramework>net8.0</TargetFramework>
650
+ </PropertyGroup>
651
+ <ItemGroup>
652
+ <PackageReference Include="Some.Package" Version="1.0.0" />
653
+ </ItemGroup>
654
+ </Project>
655
+ """),
656
+ ("src/Directory.Build.props", "<Project />"),
657
+ ("src/Directory.Packages.props", "<Project />"),
658
+ ("src/Directory.Build.targets", $"""
659
+ <Project>
660
+ <Target Name="_TEST_ThisShouldNotExecute_" AfterTargets="CoreCompile">
661
+ <WriteLinesToFile File="{errorSentinelPath}" Lines="this should not have happened" Overwrite="true" />
662
+ </Target>
663
+ </Project>
664
+ """),
665
+ ],
666
+ expectedProjects: [
667
+ new()
668
+ {
669
+ FilePath = "project.csproj",
670
+ Dependencies =
671
+ [
672
+ new("Some.Package", "1.0.0", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true)
673
+ ],
674
+ ImportedFiles = ["Directory.Build.props", "Directory.Build.targets", "Directory.Packages.props"],
675
+ Properties =
676
+ [
677
+ new("TargetFramework", "net8.0", "src/project.csproj"),
678
+ ],
679
+ TargetFrameworks = ["net8.0"],
680
+ ReferencedProjectPaths = [],
681
+ AdditionalFiles = [],
682
+ },
683
+ ]
684
+ );
685
+ Assert.False(File.Exists(errorSentinelPath), "Build targets should not have executed during discovery");
686
+ }
687
+
688
+ [Fact]
689
+ public async Task DependenciesCanBeDiscoveredWithoutCompiling_FromProjectWithMultipleTfms()
690
+ {
691
+ using var tempDir = new TemporaryDirectory();
692
+ var errorSentinelPath = Path.Combine(tempDir.DirectoryPath, "error-sentinel.txt");
693
+ await TestDiscoverAsync(
694
+ useSingleRestore: true,
695
+ startingDirectory: "src",
696
+ packages: [
697
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
698
+ ],
699
+ projectPath: "src/project.csproj",
700
+ files: [
701
+ ("src/project.csproj", """
702
+ <Project Sdk="Microsoft.NET.Sdk">
703
+ <PropertyGroup>
704
+ <TargetFrameworks>net8.0;net9.0</TargetFrameworks>
705
+ </PropertyGroup>
706
+ <ItemGroup>
707
+ <PackageReference Include="Some.Package" Version="1.0.0" />
708
+ </ItemGroup>
709
+ </Project>
710
+ """),
711
+ ("src/Directory.Build.props", "<Project />"),
712
+ ("src/Directory.Packages.props", "<Project />"),
713
+ ("src/Directory.Build.targets", $"""
714
+ <Project>
715
+ <Target Name="_TEST_ThisShouldNotExecute_" AfterTargets="CoreCompile">
716
+ <WriteLinesToFile File="{errorSentinelPath}" Lines="this should not have happened" Overwrite="true" />
717
+ </Target>
718
+ </Project>
719
+ """),
720
+ ],
721
+ expectedProjects: [
722
+ new()
723
+ {
724
+ FilePath = "project.csproj",
725
+ Dependencies =
726
+ [
727
+ new("Some.Package", "1.0.0", DependencyType.PackageReference, TargetFrameworks: ["net8.0", "net9.0"], IsDirect: true)
728
+ ],
729
+ ImportedFiles = ["Directory.Build.props", "Directory.Build.targets", "Directory.Packages.props"],
730
+ Properties =
731
+ [
732
+ new("TargetFrameworks", "net8.0;net9.0", "src/project.csproj"),
733
+ ],
734
+ TargetFrameworks = ["net8.0", "net9.0"],
735
+ ReferencedProjectPaths = [],
736
+ AdditionalFiles = [],
737
+ },
738
+ ]
739
+ );
740
+ Assert.False(File.Exists(errorSentinelPath), "Build targets should not have executed during discovery");
741
+ }
742
+
743
+ private static async Task TestDiscoverAsync(
744
+ string startingDirectory,
745
+ string projectPath,
746
+ TestFile[] files,
747
+ ImmutableArray<ExpectedSdkProjectDiscoveryResult> expectedProjects,
748
+ MockNuGetPackage[]? packages = null
749
+ )
750
+ {
751
+ await TestDiscoverAsync(useSingleRestore: true, startingDirectory, projectPath, files, expectedProjects, packages);
752
+ await TestDiscoverAsync(useSingleRestore: false, startingDirectory, projectPath, files, expectedProjects, packages);
753
+ }
754
+
755
+ private static async Task TestDiscoverAsync(
756
+ bool useSingleRestore,
757
+ string startingDirectory,
758
+ string projectPath,
759
+ TestFile[] files,
760
+ ImmutableArray<ExpectedSdkProjectDiscoveryResult> expectedProjects,
761
+ MockNuGetPackage[]? packages = null
762
+ )
633
763
  {
634
764
  using var testDirectory = await TemporaryDirectory.CreateWithContentsAsync(files);
635
765
 
@@ -637,7 +767,8 @@ public class SdkProjectDiscoveryTests : DiscoveryWorkerTestBase
637
767
 
638
768
  var logger = new TestLogger();
639
769
  var fullProjectPath = Path.Combine(testDirectory.DirectoryPath, projectPath);
640
- var projectDiscovery = await SdkProjectDiscovery.DiscoverAsync(testDirectory.DirectoryPath, Path.GetDirectoryName(fullProjectPath)!, fullProjectPath, logger);
770
+ var experimentsManager = new ExperimentsManager() { UseSingleRestore = useSingleRestore };
771
+ var projectDiscovery = await SdkProjectDiscovery.DiscoverAsync(testDirectory.DirectoryPath, Path.GetDirectoryName(fullProjectPath)!, fullProjectPath, experimentsManager, logger);
641
772
  ValidateProjectResults(expectedProjects, projectDiscovery);
642
773
  }
643
774
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.349.0
4
+ version: 0.350.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.349.0
18
+ version: 0.350.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.349.0
25
+ version: 0.350.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -552,7 +552,7 @@ licenses:
552
552
  - MIT
553
553
  metadata:
554
554
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
555
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.349.0
555
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.350.0
556
556
  rdoc_options: []
557
557
  require_paths:
558
558
  - lib