23 KiB
Coding conventions
Syntax
-
Use 2 spaces of indentation per indentation level in Nix expressions, 4 spaces in shell scripts.
-
Do not use tab characters, i.e. configure your editor to use soft tabs. For instance, use
(setq-default indent-tabs-mode nil)
in Emacs. Everybody has different tab settings so it’s asking for trouble. -
Use
lowerCamelCase
for variable names, notUpperCamelCase
. Note, this rule does not apply to package attribute names, which instead follow the rules in . -
Function calls with attribute set arguments are written as
foo { arg = ...; }
not
foo { arg = ...; }
Also fine is
foo { arg = ...; }
if it's a short call.
-
In attribute sets or lists that span multiple lines, the attribute names or list elements should be aligned:
# A long list. list = [ elem1 elem2 elem3 ]; # A long attribute set. attrs = { attr1 = short_expr; attr2 = if true then big_expr else big_expr; }; # Combined listOfAttrs = [ { attr1 = 3; attr2 = "fff"; } { attr1 = 5; attr2 = "ggg"; } ];
-
Short lists or attribute sets can be written on one line:
# A short list. list = [ elem1 elem2 elem3 ]; # A short set. attrs = { x = 1280; y = 1024; };
-
Breaking in the middle of a function argument can give hard-to-read code, like
someFunction { x = 1280; y = 1024; } otherArg yetAnotherArg
(especially if the argument is very large, spanning multiple lines).
Better:
someFunction { x = 1280; y = 1024; } otherArg yetAnotherArg
or
let res = { x = 1280; y = 1024; }; in someFunction res otherArg yetAnotherArg
-
The bodies of functions, asserts, and withs are not indented to prevent a lot of superfluous indentation levels, i.e.
{ arg1, arg2 }: assert system == "i686-linux"; stdenv.mkDerivation { ...
not
{ arg1, arg2 }: assert system == "i686-linux"; stdenv.mkDerivation { ...
-
Function formal arguments are written as:
{ arg1, arg2, arg3 }:
but if they don't fit on one line they're written as:
{ arg1, arg2, arg3 , arg4, ... , # Some comment... argN }:
-
Functions should list their expected arguments as precisely as possible. That is, write
{ stdenv, fetchurl, perl }: ...
instead of
args: with args; ...
or
{ stdenv, fetchurl, perl, ... }: ...
For functions that are truly generic in the number of arguments (such as wrappers around
mkDerivation
) that have some required arguments, you should write them using an@
-pattern:{ stdenv, doCoverageAnalysis ? false, ... } @ args: stdenv.mkDerivation (args // { ... if doCoverageAnalysis then "bla" else "" ... })
instead of
args: args.stdenv.mkDerivation (args // { ... if args ? doCoverageAnalysis && args.doCoverageAnalysis then "bla" else "" ... })
-
Unnecessary string conversions should be avoided. Do
rev = version;
instead of
rev = "${version}";
-
Building lists conditionally should be done with
lib.optional(s)
instead of usingif cond then [ ... ] else null
orif cond then [ ... ] else [ ]
.buildInputs = lib.optional stdenv.isDarwin iconv;
instead of
buildInputs = if stdenv.isDarwin then [ iconv ] else null;
As an exception, an explicit conditional expression with null can be used when fixing a important bug without triggering a mass rebuild. If this is done a follow up pull request should be created to change the code to
lib.optional(s)
. -
Arguments should be listed in the order they are used, with the exception of
lib
, which always goes first.
Package naming
The key words must, must not, required, shall, shall not, should, should not, recommended, may, and optional in this section are to be interpreted as described in RFC 2119. Only emphasized words are to be interpreted in this way.
In Nixpkgs, there are generally three different names associated with a package:
-
The
pname
attribute of the derivation. This is what most users see, in particular when usingnix-env
. -
The variable name used for the instantiated package in
all-packages.nix
, and when passing it as a dependency to other functions. Typically this is called the package attribute name. This is what Nix expression authors see. It can also be used when installing usingnix-env -iA
. -
The filename for (the directory containing) the Nix expression.
Most of the time, these are the same. For instance, the package e2fsprogs
has a pname
attribute "e2fsprogs"
, is bound to the variable name e2fsprogs
in all-packages.nix
, and the Nix expression is in pkgs/os-specific/linux/e2fsprogs/default.nix
.
There are a few naming guidelines:
-
The
pname
attribute should be identical to the upstream package name. -
The
pname
and theversion
attribute must not contain uppercase letters — e.g.,"mplayer" instead of
"MPlayer"`. -
The
version
attribute must start with a digit e.g`"0.3.1rc2". -
If a package is a commit from a repository without a version assigned, then the
version
attribute should be the latest upstream version preceding that commit, followed by-unstable-
and the date of the (fetched) commit. The date must be in"YYYY-MM-DD"
format.
Example: Given a project had its latest releases 2.2
in November 2021, and 3.0
in January 2022, a commit authored on March 15, 2022 for an upcoming bugfix release 2.2.1
would have version = "2.2-unstable-2022-03-15"
.
-
Dashes in the package
pname
should be preserved in new variable names, rather than converted to underscores or camel cased — e.g.,http-parser
instead ofhttp_parser
orhttpParser
. The hyphenated style is preferred in all three package names. -
If there are multiple versions of a package, this should be reflected in the variable names in
all-packages.nix
, e.g.json-c_0_9
andjson-c_0_11
. If there is an obvious “default” version, make an attribute likejson-c = json-c_0_9;
. See also
File naming and organisation
Names of files and directories should be in lowercase, with dashes between words — not in camel case. For instance, it should be all-packages.nix
, not allPackages.nix
or AllPackages.nix
.
Hierarchy
Each package should be stored in its own directory somewhere in the pkgs/
tree, i.e. in pkgs/category/subcategory/.../pkgname
. Below are some rules for picking the right category for a package. Many packages fall under several categories; what matters is the primary purpose of a package. For example, the libxml2
package builds both a library and some tools; but it’s a library foremost, so it goes under pkgs/development/libraries
.
When in doubt, consider refactoring the pkgs/
tree, e.g. creating new categories or splitting up an existing category.
If it’s used to support software development:
-
If it’s a library used by other packages:
development/libraries
(e.g.libxml2
)
-
If it’s a compiler:
development/compilers
(e.g.gcc
)
-
If it’s an interpreter:
development/interpreters
(e.g.guile
)
-
If it’s a (set of) development tool(s):
-
If it’s a parser generator (including lexers):
development/tools/parsing
(e.g.bison
,flex
)
-
If it’s a build manager:
development/tools/build-managers
(e.g.gnumake
)
-
If it’s a language server:
development/tools/language-servers
(e.g.ccls
orrnix-lsp
)
-
Else:
development/tools/misc
(e.g.binutils
)
-
-
Else:
development/misc
If it’s a (set of) tool(s):
(A tool is a relatively small program, especially one intended to be used non-interactively.)
-
If it’s for networking:
tools/networking
(e.g.wget
)
-
If it’s for text processing:
tools/text
(e.g.diffutils
)
-
If it’s a system utility, i.e., something related or essential to the operation of a system:
tools/system
(e.g.cron
)
-
If it’s an archiver (which may include a compression function):
tools/archivers
(e.g.zip
,tar
)
-
If it’s a compression program:
tools/compression
(e.g.gzip
,bzip2
)
-
If it’s a security-related program:
tools/security
(e.g.nmap
,gnupg
)
-
Else:
tools/misc
If it’s a shell:
shells
(e.g.bash
)
If it’s a server:
-
If it’s a web server:
servers/http
(e.g.apache-httpd
)
-
If it’s an implementation of the X Windowing System:
servers/x11
(e.g.xorg
— this includes the client libraries and programs)
-
Else:
servers/misc
If it’s a desktop environment:
desktops
(e.g.kde
,gnome
,enlightenment
)
If it’s a window manager:
applications/window-managers
(e.g.awesome
,stumpwm
)
If it’s an application:
A (typically large) program with a distinct user interface, primarily used interactively.
-
If it’s a version management system:
applications/version-management
(e.g.subversion
)
-
If it’s a terminal emulator:
applications/terminal-emulators
(e.g.alacritty
orrxvt
ortermite
)
-
If it’s a file manager:
applications/file-managers
(e.g.mc
orranger
orpcmanfm
)
-
If it’s for video playback / editing:
applications/video
(e.g.vlc
)
-
If it’s for graphics viewing / editing:
applications/graphics
(e.g.gimp
)
-
If it’s for networking:
-
If it’s a mailreader:
applications/networking/mailreaders
(e.g.thunderbird
)
-
If it’s a newsreader:
applications/networking/newsreaders
(e.g.pan
)
-
If it’s a web browser:
applications/networking/browsers
(e.g.firefox
)
-
Else:
applications/networking/misc
-
-
Else:
applications/misc
If it’s data (i.e., does not have a straight-forward executable semantics):
-
If it’s a font:
data/fonts
-
If it’s an icon theme:
data/icons
-
If it’s related to SGML/XML processing:
-
If it’s an XML DTD:
data/sgml+xml/schemas/xml-dtd
(e.g.docbook
)
-
If it’s an XSLT stylesheet:
(Okay, these are executable...)
data/sgml+xml/stylesheets/xslt
(e.g.docbook-xsl
)
-
-
If it’s a theme for a desktop environment, a window manager or a display manager:
data/themes
If it’s a game:
games
Else:
misc
Versioning
Because every version of a package in Nixpkgs creates a potential maintenance burden, old versions of a package should not be kept unless there is a good reason to do so. For instance, Nixpkgs contains several versions of GCC because other packages don’t build with the latest version of GCC. Other examples are having both the latest stable and latest pre-release version of a package, or to keep several major releases of an application that differ significantly in functionality.
If there is only one version of a package, its Nix expression should be named e2fsprogs/default.nix
. If there are multiple versions, this should be reflected in the filename, e.g. e2fsprogs/1.41.8.nix
and e2fsprogs/1.41.9.nix
. The version in the filename should leave out unnecessary detail. For instance, if we keep the latest Firefox 2.0.x and 3.5.x versions in Nixpkgs, they should be named firefox/2.0.nix
and firefox/3.5.nix
, respectively (which, at a given point, might contain versions 2.0.0.20
and 3.5.4
). If a version requires many auxiliary files, you can use a subdirectory for each version, e.g. firefox/2.0/default.nix
and firefox/3.5/default.nix
.
All versions of a package must be included in all-packages.nix
to make sure that they evaluate correctly.
Fetching Sources
There are multiple ways to fetch a package source in nixpkgs. The general guideline is that you should package reproducible sources with a high degree of availability. Right now there is only one fetcher which has mirroring support and that is fetchurl
. Note that you should also prefer protocols which have a corresponding proxy environment variable.
You can find many source fetch helpers in pkgs/build-support/fetch*
.
In the file pkgs/top-level/all-packages.nix
you can find fetch helpers, these have names on the form fetchFrom*
. The intention of these are to provide snapshot fetches but using the same api as some of the version controlled fetchers from pkgs/build-support/
. As an example going from bad to good:
-
Bad: Uses
git://
which won't be proxied.src = fetchgit { url = "git@github.com:NixOS/nix.git" url = "git://github.com/NixOS/nix.git"; rev = "1f795f9f44607cc5bec70d1300150bfefcef2aae"; hash = "sha256-7D4m+saJjbSFP5hOwpQq2FGR2rr+psQMTcyb1ZvtXsQ="; }
-
Better: This is ok, but an archive fetch will still be faster.
src = fetchgit { url = "https://github.com/NixOS/nix.git"; rev = "1f795f9f44607cc5bec70d1300150bfefcef2aae"; hash = "sha256-7D4m+saJjbSFP5hOwpQq2FGR2rr+psQMTcyb1ZvtXsQ="; }
-
Best: Fetches a snapshot archive and you get the rev you want.
src = fetchFromGitHub { owner = "NixOS"; repo = "nix"; rev = "1f795f9f44607cc5bec70d1300150bfefcef2aae"; hash = "ha256-7D4m+saJjbSFP5hOwpQq2FGR2rr+psQMTcyb1ZvtXsQ="; }
When fetching from GitHub, commits must always be referenced by their full commit hash. This is because GitHub shares commit hashes among all forks and returns 404 Not Found
when a short commit hash is ambiguous. It already happens for some short, 6-character commit hashes in nixpkgs
.
It is a practical vector for a denial-of-service attack by pushing large amounts of auto generated commits into forks and was already demonstrated against GitHub Actions Beta.
Find the value to put as hash
by running nix-shell -p nix-prefetch-github --run "nix-prefetch-github --rev 1f795f9f44607cc5bec70d1300150bfefcef2aae NixOS nix"
.
Obtaining source hash
Preferred source hash type is sha256. There are several ways to get it.
-
Prefetch URL (with
nix-prefetch-XXX URL
, whereXXX
is one ofurl
,git
,hg
,cvs
,bzr
,svn
). Hash is printed to stdout. -
Prefetch by package source (with
nix-prefetch-url '<nixpkgs>' -A PACKAGE.src
, wherePACKAGE
is package attribute name). Hash is printed to stdout.This works well when you've upgraded existing package version and want to find out new hash, but is useless if package can't be accessed by attribute or package has multiple sources (
.srcs
, architecture-dependent sources, etc). -
Upstream provided hash: use it when upstream provides
sha256
orsha512
(when upstream providesmd5
, don't use it, computesha256
instead).A little nuance is that
nix-prefetch-*
tools produce hash encoded withbase32
, but upstream usually provides hexadecimal (base16
) encoding. Fetchers understand both formats. Nixpkgs does not standardize on any one format.You can convert between formats with nix-hash, for example:
$ nix-hash --type sha256 --to-base32 HASH
-
Extracting hash from local source tarball can be done with
sha256sum
. Usenix-prefetch-url file:///path/to/tarball
if you want base32 hash. -
Fake hash: set the hash to one of
""
lib.fakeHash
lib.fakeSha256
lib.fakeSha512
in the package expression, attempt build and extract correct hash from error messages.
::: {.warning} You must use one of these four fake hashes and not some arbitrarily-chosen hash.
This is last resort method when reconstructing source URL is non-trivial and
nix-prefetch-url -A
isn’t applicable (for example, one ofkodi
dependencies). The easiest way then would be replace hash with a fake one and rebuild. Nix build will fail and error message will contain desired hash.
Obtaining hashes securely
Let's say Man-in-the-Middle (MITM) sits close to your network. Then instead of fetching source you can fetch malware, and instead of source hash you get hash of malware. Here are security considerations for this scenario:
-
http://
URLs are not secure to prefetch hash from; -
hashes from upstream (in method 3) should be obtained via secure protocol;
-
https://
URLs are secure in methods 1, 2, 3; -
https://
URLs are secure in method 5 only if you use one of the listed fake hashes. If you use any other hash,fetchurl
will pass--insecure
tocurl
and may then degrade to HTTP in case of TLS certificate expiration.
Patches
Patches available online should be retrieved using fetchpatch
.
patches = [
(fetchpatch {
name = "fix-check-for-using-shared-freetype-lib.patch";
url = "http://git.ghostscript.com/?p=ghostpdl.git;a=patch;h=8f5d285";
hash = "sha256-uRcxaCjd+WAuGrXOmGfFeu79cUILwkRdBu48mwcBE7g=";
})
];
Otherwise, you can add a .patch
file to the nixpkgs
repository. In the interest of keeping our maintenance burden to a minimum, only patches that are unique to nixpkgs
should be added in this way.
If a patch is available online but does not cleanly apply, it can be modified in some fixed ways by using additional optional arguments for fetchpatch
. Check for details.
patches = [ ./0001-changes.patch ];
If you do need to do create this sort of patch file, one way to do so is with git:
-
Move to the root directory of the source code you're patching.
$ cd the/program/source
-
If a git repository is not already present, create one and stage all of the source files.
$ git init $ git add .
-
Edit some files to make whatever changes need to be included in the patch.
-
Use git to create a diff, and pipe the output to a patch file:
$ git diff -a > nixpkgs/pkgs/the/package/0001-changes.patch
Package tests
Tests are important to ensure quality and make reviews and automatic updates easy.
The following types of tests exists:
- NixOS module tests, which spawn one or more NixOS VMs. They exercise both NixOS modules and the packaged programs used within them. For example, a NixOS module test can start a web server VM running the
nginx
module, and a client VM runningcurl
or a graphicalfirefox
, and test that they can talk to each other and display the correct content. - Nix package tests are a lightweight alternative to NixOS module tests. They should be used to create simple integration tests for packages, but cannot test NixOS services, and some programs with graphical user interfaces may also be difficult to test with them.
- The
checkPhase
of a package, which should execute the unit tests that are included in the source code of a package.
Here in the nixpkgs manual we describe mostly package tests; for module tests head over to the corresponding section in the NixOS manual.
Writing inline package tests
For very simple tests, they can be written inline:
{ …, yq-go }:
buildGoModule rec {
…
passthru.tests = {
simple = runCommand "${pname}-test" {} ''
echo "test: 1" | ${yq-go}/bin/yq eval -j > $out
[ "$(cat $out | tr -d $'\n ')" = '{"test":1}' ]
'';
};
}
Writing larger package tests
This is an example using the phoronix-test-suite
package with the current best practices.
Add the tests in passthru.tests
to the package definition like this:
{ stdenv, lib, fetchurl, callPackage }:
stdenv.mkDerivation {
…
passthru.tests = {
simple-execution = callPackage ./tests.nix { };
};
meta = { … };
}
Create tests.nix
in the package directory:
{ runCommand, phoronix-test-suite }:
let
inherit (phoronix-test-suite) pname version;
in
runCommand "${pname}-tests" { meta.timeout = 60; }
''
# automatic initial setup to prevent interactive questions
${phoronix-test-suite}/bin/phoronix-test-suite enterprise-setup >/dev/null
# get version of installed program and compare with package version
if [[ `${phoronix-test-suite}/bin/phoronix-test-suite version` != *"${version}"* ]]; then
echo "Error: program version does not match package version"
exit 1
fi
# run dummy command
${phoronix-test-suite}/bin/phoronix-test-suite dummy_module.dummy-command >/dev/null
# needed for Nix to register the command as successful
touch $out
''
Running package tests
You can run these tests with:
$ cd path/to/nixpkgs
$ nix-build -A phoronix-test-suite.tests
Examples of package tests
Here are examples of package tests:
- Jasmin compile test
- Lobster compile test
- Spacy annotation test
- Libtorch test
- Multiple tests for nanopb
Linking NixOS module tests to a package
Like package tests as shown above, NixOS module tests can also be linked to a package, so that the tests can be easily run when changing the related package.
For example, assuming we're packaging nginx
, we can link its module test via passthru.tests
:
{ stdenv, lib, nixosTests }:
stdenv.mkDerivation {
...
passthru.tests = {
nginx = nixosTests.nginx;
};
...
}
Import From Derivation
Import From Derivation (IFD) is disallowed in Nixpkgs for performance reasons: Hydra evaluates the entire package set, and sequential builds during evaluation would increase evaluation times to become impractical.
Import From Derivation can be worked around in some cases by committing generated intermediate files to version control and reading those instead.
See also NixOS Wiki: Import From Derivation.