Skip to content

Commit a99b70c

Browse files
authored
Update all projects in solution with dotnet package update (#7014)
1 parent 8719c86 commit a99b70c

26 files changed

Lines changed: 2050 additions & 160 deletions

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Update/IPackageUpdateIO.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
using System.Collections.Generic;
77
using System.Threading;
88
using System.Threading.Tasks;
9-
using NuGet.Configuration;
109
using NuGet.Common;
10+
using NuGet.Configuration;
1111
using NuGet.ProjectModel;
1212
using NuGet.Protocol.Model;
1313
using NuGet.Versioning;
@@ -112,10 +112,11 @@ void UpdatePackageReference(
112112

113113
/// <summary>Gets the assets file for a project.</summary>
114114
/// <param name="dgSpec">The restore inputs for the project.</param>
115+
/// <param name="projectPath">The path to the project to get the assets file for.</param>
115116
/// <param name="logger">The output logger</param>
116117
/// <param name="cancellationToken">The cancellation token.</param>
117118
/// <returns>The assets file for the project.</returns>
118-
Task<LockFile> GetProjectAssetsFileAsync(DependencyGraphSpec dgSpec, ILogger logger, CancellationToken cancellationToken);
119+
Task<LockFile> GetProjectAssetsFileAsync(DependencyGraphSpec dgSpec, string projectPath, ILogger logger, CancellationToken cancellationToken);
119120

120121
/// <summary>Gets the package source mapping configuration for the current settins context.</summary>
121122
/// <returns>The package source mapping settings.</returns>

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Update/PackageUpdateCommandRunner.cs

Lines changed: 227 additions & 97 deletions
Large diffs are not rendered by default.

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Update/PackageUpdateIO.cs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ bool RunMsbuildTarget(string project, string tempFile)
134134
new DependencyGraphSpecRequestProvider(providerCache, dgSpec)
135135
};
136136

137-
var globalPackagesFolder = dgSpec.GetProjectSpec(dgSpec.Restore.Single()).RestoreMetadata.PackagesPath;
137+
var globalPackagesFolder = dgSpec.GetProjectSpec(dgSpec.Restore.First()).RestoreMetadata.PackagesPath;
138138

139139
var restoreContext = new RestoreArgs()
140140
{
@@ -146,23 +146,24 @@ bool RunMsbuildTarget(string project, string tempFile)
146146
// Sources : No need to pass it, because SourceRepositories contains the already built SourceRepository objects
147147
};
148148

149-
// Generate Restore Requests. There will always be 1 request here since we are restoring for 1 project.
150149
var restoreRequests = await RestoreRunner.GetRequests(restoreContext);
151-
152-
// Run restore without commit. This will always return 1 Result pair since we are restoring for 1 request.
153150
var restoreResult = await RestoreRunner.RunWithoutCommitAsync(restoreRequests, restoreContext, cancellationToken);
154151

155152
var result = new RestoreResult
156153
{
157-
RestoreResultPair = restoreResult.Single()
154+
RestoreResultPairs = restoreResult
158155
};
159156
return result;
160157
}
161158

162159
/// <inheritdoc cref="IPackageUpdateIO.CommitAsync(IPackageUpdateIO.RestoreResult, CancellationToken)"/>
163160
public async Task CommitAsync(IPackageUpdateIO.RestoreResult restorePreviewResult, CancellationToken none)
164161
{
165-
await RestoreRunner.CommitAsync(((RestoreResult)restorePreviewResult).RestoreResultPair, CancellationToken.None);
162+
var restoreResult = (RestoreResult)restorePreviewResult;
163+
foreach (var restoreResultPair in restoreResult.RestoreResultPairs)
164+
{
165+
await RestoreRunner.CommitAsync(restoreResultPair, CancellationToken.None);
166+
}
166167
}
167168

168169
/// <inheritdoc cref="IPackageUpdateIO.UpdatePackageReference(PackageSpec, IPackageUpdateIO.RestoreResult, List{string}, PackageToUpdate, ILogger)"/>
@@ -177,9 +178,15 @@ public void UpdatePackageReference(PackageSpec updatedPackageSpec, IPackageUpdat
177178
packageTfms.Add(targetFramework.FrameworkName);
178179
}
179180

181+
var restoreResult = (RestoreResult)restorePreviewResult;
182+
var restoreResultPair = restoreResult.RestoreResultPairs.Single(pair =>
183+
string.Equals(pair.SummaryRequest.Request.Project.FilePath, updatedPackageSpec.FilePath, StringComparison.OrdinalIgnoreCase));
184+
180185
if (!AddPackageReferenceCommandRunner.TryFindResolvedVersion(packageTfms,
181186
packageDependency.Id,
182-
((RestoreResult)restorePreviewResult).RestoreResultPair.Result, logger, out NuGetVersion resolvedVersion))
187+
restoreResultPair.Result,
188+
logger,
189+
out NuGetVersion resolvedVersion))
183190
{
184191
return;
185192
}
@@ -436,9 +443,10 @@ bool PackageHasKnownVulnerability(PackageIdentity package)
436443
return highestVersion;
437444
}
438445

439-
/// <inheritdoc cref="IPackageUpdateIO.GetProjectAssetsFileAsync(DependencyGraphSpec, ILogger, CancellationToken)"/>
446+
/// <inheritdoc cref="IPackageUpdateIO.GetProjectAssetsFileAsync(DependencyGraphSpec, string, ILogger, CancellationToken)"/>
440447
public async Task<LockFile> GetProjectAssetsFileAsync(
441448
DependencyGraphSpec dgSpec,
449+
string projectPath,
442450
ILogger logger,
443451
CancellationToken cancellationToken)
444452
{
@@ -449,21 +457,23 @@ public async Task<LockFile> GetProjectAssetsFileAsync(
449457
throw new NotSupportedException();
450458
}
451459

452-
LockFile? assetsFile = previewRestoreResult.RestoreResultPair.Result.LockFile;
460+
var restoreResultPair = previewRestoreResult.RestoreResultPairs.Single(pair =>
461+
string.Equals(pair.SummaryRequest.Request.Project.FilePath, projectPath, StringComparison.OrdinalIgnoreCase));
462+
LockFile? assetsFile = restoreResultPair.Result.LockFile;
453463
if (assetsFile is null)
454464
{
455-
var packageSpec = dgSpec.GetProjectSpec(dgSpec.Restore.Single());
465+
var packageSpec = dgSpec.GetProjectSpec(projectPath);
456466
var assetsFilePath = Path.Combine(packageSpec.RestoreMetadata.OutputPath, LockFileFormat.AssetsFileName);
457467
assetsFile = new LockFileFormat().Read(assetsFilePath);
458468
}
459469

460470
return assetsFile;
461471
}
462472

463-
private class RestoreResult : IPackageUpdateIO.RestoreResult
473+
internal class RestoreResult : IPackageUpdateIO.RestoreResult
464474
{
465-
internal required RestoreResultPair RestoreResultPair { get; init; }
475+
internal required IReadOnlyList<RestoreResultPair> RestoreResultPairs { get; init; }
466476

467-
public override bool Success => RestoreResultPair.Result.Success;
477+
public override bool Success => RestoreResultPairs.All(pair => pair.Result.Success);
468478
}
469479
}

test/NuGet.Core.FuncTests/Dotnet.Integration.Test/DotnetPackageUpdateTests.cs

Lines changed: 100 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
using System.Collections.Generic;
77
using System.IO;
88
using System.Linq;
9+
using System.Text.Json;
910
using System.Threading.Tasks;
1011
using System.Xml.Linq;
1112
using System.Xml.XPath;
1213
using FluentAssertions;
1314
using NuGet.CommandLine.Xplat.Tests;
15+
using NuGet.Frameworks;
1416
using NuGet.ProjectModel;
1517
using NuGet.Test.Utility;
1618
using NuGet.Versioning;
@@ -89,10 +91,8 @@ public async Task SingleTfmProject_PackageVersionUpdated()
8991
// Assert
9092
result.ExitCode.Should().Be(0);
9193

92-
XDocument csproj = XDocument.Load(csprojPath);
93-
var packageReferenceA = csproj.XPathSelectElements("//PackageReference[@Include='NuGet.Internal.Test.a']").ToList();
94-
packageReferenceA.Count.Should().Be(1);
95-
packageReferenceA[0].Attribute("Version").Value.Should().Be("2.0.0");
94+
string version = GetPackageReferenceVersion(csprojPath, "NuGet.Internal.Test.a");
95+
version.Should().Be("2.0.0");
9696
}
9797

9898
[Fact]
@@ -144,15 +144,11 @@ public async Task SingleTfmCpmProject_PackageVersionUpdated()
144144
// Assert
145145
result.ExitCode.Should().Be(0);
146146

147-
XDocument csproj = XDocument.Load(csprojPath);
148-
var packageReferenceA = csproj.XPathSelectElements("//PackageReference[@Include='NuGet.Internal.Test.a']").ToList();
149-
packageReferenceA.Count.Should().Be(1);
150-
packageReferenceA[0].Attribute("Version").Should().BeNull();
147+
string packageReferenceVersion = GetPackageReferenceVersion(csprojPath, "NuGet.Internal.Test.a");
148+
packageReferenceVersion.Should().BeNullOrEmpty();
151149

152-
XDocument packagesProps = XDocument.Load(packagesPropsPath);
153-
var packageVersionA = packagesProps.XPathSelectElements("//PackageVersion[@Include='NuGet.Internal.Test.a']").ToList();
154-
packageVersionA.Count.Should().Be(1);
155-
packageVersionA[0].Attribute("Version").Value.Should().Be("2.0.0");
150+
string packageVersionVersion = GetPackageVersionVersion(csprojPath, "NuGet.Internal.Test.a");
151+
packageVersionVersion.Should().Be("2.0.0");
156152
}
157153

158154
[Fact]
@@ -191,10 +187,8 @@ public async Task MultiTfmProject_PackageVersionUpdated()
191187
// Assert
192188
result.ExitCode.Should().Be(0);
193189

194-
XDocument csproj = XDocument.Load(csprojPath);
195-
var packageReferenceA = csproj.XPathSelectElements("//PackageReference[@Include='NuGet.Internal.Test.a']").ToList();
196-
packageReferenceA.Count.Should().Be(1);
197-
packageReferenceA[0].Attribute("Version").Value.Should().Be("2.0.0");
190+
string version = GetPackageReferenceVersion(csprojPath, "NuGet.Internal.Test.a");
191+
version.Should().Be("2.0.0");
198192
}
199193

200194
[Fact]
@@ -302,5 +296,95 @@ public void InvalidProjectFile_OutputsMeaningfulError()
302296
// Assert
303297
result.Output.Should().Contain("MSB4025").And.Contain(csprojPath);
304298
}
299+
300+
[Theory]
301+
[InlineData(false)]
302+
[InlineData(true)]
303+
public async Task SolutionWithTwoProjects_UpdateAll_UpdatesBothProjects(bool useSlnx)
304+
{
305+
// Arrange
306+
using var testContext = new SimpleTestPathContext();
307+
File.WriteAllText(testContext.NuGetConfig, string.Format(NugetConfigFormat, testContext.PackageSource));
308+
309+
var a1 = new SimpleTestPackageContext("NuGet.Internal.Test.a", "1.0.0");
310+
var a2 = new SimpleTestPackageContext("NuGet.Internal.Test.a", "2.0.0");
311+
312+
SimpleTestPackageContext[] packages = [a1, a2];
313+
await SimpleTestPackageUtility.CreatePackagesAsync(testContext.PackageSource, packages);
314+
315+
var solution = new SimpleTestSolutionContext(testContext.SolutionRoot, useSlnx);
316+
317+
var project1 = SimpleTestProjectContext.CreateNETCore(
318+
"Project1",
319+
testContext.SolutionRoot,
320+
NuGetFramework.Parse("net9.0"));
321+
project1.AddPackageToAllFrameworks(a1);
322+
323+
var project2 = SimpleTestProjectContext.CreateNETCore(
324+
"Project2",
325+
testContext.SolutionRoot,
326+
NuGetFramework.Parse("net9.0"));
327+
project2.AddPackageToAllFrameworks(a1);
328+
329+
solution.Projects.Add(project1);
330+
solution.Projects.Add(project2);
331+
solution.Create();
332+
333+
// Act
334+
var result = _testFixture.RunDotnetExpectSuccess(
335+
workingDirectory: testContext.SolutionRoot,
336+
args: $"package update",
337+
testOutputHelper: _testOutputHelper,
338+
environmentVariables: _envVars);
339+
340+
// Assert
341+
result.ExitCode.Should().Be(0);
342+
343+
string version1 = GetPackageReferenceVersion(project1.ProjectPath, "NuGet.Internal.Test.a");
344+
version1.Should().Be("2.0.0");
345+
346+
string version2 = GetPackageReferenceVersion(project2.ProjectPath, "NuGet.Internal.Test.a");
347+
version2.Should().Be("2.0.0");
348+
}
349+
350+
private string GetItemVersion(string projectPath, string itemType, string packageName)
351+
{
352+
var result = _testFixture.RunDotnetExpectSuccess(
353+
workingDirectory: Path.GetDirectoryName(projectPath),
354+
args: $"msbuild {Path.GetFileName(projectPath)} -getItem:{itemType}",
355+
testOutputHelper: _testOutputHelper,
356+
environmentVariables: _envVars);
357+
358+
using JsonDocument document = JsonDocument.Parse(result.Output);
359+
JsonElement root = document.RootElement;
360+
361+
if (root.TryGetProperty("Items", out JsonElement items) &&
362+
items.TryGetProperty(itemType, out JsonElement itemElements))
363+
{
364+
foreach (JsonElement item in itemElements.EnumerateArray())
365+
{
366+
if (item.TryGetProperty("Identity", out JsonElement identity) &&
367+
identity.GetString() == packageName)
368+
{
369+
if (item.TryGetProperty("Version", out JsonElement version))
370+
{
371+
return version.GetString();
372+
}
373+
}
374+
}
375+
}
376+
377+
return null;
378+
}
379+
380+
private string GetPackageReferenceVersion(string projectPath, string packageName)
381+
{
382+
return GetItemVersion(projectPath, "PackageReference", packageName);
383+
}
384+
385+
private string GetPackageVersionVersion(string projectPath, string packageName)
386+
{
387+
return GetItemVersion(projectPath, "PackageVersion", packageName);
388+
}
305389
}
306390
}

test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/AddPackageCommandUtilityTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
namespace NuGet.XPlat.FuncTest
1919
{
20-
[Collection("NuGet XPlat Test Collection")]
20+
[Collection(XPlatCollection.Name)]
2121
public class AddPackageCommandUtilityTests
2222
{
2323
[Fact]
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using FluentAssertions;
9+
using NuGet.CommandLine.XPlat;
10+
using NuGet.CommandLine.XPlat.Commands.Package.Update;
11+
using NuGet.Common;
12+
using NuGet.ProjectModel;
13+
using NuGet.Test.Utility;
14+
using Test.Utility;
15+
using Xunit;
16+
17+
namespace NuGet.XPlat.FuncTest.Commands.Package.Update.PackageUpdateIOTests;
18+
19+
public class CommitAsyncTests
20+
{
21+
private static PackageUpdateIO CreatePackageUpdateIO(string solutionRoot)
22+
{
23+
var msbuildUtility = new MSBuildAPIUtility(NullLogger.Instance);
24+
var packageUpdateIO = new PackageUpdateIO(solutionRoot, msbuildUtility, TestEnvironmentVariableReader.EmptyInstance);
25+
return packageUpdateIO;
26+
}
27+
28+
[Fact]
29+
public async Task CommitAsync_AfterSuccessfulPreview_CreatesAssetsFile()
30+
{
31+
// Arrange
32+
using var testContext = new SimpleTestPathContext();
33+
34+
var packageA = new SimpleTestPackageContext("PackageA", "1.0.0");
35+
await SimpleTestPackageUtility.CreatePackagesAsync(testContext.PackageSource, packageA);
36+
37+
var projectA = SimpleTestProjectContext.CreateNETCore(
38+
"ProjectA",
39+
testContext.SolutionRoot,
40+
"net9.0");
41+
projectA.AddPackageToAllFrameworks(packageA);
42+
projectA.GlobalPackagesFolder = testContext.UserPackagesFolder;
43+
projectA.Sources = new List<NuGet.Configuration.PackageSource> { new NuGet.Configuration.PackageSource(testContext.PackageSource) };
44+
projectA.FallbackFolders = new List<string> { testContext.FallbackFolder };
45+
projectA.Save();
46+
47+
var projectB = SimpleTestProjectContext.CreateNETCore(
48+
"ProjectB",
49+
testContext.SolutionRoot,
50+
"net9.0");
51+
projectB.AddPackageToAllFrameworks(packageA);
52+
projectB.GlobalPackagesFolder = testContext.UserPackagesFolder;
53+
projectB.Sources = new List<NuGet.Configuration.PackageSource> { new NuGet.Configuration.PackageSource(testContext.PackageSource) };
54+
projectB.FallbackFolders = new List<string> { testContext.FallbackFolder };
55+
projectB.Save();
56+
57+
using var packageUpdateIO = CreatePackageUpdateIO(testContext.SolutionRoot);
58+
var dgSpec = new DependencyGraphSpec();
59+
dgSpec.AddProject(projectA.PackageSpec);
60+
dgSpec.AddProject(projectB.PackageSpec);
61+
dgSpec.AddRestore(projectA.PackageSpec.RestoreMetadata.ProjectUniqueName);
62+
dgSpec.AddRestore(projectB.PackageSpec.RestoreMetadata.ProjectUniqueName);
63+
64+
var previewResult = await packageUpdateIO.PreviewUpdatePackageReferenceAsync(
65+
dgSpec,
66+
NullLogger.Instance,
67+
CancellationToken.None);
68+
previewResult.Success.Should().BeTrue();
69+
70+
var assetsFilePathA = projectA.AssetsFileOutputPath;
71+
if (File.Exists(assetsFilePathA))
72+
{
73+
File.Delete(assetsFilePathA);
74+
}
75+
76+
var assetsFilePathB = projectB.AssetsFileOutputPath;
77+
if (File.Exists(assetsFilePathB))
78+
{
79+
File.Delete(assetsFilePathB);
80+
}
81+
82+
// Act
83+
await packageUpdateIO.CommitAsync(previewResult, CancellationToken.None);
84+
85+
// Assert
86+
File.Exists(assetsFilePathA).Should().BeTrue();
87+
88+
var lockFileA = new LockFileFormat().Read(assetsFilePathA);
89+
lockFileA.Should().NotBeNull();
90+
lockFileA.Libraries.Should().ContainSingle(lib => lib.Name == "PackageA" && lib.Version.ToString() == "1.0.0");
91+
92+
File.Exists(assetsFilePathB).Should().BeTrue();
93+
94+
var lockFileB = new LockFileFormat().Read(assetsFilePathB);
95+
lockFileB.Should().NotBeNull();
96+
lockFileB.Libraries.Should().ContainSingle(lib => lib.Name == "PackageA" && lib.Version.ToString() == "1.0.0");
97+
}
98+
}

0 commit comments

Comments
 (0)