dependabot-nuget 0.265.0 → 0.267.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/NuGetUpdater.Cli/Commands/UpdateCommand.cs +6 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +173 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalysisResult.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +92 -79
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +21 -8
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/NuGetContext.cs +36 -9
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Requirement.cs +88 -45
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +33 -16
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +44 -25
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +9 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +2 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/MissingFileException.cs +11 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NativeResult.cs +8 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +45 -42
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +19 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +2 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationResult.cs +5 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +70 -22
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +29 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +6 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +450 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/CompatibilityCheckerTests.cs +23 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/RequirementTests.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +2 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +148 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +17 -22
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestHttpServer.cs +81 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +27 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +32 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +447 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +88 -0
- data/lib/dependabot/nuget/analysis/dependency_analysis.rb +3 -0
- data/lib/dependabot/nuget/file_fetcher.rb +30 -11
- data/lib/dependabot/nuget/file_updater.rb +2 -0
- data/lib/dependabot/nuget/metadata_finder.rb +160 -2
- data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +3 -0
- data/lib/dependabot/nuget/native_helpers.rb +36 -3
- data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +1 -0
- data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +3 -0
- metadata +12 -7
@@ -12,7 +12,59 @@ namespace NuGetUpdater.Core.Analyze;
|
|
12
12
|
/// See Gem::Version for a description on how versions and requirements work
|
13
13
|
/// together in RubyGems.
|
14
14
|
/// </remarks>
|
15
|
-
public class Requirement
|
15
|
+
public abstract class Requirement
|
16
|
+
{
|
17
|
+
public abstract bool IsSatisfiedBy(NuGetVersion version);
|
18
|
+
|
19
|
+
private static readonly Dictionary<string, Version> BumpMap = [];
|
20
|
+
/// <summary>
|
21
|
+
/// Return a new version object where the next to the last revision
|
22
|
+
/// number is one greater (e.g., 5.3.1 => 5.4).
|
23
|
+
/// </summary>
|
24
|
+
/// <remarks>
|
25
|
+
/// This logic intended to be similar to RubyGems Gem::Version#bump
|
26
|
+
/// </remarks>
|
27
|
+
public static Version Bump(NuGetVersion version)
|
28
|
+
{
|
29
|
+
if (BumpMap.TryGetValue(version.OriginalVersion!, out var bumpedVersion))
|
30
|
+
{
|
31
|
+
return bumpedVersion;
|
32
|
+
}
|
33
|
+
|
34
|
+
var versionParts = version.OriginalVersion! // Get the original string this version was created from
|
35
|
+
.Split('-')[0] // Get the version part without pre-release
|
36
|
+
.Split('.') // Split into Major.Minor.Patch.Revision if present
|
37
|
+
.Select(int.Parse)
|
38
|
+
.ToArray();
|
39
|
+
|
40
|
+
if (versionParts.Length > 1)
|
41
|
+
{
|
42
|
+
versionParts = versionParts[..^1]; // Remove the last part
|
43
|
+
}
|
44
|
+
|
45
|
+
versionParts[^1]++; // Increment the new last part
|
46
|
+
|
47
|
+
bumpedVersion = NuGetVersion.Parse(string.Join('.', versionParts)).Version;
|
48
|
+
BumpMap[version.OriginalVersion!] = bumpedVersion;
|
49
|
+
|
50
|
+
return bumpedVersion;
|
51
|
+
}
|
52
|
+
|
53
|
+
public static Requirement Parse(string requirement)
|
54
|
+
{
|
55
|
+
var specificParts = requirement.Split(',');
|
56
|
+
if (specificParts.Length == 1)
|
57
|
+
{
|
58
|
+
return IndividualRequirement.ParseIndividual(requirement);
|
59
|
+
}
|
60
|
+
|
61
|
+
var specificRequirements = specificParts.Select(IndividualRequirement.ParseIndividual).ToArray();
|
62
|
+
return new MultiPartRequirement(specificRequirements);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
public class IndividualRequirement : Requirement
|
16
68
|
{
|
17
69
|
private static readonly ImmutableDictionary<string, Func<NuGetVersion, NuGetVersion, bool>> Operators = new Dictionary<string, Func<NuGetVersion, NuGetVersion, bool>>()
|
18
70
|
{
|
@@ -25,30 +77,10 @@ public class Requirement
|
|
25
77
|
["~>"] = (v, r) => v >= r && v.Version < Bump(r),
|
26
78
|
}.ToImmutableDictionary();
|
27
79
|
|
28
|
-
public static Requirement Parse(string requirement)
|
29
|
-
{
|
30
|
-
var splitIndex = requirement.LastIndexOfAny(['=', '>', '<']);
|
31
|
-
|
32
|
-
// Throw if the requirement is all operator and no version.
|
33
|
-
if (splitIndex == requirement.Length - 1)
|
34
|
-
{
|
35
|
-
throw new ArgumentException($"`{requirement}` is a invalid requirement string", nameof(requirement));
|
36
|
-
}
|
37
|
-
|
38
|
-
string[] parts = splitIndex == -1
|
39
|
-
? [requirement.Trim()]
|
40
|
-
: [requirement[..(splitIndex + 1)].Trim(), requirement[(splitIndex + 1)..].Trim()];
|
41
|
-
|
42
|
-
var op = parts.Length == 1 ? "=" : parts[0];
|
43
|
-
var version = NuGetVersion.Parse(parts[^1]);
|
44
|
-
|
45
|
-
return new Requirement(op, version);
|
46
|
-
}
|
47
|
-
|
48
80
|
public string Operator { get; }
|
49
81
|
public NuGetVersion Version { get; }
|
50
82
|
|
51
|
-
public
|
83
|
+
public IndividualRequirement(string op, NuGetVersion version)
|
52
84
|
{
|
53
85
|
if (!Operators.ContainsKey(op))
|
54
86
|
{
|
@@ -64,42 +96,53 @@ public class Requirement
|
|
64
96
|
return $"{Operator} {Version}";
|
65
97
|
}
|
66
98
|
|
67
|
-
public bool IsSatisfiedBy(NuGetVersion version)
|
99
|
+
public override bool IsSatisfiedBy(NuGetVersion version)
|
68
100
|
{
|
69
101
|
return Operators[Operator](version, Version);
|
70
102
|
}
|
71
103
|
|
72
|
-
|
73
|
-
/// <summary>
|
74
|
-
/// Return a new version object where the next to the last revision
|
75
|
-
/// number is one greater (e.g., 5.3.1 => 5.4).
|
76
|
-
/// </summary>
|
77
|
-
/// <remarks>
|
78
|
-
/// This logic intended to be similar to RubyGems Gem::Version#bump
|
79
|
-
/// </remarks>
|
80
|
-
public static Version Bump(NuGetVersion version)
|
104
|
+
public static IndividualRequirement ParseIndividual(string requirement)
|
81
105
|
{
|
82
|
-
|
106
|
+
var splitIndex = requirement.LastIndexOfAny(['=', '>', '<']);
|
107
|
+
|
108
|
+
// Throw if the requirement is all operator and no version.
|
109
|
+
if (splitIndex == requirement.Length - 1)
|
83
110
|
{
|
84
|
-
|
111
|
+
throw new ArgumentException($"`{requirement}` is a invalid requirement string", nameof(requirement));
|
85
112
|
}
|
86
113
|
|
87
|
-
|
88
|
-
.
|
89
|
-
.
|
90
|
-
.Select(int.Parse)
|
91
|
-
.ToArray();
|
114
|
+
string[] parts = splitIndex == -1
|
115
|
+
? [requirement.Trim()]
|
116
|
+
: [requirement[..(splitIndex + 1)].Trim(), requirement[(splitIndex + 1)..].Trim()];
|
92
117
|
|
93
|
-
|
118
|
+
var op = parts.Length == 1 ? "=" : parts[0];
|
119
|
+
var version = NuGetVersion.Parse(parts[^1]);
|
120
|
+
|
121
|
+
return new IndividualRequirement(op, version);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
public class MultiPartRequirement : Requirement
|
126
|
+
{
|
127
|
+
public ImmutableArray<IndividualRequirement> Parts { get; }
|
128
|
+
|
129
|
+
public MultiPartRequirement(IndividualRequirement[] parts)
|
130
|
+
{
|
131
|
+
if (parts.Length <= 1)
|
94
132
|
{
|
95
|
-
|
133
|
+
throw new ArgumentException("At least two parts are required", nameof(parts));
|
96
134
|
}
|
97
135
|
|
98
|
-
|
136
|
+
Parts = parts.ToImmutableArray();
|
137
|
+
}
|
99
138
|
|
100
|
-
|
101
|
-
|
139
|
+
public override string ToString()
|
140
|
+
{
|
141
|
+
return string.Join(", ", Parts);
|
142
|
+
}
|
102
143
|
|
103
|
-
|
144
|
+
public override bool IsSatisfiedBy(NuGetVersion version)
|
145
|
+
{
|
146
|
+
return Parts.All(part => part.IsSatisfiedBy(version));
|
104
147
|
}
|
105
148
|
}
|
@@ -66,15 +66,24 @@ internal static class VersionFinder
|
|
66
66
|
continue;
|
67
67
|
}
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
69
|
+
try
|
70
|
+
{
|
71
|
+
// a non-compliant v2 API returning 404 can cause this to throw
|
72
|
+
var existsInFeed = await feed.Exists(
|
73
|
+
packageId,
|
74
|
+
includePrerelease,
|
75
|
+
includeUnlisted: false,
|
76
|
+
nugetContext.SourceCacheContext,
|
77
|
+
NullLogger.Instance,
|
78
|
+
cancellationToken);
|
79
|
+
if (!existsInFeed)
|
80
|
+
{
|
81
|
+
continue;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
catch (FatalProtocolException)
|
77
85
|
{
|
86
|
+
// if anything goes wrong here, the package source obviously doesn't contain the requested package
|
78
87
|
continue;
|
79
88
|
}
|
80
89
|
|
@@ -162,15 +171,23 @@ internal static class VersionFinder
|
|
162
171
|
continue;
|
163
172
|
}
|
164
173
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
174
|
+
try
|
175
|
+
{
|
176
|
+
// a non-compliant v2 API returning 404 can cause this to throw
|
177
|
+
var existsInFeed = await feed.Exists(
|
178
|
+
new PackageIdentity(packageId, version),
|
179
|
+
includeUnlisted: false,
|
180
|
+
nugetContext.SourceCacheContext,
|
181
|
+
NullLogger.Instance,
|
182
|
+
cancellationToken);
|
183
|
+
if (existsInFeed)
|
184
|
+
{
|
185
|
+
return true;
|
186
|
+
}
|
187
|
+
}
|
188
|
+
catch (FatalProtocolException)
|
172
189
|
{
|
173
|
-
|
190
|
+
// if anything goes wrong here, the package source obviously doesn't contain the requested package
|
174
191
|
}
|
175
192
|
}
|
176
193
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
using System.Collections.Immutable;
|
2
|
+
using System.Net;
|
2
3
|
using System.Text.Json;
|
3
4
|
using System.Text.Json.Serialization;
|
4
5
|
|
@@ -6,6 +7,7 @@ using Microsoft.Build.Construction;
|
|
6
7
|
using Microsoft.Build.Definition;
|
7
8
|
using Microsoft.Build.Evaluation;
|
8
9
|
|
10
|
+
using NuGetUpdater.Core.Analyze;
|
9
11
|
using NuGetUpdater.Core.Utilities;
|
10
12
|
|
11
13
|
namespace NuGetUpdater.Core.Discover;
|
@@ -47,43 +49,60 @@ public partial class DiscoveryWorker
|
|
47
49
|
DirectoryPackagesPropsDiscoveryResult? directoryPackagesPropsDiscovery = null;
|
48
50
|
|
49
51
|
ImmutableArray<ProjectDiscoveryResult> projectResults = [];
|
52
|
+
WorkspaceDiscoveryResult result;
|
50
53
|
|
51
|
-
|
54
|
+
try
|
52
55
|
{
|
53
|
-
|
56
|
+
if (Directory.Exists(workspacePath))
|
57
|
+
{
|
58
|
+
_logger.Log($"Discovering build files in workspace [{workspacePath}].");
|
54
59
|
|
55
|
-
|
56
|
-
|
60
|
+
dotNetToolsJsonDiscovery = DotNetToolsJsonDiscovery.Discover(repoRootPath, workspacePath, _logger);
|
61
|
+
globalJsonDiscovery = GlobalJsonDiscovery.Discover(repoRootPath, workspacePath, _logger);
|
57
62
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
63
|
+
if (globalJsonDiscovery is not null)
|
64
|
+
{
|
65
|
+
await TryRestoreMSBuildSdksAsync(repoRootPath, workspacePath, globalJsonDiscovery.Dependencies, _logger);
|
66
|
+
}
|
62
67
|
|
63
|
-
|
68
|
+
// this next line should throw or something
|
69
|
+
projectResults = await RunForDirectoryAsnyc(repoRootPath, workspacePath);
|
64
70
|
|
65
|
-
|
71
|
+
directoryPackagesPropsDiscovery = DirectoryPackagesPropsDiscovery.Discover(repoRootPath, workspacePath, projectResults, _logger);
|
66
72
|
|
67
|
-
|
73
|
+
if (directoryPackagesPropsDiscovery is not null)
|
74
|
+
{
|
75
|
+
projectResults = projectResults.Remove(projectResults.First(p => p.FilePath.Equals(directoryPackagesPropsDiscovery.FilePath, StringComparison.OrdinalIgnoreCase)));
|
76
|
+
}
|
77
|
+
}
|
78
|
+
else
|
68
79
|
{
|
69
|
-
|
80
|
+
_logger.Log($"Workspace path [{workspacePath}] does not exist.");
|
70
81
|
}
|
82
|
+
|
83
|
+
result = new WorkspaceDiscoveryResult
|
84
|
+
{
|
85
|
+
Path = initialWorkspacePath,
|
86
|
+
DotNetToolsJson = dotNetToolsJsonDiscovery,
|
87
|
+
GlobalJson = globalJsonDiscovery,
|
88
|
+
DirectoryPackagesProps = directoryPackagesPropsDiscovery,
|
89
|
+
Projects = projectResults.OrderBy(p => p.FilePath).ToImmutableArray(),
|
90
|
+
};
|
71
91
|
}
|
72
|
-
|
92
|
+
catch (HttpRequestException ex)
|
93
|
+
when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
|
73
94
|
{
|
74
|
-
|
95
|
+
// TODO: consolidate this error handling between AnalyzeWorker, DiscoveryWorker, and UpdateWorker
|
96
|
+
result = new WorkspaceDiscoveryResult
|
97
|
+
{
|
98
|
+
ErrorType = ErrorType.AuthenticationFailure,
|
99
|
+
ErrorDetails = "(" + string.Join("|", NuGetContext.GetPackageSourceUrls(workspacePath)) + ")",
|
100
|
+
Path = initialWorkspacePath,
|
101
|
+
Projects = [],
|
102
|
+
};
|
75
103
|
}
|
76
104
|
|
77
|
-
|
78
|
-
{
|
79
|
-
Path = initialWorkspacePath,
|
80
|
-
DotNetToolsJson = dotNetToolsJsonDiscovery,
|
81
|
-
GlobalJson = globalJsonDiscovery,
|
82
|
-
DirectoryPackagesProps = directoryPackagesPropsDiscovery,
|
83
|
-
Projects = projectResults.OrderBy(p => p.FilePath).ToImmutableArray(),
|
84
|
-
};
|
85
|
-
|
86
|
-
await WriteResults(repoRootPath, outputPath, result);
|
105
|
+
await WriteResultsAsync(repoRootPath, outputPath, result);
|
87
106
|
|
88
107
|
_logger.Log("Discovery complete.");
|
89
108
|
|
@@ -293,7 +312,7 @@ public partial class DiscoveryWorker
|
|
293
312
|
return [.. results.Values];
|
294
313
|
}
|
295
314
|
|
296
|
-
|
315
|
+
internal static async Task WriteResultsAsync(string repoRootPath, string outputPath, WorkspaceDiscoveryResult result)
|
297
316
|
{
|
298
317
|
var resultPath = Path.IsPathRooted(outputPath)
|
299
318
|
? outputPath
|
@@ -2,7 +2,7 @@ using System.Collections.Immutable;
|
|
2
2
|
|
3
3
|
namespace NuGetUpdater.Core.Discover;
|
4
4
|
|
5
|
-
public sealed record WorkspaceDiscoveryResult
|
5
|
+
public sealed record WorkspaceDiscoveryResult : NativeResult
|
6
6
|
{
|
7
7
|
public required string Path { get; init; }
|
8
8
|
public bool IsSuccess { get; init; } = true;
|
@@ -93,11 +93,11 @@ internal sealed class ProjectBuildFile : XmlBuildFile
|
|
93
93
|
{
|
94
94
|
var isUpdate = false;
|
95
95
|
|
96
|
-
var name = element.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase);
|
96
|
+
var name = element.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase)?.Trim();
|
97
97
|
if (name is null)
|
98
98
|
{
|
99
99
|
isUpdate = true;
|
100
|
-
name = element.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase);
|
100
|
+
name = element.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase)?.Trim();
|
101
101
|
}
|
102
102
|
|
103
103
|
if (name is null || name.StartsWith("@("))
|
@@ -149,42 +149,6 @@ internal static class BindingRedirectManager
|
|
149
149
|
return (element.Name == "None" && string.Equals(path, "app.config", StringComparison.OrdinalIgnoreCase))
|
150
150
|
|| (element.Name == "Content" && string.Equals(path, "web.config", StringComparison.OrdinalIgnoreCase));
|
151
151
|
}
|
152
|
-
|
153
|
-
static string GetConfigFileName(XmlDocumentSyntax document)
|
154
|
-
{
|
155
|
-
var guidValue = document.Descendants()
|
156
|
-
.Where(static x => x.Name == "PropertyGroup")
|
157
|
-
.SelectMany(static x => x.Elements.Where(static x => x.Name == "ProjectGuid"))
|
158
|
-
.FirstOrDefault()
|
159
|
-
?.GetContentValue();
|
160
|
-
return guidValue switch
|
161
|
-
{
|
162
|
-
"{E24C65DC-7377-472B-9ABA-BC803B73C61A}" or "{349C5851-65DF-11DA-9384-00065B846F21}" => "Web.config",
|
163
|
-
_ => "App.config"
|
164
|
-
};
|
165
|
-
}
|
166
|
-
|
167
|
-
static string GenerateDefaultAppConfig(XmlDocumentSyntax document)
|
168
|
-
{
|
169
|
-
var frameworkVersion = GetFrameworkVersion(document);
|
170
|
-
return $"""
|
171
|
-
<?xml version="1.0" encoding="utf-8" ?>
|
172
|
-
<configuration>
|
173
|
-
<startup>
|
174
|
-
<supportedRuntime version="v4.0" sku=".NETFramework,Version={frameworkVersion}" />
|
175
|
-
</startup>
|
176
|
-
</configuration>
|
177
|
-
""";
|
178
|
-
}
|
179
|
-
|
180
|
-
static string? GetFrameworkVersion(XmlDocumentSyntax document)
|
181
|
-
{
|
182
|
-
return document.Descendants()
|
183
|
-
.Where(static x => x.Name == "PropertyGroup")
|
184
|
-
.SelectMany(static x => x.Elements.Where(static x => x.Name == "TargetFrameworkVersion"))
|
185
|
-
.FirstOrDefault()
|
186
|
-
?.GetContentValue();
|
187
|
-
}
|
188
152
|
}
|
189
153
|
|
190
154
|
private static string AddBindingRedirects(ConfigurationFile configFile, IEnumerable<Runtime_AssemblyBinding> bindingRedirects)
|
@@ -213,10 +177,24 @@ internal static class BindingRedirectManager
|
|
213
177
|
|
214
178
|
foreach (var bindingRedirect in bindingRedirects)
|
215
179
|
{
|
216
|
-
//
|
217
|
-
|
180
|
+
// If the binding redirect already exists in config, update it. Otherwise, add it.
|
181
|
+
var bindingAssemblyIdentity = new AssemblyIdentity(bindingRedirect.Name, bindingRedirect.PublicKeyToken);
|
182
|
+
if (currentBindings.Contains(bindingAssemblyIdentity))
|
218
183
|
{
|
219
|
-
|
184
|
+
// Check if there are multiple bindings in config for this assembly and remove all but the first one.
|
185
|
+
// Normally there should only be one binding per assembly identity unless the config is malformed, which we'll fix here like NuGet.exe would.
|
186
|
+
var existingBindings = currentBindings[bindingAssemblyIdentity];
|
187
|
+
if (existingBindings.Any())
|
188
|
+
{
|
189
|
+
// Remove all but the first element
|
190
|
+
foreach (var bindingElement in existingBindings.Skip(1))
|
191
|
+
{
|
192
|
+
RemoveElement(bindingElement);
|
193
|
+
}
|
194
|
+
|
195
|
+
// Update the first one with the new binding
|
196
|
+
UpdateBindingRedirectElement(existingBindings.First(), bindingRedirect);
|
197
|
+
}
|
220
198
|
}
|
221
199
|
else
|
222
200
|
{
|
@@ -228,7 +206,10 @@ internal static class BindingRedirectManager
|
|
228
206
|
}
|
229
207
|
}
|
230
208
|
|
231
|
-
return
|
209
|
+
return string.Concat(
|
210
|
+
document.Declaration?.ToString() ?? String.Empty, // Ensure the <?xml> declaration node is preserved, if present
|
211
|
+
document.ToString()
|
212
|
+
);
|
232
213
|
|
233
214
|
static XDocument GetConfiguration(string configFileContent)
|
234
215
|
{
|
@@ -278,7 +259,7 @@ internal static class BindingRedirectManager
|
|
278
259
|
}
|
279
260
|
}
|
280
261
|
|
281
|
-
static
|
262
|
+
static ILookup<AssemblyIdentity, XElement> GetAssemblyBindings(XElement runtime)
|
282
263
|
{
|
283
264
|
var dependencyAssemblyElements = runtime.Elements(AssemblyBindingName)
|
284
265
|
.Elements(DependentAssemblyName);
|
@@ -291,7 +272,12 @@ internal static class BindingRedirectManager
|
|
291
272
|
});
|
292
273
|
|
293
274
|
// Return a mapping from binding to element
|
294
|
-
|
275
|
+
// It is possible that multiple elements exist for the same assembly identity, so use a lookup (1:*) instead of a dictionary (1:1)
|
276
|
+
return assemblyElementPairs.ToLookup(
|
277
|
+
p => new AssemblyIdentity(p.Binding.Name, p.Binding.PublicKeyToken),
|
278
|
+
p => p.Element,
|
279
|
+
new AssemblyIdentityIgnoreCaseComparer()
|
280
|
+
);
|
295
281
|
}
|
296
282
|
|
297
283
|
static XElement GetAssemblyBindingElement(XElement runtime)
|
@@ -309,4 +295,21 @@ internal static class BindingRedirectManager
|
|
309
295
|
return assemblyBinding;
|
310
296
|
}
|
311
297
|
}
|
298
|
+
|
299
|
+
internal sealed record AssemblyIdentity(string Name, string PublicKeyToken);
|
300
|
+
|
301
|
+
// Case-insensitive comparer. This helps avoid creating duplicate binding redirects when there is a case form mismatch between assembly identities.
|
302
|
+
// Especially important for PublicKeyToken which is typically lowercase (using NuGet.exe), but can also be uppercase when using other tools (e.g. Visual Studio auto-resolve assembly conflicts feature).
|
303
|
+
internal sealed class AssemblyIdentityIgnoreCaseComparer : IEqualityComparer<AssemblyIdentity>
|
304
|
+
{
|
305
|
+
public bool Equals(AssemblyIdentity? x, AssemblyIdentity? y) =>
|
306
|
+
string.Equals(x?.Name, y?.Name, StringComparison.OrdinalIgnoreCase) &&
|
307
|
+
string.Equals(x?.PublicKeyToken, y?.PublicKeyToken, StringComparison.OrdinalIgnoreCase);
|
308
|
+
|
309
|
+
public int GetHashCode(AssemblyIdentity obj) =>
|
310
|
+
HashCode.Combine(
|
311
|
+
obj.Name?.ToLowerInvariant(),
|
312
|
+
obj.PublicKeyToken?.ToLowerInvariant()
|
313
|
+
);
|
314
|
+
}
|
312
315
|
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
using System.Diagnostics;
|
1
2
|
using System.Text;
|
2
3
|
using System.Xml.Linq;
|
3
4
|
using System.Xml.XPath;
|
@@ -102,9 +103,9 @@ internal static class PackagesConfigUpdater
|
|
102
103
|
Console.SetError(writer);
|
103
104
|
|
104
105
|
var currentDir = Environment.CurrentDirectory;
|
106
|
+
var existingSpawnedProcesses = GetLikelyNuGetSpawnedProcesses();
|
105
107
|
try
|
106
108
|
{
|
107
|
-
|
108
109
|
Environment.CurrentDirectory = projectDirectory;
|
109
110
|
var retryingAfterRestore = false;
|
110
111
|
|
@@ -144,6 +145,8 @@ internal static class PackagesConfigUpdater
|
|
144
145
|
goto doRestore;
|
145
146
|
}
|
146
147
|
|
148
|
+
MSBuildHelper.ThrowOnUnauthenticatedFeed(fullOutput);
|
149
|
+
MSBuildHelper.ThrowOnMissingFile(fullOutput);
|
147
150
|
throw new Exception(fullOutput);
|
148
151
|
}
|
149
152
|
}
|
@@ -157,9 +160,24 @@ internal static class PackagesConfigUpdater
|
|
157
160
|
Environment.CurrentDirectory = currentDir;
|
158
161
|
Console.SetOut(originalOut);
|
159
162
|
Console.SetError(originalError);
|
163
|
+
|
164
|
+
// NuGet.exe can spawn processes that hold on to the temporary directory, so we need to kill them
|
165
|
+
var currentSpawnedProcesses = GetLikelyNuGetSpawnedProcesses();
|
166
|
+
var deltaSpawnedProcesses = currentSpawnedProcesses.Except(existingSpawnedProcesses).ToArray();
|
167
|
+
foreach (var credProvider in deltaSpawnedProcesses)
|
168
|
+
{
|
169
|
+
logger.Log($"Ending spawned credential provider process");
|
170
|
+
credProvider.Kill();
|
171
|
+
}
|
160
172
|
}
|
161
173
|
}
|
162
174
|
|
175
|
+
private static Process[] GetLikelyNuGetSpawnedProcesses()
|
176
|
+
{
|
177
|
+
var processes = Process.GetProcesses().Where(p => p.ProcessName.StartsWith("CredentialProvider", StringComparison.OrdinalIgnoreCase) == true).ToArray();
|
178
|
+
return processes;
|
179
|
+
}
|
180
|
+
|
163
181
|
internal static string? GetPathToPackagesDirectory(ProjectBuildFile projectBuildFile, string dependencyName, string dependencyVersion, string packagesConfigPath)
|
164
182
|
{
|
165
183
|
// the packages directory can be found from the hint path of the matching dependency, e.g., when given "Newtonsoft.Json", "7.0.1", and a project like this:
|
@@ -228,6 +228,7 @@ internal static class SdkPackageUpdater
|
|
228
228
|
|
229
229
|
// see https://learn.microsoft.com/nuget/consume-packages/install-use-packages-dotnet-cli
|
230
230
|
var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"add {projectPath} package {dependencyName} --version {newDependencyVersion}", workingDirectory: Path.GetDirectoryName(projectPath));
|
231
|
+
MSBuildHelper.ThrowOnUnauthenticatedFeed(stdout);
|
231
232
|
if (exitCode != 0)
|
232
233
|
{
|
233
234
|
logger.Log($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
|
@@ -594,7 +595,7 @@ internal static class SdkPackageUpdater
|
|
594
595
|
string packageName)
|
595
596
|
=> buildFile.PackageItemNodes.Where(e =>
|
596
597
|
string.Equals(
|
597
|
-
e.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase),
|
598
|
+
(e.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase))?.Trim(),
|
598
599
|
packageName,
|
599
600
|
StringComparison.OrdinalIgnoreCase) &&
|
600
601
|
(e.GetAttributeOrSubElementValue("Version", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("VersionOverride", StringComparison.OrdinalIgnoreCase)) is not null);
|