dependabot-nuget 0.242.1 → 0.244.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.editorconfig +37 -28
  3. data/helpers/lib/NuGetUpdater/.gitignore +1 -0
  4. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/AssemblyMetadataExtractor.cs +2 -1
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +2 -2
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +178 -176
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +2 -1
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +1 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +5 -4
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs +1 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs +10 -5
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs +16 -12
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +18 -17
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs +7 -7
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +13 -20
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +9 -3
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +32 -16
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +44 -24
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +32 -13
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs +47 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs +55 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +12 -9
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +78 -54
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +16 -3
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +6 -6
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs +11 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs +18 -9
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs +2 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs +7 -7
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs +1 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +9 -9
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs +228 -75
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +22 -9
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +140 -104
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +25 -25
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +8 -9
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +198 -22
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +401 -399
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs +17 -15
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +114 -45
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +103 -87
  42. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +109 -39
  43. data/lib/dependabot/nuget/file_parser.rb +23 -4
  44. data/lib/dependabot/nuget/file_updater.rb +44 -7
  45. data/lib/dependabot/nuget/native_helpers.rb +27 -8
  46. data/lib/dependabot/nuget/nuget_client.rb +66 -23
  47. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +7 -3
  48. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +63 -59
  49. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +7 -5
  50. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +1 -1
  51. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +22 -17
  52. data/lib/dependabot/nuget/update_checker/property_updater.rb +7 -4
  53. data/lib/dependabot/nuget/update_checker/repository_finder.rb +292 -270
  54. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +11 -13
  55. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +82 -82
  56. data/lib/dependabot/nuget/update_checker/version_finder.rb +9 -5
  57. data/lib/dependabot/nuget/update_checker.rb +6 -3
  58. data/lib/dependabot/nuget/version.rb +18 -7
  59. data/lib/dependabot/nuget.rb +0 -2
  60. metadata +7 -5
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
5
5
 
6
6
  namespace NuGetUpdater.Core;
7
7
 
8
- public partial class UpdaterWorker
8
+ public class UpdaterWorker
9
9
  {
10
10
  private readonly Logger _logger;
11
11
  private readonly HashSet<string> _processedGlobalJsonPaths = new(StringComparer.OrdinalIgnoreCase);
@@ -15,33 +15,33 @@ public partial class UpdaterWorker
15
15
  _logger = logger;
16
16
  }
17
17
 
18
- public async Task RunAsync(string repoRootPath, string filePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
18
+ public async Task RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
19
19
  {
20
20
  MSBuildHelper.RegisterMSBuild();
21
21
 
22
- if (!Path.IsPathRooted(filePath) || !File.Exists(filePath))
22
+ if (!Path.IsPathRooted(workspacePath) || !File.Exists(workspacePath))
23
23
  {
24
- filePath = Path.GetFullPath(Path.Join(repoRootPath, filePath));
24
+ workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
25
25
  }
26
26
 
27
27
  if (!isTransitive)
28
28
  {
29
- await DotNetToolsJsonUpdater.UpdateDependencyAsync(repoRootPath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
29
+ await DotNetToolsJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
30
30
  }
31
31
 
32
- var extension = Path.GetExtension(filePath).ToLowerInvariant();
32
+ var extension = Path.GetExtension(workspacePath).ToLowerInvariant();
33
33
  switch (extension)
34
34
  {
35
35
  case ".sln":
36
- await RunForSolutionAsync(repoRootPath, filePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
36
+ await RunForSolutionAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
37
37
  break;
38
38
  case ".proj":
39
- await RunForProjFileAsync(repoRootPath, filePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
39
+ await RunForProjFileAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
40
40
  break;
41
41
  case ".csproj":
42
42
  case ".fsproj":
43
43
  case ".vbproj":
44
- await RunForProjectAsync(repoRootPath, filePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
44
+ await RunForProjectAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
45
45
  break;
46
46
  default:
47
47
  _logger.Log($"File extension [{extension}] is not supported.");
@@ -51,7 +51,13 @@ public partial class UpdaterWorker
51
51
  _processedGlobalJsonPaths.Clear();
52
52
  }
53
53
 
54
- private async Task RunForSolutionAsync(string repoRootPath, string solutionPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
54
+ private async Task RunForSolutionAsync(
55
+ string repoRootPath,
56
+ string solutionPath,
57
+ string dependencyName,
58
+ string previousDependencyVersion,
59
+ string newDependencyVersion,
60
+ bool isTransitive)
55
61
  {
56
62
  _logger.Log($"Running for solution [{Path.GetRelativePath(repoRootPath, solutionPath)}]");
57
63
  var projectPaths = MSBuildHelper.GetProjectPathsFromSolution(solutionPath);
@@ -61,7 +67,13 @@ public partial class UpdaterWorker
61
67
  }
62
68
  }
63
69
 
64
- private async Task RunForProjFileAsync(string repoRootPath, string projFilePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
70
+ private async Task RunForProjFileAsync(
71
+ string repoRootPath,
72
+ string projFilePath,
73
+ string dependencyName,
74
+ string previousDependencyVersion,
75
+ string newDependencyVersion,
76
+ bool isTransitive)
65
77
  {
66
78
  _logger.Log($"Running for proj file [{Path.GetRelativePath(repoRootPath, projFilePath)}]");
67
79
  if (!File.Exists(projFilePath))
@@ -69,6 +81,7 @@ public partial class UpdaterWorker
69
81
  _logger.Log($"File [{projFilePath}] does not exist.");
70
82
  return;
71
83
  }
84
+
72
85
  var projectFilePaths = MSBuildHelper.GetProjectPathsFromProject(projFilePath);
73
86
  foreach (var projectFullPath in projectFilePaths)
74
87
  {
@@ -80,12 +93,18 @@ public partial class UpdaterWorker
80
93
  }
81
94
  }
82
95
 
83
- private async Task RunForProjectAsync(string repoRootPath, string projectPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
96
+ private async Task RunForProjectAsync(
97
+ string repoRootPath,
98
+ string projectPath,
99
+ string dependencyName,
100
+ string previousDependencyVersion,
101
+ string newDependencyVersion,
102
+ bool isTransitive)
84
103
  {
85
104
  _logger.Log($"Running for project [{projectPath}]");
86
105
 
87
106
  if (!isTransitive
88
- && MSBuildHelper.GetGlobalJsonPath(repoRootPath, projectPath) is string globalJsonPath
107
+ && MSBuildHelper.GetGlobalJsonPath(repoRootPath, projectPath) is { } globalJsonPath
89
108
  && !_processedGlobalJsonPaths.Contains(globalJsonPath))
90
109
  {
91
110
  _processedGlobalJsonPaths.Add(globalJsonPath);
@@ -0,0 +1,47 @@
1
+ using System;
2
+ using System.IO;
3
+ using System.Linq;
4
+
5
+ using Microsoft.Language.Xml;
6
+
7
+ namespace NuGetUpdater.Core.Updater
8
+ {
9
+ internal class WebApplicationTargetsConditionPatcher : IDisposable
10
+ {
11
+ private string? _capturedCondition;
12
+ private readonly XmlFilePreAndPostProcessor _processor;
13
+
14
+ public WebApplicationTargetsConditionPatcher(string projectFilePath)
15
+ {
16
+ _processor = new XmlFilePreAndPostProcessor(
17
+ getContent: () => File.ReadAllText(projectFilePath),
18
+ setContent: s => File.WriteAllText(projectFilePath, s),
19
+ nodeFinder: doc => doc.Descendants()
20
+ .FirstOrDefault(e => e.Name == "Import" && e.GetAttributeValue("Project") == @"$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets")
21
+ as XmlNodeSyntax,
22
+ preProcessor: n =>
23
+ {
24
+ var element = (IXmlElementSyntax)n;
25
+ _capturedCondition = element.GetAttributeValue("Condition");
26
+ return (XmlNodeSyntax)element.RemoveAttributeByName("Condition").WithAttribute("Condition", "false");
27
+ },
28
+ postProcessor: n =>
29
+ {
30
+ var element = (IXmlElementSyntax)n;
31
+ var newElement = element.RemoveAttributeByName("Condition");
32
+ if (_capturedCondition is not null)
33
+ {
34
+ newElement = newElement.WithAttribute("Condition", _capturedCondition);
35
+ }
36
+
37
+ return (XmlNodeSyntax)newElement;
38
+ }
39
+ );
40
+ }
41
+
42
+ public void Dispose()
43
+ {
44
+ _processor.Dispose();
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,55 @@
1
+ using System;
2
+
3
+ using Microsoft.Language.Xml;
4
+
5
+ namespace NuGetUpdater.Core.Updater
6
+ {
7
+ internal class XmlFilePreAndPostProcessor : IDisposable
8
+ {
9
+ public Func<string> GetContent { get; }
10
+ public Action<string> SetContent { get; }
11
+ public Func<XmlDocumentSyntax, XmlNodeSyntax?> NodeFinder { get; }
12
+ public Func<XmlNodeSyntax, XmlNodeSyntax> PreProcessor { get; }
13
+ public Func<XmlNodeSyntax, XmlNodeSyntax> PostProcessor { get; }
14
+
15
+ public XmlFilePreAndPostProcessor(Func<string> getContent, Action<string> setContent, Func<XmlDocumentSyntax, XmlNodeSyntax?> nodeFinder, Func<XmlNodeSyntax, XmlNodeSyntax> preProcessor, Func<XmlNodeSyntax, XmlNodeSyntax> postProcessor)
16
+ {
17
+ GetContent = getContent;
18
+ SetContent = setContent;
19
+ NodeFinder = nodeFinder;
20
+ PreProcessor = preProcessor;
21
+ PostProcessor = postProcessor;
22
+ PreProcess();
23
+ }
24
+
25
+ public void Dispose()
26
+ {
27
+ PostProcess();
28
+ }
29
+
30
+ private void PreProcess() => RunProcessor(PreProcessor);
31
+
32
+ private void PostProcess() => RunProcessor(PostProcessor);
33
+
34
+ private void RunProcessor(Func<XmlNodeSyntax, XmlNodeSyntax> processor)
35
+ {
36
+ var content = GetContent();
37
+ var xml = Parser.ParseText(content);
38
+ if (xml is null)
39
+ {
40
+ return;
41
+ }
42
+
43
+ var node = NodeFinder(xml);
44
+ if (node is null)
45
+ {
46
+ return;
47
+ }
48
+
49
+ var replacementElement = processor(node);
50
+ var replacementXml = xml.ReplaceNode(node, replacementElement);
51
+ var replacementString = replacementXml.ToFullString();
52
+ SetContent(replacementString);
53
+ }
54
+ }
55
+ }
@@ -4,6 +4,7 @@ using System.IO;
4
4
  using System.Linq;
5
5
  using System.Runtime.InteropServices;
6
6
  using System.Text;
7
+ using System.Text.Encodings.Web;
7
8
  using System.Text.Json;
8
9
  using System.Text.Json.Nodes;
9
10
 
@@ -11,7 +12,7 @@ namespace NuGetUpdater.Core.Utilities
11
12
  {
12
13
  internal static class JsonHelper
13
14
  {
14
- public static JsonDocumentOptions DocumentOptions { get; } = new JsonDocumentOptions()
15
+ public static JsonDocumentOptions DocumentOptions { get; } = new JsonDocumentOptions
15
16
  {
16
17
  CommentHandling = JsonCommentHandling.Skip,
17
18
  };
@@ -24,16 +25,16 @@ namespace NuGetUpdater.Core.Utilities
24
25
 
25
26
  public static string UpdateJsonProperty(string json, string[] propertyPath, string newValue, StringComparison comparisonType = StringComparison.Ordinal)
26
27
  {
27
- var readerOptions = new JsonReaderOptions()
28
+ var readerOptions = new JsonReaderOptions
28
29
  {
29
30
  CommentHandling = JsonCommentHandling.Allow,
30
31
  };
31
32
  var bytes = Encoding.UTF8.GetBytes(json);
32
33
  var reader = new Utf8JsonReader(bytes, readerOptions);
33
34
  using var ms = new MemoryStream();
34
- var writerOptions = new JsonWriterOptions()
35
+ var writerOptions = new JsonWriterOptions
35
36
  {
36
- Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
37
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
37
38
  Indented = true,
38
39
  };
39
40
  var writer = new Utf8JsonWriter(ms, writerOptions);
@@ -70,6 +71,7 @@ namespace NuGetUpdater.Core.Utilities
70
71
  // let the default block comment writer handle it
71
72
  writer.WriteCommentValue(reader.GetComment());
72
73
  }
74
+
73
75
  break;
74
76
  case JsonTokenType.EndArray:
75
77
  writer.WriteEndArray();
@@ -126,7 +128,7 @@ namespace NuGetUpdater.Core.Utilities
126
128
  {
127
129
  currentPath.RemoveAt(currentPath.Count - 1);
128
130
  }
129
-
131
+
130
132
  currentPath[pathDepth] = pathValue;
131
133
  if (IsPathMatch(currentPath, propertyPath, comparisonType))
132
134
  {
@@ -187,18 +189,19 @@ namespace NuGetUpdater.Core.Utilities
187
189
  // quit at newline, modulo some special cases
188
190
  if (c == '\n')
189
191
  {
190
- // check for preceeding CR
192
+ // check for preceding CR
191
193
  if (IsPreceedingCharacterEqual(originalJson, prefixStart, '\r'))
192
194
  {
193
195
  prefixStart--;
194
196
  }
195
197
  }
196
198
 
197
- // check for preceeding comma
199
+ // check for preceding comma
198
200
  if (IsPreceedingCharacterEqual(originalJson, prefixStart, ','))
199
201
  {
200
202
  prefixStart--;
201
203
  }
204
+
202
205
  goto done;
203
206
  default:
204
207
  // found regular character; move forward one and quit
@@ -215,8 +218,8 @@ namespace NuGetUpdater.Core.Utilities
215
218
  private static bool IsPreceedingCharacterEqual(string originalText, int currentIndex, char expectedCharacter)
216
219
  {
217
220
  return currentIndex > 0
218
- && currentIndex < originalText.Length
219
- && originalText[currentIndex - 1] == expectedCharacter;
221
+ && currentIndex < originalText.Length
222
+ && originalText[currentIndex - 1] == expectedCharacter;
220
223
  }
221
224
  }
222
225
  }
@@ -15,6 +15,7 @@ using Microsoft.Build.Definition;
15
15
  using Microsoft.Build.Evaluation;
16
16
  using Microsoft.Build.Exceptions;
17
17
  using Microsoft.Build.Locator;
18
+ using Microsoft.Extensions.FileSystemGlobbing;
18
19
 
19
20
  using NuGetUpdater.Core.Utilities;
20
21
 
@@ -101,6 +102,7 @@ internal static partial class MSBuildHelper
101
102
  {
102
103
  var projectStack = new Stack<(string folderPath, ProjectRootElement)>();
103
104
  var projectRootElement = ProjectRootElement.Open(projFilePath);
105
+ var processedProjectFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
104
106
 
105
107
  projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(projFilePath)!), projectRootElement));
106
108
 
@@ -114,27 +116,42 @@ internal static partial class MSBuildHelper
114
116
  continue;
115
117
  }
116
118
 
117
- projectPath = PathHelper.GetFullPathFromRelative(folderPath, projectPath);
119
+ Matcher matcher = new Matcher();
120
+ matcher.AddInclude(PathHelper.NormalizePathToUnix(projectReference.Include));
118
121
 
119
- var projectExtension = Path.GetExtension(projectPath).ToLowerInvariant();
120
- if (projectExtension == ".proj")
122
+ string searchDirectory = PathHelper.NormalizePathToUnix(folderPath);
123
+
124
+ IEnumerable<string> files = matcher.GetResultsInFullPath(searchDirectory);
125
+
126
+ foreach (var file in files)
121
127
  {
122
- // If there is some MSBuild logic that needs to run to fully resolve the path skip the project
123
- if (File.Exists(projectPath))
128
+ // Check that we haven't already processed this file
129
+ if (processedProjectFiles.Contains(file))
124
130
  {
125
- var additionalProjectRootElement = ProjectRootElement.Open(projectPath);
126
- projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(projectPath)!), additionalProjectRootElement));
131
+ continue;
132
+ }
133
+
134
+ var projectExtension = Path.GetExtension(file).ToLowerInvariant();
135
+ if (projectExtension == ".proj")
136
+ {
137
+ // If there is some MSBuild logic that needs to run to fully resolve the path skip the project
138
+ if (File.Exists(file))
139
+ {
140
+ var additionalProjectRootElement = ProjectRootElement.Open(file);
141
+ projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(file)!), additionalProjectRootElement));
142
+ processedProjectFiles.Add(file);
143
+ }
144
+ }
145
+ else if (projectExtension == ".csproj" || projectExtension == ".vbproj" || projectExtension == ".fsproj")
146
+ {
147
+ yield return file;
127
148
  }
128
- }
129
- else if (projectExtension == ".csproj" || projectExtension == ".vbproj" || projectExtension == ".fsproj")
130
- {
131
- yield return projectPath;
132
149
  }
133
150
  }
134
151
  }
135
152
  }
136
153
 
137
- public static IEnumerable<Dependency> GetTopLevelPackageDependenyInfos(ImmutableArray<ProjectBuildFile> buildFiles)
154
+ public static IEnumerable<Dependency> GetTopLevelPackageDependencyInfos(ImmutableArray<ProjectBuildFile> buildFiles)
138
155
  {
139
156
  Dictionary<string, (string, bool)> packageInfo = new(StringComparer.OrdinalIgnoreCase);
140
157
  Dictionary<string, string> packageVersionInfo = new(StringComparer.OrdinalIgnoreCase);
@@ -145,11 +162,11 @@ internal static partial class MSBuildHelper
145
162
  var projectRoot = CreateProjectRootElement(buildFile);
146
163
 
147
164
  foreach (var packageItem in projectRoot.Items
148
- .Where(i => (i.ItemType == "PackageReference" || i.ItemType == "GlobalPackageReference")))
165
+ .Where(i => (i.ItemType == "PackageReference" || i.ItemType == "GlobalPackageReference")))
149
166
  {
150
167
  var versionSpecification = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value
151
- ?? packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase))?.Value
152
- ?? string.Empty;
168
+ ?? packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase))?.Value
169
+ ?? string.Empty;
153
170
  foreach (var attributeValue in new[] { packageItem.Include, packageItem.Update })
154
171
  {
155
172
  if (!string.IsNullOrWhiteSpace(attributeValue))
@@ -175,10 +192,10 @@ internal static partial class MSBuildHelper
175
192
  }
176
193
 
177
194
  foreach (var packageItem in projectRoot.Items
178
- .Where(i => i.ItemType == "PackageVersion" && !string.IsNullOrEmpty(i.Include)))
195
+ .Where(i => i.ItemType == "PackageVersion" && !string.IsNullOrEmpty(i.Include)))
179
196
  {
180
197
  packageVersionInfo[packageItem.Include] = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value
181
- ?? string.Empty;
198
+ ?? string.Empty;
182
199
  }
183
200
 
184
201
  foreach (var property in projectRoot.Properties)
@@ -187,7 +204,8 @@ internal static partial class MSBuildHelper
187
204
  // going to be used, and even then we might not be able to update it. As a best guess, we'll simply
188
205
  // skip any property that has a condition _or_ where the condition is checking for an empty string.
189
206
  var hasEmptyCondition = string.IsNullOrEmpty(property.Condition);
190
- var conditionIsCheckingForEmptyString = string.Equals(property.Condition, $"$({property.Name}) == ''", StringComparison.OrdinalIgnoreCase);
207
+ var conditionIsCheckingForEmptyString = string.Equals(property.Condition, $"$({property.Name}) == ''", StringComparison.OrdinalIgnoreCase) ||
208
+ string.Equals(property.Condition, $"'$({property.Name})' == ''", StringComparison.OrdinalIgnoreCase);
191
209
  if (hasEmptyCondition || conditionIsCheckingForEmptyString)
192
210
  {
193
211
  propertyInfo[property.Name] = property.Value;
@@ -285,7 +303,12 @@ internal static partial class MSBuildHelper
285
303
  return projectRoot;
286
304
  }
287
305
 
288
- private static async Task<string> CreateTempProjectAsync(DirectoryInfo tempDir, string repoRoot, string projectPath, string targetFramework, Dependency[] packages)
306
+ private static async Task<string> CreateTempProjectAsync(
307
+ DirectoryInfo tempDir,
308
+ string repoRoot,
309
+ string projectPath,
310
+ string targetFramework,
311
+ IReadOnlyCollection<Dependency> packages)
289
312
  {
290
313
  var projectDirectory = Path.GetDirectoryName(projectPath);
291
314
  projectDirectory ??= repoRoot;
@@ -299,39 +322,40 @@ internal static partial class MSBuildHelper
299
322
  var packageReferences = string.Join(
300
323
  Environment.NewLine,
301
324
  packages
302
- .Where(p => !string.IsNullOrWhiteSpace(p.Version)) // empty `Version` attributes will cause the temporary project to not build
325
+ // empty `Version` attributes will cause the temporary project to not build
326
+ .Where(p => !string.IsNullOrWhiteSpace(p.Version))
303
327
  // If all PackageReferences for a package are update-only mark it as such, otherwise it can cause package incoherence errors which do not exist in the repo.
304
328
  .Select(static p => $"<PackageReference {(p.IsUpdate ? "Update" : "Include")}=\"{p.Name}\" Version=\"[{p.Version}]\" />"));
305
329
 
306
330
  var projectContents = $"""
307
- <Project Sdk="Microsoft.NET.Sdk">
308
- <PropertyGroup>
309
- <TargetFramework>{targetFramework}</TargetFramework>
310
- <GenerateDependencyFile>true</GenerateDependencyFile>
311
- <RunAnalyzers>false</RunAnalyzers>
312
- </PropertyGroup>
313
- <ItemGroup>
314
- {packageReferences}
315
- </ItemGroup>
316
- <Target Name="_CollectDependencies" DependsOnTargets="GenerateBuildDependencyFile">
317
- <ItemGroup>
318
- <_NuGetPackageData Include="@(NativeCopyLocalItems)" />
319
- <_NuGetPackageData Include="@(ResourceCopyLocalItems)" />
320
- <_NuGetPackageData Include="@(RuntimeCopyLocalItems)" />
321
- <_NuGetPackageData Include="@(ResolvedAnalyzers)" />
322
- <_NuGetPackageData Include="@(_PackageDependenciesDesignTime)">
323
- <NuGetPackageId>%(_PackageDependenciesDesignTime.Name)</NuGetPackageId>
324
- <NuGetPackageVersion>%(_PackageDependenciesDesignTime.Version)</NuGetPackageVersion>
325
- </_NuGetPackageData>
326
- </ItemGroup>
327
- </Target>
328
- <Target Name="_ReportDependencies" DependsOnTargets="_CollectDependencies">
329
- <Message Text="NuGetData::Package=%(_NuGetPackageData.NuGetPackageId), Version=%(_NuGetPackageData.NuGetPackageVersion)"
330
- Condition="'%(_NuGetPackageData.NuGetPackageId)' != '' AND '%(_NuGetPackageData.NuGetPackageVersion)' != ''"
331
- Importance="High" />
332
- </Target>
333
- </Project>
334
- """;
331
+ <Project Sdk="Microsoft.NET.Sdk">
332
+ <PropertyGroup>
333
+ <TargetFramework>{targetFramework}</TargetFramework>
334
+ <GenerateDependencyFile>true</GenerateDependencyFile>
335
+ <RunAnalyzers>false</RunAnalyzers>
336
+ </PropertyGroup>
337
+ <ItemGroup>
338
+ {packageReferences}
339
+ </ItemGroup>
340
+ <Target Name="_CollectDependencies" DependsOnTargets="GenerateBuildDependencyFile">
341
+ <ItemGroup>
342
+ <_NuGetPackageData Include="@(NativeCopyLocalItems)" />
343
+ <_NuGetPackageData Include="@(ResourceCopyLocalItems)" />
344
+ <_NuGetPackageData Include="@(RuntimeCopyLocalItems)" />
345
+ <_NuGetPackageData Include="@(ResolvedAnalyzers)" />
346
+ <_NuGetPackageData Include="@(_PackageDependenciesDesignTime)">
347
+ <NuGetPackageId>%(_PackageDependenciesDesignTime.Name)</NuGetPackageId>
348
+ <NuGetPackageVersion>%(_PackageDependenciesDesignTime.Version)</NuGetPackageVersion>
349
+ </_NuGetPackageData>
350
+ </ItemGroup>
351
+ </Target>
352
+ <Target Name="_ReportDependencies" DependsOnTargets="_CollectDependencies">
353
+ <Message Text="NuGetData::Package=%(_NuGetPackageData.NuGetPackageId), Version=%(_NuGetPackageData.NuGetPackageVersion)"
354
+ Condition="'%(_NuGetPackageData.NuGetPackageId)' != '' AND '%(_NuGetPackageData.NuGetPackageVersion)' != ''"
355
+ Importance="High" />
356
+ </Target>
357
+ </Project>
358
+ """;
335
359
  var tempProjectPath = Path.Combine(tempDir.FullName, "Project.csproj");
336
360
  await File.WriteAllTextAsync(tempProjectPath, projectContents);
337
361
 
@@ -344,7 +368,7 @@ internal static partial class MSBuildHelper
344
368
  }
345
369
 
346
370
  internal static async Task<Dependency[]> GetAllPackageDependenciesAsync(
347
- string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Logger? logger = null)
371
+ string repoRoot, string projectPath, string targetFramework, IReadOnlyCollection<Dependency> packages, Logger? logger = null)
348
372
  {
349
373
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-resolution_");
350
374
  try
@@ -368,7 +392,7 @@ internal static partial class MSBuildHelper
368
392
  else
369
393
  {
370
394
  logger?.Log($"dotnet build in {nameof(GetAllPackageDependenciesAsync)} failed. STDOUT: {stdout} STDERR: {stderr}");
371
- return Array.Empty<Dependency>();
395
+ return [];
372
396
  }
373
397
  }
374
398
  finally
@@ -390,7 +414,7 @@ internal static partial class MSBuildHelper
390
414
 
391
415
  internal static async Task<ImmutableArray<ProjectBuildFile>> LoadBuildFiles(string repoRootPath, string projectPath)
392
416
  {
393
- var buildFileList = new List<string>()
417
+ var buildFileList = new List<string>
394
418
  {
395
419
  projectPath.NormalizePathToUnix() // always include the starting project
396
420
  };
@@ -409,12 +433,12 @@ internal static partial class MSBuildHelper
409
433
  // create a safe version with only certain top-level keys
410
434
  var globalJsonContent = await File.ReadAllTextAsync(safeGlobalJsonName);
411
435
  var json = JsonHelper.ParseNode(globalJsonContent);
412
- var sdks = json["msbuild-sdks"];
436
+ var sdks = json?["msbuild-sdks"];
413
437
  if (sdks is not null)
414
438
  {
415
439
  var newObject = new Dictionary<string, object>()
416
440
  {
417
- { "msbuild-sdks", sdks }
441
+ ["msbuild-sdks"] = sdks,
418
442
  };
419
443
  var newContent = JsonSerializer.Serialize(newObject);
420
444
  await File.WriteAllTextAsync(globalJsonPath, newContent);
@@ -426,7 +450,7 @@ internal static partial class MSBuildHelper
426
450
  // load the project even if it imports a file that doesn't exist (e.g. a file that's generated at restore
427
451
  // or build time).
428
452
  using var projectCollection = new ProjectCollection(); // do this in a one-off instance and don't pollute the global collection
429
- var project = Project.FromFile(projectPath, new ProjectOptions()
453
+ var project = Project.FromFile(projectPath, new ProjectOptions
430
454
  {
431
455
  LoadSettings = ProjectLoadSettings.IgnoreMissingImports,
432
456
  ProjectCollection = projectCollection,
@@ -10,6 +10,7 @@ internal static class PathHelper
10
10
  {
11
11
  MatchCasing = MatchCasing.CaseInsensitive,
12
12
  };
13
+
13
14
  private static readonly EnumerationOptions _caseSensitiveEnumerationOptions = new()
14
15
  {
15
16
  MatchCasing = MatchCasing.CaseSensitive,
@@ -39,6 +40,11 @@ internal static class PathHelper
39
40
  /// <returns>The path of the found file or null.</returns>
40
41
  public static string? GetFileInDirectoryOrParent(string initialPath, string rootPath, string fileName, bool caseSensitive = true)
41
42
  {
43
+ if (File.Exists(initialPath))
44
+ {
45
+ initialPath = Path.GetDirectoryName(initialPath)!;
46
+ }
47
+
42
48
  var candidatePaths = new List<string>();
43
49
  var rootDirectory = new DirectoryInfo(rootPath);
44
50
  var candidateDirectory = new DirectoryInfo(initialPath);
@@ -56,11 +62,18 @@ internal static class PathHelper
56
62
 
57
63
  foreach (var candidatePath in candidatePaths)
58
64
  {
59
- var files = Directory.EnumerateFiles(candidatePath, fileName, caseSensitive ? _caseSensitiveEnumerationOptions : _caseInsensitiveEnumerationOptions);
65
+ try
66
+ {
67
+ var files = Directory.EnumerateFiles(candidatePath, fileName, caseSensitive ? _caseSensitiveEnumerationOptions : _caseInsensitiveEnumerationOptions);
60
68
 
61
- if (files.Any())
69
+ if (files.Any())
70
+ {
71
+ return files.First();
72
+ }
73
+ }
74
+ catch (DirectoryNotFoundException)
62
75
  {
63
- return files.First();
76
+ // When searching for a file in a directory that doesn't exist, Directory.EnumerateFiles throws a DirectoryNotFoundException.
64
77
  }
65
78
  }
66
79
 
@@ -45,14 +45,14 @@ public static class ProcessEx
45
45
  // than enter right back into the Process type and start a wait which isn't guaranteed to be safe.
46
46
  Task.Run(() =>
47
47
  {
48
- redirectInitiated.Wait();
49
- redirectInitiated.Dispose();
50
- redirectInitiated = null;
48
+ redirectInitiated.Wait();
49
+ redirectInitiated.Dispose();
50
+ redirectInitiated = null;
51
51
 
52
- process.WaitForExit();
52
+ process.WaitForExit();
53
53
 
54
- tcs.TrySetResult((process.ExitCode, stdout.ToString(), stderr.ToString()));
55
- process.Dispose();
54
+ tcs.TrySetResult((process.ExitCode, stdout.ToString(), stderr.ToString()));
55
+ process.Dispose();
56
56
  });
57
57
  };
58
58
 
@@ -30,6 +30,17 @@ public static class XmlExtensions
30
30
  return element.Attributes.FirstOrDefault(a => a.Name.Equals(name, comparisonType));
31
31
  }
32
32
 
33
+ public static IXmlElementSyntax RemoveAttributeByName(this IXmlElementSyntax element, string attributeName, StringComparison comparisonType = StringComparison.Ordinal)
34
+ {
35
+ var attribute = element.GetAttribute(attributeName, comparisonType);
36
+ if (attribute is null)
37
+ {
38
+ return element;
39
+ }
40
+
41
+ return element.RemoveAttribute(attribute);
42
+ }
43
+
33
44
  public static string GetAttributeValue(this IXmlElementSyntax element, string name, StringComparison comparisonType)
34
45
  {
35
46
  return element.Attributes.First(a => a.Name.Equals(name, comparisonType)).Value;