Building MozJS with Nix

Nix is a package manager and build system for Linux and macOS. It is designed to be able to self-host an operating system (called NixOS) and to be able to run alongside an existing UNIX system (such as Ubuntu or macOS).

= Getting started =

Each step in this tutorial can be found in the mozjs-example GitHub repository. You can follow along with the commits in the repository, since each one corresponds to a step here.

Start out by installing the nix package manager.

= Set up a crate =

We'll start] by putting some files and Cargo dependencies. Why are we even going to bother with Nix? Because this crate depends on SpiderMonkey (that's MozJS, or the Firefox JavaScript engine), and that has very specific dependencies on its C compiler and available libraries.

You can "cargo build" if you want, but unless you have libclang lying around, it won't build.

As is normal for Rust projects, this will generate a Cargo.toml and a source file]. The example code has added some code, plus a couple of dependencies.

# Cargo.toml [package] name = "mozjs-example" version = "0.0.1" authors = ["Michael Howell "] edition = "2018"

[dependencies] smol = "0.1.5" mozjs = { git = "https://github.com/servo/rust-mozjs" }

= Set up niv =

This step is going to depend on niv, the nix version manager and the tool we use to handle auto-updating dependencies.

$ nix-env -i niv

Once that's installed, we'll use niv to generate a dependency file for the project repository itself. By making the nix repository a fixed constant, we can promise that as long as you have a spec-compliant nix interpreter, you'll be able to build the app itself.

$ niv init

The result will be two files: nix/sources.json and nix/sources.nix. The nix file is a script for importing the json file. The nix file is identical across almost all nix projects, since its purpose is just to bootstrap it, while the json file (since it's json) is easy to automatically update.

= Set up crate2nix and nix-shell =

$ niv update nixpkgs -o nitriddle -b patch-1 -r nixpkgs # doesn't yet work with stable, so upgrade to nightly nixpkgs $ niv add kolloch/crate2nix

The result of running the niv command will be a new couple of lines in the dependencies file (and also switch to a newer version of nixpkgs itself, because the Rust compiler in the old version of nixpkgs is really, really old).

To really use it, though, we'll want to create a nix-shell file. Let's do a couple of things to make everything easier for us.

# nix/dependencies.nix
 * 1) This is a file we'll write ourselves to make it easier to ensure that configurations stay the same between each environment, both build and dev.

let sources = import ./sources.nix; pkgs = import sources.nixpkgs { }; in { # This will import our pinned instance of the nixpkgs upstream repository. inherit pkgs; # This will contain all of the CLI tools that our shell uses. devDeps = [ (import sources.crate2nix {inherit pkgs;}) pkgs.cargo pkgs.llvm pkgs.autoconf213 pkgs.python3 pkgs.python2 pkgs.which pkgs.perl ]; # Pinned versions of other tools. libclang = pkgs.llvmPackages.libclang; }

Then let's write the shell.nix file, which actually defines the shell proper.

# shell.nix

let # Import dependencies (this preamble will be very common) dependencies = import ./nix/dependencies.nix; pkgs = dependencies.pkgs; in # A "shell", in nix, defines the environment variables and other dependencies # for doing development. The "clangStdenv" line makes it use LLVM's C compiler # instead of the default GNU C Compiler, because that's what SpiderMonkey # needs. pkgs.mkShell.override { stdenv = pkgs.clangStdenv; } { # This name is pretty much arbitrary. name = "mozjs-example"; # This makes it pull in crate2nix and other tools. buildInputs = dependencies.devDeps; # This is needed to build SpiderMonkey. LIBCLANG_PATH = "${dependencies.libclang.lib}/lib"; }

Now, if you run nix-shell, it should pull in crate2nix and let you use it.

= Generate Cargo.nix and write default.nix =

First, run nix-shell, then use cargo to generate a lockfile, then run crate2nix:

$ nix-shell nix$ cargo update nix$ crate2nix generate

And then, finally, let's write our main build script.

# default.nix {release ? true}: let dependencies = import ./nix/dependencies.nix; pkgs = dependencies.pkgs; # Import Cargo.nix with supplied arguments. cargoNix = pkgs.callPackage ./Cargo.nix { # Pass the "release" flag on. inherit release; # Tell it to build with clang (because mozjs needs it). stdenv = pkgs.clangStdenv; }; mozjs-example = cargoNix.rootCrate.build.override { crateOverrides = pkgs.defaultCrateOverrides // { mozjs = {src, ...}: { # Tell it to pull in all our specified deps (like autoconf). buildInputs = dependencies.devDeps; };     mozjs_sys = {src, ...}: { # Tell it to pull in all our specified deps (like autoconf). buildInputs = dependencies.devDeps; };   };  }; in mozjs-example
 * 1) Allows you to specify if this is a release build on the CLI

= Using Nix and crate2nix as the build system for a Rust package =

This is what we actually need to build it now. To actually build and run it using nix, run:

nix$ nix-build nix$ TODO