In the final post of this mini-series on Paket, I want to wrap up by discussion some of the cool features in Paket that aren’t available in the standard NuGet client. They address many areas that can be a real pain when trying to create automated, CI / CD solutions as well as simply some corner cases that we’ve grown used to in the NuGet world.
If you’ve not used Paket before, many of them you might seem a little unusual, unnecessary or even “risky”. That’s totally fine – Paket has always been about bringing cutting-edge features to dependency management on the .NET platform rather than simply being “another version” of the existing NuGet client.
This post won’t be anywhere near as large as the previous ones – it’s mostly to give you a good starting place to see some of these features before reading up in more detail on the official Paket site.
Source Code dependencies
Possibly the neatest “value-add” of Paket is the ability to depend on source code dependencies rather than NuGet packages. In other words, you could have a set of files in your companies private Git repository that are shared across many projects. Normally, you’ll resort to hosting a NuGet package for this (plus a NuGet server to host them on), or submodules (or similar). Neither of these solutions are particularly pleasant, in my opinion. Paket offers an alternative, which is the ability to have Paket download a source file (or set of files) and automatically add them to your .NET projects as linked files, which are then compiled as normal into your project.
Source code dependencies come in three flavours:
- GitHub dependencies. You provide details to the repository and file path you want to depend upon; Paket will download that file into a location controlled by paket (paket-files) similar in principle to the packages folder that are used for NuGet packages. As you add references to the file in your paket.references files, Paket adds linked file references to the project. There’s no need for NuGet packages, and dependencies are pinned against specific commits in the GitHub repository to ensure repeatable builds. A good example of this is the Dapper project, which is written as a single .cs file in GitHub and can easily be referenced through Paket as a GitHub dependency.
- Git dependencies. Git dependencies are even more powerful than GitHub dependencies, at the cost of requiring you to have Git installed. They allow you to download an entire git repository to the paket-files folder, optionally locked to a specific tag, commit or branch. You can also run an arbitrary command after restoring the git repository such as build.cmd. This is extremely useful if you wish to depend on the build output of a git repository, without needing to create a NuGet package or similar.
- HTTP references. These function in a similar way to GitHub dependencies except that they operate against an HTTP resource. This could be the link to a raw gist, or an fssnip file etc. – essentially any file available over HTTP. Unlike GitHub there’s no commit history to lock against, so it’s your responsibility to ensure that the contents of the URI are stable (i.e. repeatable) across restores.
Local Dependencies are a great way to test out a development version of a NuGet project without needing to host it. Let’s assume you have access to the source code of a NuGet-enabled project and want to add a new feature to it. You spend the time to create the feature – but how do you then test it against your “real” project that depends on the NuGet package?
- You use a secondary NuGet feed to host the development version and add this as a second host to the main project.
- You manually “override” the main project and simply make a hard project reference to the new dev version of the NuGet project.
Paket offers an alternative way of doing this, through a local dependencies file. This file contains the set of dependencies that you want to override and use a local build to generate the NuGet package to depend on. This file is never committed into source control, and acts as a temporary override mechanism on top of the standard paket.dependencies file.
What’s especially nice about this approach is that – when coupled with the fact that Paket does not embed the version number of the NuGet package into the physical package folder – means that your project references are completely unaffected by this. You can make changes to your upstream NuGet project, and then simply restore your “main” project – this will implicitly build and package the “dev” version of the NuGet package and copy it to the packages folder.
When you’re done, you simply remove the local file (or comment out the line) – Paket will then revert to using the “live” NuGet feed.
Paket allows you to specify a local folder path (or network share) as an additional cache to your packages for times when your live NuGet / MyGet feeds are unavailable e.g. server downtime, loss of internet connectivity. Once a cache is configured, every time you download a new package from your package server, Paket will implicitly copy it to the cache folder. In this way, you can silently build up a local cache of NuGet projects used by you and your team over time without any effort. If for whatever reason NuGet is unavailable, Paket will automatically check the local cache to see if it can find the required dependency.
Paket also has specialized support for when you wish to commit the packages into your repository directly and push them into source control.
If you use scripts as part of your development process (something very common in the F# world), you’ll know that having to set up references to dependencies can sometimes be painful to do. Whilst there are plans afoot to add support in scripts for NuGet and Paket files through enhancements to the #r directive, for now you can use Paket’s automatic script generation to make working with dependencies painless. Calling this command (either from the command line, or through e.g. VSCode) will cause paket to scan through all dependencies before creating a set of script files that can be loaded via the #load directive. For example calling the following will execute a script that loads in all dependencies for WebGrease.
The contents of this file look as follows:
#load @"Antlr.fsx" #load @"Newtonsoft.Json.fsx" #r "../../../packages/WebGrease/lib/WebGrease.dll"
As you can see, in addition to referencing a dll directly, the script understands that there are dependencies on other packages, and will load their dependencies first before loading the direct dependencies for the WebGrease project.
Groups are Paket’s answer to the challenge of when you need to go “outside the box” and explicitly need different versions of a dependency within the same repository. One example of this might be if you have, for example, a set of dependencies for your build but and others for your release process – for example, when you perform a CI process, you’ll need the full set of dependencies to run unit tests, create build artifacts etc.. But during your release process, you might need a completely different (and much smaller!) set of dependencies. Groups can be used as a way to clearly separate the two different use cases apart.
You should be careful about using groups – it’s possible to get yourself in the same situation as with NuGet i.e referencing different versions of the same dependency within an application – so use them with care.
That’s it for this series on Paket – hope you enjoyed it!
In the first post, I covered the overall value proposition behind Paket, and the core reasons for why it exists. In the next two posts, I gave some simple walkthroughs and examples as to how it differs from the official NuGet client in key areas, including package resolution, workflow and tooling. Finally, in this post I’ve shown some of the “value-add” features that provide ways of changing your development process completely (particularly using source file and local dependencies).
You can catch me and the rest of the Paket team on Twitter on @PaketManager – we’re always happy to answer questions or discuss issues you might be facing with package management.