Skip to content
This repository has been archived by the owner on Jul 12, 2022. It is now read-only.
Leszek 'skolima' Ciesielski edited this page May 12, 2020 · 44 revisions

NuKeeper Logo

Automagically update NuGet packages in .NET projects.

NuKeeper is a global tool on NuGet. Install with dotnet tool install nukeeper --global

Build Status Gitter NuGet

Other pages

What

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.

Why

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.

How

NuKeeper is written in .NET Core 2.1, using HTTP APIs and command-line tools. It runs on Linux and on Windows.

Libraries:

Command lines called: nuget.exe and/or dotnet.

Limitations and warnings

Source control

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.

Betas

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.

Package tools required

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.

Assembly Binding Redirects are a problem

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.

Install scripts

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.

When to use NuKeeper

Scope of NuKeeper

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:

NuKeeper is free open source, but you will have to run it yourself to update your code.

Updating libraries

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.

Footnote

Inspired by Greenkeeper.