dependabot-nuget 0.321.2 → 0.322.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 +22 -22
- data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageMapper.cs +9 -0
- data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/Program.cs +21 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +19 -11
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs +19 -9
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +21 -14
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/FrameworkCheckCommand.cs +8 -5
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +29 -16
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +20 -19
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +2 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscoveryTargetingPacks.props +2 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencySolver/IDependencySolver.cs +8 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencySolver/MSBuildDependencySolver.cs +32 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +10 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +6 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/GlobalJsonBuildFile.cs +5 -13
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/PrivateSourceTimedOutException.cs +12 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +4 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceTimedOut.cs +10 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestTextGenerator.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandler.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/GroupUpdateAllVersionsHandler.cs +2 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandler.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandler.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandler.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +6 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/FileWriterWorker.cs +376 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/IFileWriter.cs +14 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/XmlFileWriter.cs +477 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +9 -5
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationBase.cs +18 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +26 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +15 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/DependencySolver/MSBuildDependencySolverTests.cs +633 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +0 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +0 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +49 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/GlobalJsonBuildFileTests.cs +0 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs +484 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/JobErrorBaseTests.cs +7 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +11 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestTextTests.cs +21 -22
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +8 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/DotNetToolsJsonUpdaterTests.cs +181 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterTestsBase.cs +61 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests.cs +917 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests_MiscellaneousTests.cs +154 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/TestFileWriterReturnsConstantResult.cs +20 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests.cs +1620 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests_CreateUpdatedVersionRangeTests.cs +25 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/GlobalJsonUpdaterTests.cs +139 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +1961 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateOperationResultTests.cs +116 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +16 -1043
- data/helpers/lib/NuGetUpdater/global.json +1 -1
- metadata +21 -10
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +0 -375
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +0 -296
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.LockFile.cs +0 -251
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +0 -201
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +0 -3821
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +0 -2706
@@ -0,0 +1,477 @@
|
|
1
|
+
using System.Collections.Immutable;
|
2
|
+
using System.Text.RegularExpressions;
|
3
|
+
using System.Xml.Linq;
|
4
|
+
|
5
|
+
using NuGet.Versioning;
|
6
|
+
|
7
|
+
using NuGetUpdater.Core.Utilities;
|
8
|
+
|
9
|
+
namespace NuGetUpdater.Core.Updater.FileWriters;
|
10
|
+
|
11
|
+
public class XmlFileWriter : IFileWriter
|
12
|
+
{
|
13
|
+
private const string IncludeAttributeName = "Include";
|
14
|
+
private const string UpdateAttributeName = "Update";
|
15
|
+
private const string VersionMetadataName = "Version";
|
16
|
+
private const string VersionOverrideMetadataName = "VersionOverride";
|
17
|
+
|
18
|
+
private const string ItemGroupElementName = "ItemGroup";
|
19
|
+
private const string GlobalPackageReferenceElementName = "GlobalPackageReference";
|
20
|
+
private const string PackageReferenceElementName = "PackageReference";
|
21
|
+
private const string PackageVersionElementName = "PackageVersion";
|
22
|
+
private const string PropertyGroupElementName = "PropertyGroup";
|
23
|
+
|
24
|
+
private readonly ILogger _logger;
|
25
|
+
|
26
|
+
// these file extensions are valid project entrypoints; everything else is ignored
|
27
|
+
private static readonly HashSet<string> SupportedProjectFileExtensions = new(StringComparer.OrdinalIgnoreCase)
|
28
|
+
{
|
29
|
+
".csproj",
|
30
|
+
".vbproj",
|
31
|
+
".fsproj",
|
32
|
+
};
|
33
|
+
|
34
|
+
// these file extensions are valid additional files and can be updated; everything else is ignored
|
35
|
+
private static readonly HashSet<string> SupportedAdditionalFileExtensions = new(StringComparer.OrdinalIgnoreCase)
|
36
|
+
{
|
37
|
+
".props",
|
38
|
+
".targets",
|
39
|
+
};
|
40
|
+
|
41
|
+
public XmlFileWriter(ILogger logger)
|
42
|
+
{
|
43
|
+
_logger = logger;
|
44
|
+
}
|
45
|
+
|
46
|
+
public async Task<bool> UpdatePackageVersionsAsync(
|
47
|
+
DirectoryInfo repoContentsPath,
|
48
|
+
ImmutableArray<string> relativeFilePaths,
|
49
|
+
ImmutableArray<Dependency> originalDependencies,
|
50
|
+
ImmutableArray<Dependency> requiredPackageVersions,
|
51
|
+
bool addPackageReferenceElementForPinnedPackages
|
52
|
+
)
|
53
|
+
{
|
54
|
+
if (relativeFilePaths.IsDefaultOrEmpty)
|
55
|
+
{
|
56
|
+
_logger.Warn("No files to update; skipping XML update.");
|
57
|
+
return false;
|
58
|
+
}
|
59
|
+
|
60
|
+
var updatesPerformed = requiredPackageVersions.ToDictionary(d => d.Name, _ => false, StringComparer.OrdinalIgnoreCase);
|
61
|
+
var projectRelativePath = relativeFilePaths[0];
|
62
|
+
var projectExtension = Path.GetExtension(projectRelativePath);
|
63
|
+
if (!SupportedProjectFileExtensions.Contains(projectExtension))
|
64
|
+
{
|
65
|
+
_logger.Warn($"Project extension '{projectExtension}' not supported; skipping XML update.");
|
66
|
+
return false;
|
67
|
+
}
|
68
|
+
|
69
|
+
var filesAndContentsTasks = relativeFilePaths
|
70
|
+
.Where(path => SupportedProjectFileExtensions.Contains(Path.GetExtension(path)) || SupportedAdditionalFileExtensions.Contains(Path.GetExtension(path)))
|
71
|
+
.Select(async path =>
|
72
|
+
{
|
73
|
+
var content = await ReadFileContentsAsync(repoContentsPath, path);
|
74
|
+
var document = XDocument.Parse(content, LoadOptions.PreserveWhitespace);
|
75
|
+
return KeyValuePair.Create(path, document);
|
76
|
+
})
|
77
|
+
.ToArray();
|
78
|
+
var filesAndContents = (await Task.WhenAll(filesAndContentsTasks))
|
79
|
+
.ToDictionary();
|
80
|
+
foreach (var requiredPackageVersion in requiredPackageVersions)
|
81
|
+
{
|
82
|
+
var oldVersionString = originalDependencies.FirstOrDefault(d => d.Name.Equals(requiredPackageVersion.Name, StringComparison.OrdinalIgnoreCase))?.Version;
|
83
|
+
if (oldVersionString is null)
|
84
|
+
{
|
85
|
+
_logger.Warn($"Unable to find project dependency with name {requiredPackageVersion.Name}; skipping XML update.");
|
86
|
+
continue;
|
87
|
+
}
|
88
|
+
|
89
|
+
var oldVersion = NuGetVersion.Parse(oldVersionString);
|
90
|
+
var requiredVersion = NuGetVersion.Parse(requiredPackageVersion.Version!);
|
91
|
+
|
92
|
+
if (oldVersion == requiredVersion)
|
93
|
+
{
|
94
|
+
_logger.Info($"Dependency {requiredPackageVersion.Name} is already at version {requiredVersion}; no update needed.");
|
95
|
+
updatesPerformed[requiredPackageVersion.Name] = true;
|
96
|
+
continue;
|
97
|
+
}
|
98
|
+
|
99
|
+
// version numbers can be in attributes or elements and we may need to do some complicated navigation
|
100
|
+
// this object is used to perform the update once we've walked back as far as necessary
|
101
|
+
string? currentVersionString = null;
|
102
|
+
Action<string>? updateVersionLocation = null;
|
103
|
+
|
104
|
+
var packageReferenceElements = filesAndContents.Values
|
105
|
+
.SelectMany(doc => doc.Descendants().Where(e => e.Name.LocalName == PackageReferenceElementName || e.Name.LocalName == GlobalPackageReferenceElementName))
|
106
|
+
.Where(e =>
|
107
|
+
{
|
108
|
+
var attributeValue = e.Attribute(IncludeAttributeName)?.Value ?? e.Attribute(UpdateAttributeName)?.Value ?? string.Empty;
|
109
|
+
var packageNames = attributeValue.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
110
|
+
return packageNames.Any(name => name.Equals(requiredPackageVersion.Name, StringComparison.OrdinalIgnoreCase));
|
111
|
+
})
|
112
|
+
.ToArray();
|
113
|
+
|
114
|
+
if (packageReferenceElements.Length == 0)
|
115
|
+
{
|
116
|
+
// no matching `<PackageReference>` elements found; pin it as a transitive dependency
|
117
|
+
updatesPerformed[requiredPackageVersion.Name] = true; // all cases below add the dependency
|
118
|
+
|
119
|
+
// find last `<ItemGroup>` in the project...
|
120
|
+
Action addItemGroup = () => { }; // adding an ItemGroup to the project isn't always necessary, but it's much easier to prepare for it here
|
121
|
+
var projectDocument = filesAndContents[projectRelativePath];
|
122
|
+
var lastItemGroup = projectDocument.Root!.Elements()
|
123
|
+
.LastOrDefault(e => e.Name.LocalName.Equals(ItemGroupElementName, StringComparison.OrdinalIgnoreCase));
|
124
|
+
if (lastItemGroup is null)
|
125
|
+
{
|
126
|
+
_logger.Info($"No `<{ItemGroupElementName}>` element found in project; adding one.");
|
127
|
+
lastItemGroup = new XElement(XName.Get(ItemGroupElementName, projectDocument.Root.Name.NamespaceName));
|
128
|
+
addItemGroup = () => projectDocument.Root.Add(lastItemGroup);
|
129
|
+
}
|
130
|
+
|
131
|
+
// ...find where the new item should go...
|
132
|
+
var packageReferencesBeforeNew = lastItemGroup.Elements()
|
133
|
+
.Where(e => e.Name.LocalName.Equals(PackageReferenceElementName, StringComparison.OrdinalIgnoreCase))
|
134
|
+
.TakeWhile(e => (e.Attribute(IncludeAttributeName)?.Value ?? e.Attribute(UpdateAttributeName)?.Value ?? string.Empty).CompareTo(requiredPackageVersion.Name) < 0)
|
135
|
+
.ToArray();
|
136
|
+
|
137
|
+
// ...prepare a new `<PackageReference>` element...
|
138
|
+
var newElement = new XElement(
|
139
|
+
XName.Get(PackageReferenceElementName, projectDocument.Root.Name.NamespaceName),
|
140
|
+
new XAttribute(IncludeAttributeName, requiredPackageVersion.Name));
|
141
|
+
|
142
|
+
// ...add the `<PackageReference>` element if and where appropriate...
|
143
|
+
if (addPackageReferenceElementForPinnedPackages)
|
144
|
+
{
|
145
|
+
addItemGroup();
|
146
|
+
var lastPriorPackageReference = packageReferencesBeforeNew.LastOrDefault();
|
147
|
+
if (lastPriorPackageReference is not null)
|
148
|
+
{
|
149
|
+
AddAfterSiblingElement(lastPriorPackageReference, newElement);
|
150
|
+
}
|
151
|
+
else
|
152
|
+
{
|
153
|
+
// no prior package references; add to the front
|
154
|
+
var indent = GetIndentXTextFromElement(lastItemGroup, extraIndentationToAdd: " ");
|
155
|
+
lastItemGroup.AddFirst(indent, newElement);
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
// ...find the best place to add the version...
|
160
|
+
var matchingPackageVersionElement = filesAndContents.Values
|
161
|
+
.SelectMany(doc => doc.Descendants().Where(e => e.Name.LocalName.Equals(PackageVersionElementName, StringComparison.OrdinalIgnoreCase)))
|
162
|
+
.FirstOrDefault(e => (e.Attribute(IncludeAttributeName)?.Value ?? string.Empty).Trim().Equals(requiredPackageVersion.Name, StringComparison.OrdinalIgnoreCase));
|
163
|
+
if (matchingPackageVersionElement is not null)
|
164
|
+
{
|
165
|
+
// found matching `<PackageVersion>` element; if `Version` attribute is appropriate we're done, otherwise set `VersionOverride` attribute on new element
|
166
|
+
var versionAttribute = matchingPackageVersionElement.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals(VersionMetadataName, StringComparison.OrdinalIgnoreCase));
|
167
|
+
if (versionAttribute is not null &&
|
168
|
+
NuGetVersion.TryParse(versionAttribute.Value, out var existingVersion) &&
|
169
|
+
existingVersion == requiredVersion)
|
170
|
+
{
|
171
|
+
// version matches; no update needed
|
172
|
+
_logger.Info($"Dependency {requiredPackageVersion.Name} already set to {requiredVersion}; no override needed.");
|
173
|
+
}
|
174
|
+
else
|
175
|
+
{
|
176
|
+
// version doesn't match; use `VersionOverride` attribute on new element
|
177
|
+
_logger.Info($"Dependency {requiredPackageVersion.Name} set to {requiredVersion}; using `{VersionOverrideMetadataName}` attribute on new element.");
|
178
|
+
newElement.SetAttributeValue(VersionOverrideMetadataName, requiredVersion.ToString());
|
179
|
+
}
|
180
|
+
}
|
181
|
+
else
|
182
|
+
{
|
183
|
+
// no matching `<PackageVersion>` element; either add a new one, or directly set the `Version` attribute on the new element
|
184
|
+
var allPackageVersionElements = filesAndContents.Values
|
185
|
+
.SelectMany(doc => doc.Descendants().Where(e => e.Name.LocalName.Equals(PackageVersionElementName, StringComparison.OrdinalIgnoreCase)))
|
186
|
+
.ToArray();
|
187
|
+
if (allPackageVersionElements.Length > 0)
|
188
|
+
{
|
189
|
+
// add a new `<PackageVersion>` element
|
190
|
+
var newVersionElement = new XElement(XName.Get(PackageVersionElementName, projectDocument.Root.Name.NamespaceName),
|
191
|
+
new XAttribute(IncludeAttributeName, requiredPackageVersion.Name),
|
192
|
+
new XAttribute(VersionMetadataName, requiredVersion.ToString()));
|
193
|
+
var lastPriorPackageVersionElement = allPackageVersionElements
|
194
|
+
.TakeWhile(e => (e.Attribute(IncludeAttributeName)?.Value ?? string.Empty).Trim().CompareTo(requiredPackageVersion.Name) < 0)
|
195
|
+
.LastOrDefault();
|
196
|
+
if (lastPriorPackageVersionElement is not null)
|
197
|
+
{
|
198
|
+
_logger.Info($"Adding new `<{PackageVersionElementName}>` element for {requiredPackageVersion.Name} with version {requiredVersion}.");
|
199
|
+
AddAfterSiblingElement(lastPriorPackageVersionElement, newVersionElement);
|
200
|
+
}
|
201
|
+
else
|
202
|
+
{
|
203
|
+
// no prior package versions; add to the front of the document
|
204
|
+
_logger.Info($"Adding new `<{PackageVersionElementName}>` element for {requiredPackageVersion.Name} with version {requiredVersion} at the start of the document.");
|
205
|
+
var packageVersionGroup = allPackageVersionElements.First().Parent!;
|
206
|
+
var indent = GetIndentXTextFromElement(packageVersionGroup, extraIndentationToAdd: " ");
|
207
|
+
packageVersionGroup.AddFirst(indent, newVersionElement);
|
208
|
+
}
|
209
|
+
}
|
210
|
+
else
|
211
|
+
{
|
212
|
+
// add a direct `Version` attribute
|
213
|
+
newElement.SetAttributeValue(VersionMetadataName, requiredVersion.ToString());
|
214
|
+
}
|
215
|
+
}
|
216
|
+
}
|
217
|
+
else
|
218
|
+
{
|
219
|
+
// found matching `<PackageReference>` elements to update
|
220
|
+
foreach (var packageReferenceElement in packageReferenceElements)
|
221
|
+
{
|
222
|
+
// first check for matching `Version` attribute
|
223
|
+
var versionAttribute = packageReferenceElement.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals(VersionMetadataName, StringComparison.OrdinalIgnoreCase));
|
224
|
+
if (versionAttribute is not null)
|
225
|
+
{
|
226
|
+
currentVersionString = versionAttribute.Value;
|
227
|
+
updateVersionLocation = (version) => versionAttribute.Value = version;
|
228
|
+
goto doVersionUpdate;
|
229
|
+
}
|
230
|
+
|
231
|
+
// next check for `Version` child element
|
232
|
+
var versionElement = packageReferenceElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals(VersionMetadataName, StringComparison.OrdinalIgnoreCase));
|
233
|
+
if (versionElement is not null)
|
234
|
+
{
|
235
|
+
currentVersionString = versionElement.Value;
|
236
|
+
updateVersionLocation = (version) => versionElement.Value = version;
|
237
|
+
goto doVersionUpdate;
|
238
|
+
}
|
239
|
+
|
240
|
+
// check for matching `<PackageVersion>` element
|
241
|
+
var packageVersionElement = filesAndContents.Values
|
242
|
+
.SelectMany(doc => doc.Descendants().Where(e => e.Name.LocalName == PackageVersionElementName))
|
243
|
+
.FirstOrDefault(e => (e.Attribute(IncludeAttributeName)?.Value ?? string.Empty).Trim().Equals(requiredPackageVersion.Name, StringComparison.OrdinalIgnoreCase));
|
244
|
+
if (packageVersionElement is not null)
|
245
|
+
{
|
246
|
+
var packageVersionAttribute = packageVersionElement.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals(VersionMetadataName, StringComparison.OrdinalIgnoreCase));
|
247
|
+
if (packageVersionAttribute is not null)
|
248
|
+
{
|
249
|
+
currentVersionString = packageVersionAttribute.Value;
|
250
|
+
updateVersionLocation = (version) => packageVersionAttribute.Value = version;
|
251
|
+
goto doVersionUpdate;
|
252
|
+
}
|
253
|
+
else
|
254
|
+
{
|
255
|
+
var cpmVersionElement = packageVersionElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals(VersionMetadataName, StringComparison.OrdinalIgnoreCase));
|
256
|
+
if (cpmVersionElement is not null)
|
257
|
+
{
|
258
|
+
currentVersionString = cpmVersionElement.Value;
|
259
|
+
updateVersionLocation = (version) => cpmVersionElement.Value = version;
|
260
|
+
goto doVersionUpdate;
|
261
|
+
}
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
doVersionUpdate:
|
266
|
+
if (currentVersionString is not null && updateVersionLocation is not null)
|
267
|
+
{
|
268
|
+
var performedUpdate = false;
|
269
|
+
var candidateUpdateLocations = new Queue<(string VersionString, Action<string> Updater)>();
|
270
|
+
candidateUpdateLocations.Enqueue((currentVersionString, updateVersionLocation));
|
271
|
+
|
272
|
+
while (candidateUpdateLocations.TryDequeue(out var candidateUpdateLocation))
|
273
|
+
{
|
274
|
+
var candidateUpdateVersionString = candidateUpdateLocation.VersionString;
|
275
|
+
var candidateUpdater = candidateUpdateLocation.Updater;
|
276
|
+
|
277
|
+
if (NuGetVersion.TryParse(candidateUpdateVersionString, out var candidateUpdateVersion))
|
278
|
+
{
|
279
|
+
// most common: direct update
|
280
|
+
if (candidateUpdateVersion == requiredVersion)
|
281
|
+
{
|
282
|
+
// already up to date from a previous pass
|
283
|
+
updatesPerformed[requiredPackageVersion.Name] = true;
|
284
|
+
performedUpdate = true;
|
285
|
+
_logger.Info($"Dependency {requiredPackageVersion.Name} already set to {requiredVersion}; no update needed.");
|
286
|
+
break;
|
287
|
+
}
|
288
|
+
else if (candidateUpdateVersion == oldVersion)
|
289
|
+
{
|
290
|
+
// do the update here and call it good
|
291
|
+
candidateUpdater(requiredVersion.ToString());
|
292
|
+
updatesPerformed[requiredPackageVersion.Name] = true;
|
293
|
+
performedUpdate = true;
|
294
|
+
_logger.Info($"Updated dependency {requiredPackageVersion.Name} from version {oldVersion} to {requiredVersion}.");
|
295
|
+
break;
|
296
|
+
}
|
297
|
+
else
|
298
|
+
{
|
299
|
+
// no exact match found, but this may be a magic SDK package
|
300
|
+
var packageMapper = DotNetPackageCorrelationManager.GetPackageMapper();
|
301
|
+
var isSdkReplacementPackage = packageMapper.IsSdkReplacementPackage(requiredPackageVersion.Name);
|
302
|
+
if (isSdkReplacementPackage &&
|
303
|
+
candidateUpdateVersion < oldVersion && // version in XML is older than what was resolved by the SDK
|
304
|
+
oldVersion < requiredVersion) // this ensures we don't downgrade the wrong one
|
305
|
+
{
|
306
|
+
// If we're updating a top level SDK replacement package, the version listed in the project file won't
|
307
|
+
// necessarily match the resolved version that caused the update because the SDK might have replaced
|
308
|
+
// the package. To handle this scenario, we pretend the version we're searching for was actually found.
|
309
|
+
candidateUpdater(requiredVersion.ToString());
|
310
|
+
updatesPerformed[requiredPackageVersion.Name] = true;
|
311
|
+
performedUpdate = true;
|
312
|
+
_logger.Info($"Updated SDK-managed package {requiredPackageVersion.Name} from version {oldVersion} to {requiredVersion}.");
|
313
|
+
break;
|
314
|
+
}
|
315
|
+
}
|
316
|
+
}
|
317
|
+
else if (VersionRange.TryParse(candidateUpdateVersionString, out var candidateUpdateVersionRange))
|
318
|
+
{
|
319
|
+
// less common: version range
|
320
|
+
if (candidateUpdateVersionRange.Satisfies(oldVersion))
|
321
|
+
{
|
322
|
+
var updatedVersionRange = CreateUpdatedVersionRangeString(candidateUpdateVersionRange, oldVersion, requiredVersion);
|
323
|
+
candidateUpdater(updatedVersionRange);
|
324
|
+
updatesPerformed[requiredPackageVersion.Name] = true;
|
325
|
+
performedUpdate = true;
|
326
|
+
_logger.Info($"Updated dependency {requiredPackageVersion.Name} from version {oldVersion} to {requiredVersion}.");
|
327
|
+
break;
|
328
|
+
}
|
329
|
+
else if (candidateUpdateVersionRange.Satisfies(requiredVersion))
|
330
|
+
{
|
331
|
+
// already up to date from a previous pass
|
332
|
+
updatesPerformed[requiredPackageVersion.Name] = true;
|
333
|
+
performedUpdate = true;
|
334
|
+
_logger.Info($"Dependency {requiredPackageVersion.Name} version range '{candidateUpdateVersionRange}' already includes {requiredVersion}; no update needed.");
|
335
|
+
break;
|
336
|
+
}
|
337
|
+
}
|
338
|
+
|
339
|
+
// find something that looks like it contains a property expansion, even if it's surrounded by other text
|
340
|
+
var propertyInSubstringPattern = new Regex(@"(?<Prefix>[^$]*)\$\((?<PropertyName>[A-Za-z0-9_]+)\)(?<Suffix>.*$)");
|
341
|
+
// e.g., not-a-dollar-sign $ ( alphanumeric-or-underscore ) everything-else
|
342
|
+
var propertyMatch = propertyInSubstringPattern.Match(candidateUpdateVersionString);
|
343
|
+
if (propertyMatch.Success)
|
344
|
+
{
|
345
|
+
// this looks like a property; keep walking backwards with all possible elements
|
346
|
+
var propertyName = propertyMatch.Groups["PropertyName"].Value;
|
347
|
+
var propertyDefinitions = filesAndContents.Values
|
348
|
+
.SelectMany(doc => doc.Descendants().Where(e => e.Name.LocalName.Equals(propertyName, StringComparison.OrdinalIgnoreCase)))
|
349
|
+
.Where(e => e.Parent?.Name.LocalName.Equals(PropertyGroupElementName, StringComparison.OrdinalIgnoreCase) == true)
|
350
|
+
.ToArray();
|
351
|
+
foreach (var propertyDefinition in propertyDefinitions)
|
352
|
+
{
|
353
|
+
candidateUpdateLocations.Enqueue((propertyDefinition.Value, (version) => propertyDefinition.Value = version));
|
354
|
+
}
|
355
|
+
}
|
356
|
+
}
|
357
|
+
|
358
|
+
if (!performedUpdate)
|
359
|
+
{
|
360
|
+
_logger.Warn($"Unable to find appropriate location to update package {requiredPackageVersion.Name} to version {requiredPackageVersion.Version}; no update performed");
|
361
|
+
}
|
362
|
+
}
|
363
|
+
}
|
364
|
+
}
|
365
|
+
}
|
366
|
+
|
367
|
+
var performedAllUpdates = updatesPerformed.Values.All(v => v);
|
368
|
+
if (performedAllUpdates)
|
369
|
+
{
|
370
|
+
foreach (var (path, contents) in filesAndContents)
|
371
|
+
{
|
372
|
+
await WriteFileContentsAsync(repoContentsPath, path, contents.ToString());
|
373
|
+
}
|
374
|
+
}
|
375
|
+
|
376
|
+
return performedAllUpdates;
|
377
|
+
}
|
378
|
+
|
379
|
+
private static XText? GetIndentXTextFromElement(XElement element, string extraIndentationToAdd = "")
|
380
|
+
{
|
381
|
+
var indentText = (element.PreviousNode as XText)?.Value;
|
382
|
+
var indent = indentText is not null
|
383
|
+
? new XText(indentText + extraIndentationToAdd)
|
384
|
+
: null;
|
385
|
+
return indent;
|
386
|
+
}
|
387
|
+
|
388
|
+
private static void AddAfterSiblingElement(XElement siblingElement, XElement newElement, string extraIndentationToAdd = "")
|
389
|
+
{
|
390
|
+
var indent = GetIndentXTextFromElement(siblingElement, extraIndentationToAdd);
|
391
|
+
XNode nodeToAddAfter = siblingElement;
|
392
|
+
var done = false;
|
393
|
+
while (!done && nodeToAddAfter.NextNode is not null)
|
394
|
+
{
|
395
|
+
// skip over XText and XComment nodes until we find a newline
|
396
|
+
switch (nodeToAddAfter.NextNode)
|
397
|
+
{
|
398
|
+
case XText text:
|
399
|
+
if (text.Value.Contains('\n'))
|
400
|
+
{
|
401
|
+
done = true;
|
402
|
+
}
|
403
|
+
else
|
404
|
+
{
|
405
|
+
nodeToAddAfter = nodeToAddAfter.NextNode;
|
406
|
+
}
|
407
|
+
|
408
|
+
break;
|
409
|
+
case XComment comment:
|
410
|
+
if (comment.Value.Contains('\n'))
|
411
|
+
{
|
412
|
+
done = true;
|
413
|
+
}
|
414
|
+
else
|
415
|
+
{
|
416
|
+
nodeToAddAfter = nodeToAddAfter.NextNode;
|
417
|
+
}
|
418
|
+
|
419
|
+
break;
|
420
|
+
default:
|
421
|
+
done = true;
|
422
|
+
break;
|
423
|
+
}
|
424
|
+
}
|
425
|
+
|
426
|
+
nodeToAddAfter.AddAfterSelf(indent, newElement);
|
427
|
+
}
|
428
|
+
|
429
|
+
private static async Task<string> ReadFileContentsAsync(DirectoryInfo repoContentsPath, string path)
|
430
|
+
{
|
431
|
+
var fullPath = Path.Join(repoContentsPath.FullName, path);
|
432
|
+
var contents = await File.ReadAllTextAsync(fullPath);
|
433
|
+
return contents;
|
434
|
+
}
|
435
|
+
|
436
|
+
private static async Task WriteFileContentsAsync(DirectoryInfo repoContentsPath, string path, string contents)
|
437
|
+
{
|
438
|
+
var fullPath = Path.Join(repoContentsPath.FullName, path);
|
439
|
+
await File.WriteAllTextAsync(fullPath, contents);
|
440
|
+
}
|
441
|
+
|
442
|
+
public static string CreateUpdatedVersionRangeString(VersionRange existingRange, NuGetVersion existingVersion, NuGetVersion requiredVersion)
|
443
|
+
{
|
444
|
+
var newMinVersion = requiredVersion;
|
445
|
+
Func<NuGetVersion, NuGetVersion, bool> maxVersionComparer = existingRange.IsMaxInclusive
|
446
|
+
? (a, b) => a >= b
|
447
|
+
: (a, b) => a > b;
|
448
|
+
var newMaxVersion = existingVersion == existingRange.MaxVersion
|
449
|
+
? requiredVersion
|
450
|
+
: existingRange.MaxVersion is not null && maxVersionComparer(existingRange.MaxVersion, requiredVersion)
|
451
|
+
? existingRange.MaxVersion
|
452
|
+
: null;
|
453
|
+
var newRange = new VersionRange(
|
454
|
+
minVersion: newMinVersion,
|
455
|
+
includeMinVersion: true,
|
456
|
+
maxVersion: newMaxVersion,
|
457
|
+
includeMaxVersion: newMaxVersion is not null && existingRange.IsMaxInclusive
|
458
|
+
);
|
459
|
+
|
460
|
+
// special case common scenarios
|
461
|
+
|
462
|
+
// e.g., "[2.0.0, 2.0.0]" => "[2.0.0]"
|
463
|
+
if (newRange.MinVersion == newRange.MaxVersion &&
|
464
|
+
newRange.IsMaxInclusive)
|
465
|
+
{
|
466
|
+
return $"[{newRange.MinVersion}]";
|
467
|
+
}
|
468
|
+
|
469
|
+
// e.g., "[2.0.0, )" => "2.0.0"
|
470
|
+
if (newRange.MaxVersion is null)
|
471
|
+
{
|
472
|
+
return requiredVersion.ToString();
|
473
|
+
}
|
474
|
+
|
475
|
+
return newRange.ToString();
|
476
|
+
}
|
477
|
+
}
|
@@ -2,7 +2,7 @@ namespace NuGetUpdater.Core;
|
|
2
2
|
|
3
3
|
internal static class GlobalJsonUpdater
|
4
4
|
{
|
5
|
-
public static async Task UpdateDependencyAsync(
|
5
|
+
public static async Task<string?> UpdateDependencyAsync(
|
6
6
|
string repoRootPath,
|
7
7
|
string workspacePath,
|
8
8
|
string dependencyName,
|
@@ -13,7 +13,7 @@ internal static class GlobalJsonUpdater
|
|
13
13
|
if (!MSBuildHelper.TryGetGlobalJsonPath(repoRootPath, workspacePath, out var globalJsonPath))
|
14
14
|
{
|
15
15
|
logger.Info(" No global.json file found.");
|
16
|
-
return;
|
16
|
+
return null;
|
17
17
|
}
|
18
18
|
|
19
19
|
var globalJsonFile = GlobalJsonBuildFile.Open(repoRootPath, globalJsonPath, logger);
|
@@ -24,19 +24,20 @@ internal static class GlobalJsonUpdater
|
|
24
24
|
if (!containsDependency)
|
25
25
|
{
|
26
26
|
logger.Info($" Dependency [{dependencyName}] not found.");
|
27
|
-
return;
|
27
|
+
return null;
|
28
28
|
}
|
29
29
|
|
30
30
|
if (globalJsonFile.MSBuildSdks?.TryGetPropertyValue(dependencyName, out var version) != true
|
31
31
|
|| version?.GetValue<string>() is not string versionString)
|
32
32
|
{
|
33
33
|
logger.Info(" Unable to determine dependency version.");
|
34
|
-
return;
|
34
|
+
return null;
|
35
35
|
}
|
36
36
|
|
37
37
|
if (versionString != previousDependencyVersion)
|
38
38
|
{
|
39
|
-
|
39
|
+
logger.Info($" Expected old version of {previousDependencyVersion} but found {versionString}.");
|
40
|
+
return null;
|
40
41
|
}
|
41
42
|
|
42
43
|
globalJsonFile.UpdateProperty(["msbuild-sdks", dependencyName], newDependencyVersion);
|
@@ -44,6 +45,9 @@ internal static class GlobalJsonUpdater
|
|
44
45
|
if (await globalJsonFile.SaveAsync())
|
45
46
|
{
|
46
47
|
logger.Info($" Saved [{globalJsonFile.RelativePath}].");
|
48
|
+
return globalJsonFile.Path;
|
47
49
|
}
|
50
|
+
|
51
|
+
return null;
|
48
52
|
}
|
49
53
|
}
|
@@ -21,7 +21,18 @@ public abstract record UpdateOperationBase
|
|
21
21
|
public required NuGetVersion NewVersion { get; init; }
|
22
22
|
public required ImmutableArray<string> UpdatedFiles { get; init; }
|
23
23
|
|
24
|
-
|
24
|
+
protected abstract string GetReportText();
|
25
|
+
|
26
|
+
public string GetReport(bool includeFileNames)
|
27
|
+
{
|
28
|
+
var report = GetReportText();
|
29
|
+
if (includeFileNames)
|
30
|
+
{
|
31
|
+
report += $" in {string.Join(", ", UpdatedFiles)}";
|
32
|
+
}
|
33
|
+
|
34
|
+
return report;
|
35
|
+
}
|
25
36
|
|
26
37
|
public ReportedDependency ToReportedDependency(string projectPath, IEnumerable<ReportedDependency> previouslyReportedDependencies, IEnumerable<Dependency> updatedDependencies)
|
27
38
|
{
|
@@ -49,9 +60,9 @@ public abstract record UpdateOperationBase
|
|
49
60
|
};
|
50
61
|
}
|
51
62
|
|
52
|
-
internal static string GenerateUpdateOperationReport(IEnumerable<UpdateOperationBase> updateOperations)
|
63
|
+
internal static string GenerateUpdateOperationReport(IEnumerable<UpdateOperationBase> updateOperations, bool includeFileNames = true)
|
53
64
|
{
|
54
|
-
var updateMessages = updateOperations.Select(u => u.GetReport()).ToImmutableArray();
|
65
|
+
var updateMessages = updateOperations.Select(u => u.GetReport(includeFileNames)).Distinct(StringComparer.OrdinalIgnoreCase).ToImmutableArray();
|
55
66
|
if (updateMessages.Length == 0)
|
56
67
|
{
|
57
68
|
return string.Empty;
|
@@ -144,12 +155,12 @@ public abstract record UpdateOperationBase
|
|
144
155
|
public record DirectUpdate : UpdateOperationBase
|
145
156
|
{
|
146
157
|
public override string Type => nameof(DirectUpdate);
|
147
|
-
|
158
|
+
protected override string GetReportText()
|
148
159
|
{
|
149
160
|
var fromText = OldVersion is null
|
150
161
|
? string.Empty
|
151
162
|
: $"from {OldVersion} ";
|
152
|
-
return $"Updated {DependencyName} {fromText}to {NewVersion}
|
163
|
+
return $"Updated {DependencyName} {fromText}to {NewVersion}";
|
153
164
|
}
|
154
165
|
|
155
166
|
public sealed override string ToString() => GetString();
|
@@ -158,7 +169,7 @@ public record DirectUpdate : UpdateOperationBase
|
|
158
169
|
public record PinnedUpdate : UpdateOperationBase
|
159
170
|
{
|
160
171
|
public override string Type => nameof(PinnedUpdate);
|
161
|
-
|
172
|
+
protected override string GetReportText() => $"Pinned {DependencyName} at {NewVersion}";
|
162
173
|
public sealed override string ToString() => GetString();
|
163
174
|
}
|
164
175
|
|
@@ -168,7 +179,7 @@ public record ParentUpdate : UpdateOperationBase, IEquatable<UpdateOperationBase
|
|
168
179
|
public required string ParentDependencyName { get; init; }
|
169
180
|
public required NuGetVersion ParentNewVersion { get; init; }
|
170
181
|
|
171
|
-
|
182
|
+
protected override string GetReportText() => $"Updated {DependencyName} to {NewVersion} indirectly via {ParentDependencyName}/{ParentNewVersion}";
|
172
183
|
|
173
184
|
bool IEquatable<UpdateOperationBase>.Equals(UpdateOperationBase? other)
|
174
185
|
{
|
@@ -1,11 +1,15 @@
|
|
1
1
|
using System.Collections.Immutable;
|
2
|
-
using System.Net;
|
3
2
|
using System.Text.Json;
|
4
3
|
using System.Text.Json.Serialization;
|
5
4
|
|
5
|
+
using NuGet.Versioning;
|
6
|
+
|
6
7
|
using NuGetUpdater.Core.Analyze;
|
8
|
+
using NuGetUpdater.Core.DependencySolver;
|
9
|
+
using NuGetUpdater.Core.Discover;
|
7
10
|
using NuGetUpdater.Core.Run.ApiModel;
|
8
11
|
using NuGetUpdater.Core.Updater;
|
12
|
+
using NuGetUpdater.Core.Updater.FileWriters;
|
9
13
|
using NuGetUpdater.Core.Utilities;
|
10
14
|
|
11
15
|
namespace NuGetUpdater.Core;
|
@@ -73,6 +77,27 @@ public class UpdaterWorker : IUpdaterWorker
|
|
73
77
|
workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
|
74
78
|
}
|
75
79
|
|
80
|
+
if (_experimentsManager.UseNewFileUpdater)
|
81
|
+
{
|
82
|
+
var worker = new FileWriterWorker(
|
83
|
+
new DiscoveryWorker(_jobId, _experimentsManager, _logger),
|
84
|
+
new MSBuildDependencySolver(new DirectoryInfo(repoRootPath), new FileInfo(workspacePath), _experimentsManager, _logger),
|
85
|
+
new XmlFileWriter(_logger),
|
86
|
+
_logger
|
87
|
+
);
|
88
|
+
var updateOperations = await worker.RunAsync(
|
89
|
+
new DirectoryInfo(repoRootPath),
|
90
|
+
new FileInfo(workspacePath),
|
91
|
+
dependencyName,
|
92
|
+
NuGetVersion.Parse(previousDependencyVersion),
|
93
|
+
NuGetVersion.Parse(newDependencyVersion)
|
94
|
+
);
|
95
|
+
return new UpdateOperationResult()
|
96
|
+
{
|
97
|
+
UpdateOperations = updateOperations,
|
98
|
+
};
|
99
|
+
}
|
100
|
+
|
76
101
|
if (!isTransitive)
|
77
102
|
{
|
78
103
|
await DotNetToolsJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
|