dependabot-nuget 0.252.0 → 0.253.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 (23) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +27 -9
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +1 -1
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +66 -65
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +44 -6
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +1 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +181 -93
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +18 -13
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +24 -6
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +18 -10
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +25 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestBase.cs +10 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +1 -8
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +1 -1
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +0 -5
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +0 -5
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +0 -5
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +0 -5
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +0 -5
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +30 -23
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +44 -9
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterHelperTests.cs +1 -1
  23. metadata +6 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6658fe5d81acc16548fe2d8fa0aa6ad631e0699588a4595e6b8de2f382a163d8
4
- data.tar.gz: 3ced8b77f453319a715b76ccff47787a8798cb92366fe6a86d6894b855a984bf
3
+ metadata.gz: 272069533f6bea5533fb423db28c745b469884c904e393d9708091398a93f9a3
4
+ data.tar.gz: dd0c3fad1ab56f2bc32e509f89552068cc59b02cd21b1878ef1edcca84af45ba
5
5
  SHA512:
6
- metadata.gz: 958fba1507fdc26ec2e0bc3978702b9103543558552c79188e7cca93b85248f46c1d485686da353076fbab234ea150cc1bb7e11231a1765e87071cf068b2e95c
7
- data.tar.gz: 5262c02eeff331fbf8c1399ec2d8fc49bd8942f83f2c1d67a4c9f48a3c7515b0f52710cb5e9f1409c23b025719cbb68d6c43ea2db44f15fe9aa353d80a5b1bd8
6
+ metadata.gz: a66d28929da003117a1a5ef125be1acd2dd91a522f715fa1c263610a63e333e5b60ec3343836a1d19b6516527595b88425aed9f7df7a5c3f254579b1a12ec77c
7
+ data.tar.gz: 4fa773a0ff8f2c5e423fd505d111eaf7f84439f99111b68a87e77700290ae0ae97d52318ec9ddabc4d5c80e953bdaef761da96730c82baa8f5c0e36034798e3b
@@ -293,22 +293,30 @@ public partial class EntryPointTests
293
293
  );
294
294
  }
295
295
 
296
- [Fact]
297
- public async Task UpdaterDoesNotUseRepoGlobalJsonForMSBuildTasks()
296
+ [Theory]
297
+ [InlineData(null)]
298
+ [InlineData("src")]
299
+ public async Task UpdaterDoesNotUseRepoGlobalJsonForMSBuildTasks(string? workingDirectoryPath)
298
300
  {
299
301
  // This is a _very_ specific scenario where the `NuGetUpdater.Cli` tool might pick up a `global.json` from
300
302
  // the root of the repo under test and use it's `sdk` property when trying to locate MSBuild. To properly
301
303
  // test this, it must be tested in a new process where MSBuild has not been loaded yet and the runner tool
302
304
  // must be started with its working directory at the test repo's root.
303
305
  using var tempDir = new TemporaryDirectory();
304
- await File.WriteAllTextAsync(Path.Join(tempDir.DirectoryPath, "global.json"), """
306
+ var globalJsonPath = Path.Join(tempDir.DirectoryPath, "global.json");
307
+ var srcGlobalJsonPath = Path.Join(tempDir.DirectoryPath, "src", "global.json");
308
+ string globalJsonContent = """
305
309
  {
306
310
  "sdk": {
307
311
  "version": "99.99.99"
308
312
  }
309
313
  }
310
- """);
311
- await File.WriteAllTextAsync(Path.Join(tempDir.DirectoryPath, "project.csproj"), """
314
+ """;
315
+ await File.WriteAllTextAsync(globalJsonPath, globalJsonContent);
316
+ Directory.CreateDirectory(Path.Join(tempDir.DirectoryPath, "src"));
317
+ await File.WriteAllTextAsync(srcGlobalJsonPath, globalJsonContent);
318
+ var projectPath = Path.Join(tempDir.DirectoryPath, "src", "project.csproj");
319
+ await File.WriteAllTextAsync(projectPath, """
312
320
  <Project Sdk="Microsoft.NET.Sdk">
313
321
  <PropertyGroup>
314
322
  <TargetFramework>net8.0</TargetFramework>
@@ -325,7 +333,7 @@ public partial class EntryPointTests
325
333
  "--repo-root",
326
334
  tempDir.DirectoryPath,
327
335
  "--solution-or-project",
328
- Path.Join(tempDir.DirectoryPath, "project.csproj"),
336
+ projectPath,
329
337
  "--dependency",
330
338
  "Newtonsoft.Json",
331
339
  "--new-version",
@@ -336,15 +344,25 @@ public partial class EntryPointTests
336
344
  ]);
337
345
 
338
346
  // verify base run
339
- var (exitCode, output, error) = await ProcessEx.RunAsync(executableName, executableArgs, workingDirectory: tempDir.DirectoryPath);
347
+ var workingDirectory = tempDir.DirectoryPath;
348
+ if (workingDirectoryPath is not null)
349
+ {
350
+ workingDirectory = Path.Join(workingDirectory, workingDirectoryPath);
351
+ }
352
+
353
+ var (exitCode, output, error) = await ProcessEx.RunAsync(executableName, executableArgs, workingDirectory: workingDirectory);
340
354
  Assert.True(exitCode == 0, $"Error running update on unsupported SDK.\nSTDOUT:\n{output}\nSTDERR:\n{error}");
341
355
 
342
356
  // verify project update
343
- var updatedProjectContents = await File.ReadAllTextAsync(Path.Join(tempDir.DirectoryPath, "project.csproj"));
357
+ var updatedProjectContents = await File.ReadAllTextAsync(projectPath);
344
358
  Assert.Contains("13.0.1", updatedProjectContents);
345
359
 
346
360
  // verify `global.json` untouched
347
- var updatedGlobalJsonContents = await File.ReadAllTextAsync(Path.Join(tempDir.DirectoryPath, "global.json"));
361
+ var updatedGlobalJsonContents = await File.ReadAllTextAsync(globalJsonPath);
362
+ Assert.Contains("99.99.99", updatedGlobalJsonContents);
363
+
364
+ // verify `src/global.json` untouched
365
+ var updatedSrcGlobalJsonContents = await File.ReadAllTextAsync(srcGlobalJsonPath);
348
366
  Assert.Contains("99.99.99", updatedGlobalJsonContents);
349
367
  }
350
368
 
@@ -26,7 +26,7 @@ public partial class DiscoveryWorker
26
26
 
27
27
  public async Task RunAsync(string repoRootPath, string workspacePath, string outputPath)
28
28
  {
29
- MSBuildHelper.RegisterMSBuild();
29
+ MSBuildHelper.RegisterMSBuild(Environment.CurrentDirectory, repoRootPath);
30
30
 
31
31
  // When running under unit tests, the workspace path may not be rooted.
32
32
  if (!Path.IsPathRooted(workspacePath) || !Directory.Exists(workspacePath))
@@ -9,86 +9,87 @@ internal static class SdkProjectDiscovery
9
9
  public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string projectPath, Logger logger)
10
10
  {
11
11
  // Determine which targets and props files contribute to the build.
12
- var buildFiles = await MSBuildHelper.LoadBuildFilesAsync(repoRootPath, projectPath, includeSdkPropsAndTargets: true);
12
+ var (buildFiles, projectTargetFrameworks) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(repoRootPath, projectPath);
13
+ var tfms = projectTargetFrameworks.Order().ToImmutableArray();
13
14
 
14
15
  // Get all the dependencies which are directly referenced from the project file or indirectly referenced from
15
16
  // targets and props files.
16
17
  var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles);
17
18
 
18
19
  var results = ImmutableArray.CreateBuilder<ProjectDiscoveryResult>();
19
- foreach (var buildFile in buildFiles)
20
+ if (tfms.Length > 0)
20
21
  {
21
- // Only include build files that exist beneath the RepoRootPath.
22
- if (buildFile.IsOutsideBasePath)
22
+ foreach (var buildFile in buildFiles)
23
23
  {
24
- continue;
25
- }
26
-
27
- // The build file dependencies have the correct DependencyType and the TopLevelDependencies have the evaluated version.
28
- // Combine them to have the set of dependencies that are directly referenced from the build file.
29
- var fileDependencies = BuildFile.GetDependencies(buildFile)
30
- .ToDictionary(d => d.Name, StringComparer.OrdinalIgnoreCase);
31
- var sdkDependencies = fileDependencies.Values
32
- .Where(d => d.Type == DependencyType.MSBuildSdk)
33
- .ToImmutableArray();
34
- var indirectDependencies = topLevelDependencies
35
- .Where(d => !fileDependencies.ContainsKey(d.Name))
36
- .ToImmutableArray();
37
- var directDependencies = topLevelDependencies
38
- .Where(d => fileDependencies.ContainsKey(d.Name))
39
- .Select(d =>
24
+ // Only include build files that exist beneath the RepoRootPath.
25
+ if (buildFile.IsOutsideBasePath)
40
26
  {
41
- var dependency = fileDependencies[d.Name];
42
- return d with
43
- {
44
- Type = dependency.Type,
45
- IsDirect = true
46
- };
47
- }).ToImmutableArray();
48
-
49
- if (buildFile.GetFileType() == ProjectBuildFileType.Project)
50
- {
51
- // Collect information that is specific to the project file.
52
- var tfms = MSBuildHelper.GetTargetFrameworkMonikers(buildFiles)
53
- .OrderBy(tfm => tfm)
54
- .ToImmutableArray();
55
- var properties = MSBuildHelper.GetProperties(buildFiles).Values
56
- .Where(p => !p.SourceFilePath.StartsWith(".."))
57
- .OrderBy(p => p.Name)
58
- .ToImmutableArray();
59
- var referencedProjectPaths = MSBuildHelper.GetProjectPathsFromProject(projectPath)
60
- .Select(path => Path.GetRelativePath(workspacePath, path))
61
- .OrderBy(p => p)
62
- .ToImmutableArray();
27
+ continue;
28
+ }
63
29
 
64
- // Get the complete set of dependencies including transitive dependencies.
65
- var dependencies = indirectDependencies.Concat(directDependencies).ToImmutableArray();
66
- dependencies = dependencies
67
- .Select(d => d with { TargetFrameworks = tfms })
30
+ // The build file dependencies have the correct DependencyType and the TopLevelDependencies have the evaluated version.
31
+ // Combine them to have the set of dependencies that are directly referenced from the build file.
32
+ var fileDependencies = BuildFile.GetDependencies(buildFile)
33
+ .ToDictionary(d => d.Name, StringComparer.OrdinalIgnoreCase);
34
+ var sdkDependencies = fileDependencies.Values
35
+ .Where(d => d.Type == DependencyType.MSBuildSdk)
68
36
  .ToImmutableArray();
69
- var transitiveDependencies = await GetTransitiveDependencies(repoRootPath, projectPath, tfms, dependencies, logger);
70
- ImmutableArray<Dependency> allDependencies = dependencies.Concat(transitiveDependencies).Concat(sdkDependencies)
71
- .OrderBy(d => d.Name)
37
+ var indirectDependencies = topLevelDependencies
38
+ .Where(d => !fileDependencies.ContainsKey(d.Name))
72
39
  .ToImmutableArray();
40
+ var directDependencies = topLevelDependencies
41
+ .Where(d => fileDependencies.ContainsKey(d.Name))
42
+ .Select(d =>
43
+ {
44
+ var dependency = fileDependencies[d.Name];
45
+ return d with
46
+ {
47
+ Type = dependency.Type,
48
+ IsDirect = true
49
+ };
50
+ }).ToImmutableArray();
73
51
 
74
- results.Add(new()
52
+ if (buildFile.GetFileType() == ProjectBuildFileType.Project)
75
53
  {
76
- FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
77
- Properties = properties,
78
- TargetFrameworks = tfms,
79
- ReferencedProjectPaths = referencedProjectPaths,
80
- Dependencies = allDependencies,
81
- });
82
- }
83
- else
84
- {
85
- results.Add(new()
86
- {
87
- FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
88
- Dependencies = directDependencies.Concat(sdkDependencies)
54
+ // Collect information that is specific to the project file.
55
+ var properties = MSBuildHelper.GetProperties(buildFiles).Values
56
+ .Where(p => !p.SourceFilePath.StartsWith(".."))
57
+ .OrderBy(p => p.Name)
58
+ .ToImmutableArray();
59
+ var referencedProjectPaths = MSBuildHelper.GetProjectPathsFromProject(projectPath)
60
+ .Select(path => Path.GetRelativePath(workspacePath, path))
61
+ .OrderBy(p => p)
62
+ .ToImmutableArray();
63
+
64
+ // Get the complete set of dependencies including transitive dependencies.
65
+ var dependencies = indirectDependencies.Concat(directDependencies).ToImmutableArray();
66
+ dependencies = dependencies
67
+ .Select(d => d with { TargetFrameworks = tfms })
68
+ .ToImmutableArray();
69
+ var transitiveDependencies = await GetTransitiveDependencies(repoRootPath, projectPath, tfms, dependencies, logger);
70
+ ImmutableArray<Dependency> allDependencies = dependencies.Concat(transitiveDependencies).Concat(sdkDependencies)
89
71
  .OrderBy(d => d.Name)
90
- .ToImmutableArray(),
91
- });
72
+ .ToImmutableArray();
73
+
74
+ results.Add(new()
75
+ {
76
+ FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
77
+ Properties = properties,
78
+ TargetFrameworks = tfms,
79
+ ReferencedProjectPaths = referencedProjectPaths,
80
+ Dependencies = allDependencies,
81
+ });
82
+ }
83
+ else
84
+ {
85
+ results.Add(new()
86
+ {
87
+ FilePath = Path.GetRelativePath(workspacePath, buildFile.Path),
88
+ Dependencies = directDependencies.Concat(sdkDependencies)
89
+ .OrderBy(d => d.Name)
90
+ .ToImmutableArray(),
91
+ });
92
+ }
92
93
  }
93
94
  }
94
95
 
@@ -20,8 +20,7 @@ internal static class SdkPackageUpdater
20
20
  // SDK-style project, modify the XML directly
21
21
  logger.Log(" Running for SDK-style project");
22
22
 
23
- var buildFiles = await MSBuildHelper.LoadBuildFilesAsync(repoRootPath, projectPath);
24
- var tfms = MSBuildHelper.GetTargetFrameworkMonikers(buildFiles);
23
+ (ImmutableArray<ProjectBuildFile> buildFiles, string[] tfms) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(repoRootPath, projectPath);
25
24
 
26
25
  // Get the set of all top-level dependencies in the current project
27
26
  var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
@@ -42,7 +41,7 @@ internal static class SdkPackageUpdater
42
41
  return;
43
42
  }
44
43
 
45
- UpdateTopLevelDepdendency(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, peerDependencies, logger);
44
+ await UpdateTopLevelDepdendency(repoRootPath, buildFiles, tfms, dependencyName, previousDependencyVersion, newDependencyVersion, peerDependencies, logger);
46
45
  }
47
46
 
48
47
  if (!await AreDependenciesCoherentAsync(repoRootPath, projectPath, dependencyName, logger, buildFiles, tfms))
@@ -226,10 +225,10 @@ internal static class SdkPackageUpdater
226
225
  logger.Log($" Adding [{dependencyName}/{newDependencyVersion}] as a top-level package reference.");
227
226
 
228
227
  // see https://learn.microsoft.com/nuget/consume-packages/install-use-packages-dotnet-cli
229
- var (exitCode, _, _) = await ProcessEx.RunAsync("dotnet", $"add {projectPath} package {dependencyName} --version {newDependencyVersion}", workingDirectory: Path.GetDirectoryName(projectPath));
228
+ var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"add {projectPath} package {dependencyName} --version {newDependencyVersion}", workingDirectory: Path.GetDirectoryName(projectPath));
230
229
  if (exitCode != 0)
231
230
  {
232
- logger.Log($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.");
231
+ logger.Log($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
233
232
  }
234
233
  }
235
234
 
@@ -288,8 +287,10 @@ internal static class SdkPackageUpdater
288
287
  return packagesAndVersions;
289
288
  }
290
289
 
291
- private static void UpdateTopLevelDepdendency(
290
+ private static async Task UpdateTopLevelDepdendency(
291
+ string repoRootPath,
292
292
  ImmutableArray<ProjectBuildFile> buildFiles,
293
+ string[] targetFrameworks,
293
294
  string dependencyName,
294
295
  string previousDependencyVersion,
295
296
  string newDependencyVersion,
@@ -307,6 +308,43 @@ internal static class SdkPackageUpdater
307
308
  {
308
309
  TryUpdateDependencyVersion(buildFiles, packageName, previousDependencyVersion: null, newDependencyVersion: packageVersion, logger);
309
310
  }
311
+
312
+ // now make all dependency requirements coherent
313
+ Dependency[] updatedTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray();
314
+ foreach (ProjectBuildFile projectFile in buildFiles)
315
+ {
316
+ foreach (string tfm in targetFrameworks)
317
+ {
318
+ Dependency[]? resolvedDependencies = await MSBuildHelper.ResolveDependencyConflicts(repoRootPath, projectFile.Path, tfm, updatedTopLevelDependencies, logger);
319
+ if (resolvedDependencies is null)
320
+ {
321
+ logger.Log($" Unable to resolve dependency conflicts for {projectFile.Path}.");
322
+ continue;
323
+ }
324
+
325
+ // ensure the originally requested dependency was resolved to the correct version
326
+ var specificResolvedDependency = resolvedDependencies.Where(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
327
+ if (specificResolvedDependency is null)
328
+ {
329
+ logger.Log($" Unable resolve requested dependency for {dependencyName} in {projectFile.Path}.");
330
+ continue;
331
+ }
332
+
333
+ if (!newDependencyVersion.Equals(specificResolvedDependency.Version, StringComparison.OrdinalIgnoreCase))
334
+ {
335
+ logger.Log($" Inconsistent resolution for {dependencyName}; attempted upgrade to {newDependencyVersion} but resolved {specificResolvedDependency.Version}.");
336
+ continue;
337
+ }
338
+
339
+ // update all other dependencies
340
+ foreach (Dependency resolvedDependency in resolvedDependencies
341
+ .Where(d => !d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
342
+ .Where(d => d.Version is not null))
343
+ {
344
+ TryUpdateDependencyVersion(buildFiles, resolvedDependency.Name, previousDependencyVersion: null, newDependencyVersion: resolvedDependency.Version!, logger);
345
+ }
346
+ }
347
+ }
310
348
  }
311
349
 
312
350
  private static UpdateResult TryUpdateDependencyVersion(
@@ -12,7 +12,7 @@ public class UpdaterWorker
12
12
 
13
13
  public async Task RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
14
14
  {
15
- MSBuildHelper.RegisterMSBuild();
15
+ MSBuildHelper.RegisterMSBuild(Environment.CurrentDirectory, repoRootPath);
16
16
 
17
17
  if (!Path.IsPathRooted(workspacePath) || !File.Exists(workspacePath))
18
18
  {
@@ -2,6 +2,7 @@ using System.Collections.Immutable;
2
2
  using System.Diagnostics.CodeAnalysis;
3
3
  using System.Text;
4
4
  using System.Text.Json;
5
+ using System.Text.Json.Nodes;
5
6
  using System.Text.RegularExpressions;
6
7
  using System.Xml;
7
8
 
@@ -13,6 +14,7 @@ using Microsoft.Build.Locator;
13
14
  using Microsoft.Extensions.FileSystemGlobbing;
14
15
 
15
16
  using NuGet.Configuration;
17
+ using NuGet.Versioning;
16
18
 
17
19
  using NuGetUpdater.Core.Utilities;
18
20
 
@@ -24,106 +26,34 @@ internal static partial class MSBuildHelper
24
26
 
25
27
  public static bool IsMSBuildRegistered => MSBuildPath.Length > 0;
26
28
 
27
- static MSBuildHelper()
28
- {
29
- RegisterMSBuild();
30
- }
31
-
32
- public static void RegisterMSBuild()
29
+ public static void RegisterMSBuild(string currentDirectory, string rootDirectory)
33
30
  {
34
31
  // Ensure MSBuild types are registered before calling a method that loads the types
35
32
  if (!IsMSBuildRegistered)
36
33
  {
37
- var globalJsonPath = "global.json";
38
- var tempGlobalJsonPath = globalJsonPath + Guid.NewGuid().ToString();
39
- var globalJsonExists = File.Exists(globalJsonPath);
40
- try
34
+ var candidateDirectories = PathHelper.GetAllDirectoriesToRoot(currentDirectory, rootDirectory);
35
+ var globalJsonPaths = candidateDirectories.Select(d => Path.Combine(d, "global.json")).Where(File.Exists).Select(p => (p, p + Guid.NewGuid().ToString())).ToArray();
36
+ foreach (var (globalJsonPath, tempGlobalJsonPath) in globalJsonPaths)
41
37
  {
42
- if (globalJsonExists)
43
- {
44
- Console.WriteLine("Temporarily removing `global.json` for MSBuild detection.");
45
- File.Move(globalJsonPath, tempGlobalJsonPath);
46
- }
38
+ Console.WriteLine($"Temporarily removing `global.json` from `{Path.GetDirectoryName(globalJsonPath)}` for MSBuild detection.");
39
+ File.Move(globalJsonPath, tempGlobalJsonPath);
40
+ }
47
41
 
42
+ try
43
+ {
48
44
  var defaultInstance = MSBuildLocator.QueryVisualStudioInstances().First();
49
45
  MSBuildPath = defaultInstance.MSBuildPath;
50
46
  MSBuildLocator.RegisterInstance(defaultInstance);
51
47
  }
52
48
  finally
53
49
  {
54
- if (globalJsonExists)
55
- {
56
- Console.WriteLine("Restoring `global.json` after MSBuild detection.");
57
- File.Move(tempGlobalJsonPath, globalJsonPath);
58
- }
59
- }
60
- }
61
- }
62
-
63
- public static string[] GetTargetFrameworkMonikers(ImmutableArray<ProjectBuildFile> buildFiles)
64
- {
65
- HashSet<string> targetFrameworkValues = new(StringComparer.OrdinalIgnoreCase);
66
- Dictionary<string, Property> propertyInfo = new(StringComparer.OrdinalIgnoreCase);
67
-
68
- foreach (var buildFile in buildFiles)
69
- {
70
- var projectRoot = CreateProjectRootElement(buildFile);
71
-
72
- foreach (var property in projectRoot.Properties)
73
- {
74
- if (property.Name.Equals("TargetFramework", StringComparison.OrdinalIgnoreCase) ||
75
- property.Name.Equals("TargetFrameworks", StringComparison.OrdinalIgnoreCase))
76
- {
77
- if (buildFile.IsOutsideBasePath)
78
- {
79
- continue;
80
- }
81
-
82
- foreach (var tfm in property.Value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
83
- {
84
- targetFrameworkValues.Add(tfm);
85
- }
86
- }
87
- else if (property.Name.Equals("TargetFrameworkVersion", StringComparison.OrdinalIgnoreCase))
50
+ foreach (var (globalJsonpath, tempGlobalJsonPath) in globalJsonPaths)
88
51
  {
89
- if (buildFile.IsOutsideBasePath)
90
- {
91
- continue;
92
- }
93
-
94
- // For packages.config projects that use TargetFrameworkVersion, we need to convert it to TargetFramework
95
- targetFrameworkValues.Add($"net{property.Value.TrimStart('v').Replace(".", "")}");
52
+ Console.WriteLine($"Restoring `global.json` to `{Path.GetDirectoryName(globalJsonpath)}` after MSBuild discovery.");
53
+ File.Move(tempGlobalJsonPath, globalJsonpath);
96
54
  }
97
- else
98
- {
99
- propertyInfo[property.Name] = new(property.Name, property.Value, buildFile.RelativePath);
100
- }
101
- }
102
- }
103
-
104
- HashSet<string> targetFrameworks = new(StringComparer.OrdinalIgnoreCase);
105
-
106
- foreach (var targetFrameworkValue in targetFrameworkValues)
107
- {
108
- var (resultType, _, tfms, _, errorMessage) =
109
- GetEvaluatedValue(targetFrameworkValue, propertyInfo, propertiesToIgnore: ["TargetFramework", "TargetFrameworks"]);
110
- if (resultType != EvaluationResultType.Success)
111
- {
112
- continue;
113
- }
114
-
115
- if (string.IsNullOrEmpty(tfms))
116
- {
117
- continue;
118
- }
119
-
120
- foreach (var tfm in tfms.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
121
- {
122
- targetFrameworks.Add(tfm);
123
55
  }
124
56
  }
125
-
126
- return targetFrameworks.ToArray();
127
57
  }
128
58
 
129
59
  public static IEnumerable<string> GetProjectPathsFromSolution(string solutionPath)
@@ -379,6 +309,124 @@ internal static partial class MSBuildHelper
379
309
  }
380
310
  }
381
311
 
312
+ internal static async Task<Dependency[]?> ResolveDependencyConflicts(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Logger logger)
313
+ {
314
+ var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
315
+ try
316
+ {
317
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
318
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"", workingDirectory: tempDirectory.FullName);
319
+
320
+ // simple cases first
321
+ // if restore failed, nothing we can do
322
+ if (exitCode != 0)
323
+ {
324
+ return null;
325
+ }
326
+
327
+ // if no problems found, just return the current set
328
+ if (!stdOut.Contains("NU1608"))
329
+ {
330
+ return packages;
331
+ }
332
+
333
+ // now it gets complicated; look for the packages with issues
334
+ MatchCollection matches = PackageIncompatibilityWarningPattern().Matches(stdOut);
335
+ (string, NuGetVersion)[] badPackagesAndVersions = matches.Select(m => (m.Groups["PackageName"].Value, NuGetVersion.Parse(m.Groups["PackageVersion"].Value))).ToArray();
336
+ Dictionary<string, HashSet<NuGetVersion>> badPackagesAndCandidateVersionsDictionary = new(StringComparer.OrdinalIgnoreCase);
337
+
338
+ // and for each of those packages, find all versions greater than the one that's currently installed
339
+ foreach ((string packageName, NuGetVersion packageVersion) in badPackagesAndVersions)
340
+ {
341
+ // this command dumps a JSON object with all versions of the specified package from all package sources
342
+ (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"package search {packageName} --exact-match --format json", workingDirectory: tempDirectory.FullName);
343
+ if (exitCode != 0)
344
+ {
345
+ continue;
346
+ }
347
+
348
+ // ensure collection exists
349
+ if (!badPackagesAndCandidateVersionsDictionary.ContainsKey(packageName))
350
+ {
351
+ badPackagesAndCandidateVersionsDictionary.Add(packageName, new HashSet<NuGetVersion>());
352
+ }
353
+
354
+ HashSet<NuGetVersion> foundVersions = badPackagesAndCandidateVersionsDictionary[packageName];
355
+
356
+ var json = JsonHelper.ParseNode(stdOut);
357
+ if (json?["searchResult"] is JsonArray searchResults)
358
+ {
359
+ foreach (var searchResult in searchResults)
360
+ {
361
+ if (searchResult?["packages"] is JsonArray packagesArray)
362
+ {
363
+ foreach (var package in packagesArray)
364
+ {
365
+ // in 8.0.xxx SDKs, the package version is in the `latestVersion` property, but in 9.0.xxx, it's `version`
366
+ var packageVersionProperty = package?["version"] ?? package?["latestVersion"];
367
+ if (packageVersionProperty is JsonValue latestVersion &&
368
+ latestVersion.GetValueKind() == JsonValueKind.String &&
369
+ NuGetVersion.TryParse(latestVersion.ToString(), out var nugetVersion) &&
370
+ nugetVersion > packageVersion)
371
+ {
372
+ foundVersions.Add(nugetVersion);
373
+ }
374
+ }
375
+ }
376
+ }
377
+ }
378
+ }
379
+
380
+ // generate all possible combinations
381
+ (string Key, NuGetVersion v)[][] expandedLists = badPackagesAndCandidateVersionsDictionary.Select(kvp => kvp.Value.Order().Select(v => (kvp.Key, v)).ToArray()).ToArray();
382
+ IEnumerable<(string PackageName, NuGetVersion PackageVersion)>[] product = expandedLists.CartesianProduct().ToArray();
383
+
384
+ // FUTURE WORK: pre-filter individual known package incompatibilities to reduce the number of combinations, e.g., if Package.A v1.0.0
385
+ // is incompatible with Package.B v2.0.0, then remove _all_ combinations with that pair
386
+
387
+ // this is the slow part
388
+ foreach (IEnumerable<(string PackageName, NuGetVersion PackageVersion)> candidateSet in product)
389
+ {
390
+ // rebuild candidate dependency list with the relevant versions
391
+ Dictionary<string, NuGetVersion> packageVersions = candidateSet.ToDictionary(candidateSet => candidateSet.PackageName, candidateSet => candidateSet.PackageVersion);
392
+ Dependency[] candidatePackages = packages.Select(p =>
393
+ {
394
+ if (packageVersions.TryGetValue(p.Name, out var version))
395
+ {
396
+ // create a new dependency with the updated version
397
+ return new Dependency(p.Name, version.ToString(), p.Type, IsDevDependency: p.IsDevDependency, IsOverride: p.IsOverride, IsUpdate: p.IsUpdate);
398
+ }
399
+
400
+ // not the dependency we're looking for, use whatever it already was in this set
401
+ return p;
402
+ }).ToArray();
403
+
404
+ if (await DependenciesAreCoherentAsync(repoRoot, projectPath, targetFramework, candidatePackages, logger))
405
+ {
406
+ // return as soon as we find a coherent set
407
+ return candidatePackages;
408
+ }
409
+ }
410
+
411
+ // no package resolution set found
412
+ return null;
413
+ }
414
+ finally
415
+ {
416
+ tempDirectory.Delete(recursive: true);
417
+ }
418
+ }
419
+
420
+ // fully expand all possible combinations using the algorithm from here:
421
+ // https://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
422
+ private static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
423
+ {
424
+ IEnumerable<IEnumerable<T>> emptyProduct = [[]];
425
+ return sequences.Aggregate(emptyProduct, (accumulator, sequence) => from accseq in accumulator
426
+ from item in sequence
427
+ select accseq.Concat([item]));
428
+ }
429
+
382
430
  private static ProjectRootElement CreateProjectRootElement(ProjectBuildFile buildFile)
383
431
  {
384
432
  var xmlString = buildFile.Contents.ToFullString();
@@ -569,7 +617,7 @@ internal static partial class MSBuildHelper
569
617
  return directoryPackagesPropsPath is not null;
570
618
  }
571
619
 
572
- internal static async Task<ImmutableArray<ProjectBuildFile>> LoadBuildFilesAsync(string repoRootPath, string projectPath, bool includeSdkPropsAndTargets = false)
620
+ internal static async Task<(ImmutableArray<ProjectBuildFile> ProjectBuildFiles, string[] TargetFrameworks)> LoadBuildFilesAndTargetFrameworksAsync(string repoRootPath, string projectPath)
573
621
  {
574
622
  var buildFileList = new List<string>
575
623
  {
@@ -579,6 +627,7 @@ internal static partial class MSBuildHelper
579
627
  // a global.json file might cause problems with the dotnet msbuild command; create a safe version temporarily
580
628
  TryGetGlobalJsonPath(repoRootPath, projectPath, out var globalJsonPath);
581
629
  var safeGlobalJsonName = $"{globalJsonPath}{Guid.NewGuid()}";
630
+ HashSet<string> targetFrameworks = new(StringComparer.OrdinalIgnoreCase);
582
631
 
583
632
  try
584
633
  {
@@ -607,16 +656,51 @@ internal static partial class MSBuildHelper
607
656
  // load the project even if it imports a file that doesn't exist (e.g. a file that's generated at restore
608
657
  // or build time).
609
658
  using var projectCollection = new ProjectCollection(); // do this in a one-off instance and don't pollute the global collection
610
- var project = Project.FromFile(projectPath, new ProjectOptions
659
+ Project project = Project.FromFile(projectPath, new ProjectOptions
611
660
  {
612
661
  LoadSettings = ProjectLoadSettings.IgnoreMissingImports,
613
662
  ProjectCollection = projectCollection,
614
663
  });
615
664
  buildFileList.AddRange(project.Imports.Select(i => i.ImportedProject.FullPath.NormalizePathToUnix()));
665
+
666
+ // use the MSBuild-evaluated value so we don't have to try to manually parse XML
667
+ IEnumerable<ProjectProperty> targetFrameworkProperties = project.Properties.Where(p => p.Name.Equals("TargetFramework", StringComparison.OrdinalIgnoreCase)).ToList();
668
+ IEnumerable<ProjectProperty> targetFrameworksProperties = project.Properties.Where(p => p.Name.Equals("TargetFrameworks", StringComparison.OrdinalIgnoreCase)).ToList();
669
+ IEnumerable<ProjectProperty> targetFrameworkVersionProperties = project.Properties.Where(p => p.Name.Equals("TargetFrameworkVersion", StringComparison.OrdinalIgnoreCase)).ToList();
670
+ foreach (ProjectProperty tfm in targetFrameworkProperties)
671
+ {
672
+ if (!string.IsNullOrWhiteSpace(tfm.EvaluatedValue))
673
+ {
674
+ targetFrameworks.Add(tfm.EvaluatedValue);
675
+ }
676
+ }
677
+
678
+ foreach (ProjectProperty tfms in targetFrameworksProperties)
679
+ {
680
+ foreach (string tfmValue in tfms.EvaluatedValue.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
681
+ {
682
+ targetFrameworks.Add(tfmValue);
683
+ }
684
+ }
685
+
686
+ if (targetFrameworks.Count == 0)
687
+ {
688
+ // Only try this if we haven't been able to resolve anything yet. This is because deep in the SDK, a
689
+ // `TargetFramework` of `netstandard2.0` (eventually) gets turned into `v2.0` and we don't want to
690
+ // interpret that as a .NET Framework 2.0 project.
691
+ foreach (ProjectProperty tfvm in targetFrameworkVersionProperties)
692
+ {
693
+ // `v0.0` is an error case where no TFM could be evaluated
694
+ if (tfvm.EvaluatedValue != "v0.0")
695
+ {
696
+ targetFrameworks.Add($"net{tfvm.EvaluatedValue.TrimStart('v').Replace(".", "")}");
697
+ }
698
+ }
699
+ }
616
700
  }
617
701
  catch (InvalidProjectFileException)
618
702
  {
619
- return [];
703
+ return ([], []);
620
704
  }
621
705
  finally
622
706
  {
@@ -627,18 +711,22 @@ internal static partial class MSBuildHelper
627
711
  }
628
712
 
629
713
  var repoRootPathPrefix = repoRootPath.NormalizePathToUnix() + "/";
630
- var buildFiles = includeSdkPropsAndTargets
631
- ? buildFileList.Distinct()
632
- : buildFileList
633
- .Where(f => f.StartsWith(repoRootPathPrefix, StringComparison.OrdinalIgnoreCase))
634
- .Distinct();
714
+ var buildFiles = buildFileList
715
+ .Where(f => f.StartsWith(repoRootPathPrefix, StringComparison.OrdinalIgnoreCase))
716
+ .Distinct();
635
717
  var result = buildFiles
636
718
  .Where(File.Exists)
637
719
  .Select(path => ProjectBuildFile.Open(repoRootPath, path))
638
720
  .ToImmutableArray();
639
- return result;
721
+ return (result, targetFrameworks.ToArray());
640
722
  }
641
723
 
642
724
  [GeneratedRegex("^\\s*NuGetData::Package=(?<PackageName>[^,]+), Version=(?<PackageVersion>.+)$")]
643
725
  private static partial Regex PackagePattern();
726
+
727
+ // Example output:
728
+ // NU1608: Detected package version outside of dependency constraint: SpecFlow.Tools.MsBuild.Generation 3.3.30 requires SpecFlow(= 3.3.30) but version SpecFlow 3.9.74 was resolved.
729
+ // PackageName-|+++++++++++++++++++++++++++++++| |++++|-PackageVersion
730
+ [GeneratedRegex("NU1608: [^:]+: (?<PackageName>[^ ]+) (?<PackageVersion>[^ ]+)")]
731
+ private static partial Regex PackageIncompatibilityWarningPattern();
644
732
  }
@@ -34,20 +34,11 @@ internal static class PathHelper
34
34
  public static string GetFullPathFromRelative(string rootPath, string relativePath)
35
35
  => Path.GetFullPath(JoinPath(rootPath, relativePath.NormalizePathToUnix()));
36
36
 
37
- /// <summary>
38
- /// Check in every directory from <paramref name="initialPath"/> up to <paramref name="rootPath"/> for the file specified in <paramref name="fileName"/>.
39
- /// </summary>
40
- /// <returns>The path of the found file or null.</returns>
41
- public static string? GetFileInDirectoryOrParent(string initialPath, string rootPath, string fileName, bool caseSensitive = true)
37
+ public static string[] GetAllDirectoriesToRoot(string initialDirectoryPath, string rootDirectoryPath)
42
38
  {
43
- if (File.Exists(initialPath))
44
- {
45
- initialPath = Path.GetDirectoryName(initialPath)!;
46
- }
47
-
48
39
  var candidatePaths = new List<string>();
49
- var rootDirectory = new DirectoryInfo(rootPath);
50
- var candidateDirectory = new DirectoryInfo(initialPath);
40
+ var rootDirectory = new DirectoryInfo(rootDirectoryPath);
41
+ var candidateDirectory = new DirectoryInfo(initialDirectoryPath);
51
42
  while (candidateDirectory.FullName != rootDirectory.FullName)
52
43
  {
53
44
  candidatePaths.Add(candidateDirectory.FullName);
@@ -58,8 +49,22 @@ internal static class PathHelper
58
49
  }
59
50
  }
60
51
 
61
- candidatePaths.Add(rootPath);
52
+ candidatePaths.Add(rootDirectoryPath);
53
+ return candidatePaths.ToArray();
54
+ }
55
+
56
+ /// <summary>
57
+ /// Check in every directory from <paramref name="initialPath"/> up to <paramref name="rootPath"/> for the file specified in <paramref name="fileName"/>.
58
+ /// </summary>
59
+ /// <returns>The path of the found file or null.</returns>
60
+ public static string? GetFileInDirectoryOrParent(string initialPath, string rootPath, string fileName, bool caseSensitive = true)
61
+ {
62
+ if (File.Exists(initialPath))
63
+ {
64
+ initialPath = Path.GetDirectoryName(initialPath)!;
65
+ }
62
66
 
67
+ var candidatePaths = GetAllDirectoriesToRoot(initialPath, rootPath);
63
68
  foreach (var candidatePath in candidatePaths)
64
69
  {
65
70
  try
@@ -1,4 +1,5 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Diagnostics.CodeAnalysis;
2
3
  using System.Text.Json;
3
4
 
4
5
  using NuGetUpdater.Core.Discover;
@@ -29,7 +30,7 @@ public class DiscoveryWorkerTestBase
29
30
  protected static void ValidateWorkspaceResult(ExpectedWorkspaceDiscoveryResult expectedResult, WorkspaceDiscoveryResult actualResult)
30
31
  {
31
32
  Assert.NotNull(actualResult);
32
- Assert.Equal(expectedResult.FilePath, actualResult.FilePath);
33
+ Assert.Equal(expectedResult.FilePath.NormalizePathToUnix(), actualResult.FilePath.NormalizePathToUnix());
33
34
  ValidateDirectoryPackagesProps(expectedResult.DirectoryPackagesProps, actualResult.DirectoryPackagesProps);
34
35
  ValidateResultWithDependencies(expectedResult.GlobalJson, actualResult.GlobalJson);
35
36
  ValidateResultWithDependencies(expectedResult.DotNetToolsJson, actualResult.DotNetToolsJson);
@@ -50,7 +51,7 @@ public class DiscoveryWorkerTestBase
50
51
  Assert.NotNull(actualResult);
51
52
  }
52
53
 
53
- Assert.Equal(expectedResult.FilePath, actualResult.FilePath);
54
+ Assert.Equal(expectedResult.FilePath.NormalizePathToUnix(), actualResult.FilePath.NormalizePathToUnix());
54
55
  ValidateDependencies(expectedResult.Dependencies, actualResult.Dependencies);
55
56
  Assert.Equal(expectedResult.ExpectedDependencyCount ?? expectedResult.Dependencies.Length, actualResult.Dependencies.Length);
56
57
  }
@@ -64,12 +65,12 @@ public class DiscoveryWorkerTestBase
64
65
 
65
66
  foreach (var expectedProject in expectedProjects)
66
67
  {
67
- var actualProject = actualProjects.Single(p => p.FilePath == expectedProject.FilePath);
68
+ var actualProject = actualProjects.Single(p => p.FilePath.NormalizePathToUnix() == expectedProject.FilePath.NormalizePathToUnix());
68
69
 
69
- Assert.Equal(expectedProject.FilePath, actualProject.FilePath);
70
- AssertEx.Equal(expectedProject.Properties, actualProject.Properties);
70
+ Assert.Equal(expectedProject.FilePath.NormalizePathToUnix(), actualProject.FilePath.NormalizePathToUnix());
71
+ AssertEx.Equal(expectedProject.Properties, actualProject.Properties, PropertyComparer.Instance);
71
72
  AssertEx.Equal(expectedProject.TargetFrameworks, actualProject.TargetFrameworks);
72
- AssertEx.Equal(expectedProject.ReferencedProjectPaths, actualProject.ReferencedProjectPaths);
73
+ AssertEx.Equal(expectedProject.ReferencedProjectPaths.Select(PathHelper.NormalizePathToUnix), actualProject.ReferencedProjectPaths.Select(PathHelper.NormalizePathToUnix));
73
74
  ValidateDependencies(expectedProject.Dependencies, actualProject.Dependencies);
74
75
  Assert.Equal(expectedProject.ExpectedDependencyCount ?? expectedProject.Dependencies.Length, actualProject.Dependencies.Length);
75
76
  }
@@ -114,4 +115,21 @@ public class DiscoveryWorkerTestBase
114
115
  var resultJson = await File.ReadAllTextAsync(resultPath);
115
116
  return JsonSerializer.Deserialize<WorkspaceDiscoveryResult>(resultJson, DiscoveryWorker.SerializerOptions)!;
116
117
  }
118
+
119
+ internal class PropertyComparer : IEqualityComparer<Property>
120
+ {
121
+ public static PropertyComparer Instance { get; } = new();
122
+
123
+ public bool Equals(Property? x, Property? y)
124
+ {
125
+ return x?.Name == y?.Name &&
126
+ x?.Value == y?.Value &&
127
+ x?.SourceFilePath.NormalizePathToUnix() == y?.SourceFilePath.NormalizePathToUnix();
128
+ }
129
+
130
+ public int GetHashCode([DisallowNull] Property obj)
131
+ {
132
+ throw new NotImplementedException();
133
+ }
134
+ }
117
135
  }
@@ -25,11 +25,14 @@ public partial class DiscoveryWorkerTests
25
25
  <package id="NuGet.Core" version="2.11.1" targetFramework="net46" />
26
26
  <package id="NuGet.Server" version="2.11.2" targetFramework="net46" />
27
27
  <package id="RouteMagic" version="1.3" targetFramework="net46" />
28
- <package id="WebActivatorEx" version="2.1.0" targetFramework="net46"></package>
28
+ <package id="WebActivatorEx" version="2.1.0" targetFramework="net46" />
29
29
  </packages>
30
30
  """),
31
31
  ("myproj.csproj", """
32
32
  <Project>
33
+ <PropertyGroup>
34
+ <TargetFramework>net46</TargetFramework>
35
+ </PropertyGroup>
33
36
  </Project>
34
37
  """)
35
38
  ],
@@ -40,16 +43,21 @@ public partial class DiscoveryWorkerTests
40
43
  new()
41
44
  {
42
45
  FilePath = "myproj.csproj",
46
+ Properties = [
47
+ new("TargetFramework", "net46", "myproj.csproj"),
48
+ ],
49
+ TargetFrameworks = ["net46"],
43
50
  Dependencies = [
44
- new("Microsoft.CodeDom.Providers.DotNetCompilerPlatform", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: []),
45
- new("Microsoft.Net.Compilers", "1.0.1", DependencyType.PackagesConfig, TargetFrameworks: []),
46
- new("Microsoft.Web.Infrastructure", "1.0.0.0", DependencyType.PackagesConfig, TargetFrameworks: []),
47
- new("Microsoft.Web.Xdt", "2.1.1", DependencyType.PackagesConfig, TargetFrameworks: []),
48
- new("Newtonsoft.Json", "8.0.3", DependencyType.PackagesConfig, TargetFrameworks: []),
49
- new("NuGet.Core", "2.11.1", DependencyType.PackagesConfig, TargetFrameworks: []),
50
- new("NuGet.Server", "2.11.2", DependencyType.PackagesConfig, TargetFrameworks: []),
51
- new("RouteMagic", "1.3", DependencyType.PackagesConfig, TargetFrameworks: []),
52
- new("WebActivatorEx", "2.1.0", DependencyType.PackagesConfig, TargetFrameworks: []),
51
+ new("Microsoft.NETFramework.ReferenceAssemblies", "1.0.3", DependencyType.Unknown, TargetFrameworks: ["net46"], IsTransitive: true),
52
+ new("Microsoft.CodeDom.Providers.DotNetCompilerPlatform", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
53
+ new("Microsoft.Net.Compilers", "1.0.1", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
54
+ new("Microsoft.Web.Infrastructure", "1.0.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
55
+ new("Microsoft.Web.Xdt", "2.1.1", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
56
+ new("Newtonsoft.Json", "8.0.3", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
57
+ new("NuGet.Core", "2.11.1", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
58
+ new("NuGet.Server", "2.11.2", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
59
+ new("RouteMagic", "1.3", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
60
+ new("WebActivatorEx", "2.1.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
53
61
  ],
54
62
  }
55
63
  ],
@@ -376,5 +376,30 @@ public partial class DiscoveryWorkerTests
376
376
  ],
377
377
  });
378
378
  }
379
+
380
+ [Fact]
381
+
382
+ public async Task NoDependenciesReturnedIfNoTargetFrameworkCanBeResolved()
383
+ {
384
+ await TestDiscoveryAsync(
385
+ workspacePath: "",
386
+ files: [
387
+ ("myproj.csproj", """
388
+ <Project Sdk="Microsoft.NET.Sdk">
389
+ <PropertyGroup>
390
+ <TargetFramework>$(SomeCommonTfmThatCannotBeResolved)</TargetFramework>
391
+ </PropertyGroup>
392
+ <ItemGroup>
393
+ <PackageReference Include="Some.Package" Version="1.2.3" />
394
+ </ItemGroup>
395
+ </Project>
396
+ """)
397
+ ],
398
+ expectedResult: new()
399
+ {
400
+ FilePath = "",
401
+ Projects = []
402
+ });
403
+ }
379
404
  }
380
405
  }
@@ -0,0 +1,10 @@
1
+ namespace NuGetUpdater.Core.Test
2
+ {
3
+ public abstract class TestBase
4
+ {
5
+ protected TestBase()
6
+ {
7
+ MSBuildHelper.RegisterMSBuild(Environment.CurrentDirectory, Environment.CurrentDirectory);
8
+ }
9
+ }
10
+ }
@@ -1,16 +1,9 @@
1
- using System.Collections.Generic;
2
-
3
1
  using Xunit;
4
2
 
5
3
  namespace NuGetUpdater.Core.Test.Update;
6
4
 
7
- public class PackagesConfigUpdaterTests
5
+ public class PackagesConfigUpdaterTests : TestBase
8
6
  {
9
- public PackagesConfigUpdaterTests()
10
- {
11
- MSBuildHelper.RegisterMSBuild();
12
- }
13
-
14
7
  [Theory]
15
8
  [MemberData(nameof(PackagesDirectoryPathTestData))]
16
9
  public void PathToPackagesDirectoryCanBeDetermined(string projectContents, string dependencyName, string dependencyVersion, string expectedPackagesDirectoryPath)
@@ -10,7 +10,7 @@ namespace NuGetUpdater.Core.Test.Update;
10
10
  using TestFile = (string Path, string Content);
11
11
  using TestProject = (string Path, string Content, Guid ProjectId);
12
12
 
13
- public abstract class UpdateWorkerTestBase
13
+ public abstract class UpdateWorkerTestBase : TestBase
14
14
  {
15
15
  protected static Task TestNoChange(
16
16
  string dependencyName,
@@ -10,11 +10,6 @@ public partial class UpdateWorkerTests
10
10
  {
11
11
  public class DirsProj : UpdateWorkerTestBase
12
12
  {
13
- public DirsProj()
14
- {
15
- MSBuildHelper.RegisterMSBuild();
16
- }
17
-
18
13
  [Fact]
19
14
  public async Task UpdateSingleDependencyInDirsProj()
20
15
  {
@@ -8,11 +8,6 @@ public partial class UpdateWorkerTests
8
8
  {
9
9
  public class DotNetTools : UpdateWorkerTestBase
10
10
  {
11
- public DotNetTools()
12
- {
13
- MSBuildHelper.RegisterMSBuild();
14
- }
15
-
16
11
  [Fact]
17
12
  public async Task NoChangeWhenDotNetToolsJsonNotFound()
18
13
  {
@@ -8,11 +8,6 @@ public partial class UpdateWorkerTests
8
8
  {
9
9
  public class GlobalJson : UpdateWorkerTestBase
10
10
  {
11
- public GlobalJson()
12
- {
13
- MSBuildHelper.RegisterMSBuild();
14
- }
15
-
16
11
  [Fact]
17
12
  public async Task NoChangeWhenGlobalJsonNotFound()
18
13
  {
@@ -8,11 +8,6 @@ public partial class UpdateWorkerTests
8
8
  {
9
9
  public class Mixed : UpdateWorkerTestBase
10
10
  {
11
- public Mixed()
12
- {
13
- MSBuildHelper.RegisterMSBuild();
14
- }
15
-
16
11
  [Fact]
17
12
  public async Task ForPackagesProject_UpdatePackageReference_InBuildProps()
18
13
  {
@@ -9,11 +9,6 @@ public partial class UpdateWorkerTests
9
9
  {
10
10
  public class PackagesConfig : UpdateWorkerTestBase
11
11
  {
12
- public PackagesConfig()
13
- {
14
- MSBuildHelper.RegisterMSBuild();
15
- }
16
-
17
12
  [Fact]
18
13
  public async Task UpdateSingleDependencyInPackagesConfig()
19
14
  {
@@ -8,11 +8,6 @@ public partial class UpdateWorkerTests
8
8
  {
9
9
  public class Sdk : UpdateWorkerTestBase
10
10
  {
11
- public Sdk()
12
- {
13
- MSBuildHelper.RegisterMSBuild();
14
- }
15
-
16
11
  [Theory]
17
12
  [InlineData("net472")]
18
13
  [InlineData("netstandard2.0")]
@@ -175,24 +170,6 @@ public partial class UpdateWorkerTests
175
170
  ]);
176
171
  }
177
172
 
178
- [Fact]
179
- public async Task NoChange_WhenPackageHasVersionConstraint()
180
- {
181
- // Dependency package has version constraint
182
- await TestNoChangeforProject("AWSSDK.Core", "3.3.21.19", "3.7.300.20",
183
- projectContents: $"""
184
- <Project Sdk="Microsoft.NET.Sdk">
185
- <PropertyGroup>
186
- <TargetFramework>netstandard2.0</TargetFramework>
187
- </PropertyGroup>
188
- <ItemGroup>
189
- <PackageReference Include="AWSSDK.S3" Version="3.3.17.3" />
190
- <PackageReference Include="AWSSDK.Core" Version="3.3.21.19" />
191
- </ItemGroup>
192
- </Project>
193
- """);
194
- }
195
-
196
173
  [Fact]
197
174
  public async Task UpdateVersionAttribute_InProjectFile_ForPackageReferenceInclude_Windows()
198
175
  {
@@ -2558,5 +2535,35 @@ public partial class UpdateWorkerTests
2558
2535
  """
2559
2536
  );
2560
2537
  }
2538
+
2539
+ [Fact]
2540
+ public async Task UpdatingPackageAlsoUpdatesAnythingWithADependencyOnTheUpdatedPackage()
2541
+ {
2542
+ // updating SpecFlow from 3.3.30 requires that SpecFlow.Tools.MsBuild.Generation also be updated
2543
+ await TestUpdateForProject("SpecFlow", "3.3.30", "3.4.3",
2544
+ projectContents: """
2545
+ <Project Sdk="Microsoft.NET.Sdk">
2546
+ <PropertyGroup>
2547
+ <TargetFramework>netstandard2.0</TargetFramework>
2548
+ </PropertyGroup>
2549
+ <ItemGroup>
2550
+ <PackageReference Include="SpecFlow" Version="3.3.30" />
2551
+ <PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.3.30" />
2552
+ </ItemGroup>
2553
+ </Project>
2554
+ """,
2555
+ expectedProjectContents: """
2556
+ <Project Sdk="Microsoft.NET.Sdk">
2557
+ <PropertyGroup>
2558
+ <TargetFramework>netstandard2.0</TargetFramework>
2559
+ </PropertyGroup>
2560
+ <ItemGroup>
2561
+ <PackageReference Include="SpecFlow" Version="3.4.3" />
2562
+ <PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.4.3" />
2563
+ </ItemGroup>
2564
+ </Project>
2565
+ """
2566
+ );
2567
+ }
2561
2568
  }
2562
2569
  }
@@ -6,13 +6,8 @@ namespace NuGetUpdater.Core.Test.Utilities;
6
6
 
7
7
  using TestFile = (string Path, string Content);
8
8
 
9
- public class MSBuildHelperTests
9
+ public class MSBuildHelperTests : TestBase
10
10
  {
11
- public MSBuildHelperTests()
12
- {
13
- MSBuildHelper.RegisterMSBuild();
14
- }
15
-
16
11
  [Fact]
17
12
  public void GetRootedValue_FindsValue()
18
13
  {
@@ -110,15 +105,16 @@ public class MSBuildHelperTests
110
105
  [InlineData("<Project><PropertyGroup><TargetFrameworks>netstandard2.0</TargetFrameworks></PropertyGroup></Project>", "netstandard2.0", null)]
111
106
  [InlineData("<Project><PropertyGroup><TargetFrameworks> ; netstandard2.0 ; </TargetFrameworks></PropertyGroup></Project>", "netstandard2.0", null)]
112
107
  [InlineData("<Project><PropertyGroup><TargetFrameworks>netstandard2.0 ; netstandard2.1 ; </TargetFrameworks></PropertyGroup></Project>", "netstandard2.0", "netstandard2.1")]
113
- public void TfmsCanBeDeterminedFromProjectContents(string projectContents, string? expectedTfm1, string? expectedTfm2)
108
+ [InlineData("<Project><PropertyGroup><TargetFramework>netstandard2.0</TargetFramework><TargetFrameworkVersion Condition='False'>v4.7.2</TargetFrameworkVersion></PropertyGroup></Project>", "netstandard2.0", null)]
109
+ [InlineData("<Project><PropertyGroup><TargetFramework>$(PropertyThatCannotBeResolved)</TargetFramework></PropertyGroup></Project>", null, null)]
110
+ public async Task TfmsCanBeDeterminedFromProjectContents(string projectContents, string? expectedTfm1, string? expectedTfm2)
114
111
  {
115
112
  var projectPath = Path.GetTempFileName();
116
113
  try
117
114
  {
118
115
  File.WriteAllText(projectPath, projectContents);
119
116
  var expectedTfms = new[] { expectedTfm1, expectedTfm2 }.Where(tfm => tfm is not null).ToArray();
120
- var buildFile = ProjectBuildFile.Open(Path.GetDirectoryName(projectPath)!, projectPath);
121
- var actualTfms = MSBuildHelper.GetTargetFrameworkMonikers(ImmutableArray.Create(buildFile));
117
+ var (_buildFiles, actualTfms) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(Path.GetDirectoryName(projectPath)!, projectPath);
122
118
  AssertEx.Equal(expectedTfms, actualTfms);
123
119
  }
124
120
  finally
@@ -493,6 +489,45 @@ public class MSBuildHelperTests
493
489
  }
494
490
  }
495
491
 
492
+ [Fact]
493
+ public async Task DependencyConflictsCanBeResolved()
494
+ {
495
+ // the package `SpecFlow` was already updated from 3.3.30 to 3.4.3, but this causes a conflict with
496
+ // `SpecFlow.Tools.MsBuild.Generation` that needs to be resolved
497
+ var repoRoot = Directory.CreateTempSubdirectory($"test_{nameof(DependencyConflictsCanBeResolved)}_");
498
+ try
499
+ {
500
+ var projectPath = Path.Join(repoRoot.FullName, "project.csproj");
501
+ await File.WriteAllTextAsync(projectPath, """
502
+ <Project Sdk="Microsoft.NET.Sdk">
503
+ <PropertyGroup>
504
+ <TargetFramework>netstandard2.0</TargetFramework>
505
+ </PropertyGroup>
506
+ <ItemGroup>
507
+ <PackageReference Include="SpecFlow" Version="3.4.3" />
508
+ <PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.3.30" />
509
+ </ItemGroup>
510
+ </Project>
511
+ """);
512
+ var dependencies = new[]
513
+ {
514
+ new Dependency("SpecFlow", "3.4.3", DependencyType.PackageReference),
515
+ new Dependency("SpecFlow.Tools.MsBuild.Generation", "3.3.30", DependencyType.PackageReference),
516
+ };
517
+ var resolvedDependencies = await MSBuildHelper.ResolveDependencyConflicts(repoRoot.FullName, projectPath, "netstandard2.0", dependencies, new Logger(true));
518
+ Assert.NotNull(resolvedDependencies);
519
+ Assert.Equal(2, resolvedDependencies.Length);
520
+ Assert.Equal("SpecFlow", resolvedDependencies[0].Name);
521
+ Assert.Equal("3.4.3", resolvedDependencies[0].Version);
522
+ Assert.Equal("SpecFlow.Tools.MsBuild.Generation", resolvedDependencies[1].Name);
523
+ Assert.Equal("3.4.3", resolvedDependencies[1].Version);
524
+ }
525
+ finally
526
+ {
527
+ repoRoot.Delete(recursive: true);
528
+ }
529
+ }
530
+
496
531
  public static IEnumerable<object[]> GetTopLevelPackageDependencyInfosTestData()
497
532
  {
498
533
  // simple case
@@ -167,7 +167,7 @@ namespace NuGetUpdater.Core.Test.Utilities
167
167
 
168
168
  private static async Task<string[]> LoadBuildFilesFromTemp(TemporaryDirectory temporaryDirectory, string relativeProjectPath)
169
169
  {
170
- var buildFiles = await MSBuildHelper.LoadBuildFilesAsync(temporaryDirectory.DirectoryPath, $"{temporaryDirectory.DirectoryPath}/{relativeProjectPath}");
170
+ var (buildFiles, _tfms) = await MSBuildHelper.LoadBuildFilesAndTargetFrameworksAsync(temporaryDirectory.DirectoryPath, $"{temporaryDirectory.DirectoryPath}/{relativeProjectPath}");
171
171
  var buildFilePaths = buildFiles.Select(f => f.RelativePath.NormalizePathToUnix()).ToArray();
172
172
  return buildFilePaths;
173
173
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.252.0
4
+ version: 0.253.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-11 00:00:00.000000000 Z
11
+ date: 2024-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.252.0
19
+ version: 0.253.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.252.0
26
+ version: 0.253.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rubyzip
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -184,20 +184,6 @@ dependencies:
184
184
  - - "~>"
185
185
  - !ruby/object:Gem::Version
186
186
  version: 0.7.3
187
- - !ruby/object:Gem::Dependency
188
- name: stackprof
189
- requirement: !ruby/object:Gem::Requirement
190
- requirements:
191
- - - "~>"
192
- - !ruby/object:Gem::Version
193
- version: 0.2.16
194
- type: :development
195
- prerelease: false
196
- version_requirements: !ruby/object:Gem::Requirement
197
- requirements:
198
- - - "~>"
199
- - !ruby/object:Gem::Version
200
- version: 0.2.16
201
187
  - !ruby/object:Gem::Dependency
202
188
  name: turbo_tests
203
189
  requirement: !ruby/object:Gem::Requirement
@@ -314,6 +300,7 @@ files:
314
300
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs
315
301
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj
316
302
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs
303
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestBase.cs
317
304
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestExtensions.cs
318
305
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs
319
306
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs
@@ -417,7 +404,7 @@ licenses:
417
404
  - Nonstandard
418
405
  metadata:
419
406
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
420
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.252.0
407
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.253.0
421
408
  post_install_message:
422
409
  rdoc_options: []
423
410
  require_paths: