Restoring NuGet Packages with a Lock File
Written by Marcin Krystianc, Open-Source Software Developer
When it comes to installing a lot of code quickly, developers in the .NET ecosystem tend to turn to NuGet to help their cause.
Since the NuGet package manager came on the scene, it has become something of an industry standard for Microsoft-related projects. Paket exists, but NuGet is still the de facto starting point.
But the ease with which people are able to pick out and install packages can cause problems for reproducible builds. While the optionality is an obvious benefit, at run-time, standardisation is required for consistent results. And therein lies the beauty of a lock file.
The benefits of a lock file
The concept of a lock file is implemented by many package managers and NuGet is no different.
A lock file pins down all versions of the entire dependency tree of any given project. It is a single source of truth for everyone and anyone touching the package.
When a package manager installs dependencies, it can use the information from a lock file to ensure the the same versions are always installed.
NuGet should provide consistent results each time it is deployed. However there are rare occasions when it does not. What happens, for example, when a package is being removed from the NuGet feed? Inconsistent results. That’s one reason it can be wise to utilise a lock file – it guarantees the repeatability of the restore operation.
A Microsoft blog post announced the addition of the –locked-mode feature to NuGet. The blog suggests that NuGet will skip re-evaluating the full dependency graph if the lock file already exists.
At first glance it seems very reasonable. Why bother re-evaluating the dependency graph when the full list of dependencies is being listed in the lock file anyway? It should result in a dramatic performance improvement.
We imagined it would be like strapping on rocket boosters – or at least hitting the accelerator – and that our machines would be able to crunch through our algorithms with ease. So, we decided to test that theory out.
Our NuGet lock file tests
In the chart below, the blue bars represent test packages without the lock file and the red bars represent test packages with the lock file. This props file sets the RestorePackagesWithLockFile property for all projects in the solution.
As usual, we’ve used the benchmark scripts from the NuGet.Client repository with the following scenarios:
Arctic – clean all local caches before running restore, restore time impacted by NuGet packages, downloads from remote NuGet feeds
Force – already restored, run again dotnet restore with –force
NoOp – already restored, run again dotnet restore without –force
The promised performance improvement is just not there: restoring packages for a solution which uses lock files is as fast as the solution without it.
With that in mind, we decided to dig a little deeper with a .NET profiler.
- Restore operation without a lock file
Restore operation with a lock file:
But again, we did not see any performance improvement in the benchmarks.
This may be because the full dependency graph is evaluated by calling the WalkDependenciesAsync method even when the lock file is used, which is computationally cumbersome, but our research suggests that the lock file functionality doesn’t reduce NuGet restore times as advertised.
Perhaps it does for certain corner cases or for very large applications and datasets that are outside of normal operating parameters.
Nevertheless, it is a very valuable feature. While it may not result in performance speed improvements, using a lock file guarantees repeatability and integrity, which are key to ensuring the viability of any restore operation.