dependabot-nuget 0.251.0 → 0.252.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/lib/NuGetUpdater/Directory.Common.props +1 -0
- data/helpers/lib/NuGetUpdater/Directory.Packages.props +26 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +35 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/NuGetUpdater.Cli.csproj +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +4 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +251 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/NuGetUpdater.Cli.Test.csproj +3 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Dependency.cs +56 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyType.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscovery.cs +69 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscoveryResult.cs +11 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +217 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DotNetToolsJsonDiscovery.cs +30 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DotNetToolsJsonDiscoveryResult.cs +10 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/GlobalJsonDiscovery.cs +30 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/GlobalJsonDiscoveryResult.cs +10 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/IDiscoveryResult.cs +14 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +29 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscoveryResult.cs +10 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +13 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +127 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +13 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EvaluationResult.cs +8 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EvaluationResultType.cs +9 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/BuildFile.cs +6 -8
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/DotNetToolsJsonBuildFile.cs +4 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/GlobalJsonBuildFile.cs +24 -17
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +2 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +8 -13
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +100 -19
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/XmlBuildFile.cs +2 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +6 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Property.cs +6 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +23 -36
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +5 -10
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +16 -21
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +4 -19
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/HashSetExtensions.cs +14 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ImmutableArrayExtensions.cs +18 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +0 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +121 -68
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +27 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +117 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.DotNetToolsJson.cs +91 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +71 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +59 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +380 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +306 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +36 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/DotNetToolsJsonBuildFileTests.cs +1 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/GlobalJsonBuildFileTests.cs +2 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/PackagesConfigBuildFileTests.cs +4 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs +6 -5
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +4 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +38 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +12 -40
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/AssertEx.cs +272 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/DiffUtil.cs +266 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +195 -152
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterHelperTests.cs +7 -11
- data/lib/dependabot/nuget/discovery/dependency_details.rb +95 -0
- data/lib/dependabot/nuget/discovery/dependency_file_discovery.rb +126 -0
- data/lib/dependabot/nuget/discovery/directory_packages_props_discovery.rb +43 -0
- data/lib/dependabot/nuget/discovery/discovery_json_reader.rb +83 -0
- data/lib/dependabot/nuget/discovery/evaluation_details.rb +63 -0
- data/lib/dependabot/nuget/discovery/project_discovery.rb +71 -0
- data/lib/dependabot/nuget/discovery/property_details.rb +43 -0
- data/lib/dependabot/nuget/discovery/workspace_discovery.rb +66 -0
- data/lib/dependabot/nuget/file_parser.rb +19 -128
- data/lib/dependabot/nuget/file_updater.rb +28 -60
- data/lib/dependabot/nuget/native_helpers.rb +55 -0
- data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +3 -8
- data/lib/dependabot/nuget/update_checker/dependency_finder.rb +1 -0
- data/lib/dependabot/nuget/update_checker/property_updater.rb +1 -0
- data/lib/dependabot/nuget/update_checker/tfm_finder.rb +17 -152
- data/lib/dependabot/nuget/update_checker/version_finder.rb +1 -6
- data/lib/dependabot/nuget/update_checker.rb +4 -1
- metadata +43 -11
- data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +0 -71
- data/lib/dependabot/nuget/file_parser/global_json_parser.rb +0 -68
- data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +0 -92
- data/lib/dependabot/nuget/file_parser/project_file_parser.rb +0 -620
- data/lib/dependabot/nuget/file_parser/property_value_finder.rb +0 -225
- data/lib/dependabot/nuget/file_updater/property_value_updater.rb +0 -81
@@ -1,13 +1,8 @@
|
|
1
|
-
using System;
|
2
|
-
using System.Collections.Generic;
|
3
1
|
using System.Collections.Immutable;
|
4
2
|
using System.Diagnostics.CodeAnalysis;
|
5
|
-
using System.IO;
|
6
|
-
using System.Linq;
|
7
3
|
using System.Text;
|
8
4
|
using System.Text.Json;
|
9
5
|
using System.Text.RegularExpressions;
|
10
|
-
using System.Threading.Tasks;
|
11
6
|
using System.Xml;
|
12
7
|
|
13
8
|
using Microsoft.Build.Construction;
|
@@ -23,8 +18,6 @@ using NuGetUpdater.Core.Utilities;
|
|
23
18
|
|
24
19
|
namespace NuGetUpdater.Core;
|
25
20
|
|
26
|
-
using EvaluationResult = (MSBuildHelper.EvaluationResultType ResultType, string EvaluatedValue, string? ErrorMessage);
|
27
|
-
|
28
21
|
internal static partial class MSBuildHelper
|
29
22
|
{
|
30
23
|
public static string MSBuildPath { get; private set; } = string.Empty;
|
@@ -70,7 +63,7 @@ internal static partial class MSBuildHelper
|
|
70
63
|
public static string[] GetTargetFrameworkMonikers(ImmutableArray<ProjectBuildFile> buildFiles)
|
71
64
|
{
|
72
65
|
HashSet<string> targetFrameworkValues = new(StringComparer.OrdinalIgnoreCase);
|
73
|
-
Dictionary<string,
|
66
|
+
Dictionary<string, Property> propertyInfo = new(StringComparer.OrdinalIgnoreCase);
|
74
67
|
|
75
68
|
foreach (var buildFile in buildFiles)
|
76
69
|
{
|
@@ -81,6 +74,11 @@ internal static partial class MSBuildHelper
|
|
81
74
|
if (property.Name.Equals("TargetFramework", StringComparison.OrdinalIgnoreCase) ||
|
82
75
|
property.Name.Equals("TargetFrameworks", StringComparison.OrdinalIgnoreCase))
|
83
76
|
{
|
77
|
+
if (buildFile.IsOutsideBasePath)
|
78
|
+
{
|
79
|
+
continue;
|
80
|
+
}
|
81
|
+
|
84
82
|
foreach (var tfm in property.Value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
85
83
|
{
|
86
84
|
targetFrameworkValues.Add(tfm);
|
@@ -88,12 +86,17 @@ internal static partial class MSBuildHelper
|
|
88
86
|
}
|
89
87
|
else if (property.Name.Equals("TargetFrameworkVersion", StringComparison.OrdinalIgnoreCase))
|
90
88
|
{
|
89
|
+
if (buildFile.IsOutsideBasePath)
|
90
|
+
{
|
91
|
+
continue;
|
92
|
+
}
|
93
|
+
|
91
94
|
// For packages.config projects that use TargetFrameworkVersion, we need to convert it to TargetFramework
|
92
95
|
targetFrameworkValues.Add($"net{property.Value.TrimStart('v').Replace(".", "")}");
|
93
96
|
}
|
94
97
|
else
|
95
98
|
{
|
96
|
-
propertyInfo[property.Name] = property.Value;
|
99
|
+
propertyInfo[property.Name] = new(property.Name, property.Value, buildFile.RelativePath);
|
97
100
|
}
|
98
101
|
}
|
99
102
|
}
|
@@ -102,7 +105,7 @@ internal static partial class MSBuildHelper
|
|
102
105
|
|
103
106
|
foreach (var targetFrameworkValue in targetFrameworkValues)
|
104
107
|
{
|
105
|
-
var (resultType, tfms, errorMessage) =
|
108
|
+
var (resultType, _, tfms, _, errorMessage) =
|
106
109
|
GetEvaluatedValue(targetFrameworkValue, propertyInfo, propertiesToIgnore: ["TargetFramework", "TargetFrameworks"]);
|
107
110
|
if (resultType != EvaluationResultType.Success)
|
108
111
|
{
|
@@ -190,19 +193,65 @@ internal static partial class MSBuildHelper
|
|
190
193
|
}
|
191
194
|
}
|
192
195
|
|
196
|
+
public static IReadOnlyDictionary<string, Property> GetProperties(ImmutableArray<ProjectBuildFile> buildFiles)
|
197
|
+
{
|
198
|
+
Dictionary<string, Property> properties = new(StringComparer.OrdinalIgnoreCase);
|
199
|
+
|
200
|
+
foreach (var buildFile in buildFiles)
|
201
|
+
{
|
202
|
+
var projectRoot = CreateProjectRootElement(buildFile);
|
203
|
+
|
204
|
+
foreach (var property in projectRoot.Properties)
|
205
|
+
{
|
206
|
+
// Short of evaluating the entire project, there's no way to _really_ know what package version is
|
207
|
+
// going to be used, and even then we might not be able to update it. As a best guess, we'll simply
|
208
|
+
// skip any property that has a condition _or_ where the condition is checking for an empty string.
|
209
|
+
var hasEmptyCondition = string.IsNullOrEmpty(property.Condition);
|
210
|
+
var conditionIsCheckingForEmptyString = string.Equals(property.Condition, $"$({property.Name}) == ''", StringComparison.OrdinalIgnoreCase) ||
|
211
|
+
string.Equals(property.Condition, $"'$({property.Name})' == ''", StringComparison.OrdinalIgnoreCase);
|
212
|
+
if (hasEmptyCondition || conditionIsCheckingForEmptyString)
|
213
|
+
{
|
214
|
+
properties[property.Name] = new(property.Name, property.Value, buildFile.RelativePath);
|
215
|
+
}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
return properties;
|
220
|
+
}
|
221
|
+
|
193
222
|
public static IEnumerable<Dependency> GetTopLevelPackageDependencyInfos(ImmutableArray<ProjectBuildFile> buildFiles)
|
194
223
|
{
|
195
|
-
Dictionary<string, (string, bool)> packageInfo = new(StringComparer.OrdinalIgnoreCase);
|
224
|
+
Dictionary<string, (string, bool, DependencyType)> packageInfo = new(StringComparer.OrdinalIgnoreCase);
|
196
225
|
Dictionary<string, string> packageVersionInfo = new(StringComparer.OrdinalIgnoreCase);
|
197
|
-
Dictionary<string,
|
226
|
+
Dictionary<string, Property> propertyInfo = new(StringComparer.OrdinalIgnoreCase);
|
198
227
|
|
199
228
|
foreach (var buildFile in buildFiles)
|
200
229
|
{
|
201
230
|
var projectRoot = CreateProjectRootElement(buildFile);
|
202
231
|
|
232
|
+
foreach (var property in projectRoot.Properties)
|
233
|
+
{
|
234
|
+
// Short of evaluating the entire project, there's no way to _really_ know what package version is
|
235
|
+
// going to be used, and even then we might not be able to update it. As a best guess, we'll simply
|
236
|
+
// skip any property that has a condition _or_ where the condition is checking for an empty string.
|
237
|
+
var hasEmptyCondition = string.IsNullOrEmpty(property.Condition);
|
238
|
+
var conditionIsCheckingForEmptyString = string.Equals(property.Condition, $"$({property.Name}) == ''", StringComparison.OrdinalIgnoreCase) ||
|
239
|
+
string.Equals(property.Condition, $"'$({property.Name})' == ''", StringComparison.OrdinalIgnoreCase);
|
240
|
+
if (hasEmptyCondition || conditionIsCheckingForEmptyString)
|
241
|
+
{
|
242
|
+
propertyInfo[property.Name] = new(property.Name, property.Value, buildFile.RelativePath);
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
if (buildFile.IsOutsideBasePath)
|
247
|
+
{
|
248
|
+
continue;
|
249
|
+
}
|
250
|
+
|
203
251
|
foreach (var packageItem in projectRoot.Items
|
204
252
|
.Where(i => (i.ItemType == "PackageReference" || i.ItemType == "GlobalPackageReference")))
|
205
253
|
{
|
254
|
+
var dependencyType = packageItem.ItemType == "PackageReference" ? DependencyType.PackageReference : DependencyType.GlobalPackageReference;
|
206
255
|
var versionSpecification = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value
|
207
256
|
?? packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase))?.Value
|
208
257
|
?? string.Empty;
|
@@ -219,12 +268,12 @@ internal static partial class MSBuildHelper
|
|
219
268
|
var vSpec = string.IsNullOrEmpty(versionSpecification) || existingUpdate ? existingVersion : versionSpecification;
|
220
269
|
|
221
270
|
var isUpdate = existingUpdate && string.IsNullOrEmpty(packageItem.Include);
|
222
|
-
packageInfo[attributeValue] = (vSpec, isUpdate);
|
271
|
+
packageInfo[attributeValue] = (vSpec, isUpdate, dependencyType);
|
223
272
|
}
|
224
273
|
else
|
225
274
|
{
|
226
275
|
var isUpdate = !string.IsNullOrEmpty(packageItem.Update);
|
227
|
-
packageInfo[attributeValue] = (versionSpecification, isUpdate);
|
276
|
+
packageInfo[attributeValue] = (versionSpecification, isUpdate, dependencyType);
|
228
277
|
}
|
229
278
|
}
|
230
279
|
}
|
@@ -236,25 +285,11 @@ internal static partial class MSBuildHelper
|
|
236
285
|
packageVersionInfo[packageItem.Include] = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value
|
237
286
|
?? string.Empty;
|
238
287
|
}
|
239
|
-
|
240
|
-
foreach (var property in projectRoot.Properties)
|
241
|
-
{
|
242
|
-
// Short of evaluating the entire project, there's no way to _really_ know what package version is
|
243
|
-
// going to be used, and even then we might not be able to update it. As a best guess, we'll simply
|
244
|
-
// skip any property that has a condition _or_ where the condition is checking for an empty string.
|
245
|
-
var hasEmptyCondition = string.IsNullOrEmpty(property.Condition);
|
246
|
-
var conditionIsCheckingForEmptyString = string.Equals(property.Condition, $"$({property.Name}) == ''", StringComparison.OrdinalIgnoreCase) ||
|
247
|
-
string.Equals(property.Condition, $"'$({property.Name})' == ''", StringComparison.OrdinalIgnoreCase);
|
248
|
-
if (hasEmptyCondition || conditionIsCheckingForEmptyString)
|
249
|
-
{
|
250
|
-
propertyInfo[property.Name] = property.Value;
|
251
|
-
}
|
252
|
-
}
|
253
288
|
}
|
254
289
|
|
255
290
|
foreach (var (name, info) in packageInfo)
|
256
291
|
{
|
257
|
-
var (version, isUpdate) = info;
|
292
|
+
var (version, isUpdate, dependencyType) = info;
|
258
293
|
if (version.Length != 0 || !packageVersionInfo.TryGetValue(name, out var packageVersion))
|
259
294
|
{
|
260
295
|
packageVersion = version;
|
@@ -262,51 +297,51 @@ internal static partial class MSBuildHelper
|
|
262
297
|
|
263
298
|
// Walk the property replacements until we don't find another one.
|
264
299
|
var evaluationResult = GetEvaluatedValue(packageVersion, propertyInfo);
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
continue;
|
269
|
-
}
|
270
|
-
|
271
|
-
packageVersion = evaluationResult.EvaluatedValue.TrimStart('[', '(').TrimEnd(']', ')');
|
300
|
+
packageVersion = evaluationResult.ResultType == EvaluationResultType.Success
|
301
|
+
? evaluationResult.EvaluatedValue.TrimStart('[', '(').TrimEnd(']', ')')
|
302
|
+
: evaluationResult.EvaluatedValue;
|
272
303
|
|
273
304
|
// We don't know the version for range requirements or wildcard
|
274
305
|
// requirements, so return "" for these.
|
275
306
|
yield return packageVersion.Contains(',') || packageVersion.Contains('*')
|
276
|
-
? new Dependency(name, string.Empty,
|
277
|
-
: new Dependency(name, packageVersion,
|
307
|
+
? new Dependency(name, string.Empty, dependencyType, EvaluationResult: evaluationResult, IsUpdate: isUpdate)
|
308
|
+
: new Dependency(name, packageVersion, dependencyType, EvaluationResult: evaluationResult, IsUpdate: isUpdate);
|
278
309
|
}
|
279
310
|
}
|
280
311
|
|
281
312
|
/// <summary>
|
282
313
|
/// Given an MSBuild string and a set of properties, returns our best guess at the final value MSBuild will evaluate to.
|
283
314
|
/// </summary>
|
284
|
-
public static EvaluationResult GetEvaluatedValue(string msbuildString,
|
315
|
+
public static EvaluationResult GetEvaluatedValue(string msbuildString, IReadOnlyDictionary<string, Property> propertyInfo, params string[] propertiesToIgnore)
|
285
316
|
{
|
286
317
|
var ignoredProperties = new HashSet<string>(propertiesToIgnore, StringComparer.OrdinalIgnoreCase);
|
287
318
|
var seenProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
288
319
|
|
320
|
+
string originalValue = msbuildString;
|
321
|
+
string? rootPropertyName = null;
|
289
322
|
while (TryGetPropertyName(msbuildString, out var propertyName))
|
290
323
|
{
|
324
|
+
rootPropertyName = propertyName;
|
325
|
+
|
291
326
|
if (ignoredProperties.Contains(propertyName))
|
292
327
|
{
|
293
|
-
return (EvaluationResultType.PropertyIgnored, msbuildString, $"Property '{propertyName}' is ignored.");
|
328
|
+
return new(EvaluationResultType.PropertyIgnored, originalValue, msbuildString, rootPropertyName, $"Property '{propertyName}' is ignored.");
|
294
329
|
}
|
295
330
|
|
296
331
|
if (!seenProperties.Add(propertyName))
|
297
332
|
{
|
298
|
-
return (EvaluationResultType.CircularReference, msbuildString, $"Property '{propertyName}' has a circular reference.");
|
333
|
+
return new(EvaluationResultType.CircularReference, originalValue, msbuildString, rootPropertyName, $"Property '{propertyName}' has a circular reference.");
|
299
334
|
}
|
300
335
|
|
301
|
-
if (!propertyInfo.TryGetValue(propertyName, out var
|
336
|
+
if (!propertyInfo.TryGetValue(propertyName, out var property))
|
302
337
|
{
|
303
|
-
return (EvaluationResultType.PropertyNotFound, msbuildString, $"Property '{propertyName}' was not found.");
|
338
|
+
return new(EvaluationResultType.PropertyNotFound, originalValue, msbuildString, rootPropertyName, $"Property '{propertyName}' was not found.");
|
304
339
|
}
|
305
340
|
|
306
|
-
msbuildString = msbuildString.Replace($"$({propertyName})",
|
341
|
+
msbuildString = msbuildString.Replace($"$({propertyName})", property.Value);
|
307
342
|
}
|
308
343
|
|
309
|
-
return (EvaluationResultType.Success, msbuildString, null);
|
344
|
+
return new(EvaluationResultType.Success, originalValue, msbuildString, rootPropertyName, null);
|
310
345
|
}
|
311
346
|
|
312
347
|
public static bool TryGetPropertyName(string versionContent, [NotNullWhen(true)] out string? propertyName)
|
@@ -373,12 +408,13 @@ internal static partial class MSBuildHelper
|
|
373
408
|
}
|
374
409
|
}
|
375
410
|
|
376
|
-
|
411
|
+
internal static async Task<string> CreateTempProjectAsync(
|
377
412
|
DirectoryInfo tempDir,
|
378
413
|
string repoRoot,
|
379
414
|
string projectPath,
|
380
415
|
string targetFramework,
|
381
|
-
IReadOnlyCollection<Dependency> packages
|
416
|
+
IReadOnlyCollection<Dependency> packages,
|
417
|
+
bool usePackageDownload = false)
|
382
418
|
{
|
383
419
|
var projectDirectory = Path.GetDirectoryName(projectPath);
|
384
420
|
projectDirectory ??= repoRoot;
|
@@ -410,9 +446,9 @@ internal static partial class MSBuildHelper
|
|
410
446
|
Environment.NewLine,
|
411
447
|
packages
|
412
448
|
// empty `Version` attributes will cause the temporary project to not build
|
413
|
-
.Where(p => !string.IsNullOrWhiteSpace(p.Version))
|
449
|
+
.Where(p => (p.EvaluationResult is null || p.EvaluationResult.ResultType == EvaluationResultType.Success) && !string.IsNullOrWhiteSpace(p.Version))
|
414
450
|
// 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.
|
415
|
-
.Select(
|
451
|
+
.Select(p => $"<{(usePackageDownload ? "PackageDownload" : "PackageReference")} {(p.IsUpdate ? "Update" : "Include")}=\"{p.Name}\" Version=\"[{p.Version}]\" />"));
|
416
452
|
|
417
453
|
var projectContents = $"""
|
418
454
|
<Project Sdk="Microsoft.NET.Sdk">
|
@@ -465,23 +501,34 @@ internal static partial class MSBuildHelper
|
|
465
501
|
}
|
466
502
|
|
467
503
|
internal static async Task<Dependency[]> GetAllPackageDependenciesAsync(
|
468
|
-
string repoRoot,
|
504
|
+
string repoRoot,
|
505
|
+
string projectPath,
|
506
|
+
string targetFramework,
|
507
|
+
IReadOnlyCollection<Dependency> packages,
|
508
|
+
Logger? logger = null)
|
469
509
|
{
|
470
510
|
var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-resolution_");
|
471
511
|
try
|
472
512
|
{
|
513
|
+
var topLevelPackagesNames = packages.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
473
514
|
var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
|
474
515
|
|
475
516
|
var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"build \"{tempProjectPath}\" /t:_ReportDependencies", workingDirectory: tempDirectory.FullName);
|
476
517
|
|
477
518
|
if (exitCode == 0)
|
478
519
|
{
|
520
|
+
ImmutableArray<string> tfms = [targetFramework];
|
479
521
|
var lines = stdout.Split('\n').Select(line => line.Trim());
|
480
522
|
var pattern = PackagePattern();
|
481
523
|
var allDependencies = lines
|
482
524
|
.Select(line => pattern.Match(line))
|
483
525
|
.Where(match => match.Success)
|
484
|
-
.Select(match =>
|
526
|
+
.Select(match =>
|
527
|
+
{
|
528
|
+
var packageName = match.Groups["PackageName"].Value;
|
529
|
+
var isTransitive = !topLevelPackagesNames.Contains(packageName);
|
530
|
+
return new Dependency(packageName, match.Groups["PackageVersion"].Value, DependencyType.Unknown, TargetFrameworks: tfms, IsTransitive: isTransitive);
|
531
|
+
})
|
485
532
|
.ToArray();
|
486
533
|
|
487
534
|
return allDependencies;
|
@@ -504,12 +551,25 @@ internal static partial class MSBuildHelper
|
|
504
551
|
}
|
505
552
|
}
|
506
553
|
|
507
|
-
internal static
|
554
|
+
internal static bool TryGetGlobalJsonPath(string repoRootPath, string workspacePath, [NotNullWhen(returnValue: true)] out string? globalJsonPath)
|
508
555
|
{
|
509
|
-
|
556
|
+
globalJsonPath = PathHelper.GetFileInDirectoryOrParent(workspacePath, repoRootPath, "global.json", caseSensitive: false);
|
557
|
+
return globalJsonPath is not null;
|
510
558
|
}
|
511
559
|
|
512
|
-
internal static
|
560
|
+
internal static bool TryGetDotNetToolsJsonPath(string repoRootPath, string workspacePath, [NotNullWhen(returnValue: true)] out string? dotnetToolsJsonJsonPath)
|
561
|
+
{
|
562
|
+
dotnetToolsJsonJsonPath = PathHelper.GetFileInDirectoryOrParent(workspacePath, repoRootPath, "./.config/dotnet-tools.json", caseSensitive: false);
|
563
|
+
return dotnetToolsJsonJsonPath is not null;
|
564
|
+
}
|
565
|
+
|
566
|
+
internal static bool TryGetDirectoryPackagesPropsPath(string repoRootPath, string workspacePath, [NotNullWhen(returnValue: true)] out string? directoryPackagesPropsPath)
|
567
|
+
{
|
568
|
+
directoryPackagesPropsPath = PathHelper.GetFileInDirectoryOrParent(workspacePath, repoRootPath, "./Directory.Packages.props", caseSensitive: false);
|
569
|
+
return directoryPackagesPropsPath is not null;
|
570
|
+
}
|
571
|
+
|
572
|
+
internal static async Task<ImmutableArray<ProjectBuildFile>> LoadBuildFilesAsync(string repoRootPath, string projectPath, bool includeSdkPropsAndTargets = false)
|
513
573
|
{
|
514
574
|
var buildFileList = new List<string>
|
515
575
|
{
|
@@ -517,7 +577,7 @@ internal static partial class MSBuildHelper
|
|
517
577
|
};
|
518
578
|
|
519
579
|
// a global.json file might cause problems with the dotnet msbuild command; create a safe version temporarily
|
520
|
-
|
580
|
+
TryGetGlobalJsonPath(repoRootPath, projectPath, out var globalJsonPath);
|
521
581
|
var safeGlobalJsonName = $"{globalJsonPath}{Guid.NewGuid()}";
|
522
582
|
|
523
583
|
try
|
@@ -567,12 +627,13 @@ internal static partial class MSBuildHelper
|
|
567
627
|
}
|
568
628
|
|
569
629
|
var repoRootPathPrefix = repoRootPath.NormalizePathToUnix() + "/";
|
570
|
-
var
|
571
|
-
|
630
|
+
var buildFiles = includeSdkPropsAndTargets
|
631
|
+
? buildFileList.Distinct()
|
632
|
+
: buildFileList
|
633
|
+
.Where(f => f.StartsWith(repoRootPathPrefix, StringComparison.OrdinalIgnoreCase))
|
634
|
+
.Distinct();
|
635
|
+
var result = buildFiles
|
572
636
|
.Where(File.Exists)
|
573
|
-
.Distinct()
|
574
|
-
.ToArray();
|
575
|
-
var result = buildFilesInRepo
|
576
637
|
.Select(path => ProjectBuildFile.Open(repoRootPath, path))
|
577
638
|
.ToImmutableArray();
|
578
639
|
return result;
|
@@ -580,12 +641,4 @@ internal static partial class MSBuildHelper
|
|
580
641
|
|
581
642
|
[GeneratedRegex("^\\s*NuGetData::Package=(?<PackageName>[^,]+), Version=(?<PackageVersion>.+)$")]
|
582
643
|
private static partial Regex PackagePattern();
|
583
|
-
|
584
|
-
internal enum EvaluationResultType
|
585
|
-
{
|
586
|
-
Success,
|
587
|
-
PropertyIgnored,
|
588
|
-
CircularReference,
|
589
|
-
PropertyNotFound,
|
590
|
-
}
|
591
644
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
using System.
|
1
|
+
using System.Diagnostics.CodeAnalysis;
|
2
2
|
|
3
3
|
namespace NuGetUpdater.Core;
|
4
4
|
|
@@ -6,10 +6,33 @@ internal static class NuGetHelper
|
|
6
6
|
{
|
7
7
|
internal const string PackagesConfigFileName = "packages.config";
|
8
8
|
|
9
|
-
public static bool
|
9
|
+
public static bool TryGetPackagesConfigFile(string projectPath, [NotNullWhen(returnValue: true)] out string? packagesConfigPath)
|
10
10
|
{
|
11
11
|
var projectDirectory = Path.GetDirectoryName(projectPath);
|
12
|
-
|
13
|
-
|
12
|
+
|
13
|
+
packagesConfigPath = PathHelper.JoinPath(projectDirectory, PackagesConfigFileName);
|
14
|
+
if (File.Exists(packagesConfigPath))
|
15
|
+
{
|
16
|
+
return true;
|
17
|
+
}
|
18
|
+
|
19
|
+
packagesConfigPath = null;
|
20
|
+
return false;
|
21
|
+
}
|
22
|
+
|
23
|
+
internal static async Task<bool> DownloadNuGetPackagesAsync(string repoRoot, string projectPath, IReadOnlyCollection<Dependency> packages, Logger logger)
|
24
|
+
{
|
25
|
+
var tempDirectory = Directory.CreateTempSubdirectory("msbuild_sdk_restore_");
|
26
|
+
try
|
27
|
+
{
|
28
|
+
var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, "netstandard2.0", packages, usePackageDownload: true);
|
29
|
+
var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"");
|
30
|
+
|
31
|
+
return exitCode == 0;
|
32
|
+
}
|
33
|
+
finally
|
34
|
+
{
|
35
|
+
tempDirectory.Delete(recursive: true);
|
36
|
+
}
|
14
37
|
}
|
15
38
|
}
|
@@ -0,0 +1,117 @@
|
|
1
|
+
using System.Collections.Immutable;
|
2
|
+
using System.Text.Json;
|
3
|
+
|
4
|
+
using NuGetUpdater.Core.Discover;
|
5
|
+
using NuGetUpdater.Core.Test.Utilities;
|
6
|
+
|
7
|
+
using Xunit;
|
8
|
+
|
9
|
+
namespace NuGetUpdater.Core.Test.Discover;
|
10
|
+
|
11
|
+
using TestFile = (string Path, string Content);
|
12
|
+
|
13
|
+
public class DiscoveryWorkerTestBase
|
14
|
+
{
|
15
|
+
protected static async Task TestDiscoveryAsync(
|
16
|
+
string workspacePath,
|
17
|
+
TestFile[] files,
|
18
|
+
ExpectedWorkspaceDiscoveryResult expectedResult)
|
19
|
+
{
|
20
|
+
var actualResult = await RunDiscoveryAsync(files, async directoryPath =>
|
21
|
+
{
|
22
|
+
var worker = new DiscoveryWorker(new Logger(verbose: true));
|
23
|
+
await worker.RunAsync(directoryPath, workspacePath, DiscoveryWorker.DiscoveryResultFileName);
|
24
|
+
});
|
25
|
+
|
26
|
+
ValidateWorkspaceResult(expectedResult, actualResult);
|
27
|
+
}
|
28
|
+
|
29
|
+
protected static void ValidateWorkspaceResult(ExpectedWorkspaceDiscoveryResult expectedResult, WorkspaceDiscoveryResult actualResult)
|
30
|
+
{
|
31
|
+
Assert.NotNull(actualResult);
|
32
|
+
Assert.Equal(expectedResult.FilePath, actualResult.FilePath);
|
33
|
+
ValidateDirectoryPackagesProps(expectedResult.DirectoryPackagesProps, actualResult.DirectoryPackagesProps);
|
34
|
+
ValidateResultWithDependencies(expectedResult.GlobalJson, actualResult.GlobalJson);
|
35
|
+
ValidateResultWithDependencies(expectedResult.DotNetToolsJson, actualResult.DotNetToolsJson);
|
36
|
+
ValidateProjectResults(expectedResult.Projects, actualResult.Projects);
|
37
|
+
Assert.Equal(expectedResult.ExpectedProjectCount ?? expectedResult.Projects.Length, actualResult.Projects.Length);
|
38
|
+
|
39
|
+
return;
|
40
|
+
|
41
|
+
void ValidateResultWithDependencies(ExpectedDependencyDiscoveryResult? expectedResult, IDiscoveryResultWithDependencies? actualResult)
|
42
|
+
{
|
43
|
+
if (expectedResult is null)
|
44
|
+
{
|
45
|
+
Assert.Null(actualResult);
|
46
|
+
return;
|
47
|
+
}
|
48
|
+
else
|
49
|
+
{
|
50
|
+
Assert.NotNull(actualResult);
|
51
|
+
}
|
52
|
+
|
53
|
+
Assert.Equal(expectedResult.FilePath, actualResult.FilePath);
|
54
|
+
ValidateDependencies(expectedResult.Dependencies, actualResult.Dependencies);
|
55
|
+
Assert.Equal(expectedResult.ExpectedDependencyCount ?? expectedResult.Dependencies.Length, actualResult.Dependencies.Length);
|
56
|
+
}
|
57
|
+
|
58
|
+
void ValidateProjectResults(ImmutableArray<ExpectedSdkProjectDiscoveryResult> expectedProjects, ImmutableArray<ProjectDiscoveryResult> actualProjects)
|
59
|
+
{
|
60
|
+
if (expectedProjects.IsDefaultOrEmpty)
|
61
|
+
{
|
62
|
+
return;
|
63
|
+
}
|
64
|
+
|
65
|
+
foreach (var expectedProject in expectedProjects)
|
66
|
+
{
|
67
|
+
var actualProject = actualProjects.Single(p => p.FilePath == expectedProject.FilePath);
|
68
|
+
|
69
|
+
Assert.Equal(expectedProject.FilePath, actualProject.FilePath);
|
70
|
+
AssertEx.Equal(expectedProject.Properties, actualProject.Properties);
|
71
|
+
AssertEx.Equal(expectedProject.TargetFrameworks, actualProject.TargetFrameworks);
|
72
|
+
AssertEx.Equal(expectedProject.ReferencedProjectPaths, actualProject.ReferencedProjectPaths);
|
73
|
+
ValidateDependencies(expectedProject.Dependencies, actualProject.Dependencies);
|
74
|
+
Assert.Equal(expectedProject.ExpectedDependencyCount ?? expectedProject.Dependencies.Length, actualProject.Dependencies.Length);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
void ValidateDirectoryPackagesProps(ExpectedDirectoryPackagesPropsDiscovertyResult? expected, DirectoryPackagesPropsDiscoveryResult? actual)
|
79
|
+
{
|
80
|
+
ValidateResultWithDependencies(expected, actual);
|
81
|
+
Assert.Equal(expected?.IsTransitivePinningEnabled, actual?.IsTransitivePinningEnabled);
|
82
|
+
}
|
83
|
+
|
84
|
+
void ValidateDependencies(ImmutableArray<Dependency> expectedDependencies, ImmutableArray<Dependency> actualDependencies)
|
85
|
+
{
|
86
|
+
if (expectedDependencies.IsDefault)
|
87
|
+
{
|
88
|
+
return;
|
89
|
+
}
|
90
|
+
|
91
|
+
foreach (var expectedDependency in expectedDependencies)
|
92
|
+
{
|
93
|
+
var actualDependency = actualDependencies.Single(d => d.Name == expectedDependency.Name);
|
94
|
+
Assert.Equal(expectedDependency.Name, actualDependency.Name);
|
95
|
+
Assert.Equal(expectedDependency.Version, actualDependency.Version);
|
96
|
+
Assert.Equal(expectedDependency.Type, actualDependency.Type);
|
97
|
+
AssertEx.Equal(expectedDependency.TargetFrameworks, actualDependency.TargetFrameworks);
|
98
|
+
Assert.Equal(expectedDependency.IsDirect, actualDependency.IsDirect);
|
99
|
+
Assert.Equal(expectedDependency.IsTransitive, actualDependency.IsTransitive);
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
protected static async Task<WorkspaceDiscoveryResult> RunDiscoveryAsync(TestFile[] files, Func<string, Task> action)
|
105
|
+
{
|
106
|
+
// write initial files
|
107
|
+
using var temporaryDirectory = await TemporaryDirectory.CreateWithContentsAsync(files);
|
108
|
+
|
109
|
+
// run discovery
|
110
|
+
await action(temporaryDirectory.DirectoryPath);
|
111
|
+
|
112
|
+
// gather results
|
113
|
+
var resultPath = Path.Join(temporaryDirectory.DirectoryPath, DiscoveryWorker.DiscoveryResultFileName);
|
114
|
+
var resultJson = await File.ReadAllTextAsync(resultPath);
|
115
|
+
return JsonSerializer.Deserialize<WorkspaceDiscoveryResult>(resultJson, DiscoveryWorker.SerializerOptions)!;
|
116
|
+
}
|
117
|
+
}
|
@@ -0,0 +1,91 @@
|
|
1
|
+
using Xunit;
|
2
|
+
|
3
|
+
namespace NuGetUpdater.Core.Test.Discover;
|
4
|
+
|
5
|
+
public partial class DiscoveryWorkerTests
|
6
|
+
{
|
7
|
+
public class DotNetToolsJson : DiscoveryWorkerTestBase
|
8
|
+
{
|
9
|
+
[Fact]
|
10
|
+
public async Task DiscoversDependencies()
|
11
|
+
{
|
12
|
+
await TestDiscoveryAsync(
|
13
|
+
workspacePath: "",
|
14
|
+
files: [
|
15
|
+
(".config/dotnet-tools.json", """
|
16
|
+
{
|
17
|
+
"version": 1,
|
18
|
+
"isRoot": true,
|
19
|
+
"tools": {
|
20
|
+
"botsay": {
|
21
|
+
"version": "1.0.0",
|
22
|
+
"commands": [
|
23
|
+
"botsay"
|
24
|
+
]
|
25
|
+
},
|
26
|
+
"dotnetsay": {
|
27
|
+
"version": "1.0.0",
|
28
|
+
"commands": [
|
29
|
+
"dotnetsay"
|
30
|
+
]
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
"""),
|
35
|
+
],
|
36
|
+
expectedResult: new()
|
37
|
+
{
|
38
|
+
FilePath = "",
|
39
|
+
DotNetToolsJson = new()
|
40
|
+
{
|
41
|
+
FilePath = ".config/dotnet-tools.json",
|
42
|
+
Dependencies = [
|
43
|
+
new("botsay", "1.0.0", DependencyType.DotNetTool),
|
44
|
+
new("dotnetsay", "1.0.0", DependencyType.DotNetTool),
|
45
|
+
]
|
46
|
+
},
|
47
|
+
ExpectedProjectCount = 0,
|
48
|
+
});
|
49
|
+
}
|
50
|
+
|
51
|
+
[Fact]
|
52
|
+
public async Task ReportsFailure()
|
53
|
+
{
|
54
|
+
await TestDiscoveryAsync(
|
55
|
+
workspacePath: "",
|
56
|
+
files: [
|
57
|
+
(".config/dotnet-tools.json", """
|
58
|
+
{
|
59
|
+
"version": 1,
|
60
|
+
"isRoot": true,
|
61
|
+
"tools": {
|
62
|
+
"botsay": {
|
63
|
+
"version": "1.0.0",
|
64
|
+
"commands": [
|
65
|
+
"botsay"
|
66
|
+
],
|
67
|
+
},
|
68
|
+
"dotnetsay": {
|
69
|
+
"version": "1.0.0",
|
70
|
+
"commands": [
|
71
|
+
"dotnetsay"
|
72
|
+
]
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
"""),
|
77
|
+
],
|
78
|
+
expectedResult: new()
|
79
|
+
{
|
80
|
+
FilePath = "",
|
81
|
+
DotNetToolsJson = new()
|
82
|
+
{
|
83
|
+
FilePath = ".config/dotnet-tools.json",
|
84
|
+
IsSuccess = false,
|
85
|
+
ExpectedDependencyCount = 0,
|
86
|
+
},
|
87
|
+
ExpectedProjectCount = 0,
|
88
|
+
});
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|