Declaring the Project
Machine Setup
You will need to install Nix on your computer. Visit nixos.org/nix/ to do so.
The next thing you will need to create is a project. A project consits of
three files: project.nix
, default.nix
and shell.nix
. So, let's go ahead
and set up a project.nix
.
Project Setup
There are two main ways of setting up a project, flakes and everything else. Here we will cover flakes and fetchgit but any other method of fetching files will of course also work.
1 Using flakes
We need two files, flake.nix
and project.nix
,
flakes are used to handle dependencies and set
up entry points and the project file declares the components, shells and other
attributes in the project.
# flake.nix
{
description = "Project description.";
inputs = {
nedryland.url = github:goodbyekansas/nedryland;
pkgs.url = github:NixOS/nixpkgs/nixos-22.11;
flake-utils.url = github:numtide/flake-utils;
};
outputs = { nedryland, pkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs' = pkgs.legacyPackages.${system};
project = (import ./project.nix {
pkgs = pkgs';
nedryland = nedryland.lib.${system} { pkgs = pkgs'; };
});
in
{
packages = project.matrix // {
# This make `$nix build` (without arguments)
# result in a linkfarm of all components.
default = project.all;
};
devShells = project.shells;
});
}
# project.nix
{ nedryland, pkgs }:
(nedryland { inherit pkgs; }).mkProject {
name = "project-name";
components = { callFile }: {
component1 = callFile ./path/to/component.nix { };
# more components here
};
}
After adding these files to the git index, nix flakes will recognize them. Then
to build component1
just run nix build .#component1
. To get a development
shell run nix develop .#component1
. To build an inspectable file tree of the
project run just nix build
.
2 With fetchGit
We will use three files, one for creating the project, one for building and one for opening development shells.
First, you will need to import Nedryland. In this example we use fetchGit, and
since Nedryland needs a set of packages we also fetch nixpkgs and send it to
Nedryland. The we use mkProject
to create a project with a component.
# project.nix
let
pkgs = import (
builtins.fetchGit {
name = "nixpkgs";
url = ;
} { });
nedryland = import (
builtins.fetchGit {
name = "nedryland";
url = "git@github.com:goodbyekansas/nedryland.git";
ref = "refs/tags/8.2.1";
} { inherit pkgs; });
in
nedryland.mkProject {
name = "project-name";
components = { callFile }: {
component1 = callFile ./path/to/component.nix { };
# more components here
};
}
To be able to build more ergonomically, we create a default.nix and expose the project's matrix.
# default.nix
(import ./project.nix).matrix
Now nix build -f default.nix component1
(or nix-build -A component1
for the
old output) will build component1.
To add development shells we add the shell.nix
file:
# shell.nix
(import ./project.nix).shells
Then nix-shell -A component1
will launch a shell for working on component1.
mkProject
mkProject is the main function to create a Nedryland project, the function
accepts a name for the project and a default config file to use for project
configuration, and some other inputs explained below. In the
project
set returned from the above call to mkProject
, there will be more
utility functions that can be called to set up the project further.
Creating the Matrix
A project consists of components, and components have targets, which
are you build artifacts. To create components use the components
argument in
mkProject
. This argument (and all other arguments to mkProject
) can be a function and
the function can request arguments from base
(including base extension). An essential
argument to request is callFile
which will call a component with a set of packages and
components. This function accepts either a nix file and a set of overrides. It works
similar to callPackage
in nixpkgs. There is also callFunction
which does the same but
without importing a path and instead directly calling a function.
mkProject {
components = { callFile }: {
example-component = callFile ./example-component/example-component.nix {};
};
# ...
}
deployment
is described here and is simply a set with your
different deployment targets, no magic there.
extraShells
is any standard nix shells (as described
here) that you wish to have
exposed by the matrix.
lib
is a conventional key for any nix functions that you wish to expose to
projects using your project. These will simply be exposed on your project
instance as .lib
.
More details on the matrix can be found under concepts.
Configuration
A project might need configuration for example for server addresses, deploy
credentails or really anything. Nedryland contains helpers for this that can be
accessed by setting the configFile
argument to mkProject
. This file is a
TOML file that may or may not exist. If it
exists, Nedryland will parse it and make it available to components.
Environment variable overrides
Environment variables can also be used to override parts of, or the whole
configuration. To override all the content of the config, use the environment
var PROJECT_NAME_config
, where PROJECT_NAME
is the name of your project.
To override specific settings, use the environment var
PROJECT_NAME_key_setting_name
where PROJECT_NAME
is once again the name of
your project, key
is the key used below in base.parseConfig
and
setting_name
is the name of the setting used in structure
in the call to
base.parseConfig
.
Using Config in Components
When declaring a component, you can declare a dependency on some configuration from the config file like this
config = base.parseConfig {
key = "my-component";
structure = {
database = {
url = "";
user = "";
password = "";
};
};
};
The assignments set the default values in case the requested keys are not present in the configuration. In this example, the corresponding TOML could look like
[my-component]
database = { url = "tcp://something", user = "user", password = "pass" }
Then, in your component declaration, you can use config
any way you want.
Extensions
Nedryland can be extended by passing in extensions when creating the project.
This is done by passing a set in a list as the baseExtenstions
argument to
mkProject
.
This might look something like
# project.nix
nedryland.mkProject {
name = "my-project";
configFile = ./my-project.toml;
baseExtensions = [
./nedryland-extensions/stuff.nix
];
}
and inside ./nedryland-extensions/stuff.nix
you declare the extension by
providing a set that will be merged with base
from Nedryland.
# nedryland-extensions/stuff.nix
{ base, pkgs }: # all extensions are functions called with base and pkgs
{
mkMyAwesomeComponentType1 = args: {
# ...
};
mkMyAwesomeComponentType2 = args: {
# ...
};
utilityFunction = arg1: arg2:
true;
# ...
}
Base will now contain these functions and can be called with for example
base.utilityFunction
. Note that base is also an input to the extension
function, this is the base before the extension is applied. And since extensions
are applied in the order they are declared in baseExtesions
in mkProject
,
extensions can use functions defined in extensions earlier in the list.
Using extensions from another repo
You can also configure Nedryland to use base extensions from other repositories
by importing that repository. This will give you access to the
matrix of that project and it can also be sent in as
projectDependencies
when declaring your project in Nedryland. Doing so will
give you access to all base extensions from that project.
Example
# ...
otherProject = import path/to/other/project/ { };
project = nedryland.mkProject {
name = "my-project";
configFile = ./my-project.toml;
baseExtensions = [
(import ./nedryland-extensions/my-extension.nix)
];
projectDependencies = [ otherProject ];
};
This will give you access to all baseExtensions declared in otherProject
.