For many, contributing to nixpkgs can be a daunting task - the repository is extremely active, with thousands of PRs going in every week. With the breadth of what's included in nixpkgs spanning everything from bootstrapping an OS install to npm packages, it can be hard to know where to start.
This is the first in a series of posts on how to effectively contribute to nixpkgs. It helps you avoid common pitfalls and approach things in a manner that are easier to review and less likely to break things.
How is nixpkgs structured?
Nixpkgs is a monorepo, containing all the packages and infrastructure needed to build the NixOS and nixpkgs ecosystem. It's structured in a way that makes it easy to add new packages and update existing ones.
The root of the repo contains only a handful of directories:
As a first-time contributor, you don't need to go too deep. Just know that:
doc
contains the documenation used to generate the nixpkgs manual. As someone updating and adding packages, you generally don't need to change anything here.lib
contains helpful functions and utilities used to compose packages and other information about them. Once again, not a common entrypoint for first-time contributors.maintainers
contains a list of known-maintainers who look after certain package or areas of the repository, as well as helpful scripts for common maintenance tasks such as updates and listings. If you wish to add a new package, or adopt an existing one, you would need to create an entry for yourself inmaintainers/maintainer-list.nix
.nixos
contains the NixOS module system, which is used to configure NixOS systems. Some packages have associated NixOS modules that may require updates when the package is updated.pkgs
is where the magic happens. This directory contains all the packages in nixpkgs, organized by category. This is where you'll be spending most of your time.
Finding something to contribute
There are a few ways to make a meaningful contribution. Most folks start out by updating a package they use, or fixing a bug they're running into.
If you already have a contribution in mind, you can skip this section. If you're looking for something to work on, here are a few ways to find something:
- Look for issues: The nixpkgs issue tracker has frequent requests for new package additions or updates. Scrolling through this list can provide some opportunities.If you pick an open issue, make sure to mention
Closes #123
in your PR description to automatically close the issue when the PR is merged. - Check failing builds: Visit Hydra's latest build for nixpkgs#trunk and take a look at the
Newly Failing Jobs
andStill Failing Jobs
to see if anything looks interesting. - Contribute without code: There are countless opportunities for things such as documenation improvements, PR review, and other general tasks. We won't touch upon these in this post, but you can filter pull requests by tags such as has: documenation to see how others are making such contributions.
Once you've identified something to work on, it's time to get started. For this post, we'll be updating the flyctl
package to the latest version.
Getting set up
We're going to assume you already have Nix installed, or are using NixOS.
The first step is to fork the nixpkgs repository. This is done by visiting the nixpkgs repository and clicking the Fork
button in the top right corner.
Forking can take a moment as it is a large repository.
Once you have your fork, you can clone it locally. We're also going to want to add the main nixpkgs repository as a remote, so we can pull in changes from upstream.
To ensure everything is in order, let's run a quick build command:
This may not actually build anything for you - nixpkgs is heavily cached, and since we haven't updated the version yet this will likely just pull the existing binary from the cache.
Basic contribution workflow
When contributing to a large, living repository like nixpkgs, there's a few things to watch out for:
- Follow the commit and style standards - most repositories have these defined in the README or CONTRIBUTING file, or in a wiki for the project.
- Every commit is one unit of work - It should be easy to revert a single change without undoing an entire PR worth of work, especially if a PR touches multiple things.
- There may be tooling you can use - nixpkgs, and the broader Nix ecosystem, does have a variety of bots and other tooling that can be used to simplify or cross-check your work.
- When in doubt, ask - it's always easier to show your approach and ask for feedback, than to post an open ended "How do I X?" question. Members will be able to give more focused and helpful answers, as well as specific feedback on why your approach wasn't the preferred option.
- Read CONTRIBUTING.md, pkgs/README.md, and the respective section of the manual for whichever language/stack you're updating packages in (for example, for this post, that's the Go section).
[Optional] Becoming a maintainer
If you plan on becoming a listed maintainer for any package, it's best to do this step first - maintainer tracking in nixpkgs is done by evaluating a Nix expression, so you must be declared in the maintainer list before being added to a package or team.Listed maintainers are automatically tagged for Code Review by much of the automated tooling, such as GitHub and OfBorg.
To do this, you need to add an entry to maintainers/maintainer-list.nix
. This file is a list of maintainers, with each maintainer having a name, email, and a contain method.
You can fetch your githubId
by visiting https://api.github.com/users/<your-github-username>. This will return a JSON object with your user details, including your id
.
handle
does not have to match your GitHub username, but conventionally it does.
The entries in the file are sorted alphabetically by the handle
field, so make sure to insert your entry in the correct place.
Once you've added your entry, it's time to commit!
Now you can open a PR to add yourself as a maintainer. It's also possible to do this as one commit that is part of a larger PR. However, if you take that route, ensure:
- The commit adding yourself as a maintainer is the first commit in the PR.
- The focus of the PR is on the package being updated or added.
A good PR which includes maintainer updates will look something like:
maintainers: add RaghavSood
flyctl: 0.0.1 -> 0.0.2
flyctl: add RaghavSood as maintainer
By splitting each change into its own, clearly delineated commit, it is easier for reviewers to evaluate. Additionally, in the event that the package update needs to be reverted, it can be done without affecting the maintainer changes.
Updating the package
Within nixpkgs many wrappers and build systems exist to make it easier to package different kinds of applications. For basic packages, the stdenv.mkDerivation
function is used. Go has buildGoModule
, Python has buildPythonPackage
, and so on.
A trivial update to a package will generally follow the same recipe, regardless of the language or build system used:
- Update the version or revision of the package to point to the desired version.
- Update any hashes (within
src
, or for build requirements like go modules or cargo crates) to match their new state. - If required, update any dependencies or inputs.
- Fix any tests, build issues, or other problems that arise from the update.
- If required, do some housekeeping by removing any inputs, patches, or other now-useless parts of the derivation.A nix derivation is a Nix program that describes how to achieve a specific task, such as building a package.
In this post, we're simply going to make a trivial update that requires no fixes.
As of writing, the flyctl
package in nixpkgs is at version 0.2.52, while the latest upstream version is 0.2.55. It's a Go package, so we need to:
- Update the version
- Update the
src
hash - Update the go module hash
Updating the version is easy enough - we simply change the version
field in the derivation:
To get the updated src
hash, we have a few options. For most derivations, including flyctl
, the source is downloaded from an archive hosted on GitHub or Gitlab or other service. These have a predictable, defined URL, which can be used to fetch the source and calculate the hash using the nix-prefetch-url
command. Since the derivation uses the unpacked code, we must ask nip-prefetch-url
to unpack the source:
This gives us the hash in the old, base32 format. However, current standards[^4] require that hashes in nixpkgs follow the SRI format. We can convert the hash using the nix-hash
command:nixpkgs
is an incredibly large, complex intersection of thousands of contributors. Standards and styles change often, and it is common to find code in the repo that is written in an old manner. To ensure you're following the latest standards, it's best to refer to the latest PRs and commits in the repository that touch similar areas and read the description and reviews.
While this approach works, some people find it cumbersome - moreover, it doesn't work well if you are cloning the repository over git, or have another source type that isn't just a simple file.
A more common, albeit slightly "hacky" approach is to intentionally put the wrong hash in the derivation, and let nix-build
throw an error and fail. This will give you the correct hash to use. To do this, we can replace the src.hash
attribute with an empty string ""
or lib.fakeHash
:
Now, running nix-build -A flyctl
will produce an error and provide us with the correct hash:
Go modules are fixed against the vendorHash
attribute in buildGoModule
derivations. Unfortunately, there is no equivalent to nix-prefetch-url
for go modules, so the easiest way to get the new hash is to set it to lib.fakeHash
and try a build.
Once again, nix-build -A flyctl
helpfully tells us the expected hash:
And that's it! We now have everything we need for a updated, successful build:
… or do we?
Although this was supposed to be a trivial update, it turns out we have a failing test
It turns out that one of the changes between 0.2.52 and 0.2.55 added some tests that require network access. Since we're building in a sandboxed environment.Derivations in nixpkgs
are built in a sandbox with no network or filesystem access. Anything required by your program, such as dependencies (NPM packages, go modules, config files, etc.) must be explicitly provided. buildGoModules
already provides a wrapper to download go moduels from the internet and pin them using vendorHash
. However, if tests or other parts of the build process require network access, they will have to be skipped or patched to avoid it.
We can fix this by disabling the tests. buildGoModule
helpfully provides a few options to remove tests from the checkPhase.
Unfortunately, the test setup for go packages doesn't quite support the common go test ./...
pattern, so we also need to override the checkPhase
step of the build process.A build consists of multiple phases.
By disabling all of the TestToTestMachineConfig
tests, we can get a successful build.
To see the full breakdown of why these changes are necessary, you can refer to the PR description.
With the update read, it's a simple commit:
Housekeeping
flyctl
is a package I've found myself using more and more, so I'm going to adopt it as one of the maintainers - since I already exist in maintainers/maintainer-list.nix
, it's a simple change:
We can then commit that, ensuring we do it separately from the version update itself:
Lastly, I'm going to define an updateScript
. A significant portion of nixpkgs
updates are automated and various tooling exists to support this. However, it doesn't work on flyctl
as the upstream repository has non-standard release tags, and no updateScript
is defined to inform the tooling on how to process these tags. We can make use of existing tooling options to define such a script:
Again, we want to commit this as a separate unit of work from our version update and maintainer list change:
And with that, we're done!