Skip to content

Getting started using NixOS and Nix to manage dev, build, and runtime environments

Notifications You must be signed in to change notification settings

rickhull/hello-nix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Intro

This is my revised version of the instructions here: https://christine.website/blog/how-i-start-nix-2020-03-08

Christine's workflow works with any Nix install, whereas these instructions assume we're running on NixOS, using the power of Nix to maintain system state.

Rationale

Use direnv, lorri, and niv to craft a shell environment optimized for Nix workflows, on a per-directory basis.

  • Direnv maintains a project-specific shell environment, triggered by entering the project directory.
  • Lorri integrates direnv with Nix workflows, maintaining some state to avoid costly unnecessary rebuilds.
  • Niv helps manage dependencies in the Nix realm.

Install system tools

Use NixOS packages for direnv, lorri, and niv, installed at the system level. Edit /etc/nixos/configuration.nix:

  # /etc/nixos/configuration.nix
  # ...
  environment.systemPackages = with pkgs; [
    direnv
    niv
  ];
  services.lorri.enable = true;
  # ...

The lorri daemon should be running after nixos-rebuild switch, but there is currently a known issue where systemctl start lorri may be needed after the install.

Setup direnv, lorri, and niv

Add the direnv shell hook, e.g. in ~/.bashrc for bash shells:

eval "$(direnv hook bash)"`

Create a project directory and do some initialization:

$ mkdir -p hello
$ cd hello
$ lorri init # creates shell.nix and .envrc
$ niv init # creates nix/sources.json and nix/sources.nix

Direnv works via the presence of .envrc, as created by lorri init. Changing the current dir to a project dir triggers direnv behavior. However, direnv is blocked from operating unless it is specifically allowed. You will see a message like:

$ cd .
direnv: error /path/to/.envrc is blocked. Run `direnv allow` to approve its content

Run direnv allow. Now direnv should give positive output when changing to a project dir.

Update project environment

Provide a package from nixpkgs

Add the hello package (from nixpkgs) to our project environment by editing the shell.nix created by lorri init:

# shell.nix
let
  sources = import ./nix/sources.nix;
  pkgs = import sources.nixpkgs {};
in
pkgs.mkShell {
  buildInputs = [
    pkgs.hello
  ];
}

Here, we pin the source packages (specified in sources.nix and sources.json), rather than depending on the external state of nixpkgs. With lorri running as a service, you should now be able to run hello:

$ hello
Hello, world!

Add environment variable

Add HELLO to shell.nix:

# shell.nix
let
  sources = import ./nix/sources.nix;
  pkgs = import sources.nixpkgs {};
in
pkgs.mkShell {
  buildInputs = [
    pkgs.hello
  ];
  
  # Environment variables
  HELLO="world";
}

Whenever direnv notices changes, lorri will rebuild what is necessary in the background. Note that this may take some time before your changes are apparent. You will see some direnv output coming from the lorri service when the environment is updated.

$ echo $HELLO
world

OK, now we have direnv, lorri, and niv working for us.

Create Rust project

Install Rust

Let's make a demo project using Rust. Since we are managing our dependencies with niv, make niv aware of Rust packages directly from mozilla (via github):

$ niv add mozilla/nixpkgs-mozilla`

Create nix/rust.nix:

# nix/rust.nix
{ sources ? import ./sources.nix }:

let
  pkgs =
    import sources.nixpkgs {
      overlays = [ (import sources.nixpkgs-mozilla) ];
    };
  channel = "nightly";
  date = "2020-06-10";
  targets = [ ];
  chan = pkgs.rustChannelOfTargets channel date targets;
in chan

Reference rust.nix in shell.nix so that lorri will pick up the changes:

# shell.nix
let
  sources = import ./nix/sources.nix;
  rust = import ./nix/rust.nix { inherit sources; };
  pkgs = import sources.nixpkgs { };
in
pkgs.mkShell {
  buildInputs = [
    rust
  ];
}

lorri will start building in the background. lorri shell will bring this activity to the foreground if you want to see it. It will take a few minutes before your new environment is ready.

$ rustc --version
rustc 1.46.0-nightly (feb3536eb 2020-06-09)

Confirmed! Create a new Rust project:

$ cargo init --vcs git .

This creates src/main.rs and Cargo.toml. It will also initiate a git repo. Build the default hello world program:

$ cargo build

This will build src/main.rs, producing target/debug/$name_of_proj_dir. Try it:

$ target/debug/hello
Hello, world!

Add HTTP functionality

Let's serve some HTTP with Rocket. Add Rocket as a dependency to Cargo.toml:

# Cargo.toml
[dependencies]
rocket = "0.4.3"
$ cargo build

This will download all dependencies and precompile Rocket. Now let's make a dumb HTTP server. Edit src/main.rs:

# src/main.rs
#![feature(proc_macro_hygiene, decl_macro)] // language features needed by Rocket

// Import the rocket macros
#[macro_use]
extern crate rocket;

// Create route / that returns "Hello, world!"
#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

fn main() {
    rocket::ignite().mount("/", routes![index]).launch();
}
$ cargo build

This will create a binary at target/debug/hello

$ target/debug/hello &
$ curl http://localhost:8000
Hello world!
$ fg # (control-c to kill the server)

Create Nix package

Use naersk for packaging

$ niv add nmattia/naersk

Create hello.nix:

# hello.nix
# import niv sources and the pinned nixpkgs
{ sources ? import ./nix/sources.nix, pkgs ? import sources.nixpkgs { }}:
let
  # import rust compiler
  rust = import ./nix/rust.nix { inherit sources; };
  
  # configure naersk to use our pinned rust compiler
  naersk = pkgs.callPackage sources.naersk {
    rustc = rust;
    cargo = rust;
  };
  
  # tell nix-build to ignore the `target` directory
  src = builtins.filterSource
    (path: type: type != "directory" || builtins.baseNameOf path != "target")
    ./.;
in naersk.buildPackage {
  inherit src;
  remapPathPrefix =
    true; # remove nix store references for a smaller output package
}

Build it:

$ nix-build hello.nix

Run it:

$ result/bin/hello

About

Getting started using NixOS and Nix to manage dev, build, and runtime environments

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published