dependabot-nuget 0.301.1 → 0.303.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/Directory.Packages.props +5 -5
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.props +4 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +19 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscoveryTargetingPacks.props +10 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +20 -17
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +179 -28
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs +15 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs +6 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +8 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +7 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +2 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +257 -37
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +13 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/{WebApplicationTargetsConditionPatcher.cs → SpecialImportsConditionPatcher.cs} +18 -11
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationBase.cs +209 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationResult.cs +3 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +79 -24
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs +26 -11
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +48 -22
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +54 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +68 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +94 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +24 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackageReferenceUpdaterTests.cs +177 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/SpecialFilePatcherTests.cs +99 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateOperationBaseTests.cs +130 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +5 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +71 -5
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +125 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +23 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +145 -147
- data/lib/dependabot/nuget/file_parser.rb +22 -19
- metadata +13 -8
@@ -2,43 +2,50 @@ using Microsoft.Language.Xml;
|
|
2
2
|
|
3
3
|
namespace NuGetUpdater.Core.Updater
|
4
4
|
{
|
5
|
-
internal class
|
5
|
+
internal class SpecialImportsConditionPatcher : IDisposable
|
6
6
|
{
|
7
|
-
private string
|
7
|
+
private readonly List<string?> _capturedConditions = new List<string?>();
|
8
8
|
private readonly XmlFilePreAndPostProcessor _processor;
|
9
9
|
|
10
|
-
|
10
|
+
private readonly HashSet<string> ImportedFilesToIgnore = new(StringComparer.OrdinalIgnoreCase)
|
11
|
+
{
|
12
|
+
"Microsoft.TextTemplating.targets",
|
13
|
+
"Microsoft.WebApplication.targets"
|
14
|
+
};
|
15
|
+
|
16
|
+
public SpecialImportsConditionPatcher(string projectFilePath)
|
11
17
|
{
|
12
18
|
_processor = new XmlFilePreAndPostProcessor(
|
13
19
|
getContent: () => File.ReadAllText(projectFilePath),
|
14
20
|
setContent: s => File.WriteAllText(projectFilePath, s),
|
15
21
|
nodeFinder: doc => doc.Descendants()
|
16
22
|
.Where(e => e.Name == "Import")
|
17
|
-
.
|
23
|
+
.Where(e =>
|
18
24
|
{
|
19
25
|
var projectPath = e.GetAttributeValue("Project");
|
20
26
|
if (projectPath is not null)
|
21
27
|
{
|
22
28
|
var projectFileName = Path.GetFileName(projectPath.NormalizePathToUnix());
|
23
|
-
return
|
29
|
+
return ImportedFilesToIgnore.Contains(projectFileName);
|
24
30
|
}
|
25
31
|
|
26
32
|
return false;
|
27
33
|
})
|
28
|
-
|
29
|
-
preProcessor: n =>
|
34
|
+
.Cast<XmlNodeSyntax>(),
|
35
|
+
preProcessor: (i, n) =>
|
30
36
|
{
|
31
37
|
var element = (IXmlElementSyntax)n;
|
32
|
-
|
38
|
+
_capturedConditions.Add(element.GetAttributeValue("Condition"));
|
33
39
|
return (XmlNodeSyntax)element.RemoveAttributeByName("Condition").WithAttribute("Condition", "false");
|
34
40
|
},
|
35
|
-
postProcessor: n =>
|
41
|
+
postProcessor: (i, n) =>
|
36
42
|
{
|
37
43
|
var element = (IXmlElementSyntax)n;
|
38
44
|
var newElement = element.RemoveAttributeByName("Condition");
|
39
|
-
|
45
|
+
var capturedCondition = _capturedConditions[i];
|
46
|
+
if (capturedCondition is not null)
|
40
47
|
{
|
41
|
-
newElement = newElement.WithAttribute("Condition",
|
48
|
+
newElement = newElement.WithAttribute("Condition", capturedCondition);
|
42
49
|
}
|
43
50
|
|
44
51
|
return (XmlNodeSyntax)newElement;
|
@@ -0,0 +1,209 @@
|
|
1
|
+
using System.Collections.Immutable;
|
2
|
+
using System.Diagnostics.CodeAnalysis;
|
3
|
+
using System.Text.Json.Serialization;
|
4
|
+
|
5
|
+
using NuGet.Versioning;
|
6
|
+
|
7
|
+
using NuGetUpdater.Core.Utilities;
|
8
|
+
|
9
|
+
|
10
|
+
namespace NuGetUpdater.Core.Updater;
|
11
|
+
|
12
|
+
[JsonDerivedType(typeof(DirectUpdate))]
|
13
|
+
[JsonDerivedType(typeof(PinnedUpdate))]
|
14
|
+
[JsonDerivedType(typeof(ParentUpdate))]
|
15
|
+
public abstract record UpdateOperationBase
|
16
|
+
{
|
17
|
+
public abstract string Type { get; }
|
18
|
+
public required string DependencyName { get; init; }
|
19
|
+
public required NuGetVersion NewVersion { get; init; }
|
20
|
+
public required ImmutableArray<string> UpdatedFiles { get; init; }
|
21
|
+
|
22
|
+
public abstract string GetReport();
|
23
|
+
|
24
|
+
internal static string GenerateUpdateOperationReport(IEnumerable<UpdateOperationBase> updateOperations)
|
25
|
+
{
|
26
|
+
var updateMessages = updateOperations.Select(u => u.GetReport()).ToImmutableArray();
|
27
|
+
if (updateMessages.Length == 0)
|
28
|
+
{
|
29
|
+
return string.Empty;
|
30
|
+
}
|
31
|
+
|
32
|
+
var separator = "\n ";
|
33
|
+
var report = $"Performed the following updates:{separator}{string.Join(separator, updateMessages.Select(m => $"- {m}"))}";
|
34
|
+
return report;
|
35
|
+
}
|
36
|
+
|
37
|
+
internal static ImmutableArray<UpdateOperationBase> NormalizeUpdateOperationCollection(string repoRootPath, IEnumerable<UpdateOperationBase> updateOperations)
|
38
|
+
{
|
39
|
+
var groupedByKindWithCombinedFiles = updateOperations
|
40
|
+
.GroupBy(u => (u.GetType(), u.DependencyName, u.NewVersion))
|
41
|
+
.Select(g =>
|
42
|
+
{
|
43
|
+
if (g.Key.Item1 == typeof(DirectUpdate))
|
44
|
+
{
|
45
|
+
return new DirectUpdate()
|
46
|
+
{
|
47
|
+
DependencyName = g.Key.DependencyName,
|
48
|
+
NewVersion = g.Key.NewVersion,
|
49
|
+
UpdatedFiles = [.. g.SelectMany(u => u.UpdatedFiles)],
|
50
|
+
} as UpdateOperationBase;
|
51
|
+
}
|
52
|
+
else if (g.Key.Item1 == typeof(PinnedUpdate))
|
53
|
+
{
|
54
|
+
return new PinnedUpdate()
|
55
|
+
{
|
56
|
+
DependencyName = g.Key.DependencyName,
|
57
|
+
NewVersion = g.Key.NewVersion,
|
58
|
+
UpdatedFiles = [.. g.SelectMany(u => u.UpdatedFiles)],
|
59
|
+
};
|
60
|
+
}
|
61
|
+
else if (g.Key.Item1 == typeof(ParentUpdate))
|
62
|
+
{
|
63
|
+
var parentUpdate = (ParentUpdate)g.First();
|
64
|
+
return new ParentUpdate()
|
65
|
+
{
|
66
|
+
DependencyName = g.Key.DependencyName,
|
67
|
+
NewVersion = g.Key.NewVersion,
|
68
|
+
UpdatedFiles = [.. g.SelectMany(u => u.UpdatedFiles)],
|
69
|
+
ParentDependencyName = parentUpdate.ParentDependencyName,
|
70
|
+
ParentNewVersion = parentUpdate.ParentNewVersion,
|
71
|
+
};
|
72
|
+
}
|
73
|
+
else
|
74
|
+
{
|
75
|
+
throw new NotImplementedException(g.Key.Item1.FullName);
|
76
|
+
}
|
77
|
+
})
|
78
|
+
.ToImmutableArray();
|
79
|
+
var withNormalizedAndDistinctPaths = groupedByKindWithCombinedFiles
|
80
|
+
.Select(u => u with { UpdatedFiles = [.. u.UpdatedFiles.Select(f => Path.GetRelativePath(repoRootPath, f).FullyNormalizedRootedPath()).Distinct(PathComparer.Instance).OrderBy(f => f, StringComparer.Ordinal)] })
|
81
|
+
.ToImmutableArray();
|
82
|
+
var uniqueUpdateOperations = withNormalizedAndDistinctPaths.Distinct(UpdateOperationBaseComparer.Instance).ToImmutableArray();
|
83
|
+
var ordered = uniqueUpdateOperations
|
84
|
+
.OrderBy(u => u.GetType().Name)
|
85
|
+
.ThenBy(u => u.DependencyName)
|
86
|
+
.ThenBy(u => u.NewVersion)
|
87
|
+
.ThenBy(u => u.UpdatedFiles.Length)
|
88
|
+
.ThenBy(u => string.Join(",", u.UpdatedFiles))
|
89
|
+
.ThenBy(u => u is ParentUpdate parentUpdate ? parentUpdate.ParentDependencyName : string.Empty)
|
90
|
+
.ThenBy(u => u is ParentUpdate parentUpdate ? parentUpdate.ParentNewVersion : u.NewVersion)
|
91
|
+
.ToImmutableArray();
|
92
|
+
return ordered;
|
93
|
+
}
|
94
|
+
|
95
|
+
public override int GetHashCode()
|
96
|
+
{
|
97
|
+
var hash = new HashCode();
|
98
|
+
hash.Add(DependencyName);
|
99
|
+
hash.Add(NewVersion);
|
100
|
+
hash.Add(UpdatedFiles.Length);
|
101
|
+
for (int i = 0; i < UpdatedFiles.Length; i++)
|
102
|
+
{
|
103
|
+
hash.Add(UpdatedFiles[i]);
|
104
|
+
}
|
105
|
+
|
106
|
+
return hash.ToHashCode();
|
107
|
+
}
|
108
|
+
|
109
|
+
protected string GetString() => $"{GetType().Name} {{ {nameof(DependencyName)} = {DependencyName}, {nameof(NewVersion)} = {NewVersion}, {nameof(UpdatedFiles)} = {string.Join(",", UpdatedFiles)} }}";
|
110
|
+
}
|
111
|
+
|
112
|
+
public record DirectUpdate : UpdateOperationBase
|
113
|
+
{
|
114
|
+
public override string Type => nameof(DirectUpdate);
|
115
|
+
public override string GetReport() => $"Updated {DependencyName} to {NewVersion} in {string.Join("", UpdatedFiles)}";
|
116
|
+
public sealed override string ToString() => GetString();
|
117
|
+
}
|
118
|
+
|
119
|
+
public record PinnedUpdate : UpdateOperationBase
|
120
|
+
{
|
121
|
+
public override string Type => nameof(PinnedUpdate);
|
122
|
+
public override string GetReport() => $"Pinned {DependencyName} at {NewVersion} in {string.Join("", UpdatedFiles)}";
|
123
|
+
public sealed override string ToString() => GetString();
|
124
|
+
}
|
125
|
+
|
126
|
+
public record ParentUpdate : UpdateOperationBase, IEquatable<UpdateOperationBase>
|
127
|
+
{
|
128
|
+
public override string Type => nameof(ParentUpdate);
|
129
|
+
public required string ParentDependencyName { get; init; }
|
130
|
+
public required NuGetVersion ParentNewVersion { get; init; }
|
131
|
+
|
132
|
+
public override string GetReport() => $"Updated {DependencyName} to {NewVersion} indirectly via {ParentDependencyName}/{ParentNewVersion} in {string.Join("", UpdatedFiles)}";
|
133
|
+
|
134
|
+
bool IEquatable<UpdateOperationBase>.Equals(UpdateOperationBase? other)
|
135
|
+
{
|
136
|
+
if (!base.Equals(other))
|
137
|
+
{
|
138
|
+
return false;
|
139
|
+
}
|
140
|
+
|
141
|
+
if (other is not ParentUpdate otherParentUpdate)
|
142
|
+
{
|
143
|
+
return false;
|
144
|
+
}
|
145
|
+
|
146
|
+
return ParentDependencyName == otherParentUpdate.ParentDependencyName
|
147
|
+
&& ParentNewVersion == otherParentUpdate.ParentNewVersion;
|
148
|
+
}
|
149
|
+
|
150
|
+
public override int GetHashCode()
|
151
|
+
{
|
152
|
+
var hash = new HashCode();
|
153
|
+
hash.Add(base.GetHashCode());
|
154
|
+
hash.Add(ParentDependencyName);
|
155
|
+
hash.Add(ParentNewVersion);
|
156
|
+
return hash.ToHashCode();
|
157
|
+
}
|
158
|
+
|
159
|
+
public sealed override string ToString() => $"{GetType().Name} {{ {nameof(DependencyName)} = {DependencyName}, {nameof(NewVersion)} = {NewVersion}, {nameof(ParentDependencyName)} = {ParentDependencyName}, {nameof(ParentNewVersion)} = {ParentNewVersion}, {nameof(UpdatedFiles)} = {string.Join(",", UpdatedFiles)} }}";
|
160
|
+
}
|
161
|
+
|
162
|
+
public class UpdateOperationBaseComparer : IEqualityComparer<UpdateOperationBase>
|
163
|
+
{
|
164
|
+
public static UpdateOperationBaseComparer Instance = new();
|
165
|
+
|
166
|
+
public bool Equals(UpdateOperationBase? x, UpdateOperationBase? y)
|
167
|
+
{
|
168
|
+
if (x is null && y is null)
|
169
|
+
{
|
170
|
+
return true;
|
171
|
+
}
|
172
|
+
|
173
|
+
if (x is null || y is null)
|
174
|
+
{
|
175
|
+
return false;
|
176
|
+
}
|
177
|
+
|
178
|
+
if (ReferenceEquals(x, y))
|
179
|
+
{
|
180
|
+
return true;
|
181
|
+
}
|
182
|
+
|
183
|
+
if (x.GetType() != y.GetType())
|
184
|
+
{
|
185
|
+
return false;
|
186
|
+
}
|
187
|
+
|
188
|
+
if (x.DependencyName != y.DependencyName ||
|
189
|
+
x.NewVersion != y.NewVersion ||
|
190
|
+
!x.UpdatedFiles.SequenceEqual(y.UpdatedFiles))
|
191
|
+
{
|
192
|
+
return false;
|
193
|
+
}
|
194
|
+
|
195
|
+
if (x is ParentUpdate px && y is ParentUpdate py)
|
196
|
+
{
|
197
|
+
// the `.GetType()` check above ensures this is safe
|
198
|
+
if (px.ParentDependencyName != py.ParentDependencyName ||
|
199
|
+
px.ParentNewVersion != py.ParentNewVersion)
|
200
|
+
{
|
201
|
+
return false;
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
return true;
|
206
|
+
}
|
207
|
+
|
208
|
+
public int GetHashCode([DisallowNull] UpdateOperationBase obj) => obj.GetHashCode();
|
209
|
+
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
using System.Collections.Immutable;
|
1
2
|
using System.Net;
|
2
3
|
using System.Text.Json;
|
3
4
|
using System.Text.Json.Serialization;
|
@@ -19,7 +20,7 @@ public class UpdaterWorker : IUpdaterWorker
|
|
19
20
|
internal static readonly JsonSerializerOptions SerializerOptions = new()
|
20
21
|
{
|
21
22
|
WriteIndented = true,
|
22
|
-
Converters = { new JsonStringEnumConverter() },
|
23
|
+
Converters = { new JsonStringEnumConverter(), new VersionConverter() },
|
23
24
|
};
|
24
25
|
|
25
26
|
public UpdaterWorker(string jobId, ExperimentsManager experimentsManager, ILogger logger)
|
@@ -41,10 +42,10 @@ public class UpdaterWorker : IUpdaterWorker
|
|
41
42
|
// this is a convenient method for tests
|
42
43
|
internal async Task<UpdateOperationResult> RunWithErrorHandlingAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
|
43
44
|
{
|
44
|
-
UpdateOperationResult result = new(); // assumed to be ok until proven otherwise
|
45
45
|
try
|
46
46
|
{
|
47
|
-
result = await RunAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
47
|
+
var result = await RunAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
48
|
+
return result;
|
48
49
|
}
|
49
50
|
catch (Exception ex)
|
50
51
|
{
|
@@ -52,13 +53,15 @@ public class UpdaterWorker : IUpdaterWorker
|
|
52
53
|
{
|
53
54
|
workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
|
54
55
|
}
|
55
|
-
|
56
|
+
|
57
|
+
var error = JobErrorBase.ErrorFromException(ex, _jobId, workspacePath);
|
58
|
+
var result = new UpdateOperationResult()
|
56
59
|
{
|
57
|
-
|
60
|
+
UpdateOperations = [],
|
61
|
+
Error = error,
|
58
62
|
};
|
63
|
+
return result;
|
59
64
|
}
|
60
|
-
|
61
|
-
return result;
|
62
65
|
}
|
63
66
|
|
64
67
|
public async Task<UpdateOperationResult> RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
|
@@ -76,40 +79,60 @@ public class UpdaterWorker : IUpdaterWorker
|
|
76
79
|
await GlobalJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
|
77
80
|
}
|
78
81
|
|
82
|
+
UpdateOperationResult result;
|
79
83
|
var extension = Path.GetExtension(workspacePath).ToLowerInvariant();
|
80
84
|
switch (extension)
|
81
85
|
{
|
82
86
|
case ".sln":
|
83
|
-
await RunForSolutionAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
87
|
+
result = await RunForSolutionAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
84
88
|
break;
|
85
89
|
case ".proj":
|
86
|
-
await RunForProjFileAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
90
|
+
result = await RunForProjFileAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
87
91
|
break;
|
88
92
|
case ".csproj":
|
89
93
|
case ".fsproj":
|
90
94
|
case ".vbproj":
|
91
|
-
await RunForProjectAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
95
|
+
result = await RunForProjectAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
92
96
|
break;
|
93
97
|
default:
|
94
98
|
_logger.Info($"File extension [{extension}] is not supported.");
|
99
|
+
result = new UpdateOperationResult()
|
100
|
+
{
|
101
|
+
UpdateOperations = [],
|
102
|
+
};
|
95
103
|
break;
|
96
104
|
}
|
97
105
|
|
106
|
+
result = result with { UpdateOperations = UpdateOperationBase.NormalizeUpdateOperationCollection(repoRootPath, result.UpdateOperations) };
|
107
|
+
|
108
|
+
if (!_experimentsManager.NativeUpdater)
|
109
|
+
{
|
110
|
+
// native updater reports the changes elsewhere
|
111
|
+
var updateReport = UpdateOperationBase.GenerateUpdateOperationReport(result.UpdateOperations);
|
112
|
+
_logger.Info(updateReport);
|
113
|
+
}
|
114
|
+
|
98
115
|
_logger.Info("Update complete.");
|
99
116
|
|
100
117
|
_processedProjectPaths.Clear();
|
101
|
-
return
|
118
|
+
return result;
|
119
|
+
}
|
120
|
+
|
121
|
+
internal static string Serialize(UpdateOperationResult result)
|
122
|
+
{
|
123
|
+
var resultJson = JsonSerializer.Serialize(result, SerializerOptions);
|
124
|
+
return resultJson;
|
102
125
|
}
|
103
126
|
|
104
127
|
internal static async Task WriteResultFile(UpdateOperationResult result, string resultOutputPath, ILogger logger)
|
105
128
|
{
|
106
129
|
logger.Info($" Writing update result to [{resultOutputPath}].");
|
107
130
|
|
108
|
-
var resultJson =
|
131
|
+
var resultJson = Serialize(result);
|
109
132
|
await File.WriteAllTextAsync(resultOutputPath, resultJson);
|
110
133
|
}
|
111
134
|
|
112
|
-
private async Task RunForSolutionAsync(
|
135
|
+
private async Task<UpdateOperationResult> RunForSolutionAsync(
|
113
136
|
string repoRootPath,
|
114
137
|
string solutionPath,
|
115
138
|
string dependencyName,
|
@@ -118,14 +141,21 @@ public class UpdaterWorker : IUpdaterWorker
|
|
118
141
|
bool isTransitive)
|
119
142
|
{
|
120
143
|
_logger.Info($"Running for solution [{Path.GetRelativePath(repoRootPath, solutionPath)}]");
|
144
|
+
var updateOperations = new List<UpdateOperationBase>();
|
121
145
|
var projectPaths = MSBuildHelper.GetProjectPathsFromSolution(solutionPath);
|
122
146
|
foreach (var projectPath in projectPaths)
|
123
147
|
{
|
124
|
-
await RunForProjectAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
148
|
+
var projectResult = await RunForProjectAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
149
|
+
updateOperations.AddRange(projectResult.UpdateOperations);
|
125
150
|
}
|
151
|
+
|
152
|
+
return new UpdateOperationResult()
|
153
|
+
{
|
154
|
+
UpdateOperations = updateOperations.ToImmutableArray(),
|
155
|
+
};
|
126
156
|
}
|
127
157
|
|
128
|
-
private async Task RunForProjFileAsync(
|
158
|
+
private async Task<UpdateOperationResult> RunForProjFileAsync(
|
129
159
|
string repoRootPath,
|
130
160
|
string projFilePath,
|
131
161
|
string dependencyName,
|
@@ -137,21 +167,31 @@ public class UpdaterWorker : IUpdaterWorker
|
|
137
167
|
if (!File.Exists(projFilePath))
|
138
168
|
{
|
139
169
|
_logger.Info($"File [{projFilePath}] does not exist.");
|
140
|
-
return
|
170
|
+
return new UpdateOperationResult()
|
171
|
+
{
|
172
|
+
UpdateOperations = [],
|
173
|
+
};
|
141
174
|
}
|
142
175
|
|
176
|
+
var updateOperations = new List<UpdateOperationBase>();
|
143
177
|
var projectFilePaths = MSBuildHelper.GetProjectPathsFromProject(projFilePath);
|
144
178
|
foreach (var projectFullPath in projectFilePaths)
|
145
179
|
{
|
146
180
|
// If there is some MSBuild logic that needs to run to fully resolve the path skip the project
|
147
181
|
if (File.Exists(projectFullPath))
|
148
182
|
{
|
149
|
-
await RunForProjectAsync(repoRootPath, projectFullPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
183
|
+
var projectResult = await RunForProjectAsync(repoRootPath, projectFullPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
184
|
+
updateOperations.AddRange(projectResult.UpdateOperations);
|
150
185
|
}
|
151
186
|
}
|
187
|
+
|
188
|
+
return new UpdateOperationResult()
|
189
|
+
{
|
190
|
+
UpdateOperations = updateOperations.ToImmutableArray(),
|
191
|
+
};
|
152
192
|
}
|
153
193
|
|
154
|
-
private async Task RunForProjectAsync(
|
194
|
+
private async Task<UpdateOperationResult> RunForProjectAsync(
|
155
195
|
string repoRootPath,
|
156
196
|
string projectPath,
|
157
197
|
string dependencyName,
|
@@ -163,21 +203,31 @@ public class UpdaterWorker : IUpdaterWorker
|
|
163
203
|
if (!File.Exists(projectPath))
|
164
204
|
{
|
165
205
|
_logger.Info($"File [{projectPath}] does not exist.");
|
166
|
-
return
|
206
|
+
return new UpdateOperationResult()
|
207
|
+
{
|
208
|
+
UpdateOperations = [],
|
209
|
+
};
|
167
210
|
}
|
168
211
|
|
212
|
+
var updateOperations = new List<UpdateOperationBase>();
|
169
213
|
var projectFilePaths = MSBuildHelper.GetProjectPathsFromProject(projectPath);
|
170
214
|
foreach (var projectFullPath in projectFilePaths.Concat([projectPath]))
|
171
215
|
{
|
172
216
|
// If there is some MSBuild logic that needs to run to fully resolve the path skip the project
|
173
217
|
if (File.Exists(projectFullPath))
|
174
218
|
{
|
175
|
-
await RunUpdaterAsync(repoRootPath, projectFullPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
219
|
+
var performedOperations = await RunUpdaterAsync(repoRootPath, projectFullPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
|
220
|
+
updateOperations.AddRange(performedOperations);
|
176
221
|
}
|
177
222
|
}
|
223
|
+
|
224
|
+
return new UpdateOperationResult()
|
225
|
+
{
|
226
|
+
UpdateOperations = updateOperations.ToImmutableArray(),
|
227
|
+
};
|
178
228
|
}
|
179
229
|
|
180
|
-
private async Task RunUpdaterAsync(
|
230
|
+
private async Task<IEnumerable<UpdateOperationBase>> RunUpdaterAsync(
|
181
231
|
string repoRootPath,
|
182
232
|
string projectPath,
|
183
233
|
string dependencyName,
|
@@ -187,22 +237,25 @@ public class UpdaterWorker : IUpdaterWorker
|
|
187
237
|
{
|
188
238
|
if (_processedProjectPaths.Contains(projectPath))
|
189
239
|
{
|
190
|
-
return;
|
240
|
+
return [];
|
191
241
|
}
|
192
242
|
|
193
243
|
_processedProjectPaths.Add(projectPath);
|
194
244
|
|
195
245
|
_logger.Info($"Updating project [{projectPath}]");
|
196
246
|
|
247
|
+
var updateOperations = new List<UpdateOperationBase>();
|
197
248
|
var additionalFiles = ProjectHelper.GetAllAdditionalFilesFromProject(projectPath, ProjectHelper.PathFormat.Full);
|
198
249
|
var packagesConfigFullPath = additionalFiles.Where(p => Path.GetFileName(p).Equals(ProjectHelper.PackagesConfigFileName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
|
199
250
|
if (packagesConfigFullPath is not null)
|
200
251
|
{
|
201
|
-
await PackagesConfigUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, packagesConfigFullPath, _logger);
|
252
|
+
var packagesConfigOperations = await PackagesConfigUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, packagesConfigFullPath, _logger);
|
253
|
+
updateOperations.AddRange(packagesConfigOperations);
|
202
254
|
}
|
203
255
|
|
204
256
|
// Some repos use a mix of packages.config and PackageReference
|
205
|
-
await PackageReferenceUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive, _experimentsManager, _logger);
|
257
|
+
var packageReferenceOperations = await PackageReferenceUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive, _experimentsManager, _logger);
|
258
|
+
updateOperations.AddRange(packageReferenceOperations);
|
206
259
|
|
207
260
|
// Update lock file if exists
|
208
261
|
var packagesLockFullPath = additionalFiles.Where(p => Path.GetFileName(p).Equals(ProjectHelper.PackagesLockJsonFileName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
|
@@ -210,5 +263,7 @@ public class UpdaterWorker : IUpdaterWorker
|
|
210
263
|
{
|
211
264
|
await LockFileUpdater.UpdateLockFileAsync(repoRootPath, projectPath, _experimentsManager, _logger);
|
212
265
|
}
|
266
|
+
|
267
|
+
return updateOperations;
|
213
268
|
}
|
214
269
|
}
|
@@ -1,3 +1,5 @@
|
|
1
|
+
using System.Collections.Immutable;
|
2
|
+
|
1
3
|
using Microsoft.Language.Xml;
|
2
4
|
|
3
5
|
namespace NuGetUpdater.Core.Updater
|
@@ -6,11 +8,11 @@ namespace NuGetUpdater.Core.Updater
|
|
6
8
|
{
|
7
9
|
public Func<string> GetContent { get; }
|
8
10
|
public Action<string> SetContent { get; }
|
9
|
-
public Func<XmlDocumentSyntax, XmlNodeSyntax
|
10
|
-
public Func<XmlNodeSyntax, XmlNodeSyntax> PreProcessor { get; }
|
11
|
-
public Func<XmlNodeSyntax, XmlNodeSyntax> PostProcessor { get; }
|
11
|
+
public Func<XmlDocumentSyntax, IEnumerable<XmlNodeSyntax>> NodeFinder { get; }
|
12
|
+
public Func<int, XmlNodeSyntax, XmlNodeSyntax> PreProcessor { get; }
|
13
|
+
public Func<int, XmlNodeSyntax, XmlNodeSyntax> PostProcessor { get; }
|
12
14
|
|
13
|
-
public XmlFilePreAndPostProcessor(Func<string> getContent, Action<string> setContent, Func<XmlDocumentSyntax, XmlNodeSyntax
|
15
|
+
public XmlFilePreAndPostProcessor(Func<string> getContent, Action<string> setContent, Func<XmlDocumentSyntax, IEnumerable<XmlNodeSyntax>> nodeFinder, Func<int, XmlNodeSyntax, XmlNodeSyntax> preProcessor, Func<int, XmlNodeSyntax, XmlNodeSyntax> postProcessor)
|
14
16
|
{
|
15
17
|
GetContent = getContent;
|
16
18
|
SetContent = setContent;
|
@@ -29,7 +31,7 @@ namespace NuGetUpdater.Core.Updater
|
|
29
31
|
|
30
32
|
private void PostProcess() => RunProcessor(PostProcessor);
|
31
33
|
|
32
|
-
private void RunProcessor(Func<XmlNodeSyntax, XmlNodeSyntax> processor)
|
34
|
+
private void RunProcessor(Func<int, XmlNodeSyntax, XmlNodeSyntax> processor)
|
33
35
|
{
|
34
36
|
var content = GetContent();
|
35
37
|
var xml = Parser.ParseText(content);
|
@@ -38,15 +40,28 @@ namespace NuGetUpdater.Core.Updater
|
|
38
40
|
return;
|
39
41
|
}
|
40
42
|
|
41
|
-
var
|
42
|
-
|
43
|
+
var offset = 0;
|
44
|
+
var nodes = NodeFinder(xml).ToImmutableArray();
|
45
|
+
for (int i = 0; i < nodes.Length; i++)
|
43
46
|
{
|
44
|
-
|
47
|
+
// modify the node...
|
48
|
+
var node = nodes[i];
|
49
|
+
var replacementElement = processor(i, node);
|
50
|
+
|
51
|
+
// ...however, the XML structure we're using is immutable and calling `.ReplaceNode()` below will fail because the nodes are no longer equal
|
52
|
+
// find the equivalent node by offset, accounting for any changes in length
|
53
|
+
var candidateEquivalentNodes = xml.DescendantNodes().OfType<XmlNodeSyntax>().ToArray();
|
54
|
+
var equivalentNode = candidateEquivalentNodes.First(n => n.Start == node.Start + offset);
|
55
|
+
|
56
|
+
// do the actual replacement
|
57
|
+
xml = xml.ReplaceNode(equivalentNode, replacementElement);
|
58
|
+
|
59
|
+
// update our offset
|
60
|
+
var thisNodeOffset = replacementElement.ToFullString().Length - node.ToFullString().Length;
|
61
|
+
offset += thisNodeOffset;
|
45
62
|
}
|
46
63
|
|
47
|
-
var
|
48
|
-
var replacementXml = xml.ReplaceNode(node, replacementElement);
|
49
|
-
var replacementString = replacementXml.ToFullString();
|
64
|
+
var replacementString = xml.ToFullString();
|
50
65
|
SetContent(replacementString);
|
51
66
|
}
|
52
67
|
}
|