New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Central transitive dependencies should be considered only for root nodes #4611
Central transitive dependencies should be considered only for root nodes #4611
Conversation
@@ -843,7 +843,7 @@ private static List<CentralTransitiveDependencyGroup> ReadProjectFileTransitiveD | |||
NuGetFramework framework = NuGetFramework.Parse(frameworkPropertyName); | |||
var dependencies = new List<LibraryDependency>(); | |||
|
|||
JsonPackageSpecReader.ReadCentralTransitveDependencyGroup( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just fixing a typographic mistake in an internal method.
a28d352
to
1612530
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, looks good with one small comment. I had to rebase your branch and force push so be sure to pull it next time you work on it.
src/NuGet.Core/NuGet.DependencyResolver.Core/Remote/RemoteDependencyWalker.cs
Show resolved
Hide resolved
…ndencyWalker.cs Co-authored-by: Jeff Kluge <jeffkl@microsoft.com>
src/NuGet.Core/NuGet.DependencyResolver.Core/Remote/RemoteDependencyWalker.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay.
Your explanation seems good! It's been a while since we've looked at this.
A few qs about the test.
I wouldn't be the worst idea to define an end to end test as well, as the dependency walker tests rely on a lot of configuration.
Example test is ExecuteAsync_WithDowngradesInPrunedSubgraph_DoesNotRaiseNU1605, it's not CPM, but other in the same class are RestoreCommand_CentralVersion_ErrorWhenDependenciesHaveVersion.
var context = new TestRemoteWalkContext(); | ||
var provider = new DependencyProvider(); | ||
provider.Package("A", "1.0") | ||
.DependsOn("B", "1.0") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think B
needs to be centrally managed here, as B is a top level to A, which effectively means all it's direct deps need to be centrally managed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
B
is a project here so that is why it is not "centrally managed" as any package would be.
|
||
provider.Package("B", "1.0") | ||
.DependsOn("C", "1.0") | ||
.DependsOn("D", "3.0", LibraryDependencyTarget.Package, versionCentrallyManaged: true, libraryDependencyReferenceType: LibraryDependencyReferenceType.None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be centrally managed?
In the context of A, this isn't centrally managed, it's just a dependency of B.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
B
is a project that uses CPM, so the package D
dependency should be centrally managed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but not in the context of A.
A and B are restored separately.
When A is getting restored, A doesn't care about central management configured by B, it just cares about the declared dependencies (and their versions) from B.
This is tricky, as it seems like we don't really recognize the concept of transitively pinned for project 1 but not pinned for project 2.
var provider = new DependencyProvider(); | ||
provider.Package("A", "1.0") | ||
.DependsOn("B", "1.0") | ||
.DependsOn("D", "1.0", LibraryDependencyTarget.Package, versionCentrallyManaged: true, libraryDependencyReferenceType: LibraryDependencyReferenceType.None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we another another level of dependencies here?
If A is the root node, and it's centrally managed, then all it's dependencies need to be centrally managed and are direct.
…nsitive' into dev-marcink-20220504-centraltransitive # Conflicts: # src/NuGet.Core/NuGet.DependencyResolver.Core/Remote/RemoteDependencyWalker.cs # test/NuGet.Core.Tests/NuGet.DependencyResolver.Core.Tests/RemoteDependencyWalkerTests.cs
I think that all these questions are due to confusion about which dependency is a project and which is a package. I've updated the test so now it should be clearer. Please have another look.
I implemented |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the contribution @marcin-krystianc and apologies for the delays.
|
||
provider.Package("B", "1.0") | ||
.DependsOn("C", "1.0") | ||
.DependsOn("D", "3.0", LibraryDependencyTarget.Package, versionCentrallyManaged: true, libraryDependencyReferenceType: LibraryDependencyReferenceType.None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but not in the context of A.
A and B are restored separately.
When A is getting restored, A doesn't care about central management configured by B, it just cares about the declared dependencies (and their versions) from B.
This is tricky, as it seems like we don't really recognize the concept of transitively pinned for project 1 but not pinned for project 2.
Seems like our CI hook is broken. cc @zivkan |
github webhooks are (were?) having an outage: https://www.githubstatus.com/incidents/gvzcw6sd0xxb |
Can you rebase again @marcin-krystianc e9637ed didn't make it in your rebase. |
Basically NuGet.CommandLine.Test.NuGetPushCommandTest.PushCommand_InvalidInput_V2HttpSource(invalidInput: "https://invalid-2a0358f1-88f2-48c0-b68a-bb150cac00"...) is failing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
I have a hunch about some other potential bugs we may have with the transitive walking.
I'll ping everyone on the details if it indeed does turn out to be a bug.
So the thing I was worried about is that if a package is transitively specified in a child project, the parent project gets a library dependency that's Turns out it doesn't hurt transitive pinning, but I wonder if it could mess with it in a different way. This is the test I used (I basically tweaked the test being added in this change). /// <summary>
/// A 1.0 -> D 3.0 (Central transitive) -> E 1.0.0
/// -> B 1.0 -> D 2.0.0 (declared)
/// -> B 1.0 -> E 2.0.0
/// </summary>
[Fact]
public async Task RestoreNetCore_DependenciesFromRootProjectThatArePinned_ShouldBeIgnored()
{
// Arrange
using var pathContext = new SimpleTestPathContext();
// Set up solution, project, and packages
var solution = new SimpleTestSolutionContext(pathContext.SolutionRoot);
var projectB = CreateProject(pathContext, "B");
var projectA = CreateProject(pathContext, "A", projectB);
var packageD200 = new SimpleTestPackageContext("D", "2.0.0");
var packageD300 = new SimpleTestPackageContext("D", "3.0.0");
var packageE100 = new SimpleTestPackageContext("E", "1.0.0");
var packageE200 = new SimpleTestPackageContext("E", "2.0.0");
packageD300.Dependencies.Add(packageE100);
await SimpleTestPackageUtility.CreateFolderFeedV3Async(
pathContext.PackageSource,
PackageSaveMode.Defaultv3,
packageD200,
packageD300,
packageE100,
packageE200
);
solution.Projects.Add(projectA);
solution.Projects.Add(projectB);
solution.Create(pathContext.SolutionRoot);
AddPackageReferenceToProject(projectB);
CreateDirectoryPackagesPropsWithVersionForPackageD(pathContext, projectA, "3.0.0");
CreateDirectoryPackagesPropsWithVersionForPackageDAndE(pathContext, projectB, "2.0.0");
var args = new string[] {
"restore",
solution.SolutionPath,
"-Verbosity",
"detailed",
};
// Act
var r = CommandRunner.Run(
Util.GetNuGetExePath(),
pathContext.WorkingDirectory.Path,
string.Join(" ", args),
waitForExit: true);
// Assert
r.Success.Should().Be(true, because: r.AllOutput);
var assetsFile = projectA.AssetsFile;
assetsFile.Libraries.Should().HaveCount(3); // B, D & E.
var eLibrary = assetsFile.Libraries.Single(e => e.Name.Equals("E"));
eLibrary.Version.Should().Be(NuGetVersion.Parse("1.0.0"));
// Local methods
void CreateDirectoryPackagesPropsWithVersionForPackageD(SimpleTestPathContext pathContext, SimpleTestProjectContext projectContext, string version)
{
var directoryPackagesPropsContent =
@$"<Project>
<ItemGroup>
<PackageVersion Include=""D"" Version=""{version}"" />
</ItemGroup>
</Project>";
var directoryName = Path.GetDirectoryName(projectContext.ProjectPath);
File.WriteAllText(Path.Combine(directoryName, $"Directory.Packages.Props"), directoryPackagesPropsContent);
}
void CreateDirectoryPackagesPropsWithVersionForPackageDAndE(SimpleTestPathContext pathContext, SimpleTestProjectContext projectContext, string version)
{
var directoryPackagesPropsContent =
@$"<Project>
<ItemGroup>
<PackageVersion Include=""D"" Version=""{version}"" />
<PackageVersion Include=""E"" Version=""2.0.0"" />
</ItemGroup>
</Project>";
var directoryName = Path.GetDirectoryName(projectContext.ProjectPath);
File.WriteAllText(Path.Combine(directoryName, $"Directory.Packages.Props"), directoryPackagesPropsContent);
}
SimpleTestProjectContext CreateProject(SimpleTestPathContext pathContext, string name, SimpleTestProjectContext referencedProject = null)
{
var projectContext = SimpleTestProjectContext.CreateNETCoreWithSDK(
name,
pathContext.SolutionRoot,
"net472");
projectContext.Properties.Add("ManagePackageVersionsCentrally", "true");
projectContext.Properties.Add("CentralPackageTransitivePinningEnabled", "true");
if (referencedProject != null)
projectContext.AddProjectToAllFrameworks(referencedProject);
return projectContext;
}
void AddPackageReferenceToProject(SimpleTestProjectContext project)
{
var xml = project.GetXML();
ProjectFileUtils.AddItem(
xml,
"PackageReference",
"D",
NuGetFramework.AnyFramework,
new Dictionary<string, string>(),
new Dictionary<string, string>());
xml.Save(project.ProjectPath);
}
} |
@nkolev92 I've merged dev branch into mine.
Yeah, it is quite messy if you start considering graphs in which different projects have a different sets of central package versions and both use transitive pinning. That is definitely not a common scenario anyway. |
This PR has been automatically marked as stale because it has no activity for 30 days. It will be closed if no further activity occurs within another 60 days of this comment. If it is closed, you may reopen it anytime when you're ready again, as long as you don't delete the branch. |
@jeffkl's out and he'll handle it when back. It was pretty late in the game for 6.3 so we didn't feel comfortable merging. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the contribution @marcin-krystianc !
Bug
Fixes: NuGet/Home#11760
Regression? Last working version: -
Description
After this change, the DependencyWalker will check whether transitive dependency eclipses or downgrades the current library only when that transitive dependency is at the top-level node.
With this change, the example from the linked issue fails with downgrade errors (GOOD):
PR Checklist
PR has a meaningful title
PR has a linked issue.
Described changes
Tests
Documentation