NuGet issues with packages.config, project references and the solutionwide packages folder
We are starting to use NuGet and we are having some issues:
First some NuGet facts:
(Just to make sure that we have understood the premises for how NuGet works)
The packages.config (located in project-root) is created and updated as you add, update or delete packages. Within this file the package version property reflects the full version that is in use, so in this sense it is also a state. It is possible to add the allowedVersions property to the package specification, which is then a restriction on which versions that can be updated. To make package restore work, this file need to be under source control.
The packages folder is located in the solution-root and contains a downloaded version of the dependent packages in a subfolder named to match the package names (including version), to allow multiple versions of the same package to be used by different projects in a multi-project solution. It is advised not to source control these as they are binaries and the fact that package restore can recreate them when needed. When a package is updated a new folder that matches the update is created with the package in it.
The project-files contains a reference to the packages in the packages folder, in order for builds to work and for visual studio to also be able to provide autocomplete, intellisense and more. When a package is updated the references in the project-file are updated to match the new location of the package in the packages folder.
Since the packages.config file package entries contains the full version-info we constantly need to update the source control repo with changes. Or, we could ignore the changes, most of the time, but when just the version has changed we would (in most cases) be able to ignore them. This seems very unnecessary as the NuGet restore should be able to know which versions are allowed (via allowedVersions).
The allowedVersions property has to be manually added, something that is easily forgotten. We are using semantic versioning, so for us, when installing i.e. a Foo-1.1.0 version, allowedVersions="[1,2)" should be implied.
When adding the allowedVersion then NuGet package restore doesn't seem to be able to find -prerelease assemblies (maybe a bug?).
Why are packages handled by NuGet on a solution level? If you are working in a mix-and-match solution, which contains a project-A (repo-1) and project-B (repo-2), then the solution level packaging is not going to work well. That is, If you save that solution file in a separate location, things might still work afaik. But, if you then set up another solution which contains project-A (repo-1) and project-C (repo-3), then project-A would suddenly need a package-restore again, and worse, the project references would be changed to match the last change. Going back to the first solution will then have references that doesn't work. Checking in these will certainly make them not work for others.
On a package update, the project-file references are updated (to match the new foldernames with versionid in them) and will appear as an uncommitted change. Committing this change seems to be the norm, but in our opinion this should not be necessary.
Notes about ExcludeVersion (which could be suggested as a solution to the above problem:
You can only provide that option when you manually perform NuGet commands, afaik. When installing/updating packages via the NuGet menus in Visual Studio that option cannot be used. Using any of the automated tools means that the foldername and project-reference has to be fixed manually afterwards.
We know that ExcludeVersion is not the default setting, probably due to supporting the cases where someone is working in a multi-project solution, where the different projects depend on different versions of the same package.
(But, which may require substantial changes in the NuGet ecosystem?)
A - packages.config
I wish that each package element in packages.config could ditch allowedVersions and instead change version to be the range specifier. The packages element should also provide a way to separately identify which source to get the updates from. Finally, if an installed package follows Semantic Versioning then the version property should automatically setup the version-range according to the installed version.
`<?xml version="1.0" encoding="utf-8"?> <packages> <package id="Foo" version="[1,2] source="Development Feed"> </packages>`
Fix the issue with excessive commits of the package.config file, since the version property is no longer constantly updated.
No need to remember to set the range for semantically versioned projects.
Ensure that packages are fetched from the desired feed and that time is not wasted by looking for it in the wrong sources.
B - Packages folder and the names of installed packages within
I wish that the packages folder was located in each project-root and that the subfolder names was limited to the package-name only, excluding the version. This would:
Fix the issue with excessive commits of the project-files, since the project-references in the project-file now points to the same package-folder after an update.
Allows projects to use different versions of the same package as they are then truly independent of each other.
We would be very happy to hear about solutions to the problems listed.
As suggested in NuGet Enterprise - best practices for different maturity levels of packages, I think you are making things more complicated than necessary :)
- Why would you not want to capture the version of the package with which the code was compiled? This is crucial information for reliable diagnostics and repeatable builds. Given that you'd likely be committing code changes back to version control, committing details of which packages were used to help build that source is very useful.
- "when installing i.e. a Foo-1.1.0 version, allowedVersions="[1,2)" should be implied" I do not think that allowedVersions can ever really be implied, because not all NuGet packages adhere to SemVer (see the debacle with log4net 1.2.11). Setting up a grep for allowedVersions as part of either CI build or pre-commit/pre-push Dev checks should catch this. It should not change often, and it's useful to keep an eye on it (if other teams and packages are using SemVer correctly, anyhow :) ).
- To find Prerelease packages you'll need the Prerelease or -IncludePrerelease flags on nuget install.
- "If you are working in a mix-and-match solution, which contains a project-A (repo-1) and project-B (repo-2)" - why have you arranged your code like this? The code for a single solution should live all in the same repo. Breaking solution code across repos is definitely going to be painful!
- You can tell nuget to use version-less folder names for installing packages (-ExcludeVersion).
I would strongly recommend ditching the Visual Studio NuGet integration in favour using the command-line nuget.exe and build scripts instead. This relates particularly to #5 but to interaction with NuGet in general. The Visual Studio integration is nice when working solely with 3rd-party public packages from the nuget.org feed, but is not flexible enough for my liking when dealing with internal NuGet feeds and packages.