-
Notifications
You must be signed in to change notification settings - Fork 127
Home
Automagically update NuGet packages in .NET projects.
NuKeeper is a global tool on NuGet. Install with
dotnet tool install nukeeper --global
NuKeeper automates the routine task of discovering and applying NuGet package updates.
NuKeeper will compare the NuGet packages used in your solution to the latest versions available on NuGet.org or other package feeds, and:
- List available NuGet package updates on .NET code on the local file system or on a remote server.
- Apply NuGet package updates to .NET code on the local file system.
- Make pull requests containing updates to a collaboration server.
Package update automation is necessary because .NET developers are bad at applying NuGet package updates. We want to make it easier to find and apply these changes. To increase visibility of package updates, and decrease cycle time.
Why do we deploy code changes frequently but seldom update NuGet packages? In Continuous delivery, we know that there is a vicious cycle of "deploys are infrequent and contain lots of changes, therefore deploys are hard and dangerous, therefore deploys are infrequent and contain lots of changes" and a virtuous cycle of "deploys are frequent and contain incremental changes, therefore deploys are easy and low risk, therefore deploys are frequent and contain incremental changes" and so we work hard to move into the second cycle, and afterwards, life is easier.
But NuGet package updates are a form of change that should be deployed, and we likewise want to change the cycle from "NuGet package updates are infrequent and contain lots of package changes, therefore NuGet package updates are hard and dangerous..." to "NuGet package updates are frequent and contain small changes, therefore NuGet package updates are easy and routine...".
The intention is that we leverage existing automation that can compile and test pull requests, and verify if these changes are good to merge.
NuKeeper is written in .NET Core 2.1, using HTTP APIs and command-line tools. It runs on Linux and on Windows.
Libraries:
- LibGit2Sharp for git automation.
- Octokit for GitHub automation.
- NuGet.Protocol for NuGet api queries.
-
NuGet.CommandLine for updating
packages.config
style projects. - McMaster.Extensions.CommandLineUtils for command line parsing.
Command lines called: nuget.exe
and/or dotnet
.
NuKeeper works with git, no other source control systems are supported.
NuKeeper supports raising pull requests against several different public and internal hosted collaboration platforms. e.g. an internal hosted GitHub instance can be specified with the --api
option. You can also apply changes locally with the update
command.
By default, when the package version used is a prerelease (AKA a beta), later betas or release versions will be found and applied. When the package version used is not a beta, they will not. This behaviour can be controlled with the --useprerelease
option.
You will need the command line version of dotnet
2.1 or later installed. NuKeeper can run on all platforms where dotnet
runs, including Windows, linux and MacOS. Inspection should work on all of these platforms.
However not all update cases work on all platforms. In short: You can update windows-only code on windows, and update cross-platform code on any platform.
NuKeeper will invoke dotnet
or the NuGet.exe
tool as needed to update packages. The "windows only" restriction is due to the older .csproj
and packages.config
file format requiring NuGet.exe
, which is windows only. There are no plans to port NuGet.exe
; dotnet
is the portable replacement. This is another reason to update these projects to the new <PackageReference>
style, so that they can be worked with using dotnet
instead.
Updates from NuKeeper can fail in .NET full framework projects because Assembly Binding Redirects are not updated.
Assembly Binding Redirects are those entries that you can find in a web.config
or app.config
file, e.g.:
<dependentAssembly> ...
<assemblyIdentity> ...
<bindingRedirect oldVersion="1.0.0.0-7.0.0.0" newVersion="8.0.0.0" />`
NuKeeper does not update these config files. The problem here is that the assembly version numbers that might need to change are often not the same as the NuGet package version number. In fact they are not easy to work out from the NuGet package as there is no 1-1 correspondence.
Current guidance is to not change the assembly version for each new NuGet package version: "CONSIDER only including a major version in the AssemblyVersion"
The common symptom of failure is code that compiles after the update, but won't run due to assembly binding failure at application start up.
Workarounds to this issue are:
- Update the Assembly Binding Redirects by hand after using NuKeeper.
- Limit NuKeeper to minor version updates, and update well-behaved packages that follow the guidance to not change the assembly version on these updates.
- Get rid of the Assembly Binding Redirects by auto-generating them.
- Get rid of the Assembly Binding Redirects by targeting a .NET Standard or .NET Core version.
For projects using packages.config
, NuGet.exe
no longer runs install.ps1
and uninstall.ps1
scripts from command line.
Those are still executed from Visual Studio, resulting in different behaviour for packages relying on this functionality.
An example of this is StyleCop.Analyzers which (when used with a packages.config
style project) will not update the <Analyzers>
node in the project file.
This does not affect projects using <PackageReference>
project file format, as that format replaces install scripts with content transformations, which are supported.
NuKeeper, right from the start, aimed to be a package updater for all .NET scenarios. This means both .NET Core and .NET Full Framework are supported. It can work on the new "Visual Studio 2017" format of .csproj
files containing <PackageReference>
elements, and on the older "Visual Studio 2015" format, with a packages.config
file.
NuKeeper will work in .csproj
, .vbproj
and .fsproj
project files.
Private package sources are supported, as many larger companies use internal nuget feeds to manage internal artefacts. To specify package sources other than the public NuGet.org feed, it is recommended that you use a NuGet.config
file in your repository. It will be used by NuKeeper, and by other tools. NuKeeper also allows these sources to be specified on the command line.
We do not plan to make NuKeeper a general update tool for multiple languages and package managers such as Node.js NPM and Ruby gems. Such update management tools already exist, and some are listed here. These tools tend to have only basic .NET Core support and are not coded in c#. We feel that we would rather embrace the .NET project and package system to the exclusion of others, since no other tool covers this case as well.
NuKeeper was initially created raising Pull Requests for the GitHub collaboration platform, in both the public and internal enterprise versions, as this is what we used. The code was tightly coupled to GitHub Octokit. However, due to demand for other back ends, and the submitted code; it is now a model where the source control platform is selected at runtime. Supported platforms are:
-
GitHub with public
GitHub.com
or internally hosted GitHub enterprise. -
Azure DevOps on
dev.azure.com
orvisualstudio.com
- BitBucket.org
NuKeeper is free open source, but you will have to run it yourself to update your code.
If the project is a library that itself produces a NuGet package, it is usually best not to update it aggressively without cause. Consider carefully whether you want to force your users to also update entire dependency chains.
e.g. if MyFancyLib
depends upon Newtonsoft.Json
version 9.0.1
then an application that depends upon MyFancyLib
can use Newtonsoft.Json
version 9.0.1
or a later version. Updating the reference in MyFancyLib
to Newtonsoft.Json
version 10.0.3
takes away some flexibility in the application using MyFancyLib
.
It might even cause problems.
Libraries should, however, update their packages when there is a breaking change in the features that they use or another compelling reason. e.g. If MyFancyLib
uses Newtonsoft.Json
version 8.0.1
, but since it only calls JsonConvert.DeserializeObject<>
many versions of Newtonsoft.Json
can be used.
But now I am converting MyFancyLib
to NetStandard for use in .NET Core. The lowest version of Newtonsoft.Json
that supports this is 9.0.1
, so we use that.
Although there are later versions of Newtonsoft.Json
, this gives MyFancyLib
what it needs and allows clients the most choice within the constraint of supporting NetStandard. Another compelling reason to update a dependency would be if there is a bug fix that impacts the working of MyFancyLib
, so users of MyFancyLib
really should apply it.
In an end-product deployable application, frequent updating of packages is a better tactic. Supported by comprehensive automated testing, regular updates will keep your application up to date with security fixes and prevent it from relying on potentially outdated libraries.
This is an application of Postel's Law: Packages should be liberal in the range of package versions that they can accept, and applications should be strict about using up to date packages when they run.
It is similar to this rule of preferring to use a parameter of a base type or interface as it allows wider use.
Inspired by Greenkeeper.