Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
Nuget CPM Lock file Proposal (#12409) #13288
base: dev
Are you sure you want to change the base?
Nuget CPM Lock file Proposal (#12409) #13288
Changes from 2 commits
7251306
28ffc93
9d5bc8f
f6d4ae7
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
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.
Is there a reason to make the lock file path configurable in the project? AFAIK, most package managers leave it unconfigured, and that way, the package manager wouldn't have to first read the configuration file first in order to install packages, only the lock file(which makes it easier to separate into steps).
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.
The package manager would need to read the Directory.packages.props file anyways as it is what enables CentralPackageManagement in the first place? (relevant docs)
We could get away with setting a property like
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
to enable CPM Lock files and implicitly assume the<NuGetLockFilePath>
to bepackages.lock.json
but we can also achieve the same with a single property and a check$(NuGetLockFilePath) = ''
. At the end it will be up to the maintainers to give feedback which way they prefer but the lock file path will need to be configurable in any case.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 personally can't think of a great reason to not have the lock file at the top level of a repo(or at some fixed path relative to the top level), and I can think of more reasons to avoid being able to configure it(Easier to make external tools to consume the lock files and process them separately, avoiding conflicts between, say, a git submodule with it's own lock file that's configured to generate this lock file, etc.).
Given that, as far as I'm aware, most package managers(mostly python and node ones) that I've used have a fixed path for their global lock files, I thought I'd at least bring it up.
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.
You would need a way to define the "top level" location. This allows for that.
Keep in mind that not every repo-project relationship is 1:1, particularly in a monorepo situation where you might want a small handful of "top levels" rather than only one.
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.
It should just be in the same folder as the Directory.Packages.props file, the same way that the "top level" of a nodejs project is where the package.json is.
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 i feel you might have misunderstood. This is exactly the case @purepani the
<NuGetLockFilePath>
specifies the lock file for the CPM rather than each individual project. This will be located relative to theDirectory.Packages.props
at the root of the project.Now that you mention it we will likely need a property like
RestorePackagesWithLockFile
but specifically to enable CPM lock files but the lockfile will be located at the root NOT under individual projectsThere 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 see so this property is meant for each individual project rather than just the top level of the projects; that makes way more sense.
The way pnpm solves this is by symlinking the dependencies into each workspace after doing a
pnpm install
at the top level. If you only wanted some dependencies, you could just install it for that group. In this case, it is just the dependencies and not the lock file, but you could certainly symlink the lock file if you wanted(though the only advantage of that approach would be that you wouldn't have to move to the top level every time you wanted to install a package).(note: I only mentioned pnpm specifically because I know how it works a bit better. Presumably other node package managers work similarly)
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.
There's a technical complexity here since a property value doesn't get path resolved, so knowing where it comes would be required.
The simpler approach might be the already mentioned just making the file live next to the directory.packages.props.
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.
For flexibility I prefer the idea of having the lock file path configurable which also tracks consistently with lock files on a project. The better solution is probably setting the default property to be next to the
Directory.packages.props
and instead offering (if it doesn't already exist) a$(ThisCurrentFilePath)
MSBuild property which can be used to set the file path relative to the Directory.packages.props rather than something relative to the project.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.
Yeah, it's all doable, we'd just need to be specific to avoid confusion.
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 love JSON as much as the next guy. but if Directory.Packages.props, NuGet.props and most of the files are XML based, wouldn't it be better to not mix stacks? historically lock files use .json format in various programming platforms, so that might be one defense for it (but not a very compelling one).
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.
The existing related files are already JSON -
packages.lock.json
andproject.assets.json
.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.
Personally my preference is also xml but the nuget/dotnet team have already settled on a json based lock file and this change is supposed to be as small and simple as possible.
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.
The biggest challenges delivering this feature are the preexisting msbuild paradigms and we're not really tackling that here.
We need to dive deeper into the technical complexities to understand the feasibility of simply having a lock file.
How would the scope of the msbuild based restore operation be defined? Currently restore is either project based or solution based. Projects don't know about all other projects.
Here's a practical example:
You have 2 projects and 3 packages.
Project 1:
Project 2:
DPP
A 1.0.0 -> C 1.0.0
B 1.0.0 -> C 2.0.0
Without pinning:
These projects aren't necessarily correlated so the only way for them to know that they're conflicting with C is to either:
Today, a project only knows about itself and it's closure, so this would be a departure in how restore/build work, one that'd come with some perf considerations.
The 2nd idea could theoretically be solved with transitive pinning.
You can declare
C 2.0.0
in DPP, enable transitive pinning and then the versions would be resolved would be equivalent.You'd basically need to exhaustively declare all versions.
Within the current toolset, The central lock file as proposed is really a more algorithmically advanced transitive pinning that gets applied across all projects.
Another challenge is package creation for any projects that have a central lock file.
NuGet works really hard to make projects and the package versions of them behave as closely as possible.
What this means is that every PackageReference you have becomes a dependency in a generated package.
To avoid runtime issues, transitive pinning elevates those transitively pinned packages to top level dependencies. The practical example is Project 1 from above, where it could unwittingly take a dependency on C 2.0.0, and then if it gets published as a package with only its direct dependencies lead to runtime failures later.
I understand that's not really a relevant scenario here, but it is a concept that the design needs to account for.
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.
Isn't that exactly what a lock file does, by specifying the entire dependency graph and the versions used of each dependency?
I assume we would need to keep track of which dependencies are direct and which are transitive, and
dotnet pack
would only look at direct dependencies when building a package. A transitive dependency can always be elevated to a direct dependency.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.
There's a key distinction. All version resolution is project based.
Lock files don't change the resolution. They skip it.
The idea for central lock files makes the version resolution more involved and requires to account for transitive packages declared from all projects, not just the current one.
You're not just asking for a file, you're asking for a completely new resolver. Beyond scoping, the resolver part is the most challenging technical part.
"dotnet pack would only look at direct dependencies when building a package" -> This doesn't work in all scenarios.
Here's an example:
Project 1 -> Package A 1.0.0 -> Package B 1.0.0
Regular resolution leads to A 1.0.0, B 1.0.0.
When the project is packaged, it only needs A 1.0.0 as a dependency, since at minimum B 1.0.0 will be resolved due to A's dependency.
Central lock file example could bump package B to 2.0.0.
Project 1 code ends up coding against B 2.0.0.
If the Project 1 package ends up declaring A 1.0.0, then when anyone installs that package generated from project 1, they'd get B 1.0.0, because no one knows about 2.0.0 in the Project 1 graph, it's there due to how other projects work. If we don't elevate B 2.0.0 to a direct dependency, then the consumer of Project 1's generated package would end up with runtime failures.
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.
Some general food for thought for the concept of centralized lock files.
In general, you probably only need to consolidate the versions being used across your runnable projects. Your services, your tools etc, not across your libraries as well (and never downgrade ofc).
Just because you make your projects reference a specific version, that doesn't mean they'll all work. You could be referencing dependencies that reference different versions of a particular package, so consolidation is naturally going to happen in that case.
There's of course the theory for handling breaking changes in dependencies, but that'd only work if you control all libraries that reference a package with a breaking change, in which case regular CPM would work just fine.
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 prefer the idea of promoting any transitive dependency at a version outside the already resolved range into a primary dependency so that any downstream library consumers don't need to think about it
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.
We need to add the mechanics of version resolution in the unresolved questions.