dependabot-nuget 0.265.0 → 0.267.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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);
|