dependabot-nuget 0.239.0 → 0.241.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/build +45 -0
  3. data/helpers/lib/NuGetUpdater/.editorconfig +364 -0
  4. data/helpers/lib/NuGetUpdater/.gitignore +5 -0
  5. data/helpers/lib/NuGetUpdater/Directory.Build.props +10 -0
  6. data/helpers/lib/NuGetUpdater/Directory.Common.props +16 -0
  7. data/helpers/lib/NuGetUpdater/NuGetProjects/Directory.Build.props +14 -0
  8. data/helpers/lib/NuGetUpdater/NuGetProjects/Directory.Build.targets +7 -0
  9. data/helpers/lib/NuGetUpdater/NuGetProjects/Directory.Packages.props +29 -0
  10. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Build.Tasks/NuGet.Build.Tasks.csproj +27 -0
  11. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/AssemblyMetadataExtractor.cs +203 -0
  12. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/NuGet.CommandLine.csproj +33 -0
  13. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Commands/NuGet.Commands.csproj +26 -0
  14. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Common/NuGet.Common.csproj +21 -0
  15. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Config +6 -0
  16. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Configuration/NuGet.Configuration.csproj +24 -0
  17. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Credentials/NuGet.Credentials.csproj +20 -0
  18. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.DependencyResolver.Core/NuGet.DependencyResolver.Core.csproj +22 -0
  19. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Frameworks/NuGet.Frameworks.csproj +17 -0
  20. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.LibraryModel/NuGet.LibraryModel.csproj +17 -0
  21. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.PackageManagement/NuGet.PackageManagement.csproj +27 -0
  22. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Packaging/NuGet.Packaging.csproj +28 -0
  23. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.ProjectModel/NuGet.ProjectModel.csproj +20 -0
  24. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Protocol/NuGet.Protocol.csproj +21 -0
  25. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Resolver/NuGet.Resolver.csproj +20 -0
  26. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.Versioning/NuGet.Versioning.csproj +17 -0
  27. data/helpers/lib/NuGetUpdater/NuGetProjects/README.md +1 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/FrameworkCheckCommand.cs +35 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +43 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/NuGetUpdater.Cli.csproj +20 -0
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +31 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.FrameworkCheck.cs +42 -0
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +323 -0
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/NuGetUpdater.Cli.Test.csproj +24 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Dependency.cs +3 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyType.cs +12 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/BuildFile.cs +97 -0
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/DotNetToolsJsonBuildFile.cs +24 -0
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/GlobalJsonBuildFile.cs +25 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +32 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +31 -0
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +94 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/XmlBuildFile.cs +14 -0
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs +39 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs +73 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs +146 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +27 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +316 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs +87 -0
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/ConfigurationFile.cs +3 -0
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +66 -0
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +48 -0
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +172 -0
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +498 -0
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateResult.cs +7 -0
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +105 -0
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +222 -0
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/Logger.cs +24 -0
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +443 -0
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +15 -0
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +69 -0
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +66 -0
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs +124 -0
  64. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/DotNetToolsJsonBuildFileTests.cs +52 -0
  65. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/GlobalJsonBuildFileTests.cs +63 -0
  66. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/PackagesConfigBuildFileTests.cs +63 -0
  67. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs +154 -0
  68. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs +64 -0
  69. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs +122 -0
  70. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs +68 -0
  71. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +23 -0
  72. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +36 -0
  73. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestExtensions.cs +15 -0
  74. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +79 -0
  75. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs +201 -0
  76. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +147 -0
  77. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +225 -0
  78. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +217 -0
  79. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +94 -0
  80. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +938 -0
  81. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +2177 -0
  82. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs +239 -0
  83. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +394 -0
  84. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterHelperTests.cs +179 -0
  85. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +238 -0
  86. data/helpers/lib/NuGetUpdater/NuGetUpdater.sln +152 -0
  87. data/helpers/lib/NuGetUpdater/xunit.runner.json +4 -0
  88. data/lib/dependabot/nuget/cache_manager.rb +2 -0
  89. data/lib/dependabot/nuget/file_fetcher.rb +51 -40
  90. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +0 -6
  91. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +7 -22
  92. data/lib/dependabot/nuget/file_parser.rb +1 -1
  93. data/lib/dependabot/nuget/file_updater.rb +6 -2
  94. data/lib/dependabot/nuget/metadata_finder.rb +4 -4
  95. data/lib/dependabot/nuget/native_helpers.rb +7 -4
  96. data/lib/dependabot/nuget/nuget_client.rb +99 -0
  97. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +71 -0
  98. data/lib/dependabot/nuget/requirement.rb +1 -1
  99. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +2 -2
  100. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +2 -2
  101. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +1 -29
  102. data/lib/dependabot/nuget/update_checker/property_updater.rb +2 -2
  103. data/lib/dependabot/nuget/update_checker/repository_finder.rb +39 -8
  104. data/lib/dependabot/nuget/update_checker/requirements_updater.rb +2 -2
  105. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +2 -2
  106. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +2 -2
  107. data/lib/dependabot/nuget/update_checker/version_finder.rb +4 -42
  108. metadata +107 -5
@@ -0,0 +1,222 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.IO;
4
+ using System.Linq;
5
+ using System.Runtime.InteropServices;
6
+ using System.Text;
7
+ using System.Text.Json;
8
+ using System.Text.Json.Nodes;
9
+
10
+ namespace NuGetUpdater.Core.Utilities
11
+ {
12
+ internal static class JsonHelper
13
+ {
14
+ public static JsonDocumentOptions DocumentOptions { get; } = new JsonDocumentOptions()
15
+ {
16
+ CommentHandling = JsonCommentHandling.Skip,
17
+ };
18
+
19
+ public static JsonNode? ParseNode(string content)
20
+ {
21
+ var node = JsonNode.Parse(content, documentOptions: DocumentOptions);
22
+ return node;
23
+ }
24
+
25
+ public static string UpdateJsonProperty(string json, string[] propertyPath, string newValue, StringComparison comparisonType = StringComparison.Ordinal)
26
+ {
27
+ var readerOptions = new JsonReaderOptions()
28
+ {
29
+ CommentHandling = JsonCommentHandling.Allow,
30
+ };
31
+ var bytes = Encoding.UTF8.GetBytes(json);
32
+ var reader = new Utf8JsonReader(bytes, readerOptions);
33
+ using var ms = new MemoryStream();
34
+ var writerOptions = new JsonWriterOptions()
35
+ {
36
+ Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
37
+ Indented = true,
38
+ };
39
+ var writer = new Utf8JsonWriter(ms, writerOptions);
40
+
41
+ var pathDepth = -1;
42
+ var currentPath = new List<string>();
43
+ var replaceNextToken = false;
44
+ while (reader.Read())
45
+ {
46
+ if (replaceNextToken)
47
+ {
48
+ // replace this specific token
49
+ writer.WriteStringValue(newValue);
50
+ replaceNextToken = false;
51
+ }
52
+ else
53
+ {
54
+ // just mirror the token
55
+ switch (reader.TokenType)
56
+ {
57
+ case JsonTokenType.Comment:
58
+ var commentValue = reader.GetComment();
59
+ var commentStart = json.Substring((int)reader.TokenStartIndex, 2);
60
+ if (commentStart == "//")
61
+ {
62
+ // Utf8JsonWriter only supports block comments, so we have to manually inject a single line comment when appropriate
63
+ writer.Flush();
64
+ var commentPrefix = GetCurrentTokenTriviaPrefix((int)reader.TokenStartIndex, json);
65
+ ms.Write(Encoding.UTF8.GetBytes(commentPrefix));
66
+ ms.Write(Encoding.UTF8.GetBytes("//" + commentValue));
67
+ }
68
+ else
69
+ {
70
+ // let the default block comment writer handle it
71
+ writer.WriteCommentValue(reader.GetComment());
72
+ }
73
+ break;
74
+ case JsonTokenType.EndArray:
75
+ writer.WriteEndArray();
76
+ break;
77
+ case JsonTokenType.EndObject:
78
+ writer.WriteEndObject();
79
+ pathDepth--;
80
+ break;
81
+ case JsonTokenType.False:
82
+ writer.WriteBooleanValue(false);
83
+ break;
84
+ case JsonTokenType.None:
85
+ // do nothing
86
+ break;
87
+ case JsonTokenType.Null:
88
+ writer.WriteNullValue();
89
+ break;
90
+ case JsonTokenType.Number:
91
+ writer.WriteNumberValue(reader.GetDouble());
92
+ break;
93
+ case JsonTokenType.PropertyName:
94
+ writer.WritePropertyName(reader.GetString()!);
95
+ break;
96
+ case JsonTokenType.StartArray:
97
+ writer.WriteStartArray();
98
+ break;
99
+ case JsonTokenType.StartObject:
100
+ writer.WriteStartObject();
101
+ pathDepth++;
102
+ break;
103
+ case JsonTokenType.String:
104
+ writer.WriteStringValue(reader.GetString());
105
+ break;
106
+ case JsonTokenType.True:
107
+ writer.WriteBooleanValue(true);
108
+ break;
109
+ default:
110
+ throw new NotImplementedException($"Unexpected token type: {reader.TokenType}");
111
+ }
112
+ }
113
+
114
+ // see if we need to replace the next token
115
+ if (reader.TokenType == JsonTokenType.PropertyName)
116
+ {
117
+ var pathValue = reader.GetString()!;
118
+
119
+ // ensure the current path object is of the correct size
120
+ while (currentPath.Count < pathDepth + 1)
121
+ {
122
+ currentPath.Add(string.Empty);
123
+ }
124
+
125
+ while (currentPath.Count > 0 && currentPath.Count > pathDepth + 1)
126
+ {
127
+ currentPath.RemoveAt(currentPath.Count - 1);
128
+ }
129
+
130
+ currentPath[pathDepth] = pathValue;
131
+ if (IsPathMatch(currentPath, propertyPath, comparisonType))
132
+ {
133
+ replaceNextToken = true;
134
+ }
135
+ }
136
+ }
137
+
138
+ writer.Flush();
139
+ ms.Flush();
140
+ ms.Seek(0, SeekOrigin.Begin);
141
+ var resultBytes = ms.ToArray();
142
+ var resultJson = Encoding.UTF8.GetString(resultBytes);
143
+
144
+ // single line comments might have had a trailing comma appended by the property writer that we can't
145
+ // control, so we have to manually correct for it
146
+ var originalJsonLines = json.Split('\n').Select(l => l.TrimEnd('\r')).ToArray();
147
+ var updatedJsonLines = resultJson.Split('\n').Select(l => l.TrimEnd('\r')).ToArray();
148
+ for (int i = 0; i < Math.Min(originalJsonLines.Length, updatedJsonLines.Length); i++)
149
+ {
150
+ if (updatedJsonLines[i].EndsWith(",") && !originalJsonLines[i].EndsWith(","))
151
+ {
152
+ updatedJsonLines[i] = updatedJsonLines[i][..^1];
153
+ }
154
+ }
155
+
156
+
157
+ resultJson = string.Join('\n', updatedJsonLines);
158
+
159
+ // the JSON writer doesn't properly maintain newlines, so we need to normalize everything
160
+ resultJson = resultJson.Replace("\r\n", "\n"); // CRLF => LF
161
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
162
+ {
163
+ resultJson = resultJson.Replace("\n", "\r\n"); // LF => CRLF
164
+ }
165
+
166
+ return resultJson;
167
+ }
168
+
169
+ private static bool IsPathMatch(List<string> currentPath, string[] expectedPath, StringComparison comparisonType) =>
170
+ currentPath.Count == expectedPath.Length &&
171
+ currentPath.Zip(expectedPath).All(pair => string.Compare(pair.First, pair.Second, comparisonType) == 0);
172
+
173
+ private static string GetCurrentTokenTriviaPrefix(int tokenStartIndex, string originalJson)
174
+ {
175
+ var prefixStart = tokenStartIndex - 1;
176
+ for (; prefixStart >= 0; prefixStart--)
177
+ {
178
+ var c = originalJson[prefixStart];
179
+ switch (c)
180
+ {
181
+ case ' ':
182
+ case '\t':
183
+ // just more whitespace; keep looking
184
+ break;
185
+ case '\r':
186
+ case '\n':
187
+ // quit at newline, modulo some special cases
188
+ if (c == '\n')
189
+ {
190
+ // check for preceeding CR
191
+ if (IsPreceedingCharacterEqual(originalJson, prefixStart, '\r'))
192
+ {
193
+ prefixStart--;
194
+ }
195
+ }
196
+
197
+ // check for preceeding comma
198
+ if (IsPreceedingCharacterEqual(originalJson, prefixStart, ','))
199
+ {
200
+ prefixStart--;
201
+ }
202
+ goto done;
203
+ default:
204
+ // found regular character; move forward one and quit
205
+ prefixStart++;
206
+ goto done;
207
+ }
208
+ }
209
+
210
+ done:
211
+ var prefix = originalJson.Substring(prefixStart, tokenStartIndex - prefixStart);
212
+ return prefix;
213
+ }
214
+
215
+ private static bool IsPreceedingCharacterEqual(string originalText, int currentIndex, char expectedCharacter)
216
+ {
217
+ return currentIndex > 0
218
+ && currentIndex < originalText.Length
219
+ && originalText[currentIndex - 1] == expectedCharacter;
220
+ }
221
+ }
222
+ }
@@ -0,0 +1,24 @@
1
+ using System;
2
+ using System.IO;
3
+
4
+ namespace NuGetUpdater.Core;
5
+
6
+ public sealed class Logger
7
+ {
8
+ public bool Verbose { get; set; }
9
+ private readonly TextWriter _logOutput;
10
+
11
+ public Logger(bool verbose)
12
+ {
13
+ Verbose = verbose;
14
+ _logOutput = Console.Out;
15
+ }
16
+
17
+ public void Log(string message)
18
+ {
19
+ if (Verbose)
20
+ {
21
+ _logOutput.WriteLine(message);
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,443 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Collections.Immutable;
4
+ using System.Diagnostics.CodeAnalysis;
5
+ using System.IO;
6
+ using System.Linq;
7
+ using System.Text;
8
+ using System.Text.Json;
9
+ using System.Text.RegularExpressions;
10
+ using System.Threading.Tasks;
11
+ using System.Xml;
12
+
13
+ using Microsoft.Build.Construction;
14
+ using Microsoft.Build.Definition;
15
+ using Microsoft.Build.Evaluation;
16
+ using Microsoft.Build.Exceptions;
17
+ using Microsoft.Build.Locator;
18
+
19
+ using NuGetUpdater.Core.Utilities;
20
+
21
+ namespace NuGetUpdater.Core;
22
+
23
+ internal static partial class MSBuildHelper
24
+ {
25
+ public static string MSBuildPath { get; private set; } = string.Empty;
26
+
27
+ public static bool IsMSBuildRegistered => MSBuildPath.Length > 0;
28
+
29
+ static MSBuildHelper()
30
+ {
31
+ RegisterMSBuild();
32
+ }
33
+
34
+ public static void RegisterMSBuild()
35
+ {
36
+ // Ensure MSBuild types are registered before calling a method that loads the types
37
+ if (!IsMSBuildRegistered)
38
+ {
39
+ var defaultInstance = MSBuildLocator.QueryVisualStudioInstances().First();
40
+ MSBuildPath = defaultInstance.MSBuildPath;
41
+ MSBuildLocator.RegisterInstance(defaultInstance);
42
+ }
43
+ }
44
+
45
+ public static string[] GetTargetFrameworkMonikers(ImmutableArray<ProjectBuildFile> buildFiles)
46
+ {
47
+ HashSet<string> targetFrameworkValues = new(StringComparer.OrdinalIgnoreCase);
48
+ Dictionary<string, string> propertyInfo = new(StringComparer.OrdinalIgnoreCase);
49
+
50
+ foreach (var buildFile in buildFiles)
51
+ {
52
+ var projectRoot = CreateProjectRootElement(buildFile);
53
+
54
+ foreach (var property in projectRoot.Properties)
55
+ {
56
+ if (property.Name.Equals("TargetFramework", StringComparison.OrdinalIgnoreCase) ||
57
+ property.Name.Equals("TargetFrameworks", StringComparison.OrdinalIgnoreCase))
58
+ {
59
+ targetFrameworkValues.Add(property.Value);
60
+ }
61
+ else if (property.Name.Equals("TargetFrameworkVersion", StringComparison.OrdinalIgnoreCase))
62
+ {
63
+ // For packages.config projects that use TargetFrameworkVersion, we need to convert it to TargetFramework
64
+ targetFrameworkValues.Add($"net{property.Value.TrimStart('v').Replace(".", "")}");
65
+ }
66
+ else
67
+ {
68
+ propertyInfo[property.Name] = property.Value;
69
+ }
70
+ }
71
+ }
72
+
73
+ HashSet<string> targetFrameworks = new(StringComparer.OrdinalIgnoreCase);
74
+
75
+ foreach (var targetFrameworkValue in targetFrameworkValues)
76
+ {
77
+ var tfms = targetFrameworkValue;
78
+ tfms = GetRootedValue(tfms, propertyInfo);
79
+
80
+ if (string.IsNullOrEmpty(tfms))
81
+ {
82
+ continue;
83
+ }
84
+
85
+ foreach (var tfm in tfms.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
86
+ {
87
+ targetFrameworks.Add(tfm);
88
+ }
89
+ }
90
+
91
+ return targetFrameworks.ToArray();
92
+ }
93
+
94
+ public static IEnumerable<string> GetProjectPathsFromSolution(string solutionPath)
95
+ {
96
+ var solution = SolutionFile.Parse(solutionPath);
97
+ return solution.ProjectsInOrder.Select(p => p.AbsolutePath);
98
+ }
99
+
100
+ public static IEnumerable<string> GetProjectPathsFromProject(string projFilePath)
101
+ {
102
+ var projectStack = new Stack<(string folderPath, ProjectRootElement)>();
103
+ var projectRootElement = ProjectRootElement.Open(projFilePath);
104
+
105
+ projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(projFilePath)!), projectRootElement));
106
+
107
+ while (projectStack.Count > 0)
108
+ {
109
+ var (folderPath, tmpProject) = projectStack.Pop();
110
+ foreach (var projectReference in tmpProject.Items.Where(static x => x.ItemType == "ProjectReference" || x.ItemType == "ProjectFile"))
111
+ {
112
+ if (projectReference.Include is not { } projectPath)
113
+ {
114
+ continue;
115
+ }
116
+
117
+ projectPath = PathHelper.GetFullPathFromRelative(folderPath, projectPath);
118
+
119
+ var projectExtension = Path.GetExtension(projectPath).ToLowerInvariant();
120
+ if (projectExtension == ".proj")
121
+ {
122
+ // If there is some MSBuild logic that needs to run to fully resolve the path skip the project
123
+ if (File.Exists(projectPath))
124
+ {
125
+ var additionalProjectRootElement = ProjectRootElement.Open(projectPath);
126
+ projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(projectPath)!), additionalProjectRootElement));
127
+ }
128
+ }
129
+ else if (projectExtension == ".csproj" || projectExtension == ".vbproj" || projectExtension == ".fsproj")
130
+ {
131
+ yield return projectPath;
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ public static IEnumerable<Dependency> GetTopLevelPackageDependenyInfos(ImmutableArray<ProjectBuildFile> buildFiles)
138
+ {
139
+ Dictionary<string, string> packageInfo = new(StringComparer.OrdinalIgnoreCase);
140
+ Dictionary<string, string> packageVersionInfo = new(StringComparer.OrdinalIgnoreCase);
141
+ Dictionary<string, string> propertyInfo = new(StringComparer.OrdinalIgnoreCase);
142
+
143
+ foreach (var buildFile in buildFiles)
144
+ {
145
+ var projectRoot = CreateProjectRootElement(buildFile);
146
+
147
+ foreach (var packageItem in projectRoot.Items
148
+ .Where(i => (i.ItemType == "PackageReference" || i.ItemType == "GlobalPackageReference")))
149
+ {
150
+ 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;
153
+ foreach (var attributeValue in new[] { packageItem.Include, packageItem.Update })
154
+ {
155
+ if (!string.IsNullOrWhiteSpace(attributeValue))
156
+ {
157
+ packageInfo[attributeValue] = versionSpecification;
158
+ }
159
+ }
160
+ }
161
+
162
+ foreach (var packageItem in projectRoot.Items
163
+ .Where(i => i.ItemType == "PackageVersion" && !string.IsNullOrEmpty(i.Include)))
164
+ {
165
+ packageVersionInfo[packageItem.Include] = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value
166
+ ?? string.Empty;
167
+ }
168
+
169
+ foreach (var property in projectRoot.Properties)
170
+ {
171
+ // Short of evaluating the entire project, there's no way to _really_ know what package version is
172
+ // going to be used, and even then we might not be able to update it. As a best guess, we'll simply
173
+ // skip any property that has a condition _or_ where the condition is checking for an empty string.
174
+ var hasEmptyCondition = string.IsNullOrEmpty(property.Condition);
175
+ var conditionIsCheckingForEmptyString = string.Equals(property.Condition, $"$({property.Name}) == ''", StringComparison.OrdinalIgnoreCase);
176
+ if (hasEmptyCondition || conditionIsCheckingForEmptyString)
177
+ {
178
+ propertyInfo[property.Name] = property.Value;
179
+ }
180
+ }
181
+ }
182
+
183
+ foreach (var (name, version) in packageInfo)
184
+ {
185
+ if (version.Length != 0 || !packageVersionInfo.TryGetValue(name, out var packageVersion))
186
+ {
187
+ packageVersion = version;
188
+ }
189
+
190
+ // Walk the property replacements until we don't find another one.
191
+ packageVersion = GetRootedValue(packageVersion, propertyInfo);
192
+
193
+ packageVersion = packageVersion.TrimStart('[', '(').TrimEnd(']', ')');
194
+
195
+ // We don't know the version for range requirements or wildcard
196
+ // requirements, so return "" for these.
197
+ yield return packageVersion.Contains(',') || packageVersion.Contains('*')
198
+ ? new Dependency(name, string.Empty, DependencyType.Unknown)
199
+ : new Dependency(name, packageVersion, DependencyType.Unknown);
200
+ }
201
+ }
202
+
203
+ /// <summary>
204
+ /// Given an MSBuild string and a set of properties, returns our best guess at the final value MSBuild will evaluate to.
205
+ /// </summary>
206
+ /// <param name="msbuildString"></param>
207
+ /// <param name="propertyInfo"></param>
208
+ /// <returns></returns>
209
+ public static string GetRootedValue(string msbuildString, Dictionary<string, string> propertyInfo)
210
+ {
211
+ var seenProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
212
+ while (TryGetPropertyName(msbuildString, out var propertyName))
213
+ {
214
+ if (!seenProperties.Add(propertyName))
215
+ {
216
+ throw new InvalidDataException($"Property '{propertyName}' has a circular reference.");
217
+ }
218
+
219
+ msbuildString = propertyInfo.TryGetValue(propertyName, out var propertyValue)
220
+ ? msbuildString.Replace($"$({propertyName})", propertyValue)
221
+ : throw new InvalidDataException($"Property '{propertyName}' was not found.");
222
+ }
223
+
224
+ return msbuildString;
225
+ }
226
+
227
+ public static bool TryGetPropertyName(string versionContent, [NotNullWhen(true)] out string? propertyName)
228
+ {
229
+ var startIndex = versionContent.IndexOf("$(", StringComparison.Ordinal);
230
+ if (startIndex != -1)
231
+ {
232
+ var endIndex = versionContent.IndexOf(')', startIndex);
233
+ if (endIndex != -1)
234
+ {
235
+ propertyName = versionContent.Substring(startIndex + 2, endIndex - startIndex - 2);
236
+ return true;
237
+ }
238
+ }
239
+
240
+ propertyName = null;
241
+ return false;
242
+ }
243
+
244
+ internal static async Task<bool> DependenciesAreCoherentAsync(string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Logger logger)
245
+ {
246
+ var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-coherence_");
247
+ try
248
+ {
249
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
250
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"build \"{tempProjectPath}\"");
251
+
252
+ // NU1608: Detected package version outside of dependency constraint
253
+
254
+ return exitCode == 0 && !stdOut.Contains("NU1608");
255
+ }
256
+ finally
257
+ {
258
+ tempDirectory.Delete(recursive: true);
259
+ }
260
+ }
261
+
262
+ private static ProjectRootElement CreateProjectRootElement(ProjectBuildFile buildFile)
263
+ {
264
+ var xmlString = buildFile.Contents.ToFullString();
265
+ using var xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlString));
266
+ using var xmlReader = XmlReader.Create(xmlStream);
267
+ var projectRoot = ProjectRootElement.Create(xmlReader);
268
+
269
+ return projectRoot;
270
+ }
271
+
272
+ private static async Task<string> CreateTempProjectAsync(DirectoryInfo tempDir, string repoRoot, string projectPath, string targetFramework, Dependency[] packages)
273
+ {
274
+ var projectDirectory = Path.GetDirectoryName(projectPath);
275
+ projectDirectory ??= repoRoot;
276
+ var topLevelFiles = Directory.GetFiles(repoRoot);
277
+ var nugetConfigPath = PathHelper.GetFileInDirectoryOrParent(projectDirectory, repoRoot, "NuGet.Config", caseSensitive: false);
278
+ if (nugetConfigPath is not null)
279
+ {
280
+ File.Copy(nugetConfigPath, Path.Combine(tempDir.FullName, "NuGet.Config"));
281
+ }
282
+
283
+ var packageReferences = string.Join(
284
+ Environment.NewLine,
285
+ packages
286
+ .Where(p => !string.IsNullOrWhiteSpace(p.Version)) // empty `Version` attributes will cause the temporary project to not build
287
+ .Select(static p => $"<PackageReference Include=\"{p.Name}\" Version=\"[{p.Version}]\" />"));
288
+
289
+ var projectContents = $"""
290
+ <Project Sdk="Microsoft.NET.Sdk">
291
+ <PropertyGroup>
292
+ <TargetFramework>{targetFramework}</TargetFramework>
293
+ <GenerateDependencyFile>true</GenerateDependencyFile>
294
+ </PropertyGroup>
295
+ <ItemGroup>
296
+ {packageReferences}
297
+ </ItemGroup>
298
+ <Target Name="_CollectDependencies" DependsOnTargets="GenerateBuildDependencyFile">
299
+ <ItemGroup>
300
+ <_NuGetPackageData Include="@(NativeCopyLocalItems)" />
301
+ <_NuGetPackageData Include="@(ResourceCopyLocalItems)" />
302
+ <_NuGetPackageData Include="@(RuntimeCopyLocalItems)" />
303
+ <_NuGetPackageData Include="@(ResolvedAnalyzers)" />
304
+ <_NuGetPackageData Include="@(_PackageDependenciesDesignTime)">
305
+ <NuGetPackageId>%(_PackageDependenciesDesignTime.Name)</NuGetPackageId>
306
+ <NuGetPackageVersion>%(_PackageDependenciesDesignTime.Version)</NuGetPackageVersion>
307
+ </_NuGetPackageData>
308
+ </ItemGroup>
309
+ </Target>
310
+ <Target Name="_ReportDependencies" DependsOnTargets="_CollectDependencies">
311
+ <Message Text="NuGetData::Package=%(_NuGetPackageData.NuGetPackageId), Version=%(_NuGetPackageData.NuGetPackageVersion)"
312
+ Condition="'%(_NuGetPackageData.NuGetPackageId)' != '' AND '%(_NuGetPackageData.NuGetPackageVersion)' != ''"
313
+ Importance="High" />
314
+ </Target>
315
+ </Project>
316
+ """;
317
+ var tempProjectPath = Path.Combine(tempDir.FullName, "Project.csproj");
318
+ await File.WriteAllTextAsync(tempProjectPath, projectContents);
319
+
320
+ // prevent directory crawling
321
+ await File.WriteAllTextAsync(Path.Combine(tempDir.FullName, "Directory.Build.props"), "<Project />");
322
+ await File.WriteAllTextAsync(Path.Combine(tempDir.FullName, "Directory.Build.targets"), "<Project />");
323
+ await File.WriteAllTextAsync(Path.Combine(tempDir.FullName, "Directory.Packages.props"), "<Project />");
324
+
325
+ return tempProjectPath;
326
+ }
327
+
328
+ internal static async Task<Dependency[]> GetAllPackageDependenciesAsync(
329
+ string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Logger? logger = null)
330
+ {
331
+ var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-resolution_");
332
+ try
333
+ {
334
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
335
+
336
+ var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"build \"{tempProjectPath}\" /t:_ReportDependencies");
337
+
338
+ if (exitCode == 0)
339
+ {
340
+ var lines = stdout.Split('\n').Select(line => line.Trim());
341
+ var pattern = PackagePattern();
342
+ var allDependencies = lines
343
+ .Select(line => pattern.Match(line))
344
+ .Where(match => match.Success)
345
+ .Select(match => new Dependency(match.Groups["PackageName"].Value, match.Groups["PackageVersion"].Value, DependencyType.Unknown))
346
+ .ToArray();
347
+
348
+ return allDependencies;
349
+ }
350
+ else
351
+ {
352
+ logger?.Log($"dotnet build in {nameof(GetAllPackageDependenciesAsync)} failed. STDOUT: {stdout} STDERR: {stderr}");
353
+ return Array.Empty<Dependency>();
354
+ }
355
+ }
356
+ finally
357
+ {
358
+ try
359
+ {
360
+ tempDirectory.Delete(recursive: true);
361
+ }
362
+ catch
363
+ {
364
+ }
365
+ }
366
+ }
367
+
368
+ internal static string? GetGlobalJsonPath(string repoRootPath, string projectPath)
369
+ {
370
+ return PathHelper.GetFileInDirectoryOrParent(Path.GetDirectoryName(projectPath)!, repoRootPath, "global.json");
371
+ }
372
+
373
+ internal static async Task<ImmutableArray<ProjectBuildFile>> LoadBuildFiles(string repoRootPath, string projectPath)
374
+ {
375
+ var buildFileList = new List<string>()
376
+ {
377
+ projectPath.NormalizePathToUnix() // always include the starting project
378
+ };
379
+
380
+ // a global.json file might cause problems with the dotnet msbuild command; create a safe version temporarily
381
+ var globalJsonPath = GetGlobalJsonPath(repoRootPath, projectPath);
382
+ var safeGlobalJsonName = $"{globalJsonPath}{Guid.NewGuid()}";
383
+
384
+ try
385
+ {
386
+ // move the original
387
+ if (globalJsonPath is not null)
388
+ {
389
+ File.Move(globalJsonPath, safeGlobalJsonName);
390
+
391
+ // create a safe version with only certain top-level keys
392
+ var globalJsonContent = await File.ReadAllTextAsync(safeGlobalJsonName);
393
+ var json = JsonHelper.ParseNode(globalJsonContent);
394
+ var sdks = json["msbuild-sdks"];
395
+ if (sdks is not null)
396
+ {
397
+ var newObject = new Dictionary<string, object>()
398
+ {
399
+ { "msbuild-sdks", sdks }
400
+ };
401
+ var newContent = JsonSerializer.Serialize(newObject);
402
+ await File.WriteAllTextAsync(globalJsonPath, newContent);
403
+ }
404
+ }
405
+
406
+ // This is equivalent to running the command `dotnet msbuild <projectPath> /pp` to preprocess the file.
407
+ // The only difference is that we're specifying the `IgnoreMissingImports` flag which will allow us to
408
+ // load the project even if it imports a file that doesn't exist (e.g. a file that's generated at restore
409
+ // or build time).
410
+ using var projectCollection = new ProjectCollection(); // do this in a one-off instance and don't pollute the global collection
411
+ var project = Project.FromFile(projectPath, new ProjectOptions()
412
+ {
413
+ LoadSettings = ProjectLoadSettings.IgnoreMissingImports,
414
+ ProjectCollection = projectCollection,
415
+ });
416
+ buildFileList.AddRange(project.Imports.Select(i => i.ImportedProject.FullPath.NormalizePathToUnix()));
417
+ }
418
+ catch (InvalidProjectFileException)
419
+ {
420
+ return [];
421
+ }
422
+ finally
423
+ {
424
+ if (globalJsonPath is not null)
425
+ {
426
+ File.Move(safeGlobalJsonName, globalJsonPath, overwrite: true);
427
+ }
428
+ }
429
+
430
+ var repoRootPathPrefix = repoRootPath.NormalizePathToUnix() + "/";
431
+ var buildFilesInRepo = buildFileList
432
+ .Where(f => f.StartsWith(repoRootPathPrefix, StringComparison.OrdinalIgnoreCase))
433
+ .Distinct()
434
+ .ToArray();
435
+ var result = buildFilesInRepo
436
+ .Select(path => ProjectBuildFile.Open(repoRootPath, path))
437
+ .ToImmutableArray();
438
+ return result;
439
+ }
440
+
441
+ [GeneratedRegex("^\\s*NuGetData::Package=(?<PackageName>[^,]+), Version=(?<PackageVersion>.+)$")]
442
+ private static partial Regex PackagePattern();
443
+ }