Skip to content
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

Allow users to determine package resolution strategy during package restore - direct or transitive #5553

Open
Wil73 opened this issue Jul 8, 2017 · 104 comments
Labels
Milestone

Comments

@Wil73
Copy link

Wil73 commented Jul 8, 2017

I am not going to debate if highest or lowest should be the default: leave the actual behavior up to the consumer.

Why on earth should Microsoft FORCE users to adopt the "safest is best" strategy in the first place?

Is there any reason why we cannot add a switch to nuget.exe and NPM that allows the user to decide their own package restore strategy?

This way consumers of nuget can align nuget behavior to their own business strategies, be they conservative or aggressive.

What I don't understand is why Microsoft has to decide this for consumers in the first place.

To me this is a straight up consumer decision based on private business strategy and policy.

It makes absolutely no sense why a tool vendor should allow themselves to unilaterally decide business strategy for all nuget consumers on the planet without an option to override that.

It's been two years and there's no apparent movement on this issue.

Leave lowest version as the default strategy if you want Microsoft, but for goodness sakes offer a switch to allow consumers to make their own business decisions.

@Wil73 Wil73 changed the title Why must resolve to Lowest Version - Allow users to determine package resolution strategy during package restore Why must resolve to Lowest Version? Allow users to determine package resolution strategy during package restore Jul 8, 2017
@davidfowl
Copy link
Member

I am not going to debate if highest or lowest should be the default: leave the actual behavior up to the consumer.

Lowest should be the default, but that's just my opinion. It's the best way to get a "working set of packages". Everyone in the graph gets what they compiled against and things only roll forward minimally if sibling packages need higher versions.

Why on earth should Microsoft FORCE users to adopt the "safest is best" strategy in the first place?

It's more of a "safest is default strategy" because people want to get a working set of packages by default. In a system where you need binary compatibility between components to in order to make things function things have a better chance of working if you stay closer to what components build, and test against.

Is there any reason why we cannot add a switch to nuget.exe and NPM that allows the user to decide their own package restore strategy?

This is already possible when using packages.config based projects by picking the dependency behavior on install:

image

We don't own NPM, that's the node package manager, not sure if you mean something else.

To properly do this with PackageReference, a lock file needs to be introduced (which has been discussed anyways).

@Wil73
Copy link
Author

Wil73 commented Jul 8, 2017

I am not going to debate what the default ought to be. I really am not concerned over that. Technically this is NOT a default when you can't configure it otherwise. It's only a default if you can change it. We can't.

My company is frustrated that Microsoft will not allow businesses to DECIDE Nuget resolution behavior for themselves.

It makes absolutely no sense why Microsoft should have a monopoly on the business policies of the private sector: this is in effect what Microsoft does here.

Make package resolution configurable via a simple switch.

By NPM I mean NuGet Package manager in Visual Studio.

I don't see why any lock file is required. Just add an additional switch in nuget.exe and Nuget Package Manager that indicates if lowest, highest etc should be the expected behavior.

Package.Config is for NuGet 1 and 2. That's well deprecated by now.

This is clearly a business decision that Microsoft currently has constrained to Microsoft to decide.

There are many switches in Nuget already. I don't understand why we cannot simply add one more.

I fail to understand why it takes over two years for Microsoft to resolve this situation. The current work around is to explicitly declare EVERY dependency in package references (for Nuget 4) and in Project.Json (for Nuget 3). This is a clumsy and labor intensive work around.

Just add a darn switch to indicate Nuget's expected resolution behavior so users can manage it themselves.

It makes no sense why Microsoft must control this in the first place.

Just add a switch.

@davidfowl
Copy link
Member

I don't see why any lock file is required. Just add an additional switch in nuget.exe and Nuget Package Manager that indicates if lowest, highest etc should be the expected behavior.

It's required with PackageReference because there's no way to make restore deterministic if you don't add it. If there's no asset you can commit to source control to lock dependencies then restoring the latest versions of all dependencies could give dramatically different results build to build. That might not be an issue if you tightly control the feeds but it is in the general case so just adding a switch isn't sufficient for the masses. This feature needs to be paired with a lock file to make it sane to use for the majority of customers.

@Wil73
Copy link
Author

Wil73 commented Jul 9, 2017

I think having the flexibility to choose resolution strategies is just as important as having a deterministic build. I do agree a deterministic build is important, but so is flexibility, which has been in short supply for long enough.

There is so much debate as to what the restore default should be (highest or lowest), but the point is irrelevant: higher or lower should be a choice the business makes, not Microsoft.

If I want a predictable build I can always just stick with project.json, check in my project.lock.json file with lock set to true, or just use fixed version numbers (the latter is the most predictable way, but the least flexible and most labor intensive). A package can get de-listed at any moment, so defaulting to lowest version does not guarantee a predictable build output either.

Microsoft needs to add switches to allow business to define their own resolution defaults. Microsoft has no business making these decisions for businesses in the private sector.

@davidfowl
Copy link
Member

There is so much debate as to what the restore default should be (highest or lowest), but the point is irrelevant: higher or lower should be a choice the business makes, not Microsoft.

Sure. Like I said, it's possible with packages.config and that functionality should be ported to PackageReference (project.json is pretty much deprecated at this point) along with a way to lock the graph after resolution.

I think having the flexibility to choose resolution strategies is just as important as having a deterministic build. I do agree a deterministic build is important, but so is flexibility, which has been in short supply for long enough.

I think you can have both though. IMO it wouldn't be great if we had a feature that most people couldn't use because we didn't think through the end to end.

If I want a predictable build I can always just stick with project.json, check in my project.lock.json file with lock set to true,

That doesn't work. Locked: true isn't supported anymore (hasn't been for a while).

or just use fixed version numbers (the latter is the most predictable way, but the least flexible and most labor intensive).

Fixed versions of the entire dependency graph would work, but it's extremely labor intensive.

A package can get de-listed at any moment, so defaulting to lowest version does not guarantee a predictable build output either.

I believe this isn't respected when resolving dependencies in project.json so it is deterministic.

@Wil73
Copy link
Author

Wil73 commented Jul 10, 2017

project.json will resolve to the lowest version it can find. If the version the build uses normally gets de-listed your deterministic build is out the window.

If you are going to use dependencies from nuget.org or any other third party site, you cannot get a deterministic build every time, even with nuget restore working as it does now.

When you depend on a third party for some of your libraries you cannot determine what your build will look like every time no.

The alternative is to host any libraries you use locally so you have complete control over their list status.

I think we both agree package.config with fixed version numbers is far too labor intensive.

Project.json is far more flexible than package.config. Package.config is even more deprecated and out of date than project.json.

We still use project.json where I work along with Nuget 3.5 since Nuget 4 does not offer any more flexibility with regards to package resolution options.

When Microsoft smartens up and lets go of its monopoly on package resolution strategy logic we will adopt a later version.

As it stands now Nuget 4 does not offer anything that Nuget 3.5 doesn't already do for us. Locked: true does work for us with Nuget 3.5. Though we don't want fixed version numbers. We can update a library quite regularly. We need flexibility here more than we need predictability.

We use TFS automated builds with a fully featured Artifact Server here as well. If we need any build of any configuration we can pull that from TFS in just seconds. Flexible builds are a more pressing need for my company than deterministic builds.

The Lower version is no guarantee of safer. If that was the case we would all still be running Windows 95.

@davidfowl
Copy link
Member

project.json will resolve to the lowest version it can find. If the version the build uses normally gets de-listed your deterministic build is out the window.

  1. It's super uncommon to de-list things. That said, I'm not even sure restore respects this.
  2. If you delete things, your build is broken anyways.

If you are going to use dependencies from nuget.org or any other third party site, you cannot get a deterministic build every time, even with nuget restore working as it does now.

You can 98% of the time (I made that up), because de-listing on nuget.org is rare and deleting is impossible. Now, if you're mirroring your dependencies to a myget feed or controlled feed, it's even better since you'd rarely delete or de-list anything that was being depended on.

However, de-listing/deleting is rare, if we support resolving the latest dependency version without a sane story to lock it, we're turning the 2% case into the 98% case.

When you depend on a third party for some of your libraries you cannot determine what your build will look like every time no.

Not following this argument.

Project.json is far more flexible than package.config. Package.config is even more deprecated and out of date than project.json.

We still use project.json where I work along with Nuget 3.5 since Nuget 4 does not offer any more flexibility with regards to package resolution options.

Hopefully you'll switch to PackageReference since that's where all of our energy is at the moment. Good to know locked: true still works in the version of the client that you're using.

All of that said I agree with you, I just think when we do that feature it'll be accompanied with a lock file so that things remain deterministic. That shouldn't be too hard to accomplish regardless.

@Wil73
Copy link
Author

Wil73 commented Jul 10, 2017

I would have to double check for Nuget 4, but you can get a deterministic build using highest version with a version number limit. I don't think Nuget 4 will allow this scenario out of the box either. It's not 100% predictable, but the point is we cannot even configure that.

If you want to lock, then use VS 2015 and Nuget 3.5. You just have to check in your project.lock.json.
That works just fine. I haven't investigated locking for Nuget 4 and VS 2017, but it sounds like Microsoft dropped the ball yet again.

I like the high level design of NuGet, but the implementation is dreadful on many levels. MS has had over five years now to get NuGet to a sensible level and it's still not there.

If locks are out the window with Nuget 4, and Nuget 4 STILL won't offer a switch for businesses to configure their own business policies how is Nuget 4 any better?

Moving references to the project file accomplishes what? That work effort could have been put into offering what businesses need: configurable restore policies and restore locks. I don't recall seeing a single post of anyone complaining that the project.json file format urgently needs a renovation.

There are hundreds (I haven't counted, but I stopped a long time ago) of people complaining about the inflexible lowest is best logic hard coded into Nuget right now.

On the face of it, it looks like MS wants everyone to use the latest version of their software, but the oldest version of everyone else's software. I find it ironic how aggressively Microsoft endorses adoption of new releases of their products, but Nuget logic has a "lowest is safer" constraint hard coded into the binary.

If lowest was safer we would all still be running Windows 95.

@davidfowl
Copy link
Member

I would have to double check for Nuget 4, but you can get a deterministic build using highest version with a version number limit. I don't think Nuget 4 will allow this scenario out of the box either. It's not 100% predictable, but the point is we cannot even configure that.

Not only is it not predictable, it's probably more broken (especially for dependencies out of your control).

If you want to lock, then use VS 2015 and Nuget 3.5. You just have to check in your project.lock.json.
That works just fine.

VS 2017 with PackageReference is the future of nuget and it needs a proper lock file story.

@Wil73
Copy link
Author

Wil73 commented Jul 10, 2017

How is getting the latest version "out of control?"

Our approach at our company is to use a fixed version number for third party dependencies and use the highest version for in house packages. We don't update third party dependencies nearly as often as we update internal packages. We have a very large in house class library framework that relies on nuget to be sensible: right now it's not. We can make nuget sensible, but we have to hack project.json to make that happen.

This is not out of control. Our dependencies are quite stable. We have a solid change management process and automated testing. Adopting a new version when that version gets released does not make a process out of control. It just makes your company agile and aggressive in adopting the new.

This goes back to my original point: this is a controversial debate left to the business to decide, not a tool vendor.

I cannot say for any given company if lower or higher version resolution strategy is better. I think it depends on the company and how aggressively they want to adopt new tools. Again this is such a controversial topic it is absurd to have a tool vendor make these decisions for everyone and hard code those policies into their binaries.

That being said, just offer a darn switch. That ends all debate on the matter. lol

@davidfowl
Copy link
Member

How is getting the latest version "out of control?"

You misread, I said "dependencies that are out of your control".

We don't update third party dependencies nearly as often as we update internal packages.

Sure. How do you restrict the resolution strategy to only update certain dependencies?

This goes back to my original point: this is a controversial debate left to the business to decide, not a tool vendor.

I think I agreed with you each time you made this comment but my position hasn't changed when it comes to needing a lock file with this feature.

That being said, just offer a darn switch. That ends all debate on the matter. lol

I have little say in what the nuget team does here so I can't say how or when this would ever happen. I'm guessing it won't work on VS 2015 with project.json though (I could be wrong). I'm also guessing this mode needs to be source controllable (probably in nuget.config) since you'd want command line restore and VS restore to do the same thing.

@Wil73
Copy link
Author

Wil73 commented Jul 10, 2017

You can use project.json syntax to restrict to specific versions or simply de-list other packages so you only have one version in your package line. It's a hack, but it works.

Yes, you would want a switch or option for both NPM and the command line restore of course.

@anangaur
Copy link
Member

That's a long thread over the weekend. :)
From what I have observed (here - this thread as well as talking to other folks), the requirements seem to be the following:

  1. Allow users to decide the dependency resolution strategy - direct as well as transitive
  2. Allow users to lock down on the dependency versions in the transitive world - to enable repeatable build

Both # 1 and # 2 seem to be lacking with PackageReference. # 2 seems like already working with lowest is the best strategy but will falter with # 1 coming into the fore. So I think NuGet will have to solve both together atleast for PackageReference.

@anangaur anangaur changed the title Why must resolve to Lowest Version? Allow users to determine package resolution strategy during package restore Allow users to determine package resolution strategy during package restore - direct or transitive Sep 25, 2017
@emgarten emgarten added this to the Backlog milestone Oct 18, 2017
@giggio
Copy link

giggio commented Oct 18, 2017

What is the timeline to resolve this? This is very much needed. Requests for this goes back to 2015 (see #4789 and aspnet/dnx#2657).

@Wil73
Copy link
Author

Wil73 commented Oct 18, 2017

I think they just closed it and ignored me. And yes, MANY people have raised this as a SERIOUS flaw in the Nuget system. It's been over five years and it still behaves the same way with no flexibility.

I have seen MANY tickets opened for this and they just close them and walk away.

@davidfowl
Copy link
Member

I think they just closed it and ignored me.

Closed what? The issue is still open. Like I said before, a lock file is needed before any realistic progress can be made here.

And yes, MANY people have raised this as a SERIOUS flaw in the Nuget system. It's been over five years and it still behaves the same way with no flexibility.

It has been raised many times but honestly when you talk through the implications, people ignore the consequences that don't affect their immediate needs. We need to think about the larger ecosystem impact when we design features like this.

As a super basic simple example, any package that transitively references Newtonsoft.Json say 6.0.8, will now start pulling in 10.0.1. Not only that, but you end up with a completely jagged untested dependency graph by default. Here's an example of that going bad:

https://orientman.wordpress.com/2017/08/22/how-_not_-to-upgrade-to-asp-net-core-2-0-just-yet-with-paket/

I also think @anangaur wrote a spec for a proposed solution to the lock file but I can't find it on the wiki.

@anangaur
Copy link
Member

anangaur commented Oct 18, 2017

@Wil73 We closed many related issues that were essentially the same ask in this issue. This issue is still open :)

Here is the "Enable repeatable builds via lock file" spec.

@Wil73
Copy link
Author

Wil73 commented Oct 18, 2017

I have yet to grasp what this has to do with adding a switch of some sort to nuget RESTORE to ensure that packages resolve by default to the HIGHEST version available?

@Wil73
Copy link
Author

Wil73 commented Oct 18, 2017

I fail to see how on earth a lock file has any relevance at all here either.

@anangaur
Copy link
Member

We already discussed this in the earlier comments. We would need to handle both scenarios together.

@giggio
Copy link

giggio commented Oct 18, 2017

Just use a default that maintains the current behavior, and allow for different expressions of version ranges. Npm does this brilliantly with the ^ and ~ characters, as I pointed out on #4789.
This way people that don't want to change the current behavior just keep doing what they do, and people who do want it use the new characters.
Don't get me wrong, I love the idea of a lockfile, but these are orthogonal needs and can be worked in parallel.

@Wil73
Copy link
Author

Wil73 commented Oct 18, 2017

Sorry, but that's just silly. Every time you come up with a new version you then have to edit all the packages that use the new version to increase the darn range.

We have a * as an option right now, but that only works for direct dependencies... child dependencies still resolve to LOWEST version despite the * in the grandparent library. Ranges will not resolve this problem; ranges will just create a maintenance nightmare.

@viceice
Copy link

viceice commented Oct 28, 2022

Would be nice to have the dependencyVersion 1 option again, so every body can decide which updated to get

Footnotes

  1. https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file#config-section

@philipborg
Copy link

philipborg commented Dec 13, 2022

My issue is in large part that there is no pretty way to solve outdated transient dependencies, especially when there is a critical security issue which is often. If I want to update the transient dependency I currently need to add it as a direct dependency, even though I have no actual direct dependency on it. This both bloats the project file, confuses the hell out of any project readers and observations of dependency trees either using tools or lock files.

I would prefer to be able to set both default transient dependency resolution and/or (preferably and) override transient dependencies in a clearer manner which doesn't add it as a direct dependency. Ideally for specific transient dependencies.

@tdhintz
Copy link

tdhintz commented Dec 16, 2022

It's crazy that there is no plan articulated for this problem. Our vulnerability scanners check dependencies and report problems on dependencies we don't even deploy. Manually managing these alerts is not practical. Automation is needed.

@AgSync-Aaron
Copy link

It's crazy that there is no plan articulated for this problem. Our vulnerability scanners check dependencies and report problems on dependencies we don't even deploy. Manually managing these alerts is not practical. Automation is needed.

Take a hintz! Microsoft will never fix this issue until there is some bad press about it. Microsoft is being apathetic to the risk they are putting their own customers in, in regard to critical vulnerabilities.

Maybe this project can become mainstream... https://fsprojects.github.io/Paket/

@davidfowl
Copy link
Member

I just re-read this, and there's been lots of changes to nuget since this was filed ~5 years ago. Have the problems changed? There are now lock files and central package management that allows you to control the resolution of packages whether they are transitive or not.

I don't want to come off as being "paternalistic" and I want developers to have control, but package dependency resolution requires a global view of the world (the world being your project's transitive closure). Packet had to work around many assumptions in how package versioning works, and some strategies will never work with certain packages.

What might help move this forward are examples of problems and existing workarounds. That would help narrow down how the existing tools have gaps. Some questions on the top of my head:

  1. Where do you specify strategy, and where would it be persisted?
  2. How do you specify this for transitive deps and where is it persisted in that case?
  3. What's the granularity of specifying this strategy?
  4. When does NuGet decide to check for updates? This probably needs to be an explicit gesture (we must be solving this problem today).
  5. Does this mode require a lock file? If not, I guess the assets file can be used in place while it exists?
  6. Is this something that is only expressed when consuming packages or also when building them? Or is it both?

@giggio
Copy link

giggio commented Dec 19, 2022

@davidfowl

  1. .csproj (npm style which is already well understood, with ~ and ^)
  2. through command line, persisted in lock file
  3. can you elaborate? what do you mean?
  4. NuGet never updates anything until the user requests it, for restores it uses the information it has from .csproj and a lock file, if it exists
  5. no, but it can help, see item 2
  6. I'm not sure about this one, but probably both, so behavior is consistent between uses

@tdhintz
Copy link

tdhintz commented Dec 19, 2022

@davidfowl

  1. We specify a NuGet config in the build pipeline with the specification we want.
  2. The NuGet configuration file referenced through the command line in the build YML.
  3. My desire is that the transitive policy applies to all transitives, regardless the source.
  4. When I run the restore command (via the YML).
  5. ?
  6. When the restore is run using the NuGet configuration I specify.

@mcm-ham
Copy link

mcm-ham commented Jan 3, 2023

It's crazy that there is no plan articulated for this problem. Our vulnerability scanners check dependencies and report problems on dependencies we don't even deploy. Manually managing these alerts is not practical. Automation is needed.

I'm in the same boat, however there are other approaches that could be implemented for this specific concern, I've suggested these in a separate ticket #12341

@clyvari
Copy link

clyvari commented Feb 10, 2023

I stumbled upon this discussion looking to do exactly as the OP stated, however reading the discussion and thinking long and hard about it, I definitely changed my mind: I don't think this feature is really needed nor desirable as stated, but I think some tweaks to the current features could help.

Keep in mind I'm talking only about transitive packages, since we have the ability, to specify Version=* in *.csproj files for direct packages. It's about the ability to configure one way or the other the resolution for transitive packages of a dependency you have.

Security

I think relying on "highest possible" dependency version resolution for security is a terrible idea, since there is absolutely no guarantee you will get a version without security issues. And assuming we could specify * for a transitive dependency version, there is no guarantee you will get the latest version anyway.
So that would be something that makes you think you are getting the latest and greatest patched versions of everything, when in fact you do not. The security argument is, in my opinion irrelevant.

--

Another point: we can classify packages in two types.

  • The purely technical ones (Newtonsoft, ...) that are "algorithms-only", they are self contained.
  • The packages that interact with the outside world: A nuget package to communicate with an Azure API for instance.

Self-contained packages

I don't think there is a need to change anything. As long as they work, they work, even if the version used is "ancient". And if you do rely on specific optimizations or features of a transitive package, well it should definitely be part of your direct project dependencies.

External resources packages

There is usually NO good reason to use an older version of those packages. Most of the time, you do want the latest package possible. There is usually only 1 instance of a Web API you want to consume, that definitely works with the latest packages, and most definitely do not work with older packages.
And for those packages, even tough the code interface did not change, it is entirely possible that internal changes produce different Requests to the External API: Even though your code can compile AND run without exceptions, it might not work the the resource you want to consume.

So those packages sound like a good candidate for the feature. However, those packages are well identified, you definitely know which resources you are consuming (or else you have more pressing concerns), and can already use Central Package Management Transitive Pinning to tell which version you want (but you cannot at the moment specify Version=* in central package management, which is one of my suggestions).
But most likely, they should be included as direct dependecies of your poject, see suggestion 2).

TL;DR: I think we only really need custom transitive dependency resolution for a small well known subset of packages, allowing global "Highest"-version for transitive packages will cause more trouble than it is worth.

--

So then, a couple of suggestions:

1. Allow floating versions in Central Package Management (CPM)

Basically allow the exact syntax we have in *.csproj at the moment. Using floating versions at the moment result in error NU1011.
Maybe only allow floating versions when packages.lock.json is enabled. CPM does work with package lock, so I don't see any blocker.

2. Add a .nuspec option to mark some dependencies as "direct"

It helps solves the issue for packages that depend on an "API nuget".

Exemple: An helper library (Lib1) that simplifies working with an Azure API. This helper library has a dependency on an Azure API Nuget (Azure.X.nupkg).

As a Nuget publisher, my package (Lib1.nupkg) works for the foreseeable future, with version >= 2.0.0 of Azure.X.nupkg. I would like to mark this dependency as "direct", so a user of my helper library has more control over this dependency.
As consumer of Lib1, I want to be able to easily control the Azure.X dependency and update it when needed.

With the new .nuspec "direct" dependency option, as a consumer of Lib1, adding Lib1.nupkg to my project would add Lib1 as a direct reference, but also Azure.X as a direct dependency, instead of a transitive one.

My *.csproj would look like this after adding only Lib1 :

<Project Sdk="Microsoft.NET.Sdk">
  <!-- ... -->
  <ItemGroup>
    <PackageVersion Include="Lib1" Version="1.0.0" />
    <!-- Below was added automatically, this dependency is less hidden and I can easily mark it as floating if I wish to. -->
    <!-- "direct" dependencies in Azure.X package (if any) should recursively follow the same logic, and be included here -->
    <!-- Since it's not a transitive dependency, I will easily be able to update this package -->
    <PackageVersion Include="Azure.X" Version "2.0.0" />
  </ItemGroup>
</Project>

Nuget that should be referenced directly because they are "important" to your project can now be automatically included.
When there is an update to this package, I will be prompted to update this dependency in the future.

3. Warn if my code use transitive package code

I'll keep this one short since it's not related to Nuget directly: I should not be able to use code existing only in a transitive dependency without at least a warning, hinting me at including the package I'm using as a direct dependency.

--

Having thought about all the use cases I have met, I think those first 2 suggestions would cover pretty much everything, and allow me to be pretty confident in the management of my transitive packages.

@voroninp
Copy link

@clyvari If this strategy can be set individually for each dependency/package source, I'd turn it on for packages in my private feed, so whenever minor or patch get bump, packages are automatically updated.

@clyvari
Copy link

clyvari commented Feb 10, 2023

@voroninp That's already possible for direct dependecies of your projet. If you add a depency with Version=2.*, you will automatically use the latest version possible that is <3.0.
It's only an issue for transitive dependencies, when you add a nuget Lib1 that itself uses, say, System.Text.Json. You don't really care what version of System.Text.Json Lib1 uses, as long as it works. And if you do, well that means you care enough about System.Text.Json to add it as a direct dependency of your project.

@voroninp
Copy link

@clyvari , let's replace System.Text.Json with MyLib and imagine larger (deeper) dependency graph.

Now imagine, we found a bug in MyLib.

How many manipulations/rebuilds are required to update end project with the fixed MyLib depending on resolution strategy?

@clyvari
Copy link

clyvari commented Feb 10, 2023

@voroninp Assuming a bug in any dependency, at the moment you can (and probably should) use CPM with Transitive Pinning.
It's 1 commit to the Dependency.Packages.props file, and 1 build.

If at the root of a solution, it will be common to all projects in a solution, useful if the dependency is used in multiple projects.

Here is why using "highest possible" resolution strategy isn't sufficient :
Let's assume you have a several packages referencing VulnerableLib.
One requires VulnerableLib Version=[5.2, ), another VulnerableLib [5.0, 5.7).
Latest VulnerableLib version is 5.7
You specify "Highest possible version" as a resolution strategy.

The version that satisfy your requirement is 5.6.
Let's say a bug is found in VulnerableLib 5.x versions, fixed in version 5.8

Despite your "Highest possible version" as a resolution strategy, VulnerableLib will still resolve to vulnerable version 5.6.

And worse, because it is not intuitive (the version requirements might be deep down the dependency tree), you might be mislead into thinking: "VulnerableLib was bumped, all my projects use "Highest possible version" for transitive packages, I'm safe."

@tdhintz
Copy link

tdhintz commented Feb 10, 2023 via email

@voroninp
Copy link

@clyvari > another VulnerableLib [5.0, 5.7)

I'd question first this range ;-)

@seanamos
Copy link

seanamos commented Feb 17, 2023

I never understood why Nuget had to re-invent the wheel here and come up with this bonkers system that we now sit with.

It's confusing for users coming from anywhere else and almost ensures you are going to have security vulnerabilities, even though patches are available. It's also encouraged an ecosystem where only a minimum version constraint is specified on dependencies, MS packages being a prime example of this.

Pre-dating Nuget, package managers had some simple concepts:

  1. There is a lock file for repeatability.
  2. Packages specify their dependencies.
  3. Those dependencies have a minimum and a maximum version constraint.
  4. Maximum compatible versions are chosen by default.

@springy76
Copy link

NuGet developed (or maybe always was) into a new kind of DLL hell - source of random "Method not found" exceptions at runtime (can't the exe-compiler or at least the publish task just check everything once?)

I'm just right now adding the net6 package ErikEJ.EntityFrameworkCore.SqlServer.DateOnlyTimeOnly to a net7 project which already uses EFcore.SqlServer 7.0.x, the package itself only to extend EFcore.SqlServer 6.0.0+

Thats what "Preview Changes" tells me:

Uninstalling:

Microsoft.Win32.Registry.5.0.0
System.Globalization.4.3.0
System.IO.4.3.0
System.Reflection.4.3.0
System.Reflection.Primitives.4.3.0
System.Resources.ResourceManager.4.3.0
System.Threading.Tasks.4.3.0

Updates:

Azure.Core.1.24.0 -> Azure.Core.1.25.0
Azure.Identity.1.6.0 -> Azure.Identity.1.7.0
Microsoft.Data.SqlClient.5.0.1 -> Microsoft.Data.SqlClient.5.1.0
Microsoft.Data.SqlClient.SNI.runtime.5.0.1 -> Microsoft.Data.SqlClient.SNI.runtime.5.1.0
Microsoft.Identity.Client.4.45.0 -> Microsoft.Identity.Client.4.47.2
Microsoft.IdentityModel.Abstractions.6.21.0 -> Microsoft.IdentityModel.Abstractions.6.24.0
Microsoft.IdentityModel.JsonWebTokens.6.21.0 -> Microsoft.IdentityModel.JsonWebTokens.6.24.0
Microsoft.IdentityModel.Logging.6.21.0 -> Microsoft.IdentityModel.Logging.6.24.0
Microsoft.IdentityModel.Protocols.6.21.0 -> Microsoft.IdentityModel.Protocols.6.24.0
Microsoft.IdentityModel.Protocols.OpenIdConnect.6.21.0 -> Microsoft.IdentityModel.Protocols.OpenIdConnect.6.24.0
Microsoft.IdentityModel.Tokens.6.21.0 -> Microsoft.IdentityModel.Tokens.6.24.0
Microsoft.NETCore.Platforms.5.0.0 -> Microsoft.NETCore.Platforms.1.1.0
Microsoft.Win32.SystemEvents.5.0.0 -> Microsoft.Win32.SystemEvents.6.0.0
System.Buffers.4.5.1 -> System.Buffers.4.5.0
System.Configuration.ConfigurationManager.5.0.0 -> System.Configuration.ConfigurationManager.6.0.1
System.Diagnostics.DiagnosticSource.5.0.0 -> System.Diagnostics.DiagnosticSource.6.0.0
System.Drawing.Common.5.0.0 -> System.Drawing.Common.6.0.0
System.IdentityModel.Tokens.Jwt.6.21.0 -> System.IdentityModel.Tokens.Jwt.6.24.0
System.Runtime.Caching.5.0.0 -> System.Runtime.Caching.6.0.0
System.Security.AccessControl.5.0.0 -> System.Security.AccessControl.6.0.0
System.Security.Cryptography.ProtectedData.5.0.0 -> System.Security.Cryptography.ProtectedData.6.0.0
System.Security.Permissions.5.0.0 -> System.Security.Permissions.6.0.0
System.Text.Encoding.CodePages.5.0.0 -> System.Text.Encoding.CodePages.6.0.0
System.Windows.Extensions.5.0.0 -> System.Windows.Extensions.6.0.0

Installing:

ErikEJ.EntityFrameworkCore.SqlServer.DateOnlyTimeOnly.7.0.1
System.Runtime.CompilerServices.Unsafe.6.0.0

It's a wild mix of up- and downgrades, outtake:

Microsoft.NETCore.Platforms.5.0.0 -> Microsoft.NETCore.Platforms.1.1.0
Microsoft.Win32.SystemEvents.5.0.0 -> Microsoft.Win32.SystemEvents.6.0.0
System.Buffers.4.5.1 -> System.Buffers.4.5.0

I can just only hope this has no serious effect on the final app.

@yanivru
Copy link

yanivru commented Aug 10, 2023

In my company, we develop several nuget packages and consume them in other programs.
E.g. P1 <- N1 <- E1
Program1 (P1) references internal nuget N1 that references some external nuget (E1). When I fix bugs in N1 we increase the patch part of the version.

P1 references N1 with floating version (e.g. 1.*). So that P1 (and all other Ps) will take all the bug fixes automatically in next build.

Now if a newer version of E1 was released (e.g. 1.0.1), we want P1 to get it (cause it's might contain a bug fix, or security fix). What we usually do is update N1 to reference the newer N1 (1.0.1), otherwise, we will need to go over all the consuming projects (P1, P2, …) and reference E1 (1.0.1) directly, which is ugly and time consuming.

But since P1 might be directly refencing older version of E1 (1.0.0) it might cause a warning of package downgrade, and because treat warnings as errors it breaks P1.

So eventually the change to N1 is a breaking change. Which is not ideal. Either we break some builds or we increase major of N1 each time we change some minor version of some referenced nuget package (time consuming cause all P's need to be changed).

Is there a better way to do it, that will solve both problems?

What I would like to do is that N1 references the minimum version it requires (e.g. E1 1.0.0) and when P1 is compiled it takes the highest E1, with the bug fixes. This way there won't be a package downgrade error and still the fixes will arrive to all refencing programs (Px). This is not feasible currently since there is no "Biggest major" dependency resolution.

We don't really care about deterministic builds. Our builds are not deterministic anyway, since we use floating versions and for other reasons.

@brentthierens
Copy link

Long time no response anymore, any update on this? I really need to be able to use the latest minor version of dependencies without having to recompile all other packages

@Madajevas
Copy link

Is this even being worked on?

@giggio
Copy link

giggio commented Feb 16, 2024

Is this even being worked on?

@Madajevas It doesn't seem that this is even being discussed, they just released their roadmap for .NET 9 and it is not in it. See #13143.

@supergibbs
Copy link

supergibbs commented Mar 7, 2024

Pretty sure similar examples have been made but here is another one:

I was addressing CVE-2024-21319 identified in System.IdentityModel.Tokens.Jwt:6.24.0. I did not use this directly but I was using latest version of Microsoft.EntityFrameworkCore.SqlServer:8.0.2 which brought in Microsoft.Data.SqlClient:5.1.4 -> Microsoft.IdentityModel.Protocols.OpenIdConnect:6.24.0 and then System.IdentityModel.Tokens.Jwt:6.24.0

My code was working fine so to fix this, I explicitly brought in Microsoft.Data.SqlClient:5.2.0.

This is a simple example but imagine there are more. Now I am having to manage versions of dependencies for dependencies and at scale I can see this causing conflicts and a lot of work to get it right. I don't want to specify every transitive package explicitly and I don't want to miss out on minor fixes and improvements. If I do specify, I run the risk of it not actually being needed if whatever library used it changes. I'd prefer to use the latest patch or even minor version automatically and if there are issues, explicitly "downgrade" as needed. Like maybe 5.3.0 comes out and breaks Microsoft.EntityFrameworkCore.SqlServer, I can specify Microsoft.Data.SqlClient:5.2.* instead.

Lots of opinions here, I think everyone would be happy if we had the option though. For fun, run: dotnet list package --outdated --include-transitive on a large solution and see how much is out of date even if your direct dependencies aren't.

@JonDouglas
Copy link
Contributor

Hello,

I’m proposing a solution to this issue by introducing an opt-in mode for SemVer compatibility, which would align NuGet with the default behaviors of other modern package managers. This approach would allow developers to choose whether they want to enable SemVer-compatible resolution for both top-level and transitive dependencies.

I've created a new issue to discuss this proposal in more detail:
#13779

Please feel free to add your comments and upvote over there. Since this issue has over 100 comments and has been open for a while, shifting the conversation to a new issue will help us focus on whether this feature makes sense and how it could resolve the original challenges brought up here.

Do also check out some of our docs on this topic here which provides guidance today:

https://learn.microsoft.com/nuget/concepts/auditing-packages

https:/NuGet/docs.microsoft.com-nuget/pull/3336/files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests