dependabot-nuget 0.266.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.Test/EntryPointTests.Analyze.cs +173 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +27 -10
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/NuGetContext.cs +12 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Requirement.cs +88 -45
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/MissingFileException.cs +11 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +45 -42
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +8 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +10 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +2 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/RequirementTests.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +16 -21
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +360 -0
- data/lib/dependabot/nuget/file_fetcher.rb +29 -10
- data/lib/dependabot/nuget/file_updater.rb +2 -0
- data/lib/dependabot/nuget/native_helpers.rb +2 -0
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19fd9d2635d3d1f5737870ebe8bcd155fb5e90557c7eec2b6832233bb0573b29
|
4
|
+
data.tar.gz: d1bd4ff99ae3fd8319a796cf04ba3a859c084cd465013cd5a1c8b106969a8183
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b1c5c0ed9ca379e3c209c21dbc047e7364a9c3f7eda956ebd11a6f0f44b5a3a3e5a7fe9493ccafe82ac98bad132ab2323a56817c2f234c135814282cd229c30
|
7
|
+
data.tar.gz: c0f297752cb28d6c32df1261ab10199a5f39e17af23f3c3e46ed76aafd59785e79cd43c81e1a164b8fe92b9f7763f50e3d2c4a8d34c9bb59a4976d0e6eb44be4
|
@@ -134,6 +134,179 @@ public partial class EntryPointTests
|
|
134
134
|
);
|
135
135
|
}
|
136
136
|
|
137
|
+
[Fact]
|
138
|
+
public async Task DotNetToolsJsonCanBeAnalyzed()
|
139
|
+
{
|
140
|
+
var repoMetadata = XElement.Parse("""<repository type="git" url="https://nuget.example.com/some-global-tool" />""");
|
141
|
+
await RunAsync(path =>
|
142
|
+
[
|
143
|
+
"analyze",
|
144
|
+
"--repo-root",
|
145
|
+
path,
|
146
|
+
"--discovery-file-path",
|
147
|
+
Path.Join(path, "discovery.json"),
|
148
|
+
"--dependency-file-path",
|
149
|
+
Path.Join(path, "some-global-tool.json"),
|
150
|
+
"--analysis-folder-path",
|
151
|
+
Path.Join(path, AnalyzeWorker.AnalysisDirectoryName),
|
152
|
+
"--verbose",
|
153
|
+
],
|
154
|
+
packages:
|
155
|
+
[
|
156
|
+
MockNuGetPackage.CreateDotNetToolPackage("some-global-tool", "1.0.0", "net8.0", additionalMetadata: [repoMetadata]),
|
157
|
+
MockNuGetPackage.CreateDotNetToolPackage("some-global-tool", "1.1.0", "net8.0", additionalMetadata: [repoMetadata]),
|
158
|
+
],
|
159
|
+
dependencyName: "some-global-tool",
|
160
|
+
initialFiles:
|
161
|
+
[
|
162
|
+
(".config/dotnet-tools.json", """
|
163
|
+
{
|
164
|
+
"version": 1,
|
165
|
+
"isRoot": true,
|
166
|
+
"tools": {
|
167
|
+
"some-global-tool": {
|
168
|
+
"version": "1.0.0",
|
169
|
+
"commands": [
|
170
|
+
"some-global-tool"
|
171
|
+
]
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
"""),
|
176
|
+
("discovery.json", """
|
177
|
+
{
|
178
|
+
"Path": "",
|
179
|
+
"IsSuccess": true,
|
180
|
+
"Projects": [],
|
181
|
+
"DotNetToolsJson": {
|
182
|
+
"FilePath": ".config/dotnet-tools.json",
|
183
|
+
"IsSuccess": true,
|
184
|
+
"Dependencies": [
|
185
|
+
{
|
186
|
+
"Name": "some-global-tool",
|
187
|
+
"Version": "1.0.0",
|
188
|
+
"Type": "DotNetTool",
|
189
|
+
"EvaluationResult": null,
|
190
|
+
"TargetFrameworks": null,
|
191
|
+
"IsDevDependency": false,
|
192
|
+
"IsDirect": false,
|
193
|
+
"IsTransitive": false,
|
194
|
+
"IsOverride": false,
|
195
|
+
"IsUpdate": false,
|
196
|
+
"InfoUrl": null
|
197
|
+
}
|
198
|
+
]
|
199
|
+
}
|
200
|
+
}
|
201
|
+
"""),
|
202
|
+
("some-global-tool.json", """
|
203
|
+
{
|
204
|
+
"Name": "some-global-tool",
|
205
|
+
"Version": "1.0.0",
|
206
|
+
"IsVulnerable": false,
|
207
|
+
"IgnoredVersions": [],
|
208
|
+
"Vulnerabilities": []
|
209
|
+
}
|
210
|
+
"""),
|
211
|
+
],
|
212
|
+
expectedResult: new()
|
213
|
+
{
|
214
|
+
UpdatedVersion = "1.1.0",
|
215
|
+
CanUpdate = true,
|
216
|
+
VersionComesFromMultiDependencyProperty = false,
|
217
|
+
UpdatedDependencies =
|
218
|
+
[
|
219
|
+
new Dependency("some-global-tool", "1.1.0", DependencyType.DotNetTool, TargetFrameworks: null, IsDirect: true, InfoUrl: "https://nuget.example.com/some-global-tool")
|
220
|
+
],
|
221
|
+
}
|
222
|
+
);
|
223
|
+
}
|
224
|
+
|
225
|
+
[Fact]
|
226
|
+
public async Task GlobalJsonCanBeAnalyzed()
|
227
|
+
{
|
228
|
+
var repoMetadata = XElement.Parse("""<repository type="git" url="https://nuget.example.com/some.msbuild.sdk" />""");
|
229
|
+
await RunAsync(path =>
|
230
|
+
[
|
231
|
+
"analyze",
|
232
|
+
"--repo-root",
|
233
|
+
path,
|
234
|
+
"--discovery-file-path",
|
235
|
+
Path.Join(path, "discovery.json"),
|
236
|
+
"--dependency-file-path",
|
237
|
+
Path.Join(path, "Some.MSBuild.Sdk.json"),
|
238
|
+
"--analysis-folder-path",
|
239
|
+
Path.Join(path, AnalyzeWorker.AnalysisDirectoryName),
|
240
|
+
"--verbose",
|
241
|
+
],
|
242
|
+
packages:
|
243
|
+
[
|
244
|
+
MockNuGetPackage.CreateMSBuildSdkPackage("Some.MSBuild.Sdk", "1.0.0", "net8.0", additionalMetadata: [repoMetadata]),
|
245
|
+
MockNuGetPackage.CreateMSBuildSdkPackage("Some.MSBuild.Sdk", "1.1.0", "net8.0", additionalMetadata: [repoMetadata]),
|
246
|
+
],
|
247
|
+
dependencyName: "Some.MSBuild.Sdk",
|
248
|
+
initialFiles:
|
249
|
+
[
|
250
|
+
("global.json", """
|
251
|
+
{
|
252
|
+
"sdk": {
|
253
|
+
"version": "8.0.300",
|
254
|
+
"rollForward": "latestPatch"
|
255
|
+
},
|
256
|
+
"msbuild-sdks": {
|
257
|
+
"Some.MSBuild.Sdk": "1.0.0"
|
258
|
+
}
|
259
|
+
}
|
260
|
+
"""),
|
261
|
+
("discovery.json", """
|
262
|
+
{
|
263
|
+
"Path": "",
|
264
|
+
"IsSuccess": true,
|
265
|
+
"Projects": [],
|
266
|
+
"GlobalJson": {
|
267
|
+
"FilePath": "global.json",
|
268
|
+
"IsSuccess": true,
|
269
|
+
"Dependencies": [
|
270
|
+
{
|
271
|
+
"Name": "Some.MSBuild.Sdk",
|
272
|
+
"Version": "1.0.0",
|
273
|
+
"Type": "MSBuildSdk",
|
274
|
+
"EvaluationResult": null,
|
275
|
+
"TargetFrameworks": null,
|
276
|
+
"IsDevDependency": false,
|
277
|
+
"IsDirect": false,
|
278
|
+
"IsTransitive": false,
|
279
|
+
"IsOverride": false,
|
280
|
+
"IsUpdate": false,
|
281
|
+
"InfoUrl": null
|
282
|
+
}
|
283
|
+
]
|
284
|
+
}
|
285
|
+
}
|
286
|
+
"""),
|
287
|
+
("Some.MSBuild.Sdk.json", """
|
288
|
+
{
|
289
|
+
"Name": "Some.MSBuild.Sdk",
|
290
|
+
"Version": "1.0.0",
|
291
|
+
"IsVulnerable": false,
|
292
|
+
"IgnoredVersions": [],
|
293
|
+
"Vulnerabilities": []
|
294
|
+
}
|
295
|
+
"""),
|
296
|
+
],
|
297
|
+
expectedResult: new()
|
298
|
+
{
|
299
|
+
UpdatedVersion = "1.1.0",
|
300
|
+
CanUpdate = true,
|
301
|
+
VersionComesFromMultiDependencyProperty = false,
|
302
|
+
UpdatedDependencies =
|
303
|
+
[
|
304
|
+
new Dependency("Some.MSBuild.Sdk", "1.1.0", DependencyType.MSBuildSdk, TargetFrameworks: null, IsDirect: true, InfoUrl: "https://nuget.example.com/some.msbuild.sdk")
|
305
|
+
],
|
306
|
+
}
|
307
|
+
);
|
308
|
+
}
|
309
|
+
|
137
310
|
private static async Task RunAsync(Func<string, string[]> getArgs, string dependencyName, TestFile[] initialFiles, ExpectedAnalysisResult expectedResult, MockNuGetPackage[]? packages = null)
|
138
311
|
{
|
139
312
|
var actualResult = await RunAnalyzerAsync(dependencyName, initialFiles, async path =>
|
@@ -51,23 +51,21 @@ public partial class AnalyzeWorker
|
|
51
51
|
=> p.Dependencies.Where(d => !d.IsTransitive &&
|
52
52
|
d.EvaluationResult?.RootPropertyName is not null)
|
53
53
|
).ToImmutableArray();
|
54
|
+
var dotnetToolsHasDependency = discovery.DotNetToolsJson?.Dependencies.Any(d => d.Name.Equals(dependencyInfo.Name, StringComparison.OrdinalIgnoreCase)) == true;
|
55
|
+
var globalJsonHasDependency = discovery.GlobalJson?.Dependencies.Any(d => d.Name.Equals(dependencyInfo.Name, StringComparison.OrdinalIgnoreCase)) == true;
|
54
56
|
|
55
57
|
bool usesMultiDependencyProperty = false;
|
56
58
|
NuGetVersion? updatedVersion = null;
|
57
59
|
ImmutableArray<Dependency> updatedDependencies = [];
|
58
60
|
|
59
|
-
bool
|
61
|
+
bool isProjectUpdateNecessary = IsUpdateNecessary(dependencyInfo, projectsWithDependency);
|
62
|
+
var isUpdateNecessary = isProjectUpdateNecessary || dotnetToolsHasDependency || globalJsonHasDependency;
|
60
63
|
using var nugetContext = new NuGetContext(startingDirectory);
|
61
64
|
AnalysisResult result;
|
62
65
|
try
|
63
66
|
{
|
64
67
|
if (isUpdateNecessary)
|
65
68
|
{
|
66
|
-
if (!Directory.Exists(nugetContext.TempPackageDirectory))
|
67
|
-
{
|
68
|
-
Directory.CreateDirectory(nugetContext.TempPackageDirectory);
|
69
|
-
}
|
70
|
-
|
71
69
|
_logger.Log($" Determining multi-dependency property.");
|
72
70
|
var multiDependencies = DetermineMultiDependencyDetails(
|
73
71
|
discovery,
|
@@ -99,16 +97,35 @@ public partial class AnalyzeWorker
|
|
99
97
|
CancellationToken.None);
|
100
98
|
|
101
99
|
_logger.Log($" Finding updated peer dependencies.");
|
102
|
-
|
103
|
-
|
100
|
+
if (updatedVersion is null)
|
101
|
+
{
|
102
|
+
updatedDependencies = [];
|
103
|
+
}
|
104
|
+
else if (isProjectUpdateNecessary)
|
105
|
+
{
|
106
|
+
updatedDependencies = await FindUpdatedDependenciesAsync(
|
104
107
|
repoRoot,
|
105
108
|
discovery,
|
106
109
|
dependenciesToUpdate,
|
107
110
|
updatedVersion,
|
108
111
|
nugetContext,
|
109
112
|
_logger,
|
110
|
-
CancellationToken.None)
|
111
|
-
|
113
|
+
CancellationToken.None);
|
114
|
+
}
|
115
|
+
else if (dotnetToolsHasDependency)
|
116
|
+
{
|
117
|
+
var infoUrl = await nugetContext.GetPackageInfoUrlAsync(dependencyInfo.Name, updatedVersion.ToNormalizedString(), CancellationToken.None);
|
118
|
+
updatedDependencies = [new Dependency(dependencyInfo.Name, updatedVersion.ToNormalizedString(), DependencyType.DotNetTool, IsDirect: true, InfoUrl: infoUrl)];
|
119
|
+
}
|
120
|
+
else if (globalJsonHasDependency)
|
121
|
+
{
|
122
|
+
var infoUrl = await nugetContext.GetPackageInfoUrlAsync(dependencyInfo.Name, updatedVersion.ToNormalizedString(), CancellationToken.None);
|
123
|
+
updatedDependencies = [new Dependency(dependencyInfo.Name, updatedVersion.ToNormalizedString(), DependencyType.MSBuildSdk, IsDirect: true, InfoUrl: infoUrl)];
|
124
|
+
}
|
125
|
+
else
|
126
|
+
{
|
127
|
+
throw new InvalidOperationException("Unreachable.");
|
128
|
+
}
|
112
129
|
|
113
130
|
//TODO: At this point we should add the peer dependencies to a queue where
|
114
131
|
// we will analyze them one by one to see if they themselves are part of a
|
@@ -37,12 +37,23 @@ internal record NuGetContext : IDisposable
|
|
37
37
|
.Where(p => p.IsEnabled)
|
38
38
|
.ToImmutableArray();
|
39
39
|
Logger = logger ?? NullLogger.Instance;
|
40
|
-
TempPackageDirectory = Path.Combine(Path.GetTempPath(), ".
|
40
|
+
TempPackageDirectory = Path.Combine(Path.GetTempPath(), $"dependabot-packages_{Guid.NewGuid():d}");
|
41
|
+
Directory.CreateDirectory(TempPackageDirectory);
|
41
42
|
}
|
42
43
|
|
43
44
|
public void Dispose()
|
44
45
|
{
|
45
46
|
SourceCacheContext.Dispose();
|
47
|
+
if (Directory.Exists(TempPackageDirectory))
|
48
|
+
{
|
49
|
+
try
|
50
|
+
{
|
51
|
+
Directory.Delete(TempPackageDirectory, recursive: true);
|
52
|
+
}
|
53
|
+
catch
|
54
|
+
{
|
55
|
+
}
|
56
|
+
}
|
46
57
|
}
|
47
58
|
|
48
59
|
private readonly Dictionary<PackageIdentity, string?> _packageInfoUrlCache = new();
|
@@ -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
|
}
|
@@ -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
|
}
|
@@ -73,6 +73,14 @@ public class UpdaterWorker
|
|
73
73
|
ErrorDetails = "(" + string.Join("|", NuGetContext.GetPackageSourceUrls(workspacePath)) + ")",
|
74
74
|
};
|
75
75
|
}
|
76
|
+
catch (MissingFileException ex)
|
77
|
+
{
|
78
|
+
result = new()
|
79
|
+
{
|
80
|
+
ErrorType = ErrorType.MissingFile,
|
81
|
+
ErrorDetails = ex.FilePath,
|
82
|
+
};
|
83
|
+
}
|
76
84
|
|
77
85
|
_processedProjectPaths.Clear();
|
78
86
|
if (resultOutputPath is { })
|
@@ -620,6 +620,16 @@ internal static partial class MSBuildHelper
|
|
620
620
|
}
|
621
621
|
}
|
622
622
|
|
623
|
+
internal static void ThrowOnMissingFile(string output)
|
624
|
+
{
|
625
|
+
var missingFilePattern = new Regex(@"The imported project \""(?<FilePath>.*)\"" was not found");
|
626
|
+
var match = missingFilePattern.Match(output);
|
627
|
+
if (match.Success)
|
628
|
+
{
|
629
|
+
throw new MissingFileException(match.Groups["FilePath"].Value);
|
630
|
+
}
|
631
|
+
}
|
632
|
+
|
623
633
|
internal static bool TryGetGlobalJsonPath(string repoRootPath, string workspacePath, [NotNullWhen(returnValue: true)] out string? globalJsonPath)
|
624
634
|
{
|
625
635
|
globalJsonPath = PathHelper.GetFileInDirectoryOrParent(workspacePath, repoRootPath, "global.json", caseSensitive: false);
|
@@ -390,6 +390,8 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
|
|
390
390
|
</m:properties>
|
391
391
|
</entry>
|
392
392
|
"""));
|
393
|
+
case "/api/v2/package/Some.Package/1.0.0":
|
394
|
+
return (200, MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0").GetZipStream().ReadAllBytes());
|
393
395
|
case "/api/v2/package/Some.Package/1.2.3":
|
394
396
|
return (200, MockNuGetPackage.CreateSimplePackage("Some.Package", "1.2.3", "net8.0").GetZipStream().ReadAllBytes());
|
395
397
|
case "/api/v2/FindPackagesById()?id='Microsoft.WindowsDesktop.App.Ref'&semVerLevel=2.0.0":
|
@@ -32,6 +32,7 @@ public class RequirementTests
|
|
32
32
|
[InlineData("2.0", "~> 1.0", false)]
|
33
33
|
[InlineData("1", "~> 1", true)]
|
34
34
|
[InlineData("2", "~> 1", false)]
|
35
|
+
[InlineData("5.3.8", "< 6, > 5.2.4", true)]
|
35
36
|
public void IsSatisfiedBy(string versionString, string requirementString, bool expected)
|
36
37
|
{
|
37
38
|
var version = NuGetVersion.Parse(versionString);
|
@@ -1,3 +1,4 @@
|
|
1
|
+
using System.Collections.Immutable;
|
1
2
|
using System.IO.Compression;
|
2
3
|
using System.Security.Cryptography;
|
3
4
|
using System.Text;
|
@@ -95,7 +96,7 @@ namespace NuGetUpdater.Core.Test
|
|
95
96
|
/// Creates a mock NuGet package with a single assembly in the appropriate `lib/` directory. The assembly will
|
96
97
|
/// contain the appropriate `AssemblyVersion` attribute and nothing else.
|
97
98
|
/// </summary>
|
98
|
-
public static MockNuGetPackage CreatePackageWithAssembly(string id, string version, string targetFramework, string assemblyVersion, (string? TargetFramework, (string Id, string Version)[] Packages)[]? dependencyGroups = null)
|
99
|
+
public static MockNuGetPackage CreatePackageWithAssembly(string id, string version, string targetFramework, string assemblyVersion, ImmutableArray<byte>? assemblyPublicKey = null, (string? TargetFramework, (string Id, string Version)[] Packages)[]? dependencyGroups = null)
|
99
100
|
{
|
100
101
|
return new(
|
101
102
|
id,
|
@@ -104,7 +105,7 @@ namespace NuGetUpdater.Core.Test
|
|
104
105
|
DependencyGroups: dependencyGroups,
|
105
106
|
Files:
|
106
107
|
[
|
107
|
-
($"lib/{targetFramework}/{id}.dll", CreateAssembly(id, assemblyVersion))
|
108
|
+
($"lib/{targetFramework}/{id}.dll", CreateAssembly(id, assemblyVersion, assemblyPublicKey))
|
108
109
|
]
|
109
110
|
);
|
110
111
|
}
|
@@ -130,19 +131,14 @@ namespace NuGetUpdater.Core.Test
|
|
130
131
|
);
|
131
132
|
}
|
132
133
|
|
133
|
-
public static MockNuGetPackage CreateDotNetToolPackage(string id, string version, string targetFramework)
|
134
|
+
public static MockNuGetPackage CreateDotNetToolPackage(string id, string version, string targetFramework, XElement[]? additionalMetadata = null)
|
134
135
|
{
|
136
|
+
var packageMetadata = new XElement("packageTypes", new XElement("packageType", new XAttribute("name", "DotnetTool")));
|
137
|
+
var allMetadata = new[] { packageMetadata }.Concat(additionalMetadata ?? []).ToArray();
|
135
138
|
return new(
|
136
139
|
id,
|
137
140
|
version,
|
138
|
-
AdditionalMetadata:
|
139
|
-
[
|
140
|
-
new XElement("packageTypes",
|
141
|
-
new XElement("packageType",
|
142
|
-
new XAttribute("name", "DotnetTool")
|
143
|
-
)
|
144
|
-
)
|
145
|
-
],
|
141
|
+
AdditionalMetadata: allMetadata,
|
146
142
|
Files:
|
147
143
|
[
|
148
144
|
($"tools/{targetFramework}/any/DotnetToolSettings.xml", Encoding.UTF8.GetBytes($"""
|
@@ -157,8 +153,10 @@ namespace NuGetUpdater.Core.Test
|
|
157
153
|
);
|
158
154
|
}
|
159
155
|
|
160
|
-
public static MockNuGetPackage CreateMSBuildSdkPackage(string id, string version, string? sdkPropsContent = null, string? sdkTargetsContent = null)
|
156
|
+
public static MockNuGetPackage CreateMSBuildSdkPackage(string id, string version, string? sdkPropsContent = null, string? sdkTargetsContent = null, XElement[]? additionalMetadata = null)
|
161
157
|
{
|
158
|
+
var packageMetadata = new XElement("packageTypes", new XElement("packageType", new XAttribute("name", "MSBuildSdk")));
|
159
|
+
var allMetadata = new[] { packageMetadata }.Concat(additionalMetadata ?? []).ToArray();
|
162
160
|
sdkPropsContent ??= """
|
163
161
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
164
162
|
</Project>
|
@@ -170,14 +168,7 @@ namespace NuGetUpdater.Core.Test
|
|
170
168
|
return new(
|
171
169
|
id,
|
172
170
|
version,
|
173
|
-
AdditionalMetadata:
|
174
|
-
[
|
175
|
-
new XElement("packageTypes",
|
176
|
-
new XElement("packageType",
|
177
|
-
new XAttribute("name", "MSBuildSdk")
|
178
|
-
)
|
179
|
-
)
|
180
|
-
],
|
171
|
+
AdditionalMetadata: additionalMetadata,
|
181
172
|
Files:
|
182
173
|
[
|
183
174
|
("Sdk/Sdk.props", Encoding.UTF8.GetBytes(sdkPropsContent)),
|
@@ -271,9 +262,13 @@ namespace NuGetUpdater.Core.Test
|
|
271
262
|
return _stream;
|
272
263
|
}
|
273
264
|
|
274
|
-
private static byte[] CreateAssembly(string assemblyName, string assemblyVersion)
|
265
|
+
private static byte[] CreateAssembly(string assemblyName, string assemblyVersion, ImmutableArray<byte>? assemblyPublicKey = null)
|
275
266
|
{
|
276
267
|
CSharpCompilationOptions compilationOptions = new(OutputKind.DynamicallyLinkedLibrary);
|
268
|
+
if (assemblyPublicKey is not null)
|
269
|
+
{
|
270
|
+
compilationOptions = compilationOptions.WithCryptoPublicKey(assemblyPublicKey.Value);
|
271
|
+
}
|
277
272
|
CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, options: compilationOptions)
|
278
273
|
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
|
279
274
|
.AddSyntaxTrees(CSharpSyntaxTree.ParseText($"[assembly: System.Reflection.AssemblyVersionAttribute(\"{assemblyVersion}\")]"));
|
data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
using System.Collections.Immutable;
|
2
|
+
using System.Text.Json;
|
3
|
+
|
1
4
|
using NuGetUpdater.Core.Updater;
|
2
5
|
|
3
6
|
using Xunit;
|
@@ -1013,6 +1016,307 @@ public partial class UpdateWorkerTests
|
|
1013
1016
|
);
|
1014
1017
|
}
|
1015
1018
|
|
1019
|
+
[Fact]
|
1020
|
+
public async Task UpdateBindingRedirect_DuplicateRedirectsForTheSameAssemblyAreRemoved()
|
1021
|
+
{
|
1022
|
+
await TestUpdateForProject("Some.Package", "7.0.1", "13.0.1",
|
1023
|
+
packages:
|
1024
|
+
[
|
1025
|
+
MockNuGetPackage.CreatePackageWithAssembly("Some.Package", "7.0.1", "net45", "7.0.0.0"),
|
1026
|
+
MockNuGetPackage.CreatePackageWithAssembly("Some.Package", "13.0.1", "net45", "13.0.0.0"),
|
1027
|
+
],
|
1028
|
+
projectContents: """
|
1029
|
+
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
1030
|
+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
1031
|
+
<PropertyGroup>
|
1032
|
+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
1033
|
+
</PropertyGroup>
|
1034
|
+
<ItemGroup>
|
1035
|
+
<None Include="packages.config" />
|
1036
|
+
</ItemGroup>
|
1037
|
+
<ItemGroup>
|
1038
|
+
<None Include="app.config" />
|
1039
|
+
</ItemGroup>
|
1040
|
+
<ItemGroup>
|
1041
|
+
<Reference Include="Some.Package, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null">
|
1042
|
+
<HintPath>packages\Some.Package.7.0.1\lib\net45\Some.Package.dll</HintPath>
|
1043
|
+
<Private>True</Private>
|
1044
|
+
</Reference>
|
1045
|
+
</ItemGroup>
|
1046
|
+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
1047
|
+
</Project>
|
1048
|
+
""",
|
1049
|
+
packagesConfigContents: """
|
1050
|
+
<packages>
|
1051
|
+
<package id="Some.Package" version="7.0.1" targetFramework="net45" />
|
1052
|
+
</packages>
|
1053
|
+
""",
|
1054
|
+
additionalFiles:
|
1055
|
+
[
|
1056
|
+
("app.config", """
|
1057
|
+
<configuration>
|
1058
|
+
<runtime>
|
1059
|
+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
1060
|
+
<dependentAssembly>
|
1061
|
+
<assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
|
1062
|
+
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
1063
|
+
</dependentAssembly>
|
1064
|
+
<dependentAssembly>
|
1065
|
+
<assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
|
1066
|
+
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
1067
|
+
</dependentAssembly>
|
1068
|
+
</assemblyBinding>
|
1069
|
+
</runtime>
|
1070
|
+
</configuration>
|
1071
|
+
""")
|
1072
|
+
],
|
1073
|
+
expectedProjectContents: """
|
1074
|
+
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
1075
|
+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
1076
|
+
<PropertyGroup>
|
1077
|
+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
1078
|
+
</PropertyGroup>
|
1079
|
+
<ItemGroup>
|
1080
|
+
<None Include="packages.config" />
|
1081
|
+
</ItemGroup>
|
1082
|
+
<ItemGroup>
|
1083
|
+
<None Include="app.config" />
|
1084
|
+
</ItemGroup>
|
1085
|
+
<ItemGroup>
|
1086
|
+
<Reference Include="Some.Package, Version=13.0.0.0, Culture=neutral, PublicKeyToken=null">
|
1087
|
+
<HintPath>packages\Some.Package.13.0.1\lib\net45\Some.Package.dll</HintPath>
|
1088
|
+
<Private>True</Private>
|
1089
|
+
</Reference>
|
1090
|
+
</ItemGroup>
|
1091
|
+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
1092
|
+
</Project>
|
1093
|
+
""",
|
1094
|
+
expectedPackagesConfigContents: """
|
1095
|
+
<?xml version="1.0" encoding="utf-8"?>
|
1096
|
+
<packages>
|
1097
|
+
<package id="Some.Package" version="13.0.1" targetFramework="net45" />
|
1098
|
+
</packages>
|
1099
|
+
""",
|
1100
|
+
additionalFilesExpected:
|
1101
|
+
[
|
1102
|
+
("app.config", """
|
1103
|
+
<configuration>
|
1104
|
+
<runtime>
|
1105
|
+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
1106
|
+
<dependentAssembly>
|
1107
|
+
<assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
|
1108
|
+
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
|
1109
|
+
</dependentAssembly>
|
1110
|
+
</assemblyBinding>
|
1111
|
+
</runtime>
|
1112
|
+
</configuration>
|
1113
|
+
""")
|
1114
|
+
]
|
1115
|
+
);
|
1116
|
+
}
|
1117
|
+
|
1118
|
+
[Fact]
|
1119
|
+
public async Task UpdateBindingRedirect_ExistingRedirectForAssemblyPublicKeyTokenDiffersByCase()
|
1120
|
+
{
|
1121
|
+
// Generated using "sn -k keypair.snk && sn -p keypair.snk public.snk" then converting public.snk to base64
|
1122
|
+
// https://learn.microsoft.com/en-us/dotnet/standard/assembly/create-public-private-key-pair
|
1123
|
+
var assemblyStrongNamePublicKey = Convert.FromBase64String(
|
1124
|
+
"ACQAAASAAACUAAAABgIAAAAkAABSU0ExAAQAAAEAAQAJJW4hmKpxa9pU0JPDvJ9KqjvfQuMUovGtFjkZ9b0i1KQ/7kqEOjW3Va0eGpU7Kz0qHp14iYQ3SsMzBZU3mZ2Ezeqg+dCVuDk7o2lp++4m1FstHsebtXBetyOzWkneo+3iKSzOQ7bOXj2s5M9umqRPk+yj0ZBILf+HvfAd07iIuQ=="
|
1125
|
+
).ToImmutableArray();
|
1126
|
+
|
1127
|
+
await TestUpdateForProject("Some.Package", "7.0.1", "13.0.1",
|
1128
|
+
packages:
|
1129
|
+
[
|
1130
|
+
MockNuGetPackage.CreatePackageWithAssembly("Some.Package", "7.0.1", "net45", "7.0.0.0", assemblyPublicKey: assemblyStrongNamePublicKey),
|
1131
|
+
MockNuGetPackage.CreatePackageWithAssembly("Some.Package", "13.0.1", "net45", "13.0.0.0", assemblyPublicKey: assemblyStrongNamePublicKey),
|
1132
|
+
],
|
1133
|
+
projectContents: """
|
1134
|
+
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
1135
|
+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
1136
|
+
<PropertyGroup>
|
1137
|
+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
1138
|
+
</PropertyGroup>
|
1139
|
+
<ItemGroup>
|
1140
|
+
<None Include="packages.config" />
|
1141
|
+
</ItemGroup>
|
1142
|
+
<ItemGroup>
|
1143
|
+
<None Include="app.config" />
|
1144
|
+
</ItemGroup>
|
1145
|
+
<ItemGroup>
|
1146
|
+
<Reference Include="Some.Package, Version=7.0.0.0, Culture=neutral, PublicKeyToken=13523fc3be375af1">
|
1147
|
+
<HintPath>packages\Some.Package.7.0.1\lib\net45\Some.Package.dll</HintPath>
|
1148
|
+
<Private>True</Private>
|
1149
|
+
</Reference>
|
1150
|
+
</ItemGroup>
|
1151
|
+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
1152
|
+
</Project>
|
1153
|
+
""",
|
1154
|
+
packagesConfigContents: """
|
1155
|
+
<packages>
|
1156
|
+
<package id="Some.Package" version="7.0.1" targetFramework="net45" />
|
1157
|
+
</packages>
|
1158
|
+
""",
|
1159
|
+
additionalFiles:
|
1160
|
+
[
|
1161
|
+
("app.config", """
|
1162
|
+
<configuration>
|
1163
|
+
<runtime>
|
1164
|
+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
1165
|
+
<dependentAssembly>
|
1166
|
+
<assemblyIdentity name="Some.Package" publicKeyToken="13523FC3BE375AF1" culture="neutral" />
|
1167
|
+
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
1168
|
+
</dependentAssembly>
|
1169
|
+
</assemblyBinding>
|
1170
|
+
</runtime>
|
1171
|
+
</configuration>
|
1172
|
+
""")
|
1173
|
+
],
|
1174
|
+
expectedProjectContents: """
|
1175
|
+
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
1176
|
+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
1177
|
+
<PropertyGroup>
|
1178
|
+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
1179
|
+
</PropertyGroup>
|
1180
|
+
<ItemGroup>
|
1181
|
+
<None Include="packages.config" />
|
1182
|
+
</ItemGroup>
|
1183
|
+
<ItemGroup>
|
1184
|
+
<None Include="app.config" />
|
1185
|
+
</ItemGroup>
|
1186
|
+
<ItemGroup>
|
1187
|
+
<Reference Include="Some.Package, Version=13.0.0.0, Culture=neutral, PublicKeyToken=13523fc3be375af1">
|
1188
|
+
<HintPath>packages\Some.Package.13.0.1\lib\net45\Some.Package.dll</HintPath>
|
1189
|
+
<Private>True</Private>
|
1190
|
+
</Reference>
|
1191
|
+
</ItemGroup>
|
1192
|
+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
1193
|
+
</Project>
|
1194
|
+
""",
|
1195
|
+
expectedPackagesConfigContents: """
|
1196
|
+
<?xml version="1.0" encoding="utf-8"?>
|
1197
|
+
<packages>
|
1198
|
+
<package id="Some.Package" version="13.0.1" targetFramework="net45" />
|
1199
|
+
</packages>
|
1200
|
+
""",
|
1201
|
+
additionalFilesExpected:
|
1202
|
+
[
|
1203
|
+
("app.config", """
|
1204
|
+
<configuration>
|
1205
|
+
<runtime>
|
1206
|
+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
1207
|
+
<dependentAssembly>
|
1208
|
+
<assemblyIdentity name="Some.Package" publicKeyToken="13523FC3BE375AF1" culture="neutral" />
|
1209
|
+
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
|
1210
|
+
</dependentAssembly>
|
1211
|
+
</assemblyBinding>
|
1212
|
+
</runtime>
|
1213
|
+
</configuration>
|
1214
|
+
""")
|
1215
|
+
]
|
1216
|
+
);
|
1217
|
+
}
|
1218
|
+
|
1219
|
+
[Fact]
|
1220
|
+
public async Task UpdateBindingRedirect_ConfigXmlDeclarationNodeIsPreserved()
|
1221
|
+
{
|
1222
|
+
await TestUpdateForProject("Some.Package", "7.0.1", "13.0.1",
|
1223
|
+
packages:
|
1224
|
+
[
|
1225
|
+
MockNuGetPackage.CreatePackageWithAssembly("Some.Package", "7.0.1", "net45", "7.0.0.0"),
|
1226
|
+
MockNuGetPackage.CreatePackageWithAssembly("Some.Package", "13.0.1", "net45", "13.0.0.0"),
|
1227
|
+
],
|
1228
|
+
projectContents: """
|
1229
|
+
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
1230
|
+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
1231
|
+
<PropertyGroup>
|
1232
|
+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
1233
|
+
</PropertyGroup>
|
1234
|
+
<ItemGroup>
|
1235
|
+
<None Include="packages.config" />
|
1236
|
+
</ItemGroup>
|
1237
|
+
<ItemGroup>
|
1238
|
+
<None Include="app.config" />
|
1239
|
+
</ItemGroup>
|
1240
|
+
<ItemGroup>
|
1241
|
+
<Reference Include="Some.Package, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null">
|
1242
|
+
<HintPath>packages\Some.Package.7.0.1\lib\net45\Some.Package.dll</HintPath>
|
1243
|
+
<Private>True</Private>
|
1244
|
+
</Reference>
|
1245
|
+
</ItemGroup>
|
1246
|
+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
1247
|
+
</Project>
|
1248
|
+
""",
|
1249
|
+
packagesConfigContents: """
|
1250
|
+
<packages>
|
1251
|
+
<package id="Some.Package" version="7.0.1" targetFramework="net45" />
|
1252
|
+
</packages>
|
1253
|
+
""",
|
1254
|
+
additionalFiles:
|
1255
|
+
[
|
1256
|
+
("app.config", """
|
1257
|
+
<?xml version="1.0" encoding="utf-8"?>
|
1258
|
+
<configuration>
|
1259
|
+
<runtime>
|
1260
|
+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
1261
|
+
<dependentAssembly>
|
1262
|
+
<assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
|
1263
|
+
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
1264
|
+
</dependentAssembly>
|
1265
|
+
<dependentAssembly>
|
1266
|
+
<assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
|
1267
|
+
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
1268
|
+
</dependentAssembly>
|
1269
|
+
</assemblyBinding>
|
1270
|
+
</runtime>
|
1271
|
+
</configuration>
|
1272
|
+
""")
|
1273
|
+
],
|
1274
|
+
expectedProjectContents: """
|
1275
|
+
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
1276
|
+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
1277
|
+
<PropertyGroup>
|
1278
|
+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
1279
|
+
</PropertyGroup>
|
1280
|
+
<ItemGroup>
|
1281
|
+
<None Include="packages.config" />
|
1282
|
+
</ItemGroup>
|
1283
|
+
<ItemGroup>
|
1284
|
+
<None Include="app.config" />
|
1285
|
+
</ItemGroup>
|
1286
|
+
<ItemGroup>
|
1287
|
+
<Reference Include="Some.Package, Version=13.0.0.0, Culture=neutral, PublicKeyToken=null">
|
1288
|
+
<HintPath>packages\Some.Package.13.0.1\lib\net45\Some.Package.dll</HintPath>
|
1289
|
+
<Private>True</Private>
|
1290
|
+
</Reference>
|
1291
|
+
</ItemGroup>
|
1292
|
+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
1293
|
+
</Project>
|
1294
|
+
""",
|
1295
|
+
expectedPackagesConfigContents: """
|
1296
|
+
<?xml version="1.0" encoding="utf-8"?>
|
1297
|
+
<packages>
|
1298
|
+
<package id="Some.Package" version="13.0.1" targetFramework="net45" />
|
1299
|
+
</packages>
|
1300
|
+
""",
|
1301
|
+
additionalFilesExpected:
|
1302
|
+
[
|
1303
|
+
("app.config", """
|
1304
|
+
<?xml version="1.0" encoding="utf-8"?>
|
1305
|
+
<configuration>
|
1306
|
+
<runtime>
|
1307
|
+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
1308
|
+
<dependentAssembly>
|
1309
|
+
<assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
|
1310
|
+
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
|
1311
|
+
</dependentAssembly>
|
1312
|
+
</assemblyBinding>
|
1313
|
+
</runtime>
|
1314
|
+
</configuration>
|
1315
|
+
""")
|
1316
|
+
]
|
1317
|
+
);
|
1318
|
+
}
|
1319
|
+
|
1016
1320
|
[Fact]
|
1017
1321
|
public async Task PackagesConfigUpdateCanHappenEvenWithMismatchedVersionNumbers()
|
1018
1322
|
{
|
@@ -1422,6 +1726,62 @@ public partial class UpdateWorkerTests
|
|
1422
1726
|
""");
|
1423
1727
|
}
|
1424
1728
|
|
1729
|
+
[Fact]
|
1730
|
+
public async Task MissingTargetsAreReported()
|
1731
|
+
{
|
1732
|
+
using var temporaryDirectory = await TemporaryDirectory.CreateWithContentsAsync(
|
1733
|
+
[
|
1734
|
+
("project.csproj", """
|
1735
|
+
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
1736
|
+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
1737
|
+
<Import Project="this.file.does.not.exist.targets" />
|
1738
|
+
<PropertyGroup>
|
1739
|
+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
1740
|
+
</PropertyGroup>
|
1741
|
+
<ItemGroup>
|
1742
|
+
<None Include="packages.config" />
|
1743
|
+
</ItemGroup>
|
1744
|
+
<ItemGroup>
|
1745
|
+
<Reference Include="Some.Package, Version=1.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
1746
|
+
<HintPath>packages\Some.Package.1.0.0\lib\net45\Some.Package.dll</HintPath>
|
1747
|
+
<Private>True</Private>
|
1748
|
+
</Reference>
|
1749
|
+
</ItemGroup>
|
1750
|
+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
1751
|
+
</Project>
|
1752
|
+
"""),
|
1753
|
+
("packages.config", """
|
1754
|
+
<packages>
|
1755
|
+
<package id="Some.Package" version="1.0.0" targetFramework="net45" />
|
1756
|
+
</packages>
|
1757
|
+
"""),
|
1758
|
+
("NuGet.Config", """
|
1759
|
+
<configuration>
|
1760
|
+
<packageSources>
|
1761
|
+
<clear />
|
1762
|
+
<add key="private_feed" value="packages" />
|
1763
|
+
</packageSources>
|
1764
|
+
</configuration>
|
1765
|
+
""")
|
1766
|
+
]
|
1767
|
+
);
|
1768
|
+
MockNuGetPackage[] packages =
|
1769
|
+
[
|
1770
|
+
MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net45"),
|
1771
|
+
MockNuGetPackage.CreateSimplePackage("Some.Package", "1.1.0", "net45"),
|
1772
|
+
];
|
1773
|
+
await MockNuGetPackagesInDirectory(packages, Path.Combine(temporaryDirectory.DirectoryPath, "packages"));
|
1774
|
+
var resultOutputPath = Path.Combine(temporaryDirectory.DirectoryPath, "result.json");
|
1775
|
+
|
1776
|
+
var worker = new UpdaterWorker(new Logger(verbose: true));
|
1777
|
+
await worker.RunAsync(temporaryDirectory.DirectoryPath, "project.csproj", "Some.Package", "1.0.0", "1.1.0", isTransitive: false, resultOutputPath: resultOutputPath);
|
1778
|
+
|
1779
|
+
var resultContents = await File.ReadAllTextAsync(resultOutputPath);
|
1780
|
+
var result = JsonSerializer.Deserialize<UpdateOperationResult>(resultContents, UpdaterWorker.SerializerOptions)!;
|
1781
|
+
Assert.Equal(ErrorType.MissingFile, result.ErrorType);
|
1782
|
+
Assert.Equal(Path.Combine(temporaryDirectory.DirectoryPath, "this.file.does.not.exist.targets"), result.ErrorDetails);
|
1783
|
+
}
|
1784
|
+
|
1425
1785
|
[Fact]
|
1426
1786
|
public async Task ReportsPrivateSourceAuthenticationFailure()
|
1427
1787
|
{
|
@@ -51,20 +51,22 @@ module Dependabot
|
|
51
51
|
@fetched_files = T.let({}, T::Hash[String, T::Array[Dependabot::DependencyFile]])
|
52
52
|
@nuget_config_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
|
53
53
|
@packages_config_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
|
54
|
+
@assembly_binding_redirect_config_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
|
54
55
|
end
|
55
56
|
|
56
57
|
sig { override.returns(T::Array[DependencyFile]) }
|
57
58
|
def fetch_files
|
58
|
-
fetched_files = [
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
59
|
+
fetched_files = [
|
60
|
+
*project_files,
|
61
|
+
*directory_build_files,
|
62
|
+
*imported_property_files,
|
63
|
+
*packages_config_files,
|
64
|
+
*assembly_binding_redirect_config_files,
|
65
|
+
*nuget_config_files,
|
66
|
+
global_json,
|
67
|
+
dotnet_tools_json,
|
68
|
+
packages_props
|
69
|
+
].compact
|
68
70
|
|
69
71
|
# dedup files based on their absolute path
|
70
72
|
fetched_files = fetched_files.uniq do |fetched_file|
|
@@ -128,6 +130,23 @@ module Dependabot
|
|
128
130
|
end
|
129
131
|
end
|
130
132
|
|
133
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
134
|
+
def assembly_binding_redirect_config_files
|
135
|
+
return @assembly_binding_redirect_config_files if @assembly_binding_redirect_config_files
|
136
|
+
|
137
|
+
candidate_paths =
|
138
|
+
[*project_files.map { |f| File.dirname(f.name) }, "."].uniq
|
139
|
+
|
140
|
+
# Assembly binding redirects can appear in any app/web.config file for a .NET Framework project
|
141
|
+
# https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/redirect-assembly-versions#specify-assembly-binding-in-configuration-files
|
142
|
+
@assembly_binding_redirect_config_files =
|
143
|
+
candidate_paths.filter_map do |dir|
|
144
|
+
file = repo_contents(dir: dir)
|
145
|
+
.find { |f| f.name.match?(/^(app|web)\.config$/i) }
|
146
|
+
fetch_file_from_host(File.join(dir, file.name)) if file
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
131
150
|
# rubocop:disable Metrics/PerceivedComplexity
|
132
151
|
sig { returns(T.nilable(T::Array[T.untyped])) }
|
133
152
|
def sln_file_names
|
@@ -261,6 +261,8 @@ module Dependabot
|
|
261
261
|
# no issue
|
262
262
|
when "AuthenticationFailure"
|
263
263
|
raise PrivateSourceAuthenticationFailure, error_details
|
264
|
+
when "MissingFile"
|
265
|
+
raise DependencyFileNotFound, error_details
|
264
266
|
else
|
265
267
|
raise "Unexpected error type from native tool: #{error_type}: #{error_details}"
|
266
268
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dependabot-nuget
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.267.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dependabot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dependabot-common
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.267.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.267.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rubyzip
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -383,6 +383,7 @@ files:
|
|
383
383
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs
|
384
384
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs
|
385
385
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs
|
386
|
+
- helpers/lib/NuGetUpdater/NuGetUpdater.Core/MissingFileException.cs
|
386
387
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core/NativeResult.cs
|
387
388
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj
|
388
389
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core/Property.cs
|
@@ -460,7 +461,7 @@ licenses:
|
|
460
461
|
- MIT
|
461
462
|
metadata:
|
462
463
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
463
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
464
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.267.0
|
464
465
|
post_install_message:
|
465
466
|
rdoc_options: []
|
466
467
|
require_paths:
|