BEAM Languages (Erlang, Elixir & LFE)Introduction
In this document and related Nix expressions, we use the term, BEAM, to describe the environment. BEAM is the name of the Erlang Virtual Machine and, as far as we're concerned, from a packaging perspective, all languages that run on the BEAM are interchangeable. That which varies, like the build system, is transparent to users of any given BEAM package, so we make no distinction.
Structure
All BEAM-related expressions are available via the top-level beam attribute, which includes:
interpreters: a set of compilers running on the BEAM, including multiple Erlang/OTP versions (beam.interpreters.erlangR19, etc), Elixir (beam.interpreters.elixir) and LFE (beam.interpreters.lfe).
packages: a set of package sets, each compiled with a specific Erlang/OTP version, e.g. beam.packages.erlangR19.
The default Erlang compiler, defined by beam.interpreters.erlang, is aliased as erlang. The default BEAM package set is defined by beam.packages.erlang and aliased at the top level as beamPackages.
To create a package set built with a custom Erlang version, use the lambda, beam.packagesWith, which accepts an Erlang/OTP derivation and produces a package set similar to beam.packages.erlang.
Many Erlang/OTP distributions available in beam.interpreters have versions with ODBC and/or Java enabled. For example, there's beam.interpreters.erlangR19_odbc_javac, which corresponds to beam.interpreters.erlangR19.
We also provide the lambda, beam.packages.erlang.callPackage, which simplifies writing BEAM package definitions by injecting all packages from beam.packages.erlang into the top-level context.
Build ToolsRebar3
By default, Rebar3 wants to manage its own dependencies. This is perfectly acceptable in the normal, non-Nix setup, but in the Nix world, it is not. To rectify this, we provide two versions of Rebar3:
rebar3: patched to remove the ability to download anything. When not running it via nix-shell or nix-build, it's probably not going to work as desired.
rebar3-open: the normal, unmodified Rebar3. It should work exactly as would any other version of Rebar3. Any Erlang package should rely on rebar3 instead. See .
Mix & Erlang.mk
Both Mix and Erlang.mk work exactly as expected. There is a bootstrap process that needs to be run for both, however, which is supported by the buildMix and buildErlangMk derivations, respectively.
How to Install BEAM Packages
BEAM packages are not registered at the top level, simply because they are not relevant to the vast majority of Nix users. They are installable using the beam.packages.erlang attribute set (aliased as beamPackages), which points to packages built by the default Erlang/OTP version in Nixpkgs, as defined by beam.interpreters.erlang. To list the available packages in beamPackages, use the following command:
$ nix-env -f "<nixpkgs>" -qaP -A beamPackages
beamPackages.esqlite esqlite-0.2.1
beamPackages.goldrush goldrush-0.1.7
beamPackages.ibrowse ibrowse-4.2.2
beamPackages.jiffy jiffy-0.14.5
beamPackages.lager lager-3.0.2
beamPackages.meck meck-0.8.3
beamPackages.rebar3-pc pc-1.1.0
To install any of those packages into your profile, refer to them by their attribute path (first column):
$ nix-env -f "<nixpkgs>" -iA beamPackages.ibrowse
The attribute path of any BEAM package corresponds to the name of that particular package in Hex or its OTP Application/Release name.
Packaging BEAM ApplicationsErlang ApplicationsRebar3 Packages
The Nix function, buildRebar3, defined in beam.packages.erlang.buildRebar3 and aliased at the top level, can be used to build a derivation that understands how to build a Rebar3 project. For example, we can build hex2nix as follows:
{ stdenv, fetchFromGitHub, buildRebar3, ibrowse, jsx, erlware_commons }:
buildRebar3 rec {
name = "hex2nix";
version = "0.0.1";
src = fetchFromGitHub {
owner = "ericbmerritt";
repo = "hex2nix";
rev = "${version}";
sha256 = "1w7xjidz1l5yjmhlplfx7kphmnpvqm67w99hd2m7kdixwdxq0zqg";
};
beamDeps = [ ibrowse jsx erlware_commons ];
}
Such derivations are callable with beam.packages.erlang.callPackage (see ). To call this package using the normal callPackage, refer to dependency packages via beamPackages, e.g. beamPackages.ibrowse.
Notably, buildRebar3 includes beamDeps, while stdenv.mkDerivation does not. BEAM dependencies added there will be correctly handled by the system.
If a package needs to compile native code via Rebar3's port compilation mechanism, add compilePort = true; to the derivation.
Erlang.mk Packages
Erlang.mk functions similarly to Rebar3, except we use buildErlangMk instead of buildRebar3.
{ buildErlangMk, fetchHex, cowlib, ranch }:
buildErlangMk {
name = "cowboy";
version = "1.0.4";
src = fetchHex {
pkg = "cowboy";
version = "1.0.4";
sha256 = "6a0edee96885fae3a8dd0ac1f333538a42e807db638a9453064ccfdaa6b9fdac";
};
beamDeps = [ cowlib ranch ];
meta = {
description = ''
Small, fast, modular HTTP server written in Erlang
'';
license = stdenv.lib.licenses.isc;
homepage = https://github.com/ninenines/cowboy;
};
}
Mix Packages
Mix functions similarly to Rebar3, except we use buildMix instead of buildRebar3.
{ buildMix, fetchHex, plug, absinthe }:
buildMix {
name = "absinthe_plug";
version = "1.0.0";
src = fetchHex {
pkg = "absinthe_plug";
version = "1.0.0";
sha256 = "08459823fe1fd4f0325a8bf0c937a4520583a5a26d73b193040ab30a1dfc0b33";
};
beamDeps = [ plug absinthe ];
meta = {
description = ''
A plug for Absinthe, an experimental GraphQL toolkit
'';
license = stdenv.lib.licenses.bsd3;
homepage = https://github.com/CargoSense/absinthe_plug;
};
}
Alternatively, we can use buildHex as a shortcut:
{ buildHex, buildMix, plug, absinthe }:
buildHex {
name = "absinthe_plug";
version = "1.0.0";
sha256 = "08459823fe1fd4f0325a8bf0c937a4520583a5a26d73b193040ab30a1dfc0b33";
builder = buildMix;
beamDeps = [ plug absinthe ];
meta = {
description = ''
A plug for Absinthe, an experimental GraphQL toolkit
'';
license = stdenv.lib.licenses.bsd3;
homepage = https://github.com/CargoSense/absinthe_plug;
};
}
How to DevelopAccessing an Environment
Often, we simply want to access a valid environment that contains a specific package and its dependencies. We can accomplish that with the env attribute of a derivation. For example, let's say we want to access an Erlang REPL with ibrowse loaded up. We could do the following:
$ nix-shell -A beamPackages.ibrowse.env --run "erl"Erlang/OTP 18 [erts-7.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V7.0 (abort with ^G)1> m(ibrowse).Module: ibrowse
MD5: 3b3e0137d0cbb28070146978a3392945
Compiled: January 10 2016, 23:34
Object file: /nix/store/g1rlf65rdgjs4abbyj4grp37ry7ywivj-ibrowse-4.2.2/lib/erlang/lib/ibrowse-4.2.2/ebin/ibrowse.beam
Compiler options: [{outdir,"/tmp/nix-build-ibrowse-4.2.2.drv-0/hex-source-ibrowse-4.2.2/_build/default/lib/ibrowse/ebin"},
debug_info,debug_info,nowarn_shadow_vars,
warn_unused_import,warn_unused_vars,warnings_as_errors,
{i,"/tmp/nix-build-ibrowse-4.2.2.drv-0/hex-source-ibrowse-4.2.2/_build/default/lib/ibrowse/include"}]
Exports:
add_config/1 send_req_direct/7
all_trace_off/0 set_dest/3
code_change/3 set_max_attempts/3
get_config_value/1 set_max_pipeline_size/3
get_config_value/2 set_max_sessions/3
get_metrics/0 show_dest_status/0
get_metrics/2 show_dest_status/1
handle_call/3 show_dest_status/2
handle_cast/2 spawn_link_worker_process/1
handle_info/2 spawn_link_worker_process/2
init/1 spawn_worker_process/1
module_info/0 spawn_worker_process/2
module_info/1 start/0
rescan_config/0 start_link/0
rescan_config/1 stop/0
send_req/3 stop_worker_process/1
send_req/4 stream_close/1
send_req/5 stream_next/1
send_req/6 terminate/2
send_req_direct/4 trace_off/0
send_req_direct/5 trace_off/2
send_req_direct/6 trace_on/0
trace_on/2
ok2>
Notice the -A beamPackages.ibrowse.env. That is the key to this functionality.
Creating a Shell
Getting access to an environment often isn't enough to do real development. Usually, we need to create a shell.nix file and do our development inside of the environment specified therein. This file looks a lot like the packaging described above, except that src points to the project root and we call the package directly.
{ pkgs ? import "<nixpkgs"> {} }:
with pkgs;
let
f = { buildRebar3, ibrowse, jsx, erlware_commons }:
buildRebar3 {
name = "hex2nix";
version = "0.1.0";
src = ./.;
beamDeps = [ ibrowse jsx erlware_commons ];
};
drv = beamPackages.callPackage f {};
in
drv
Building in a Shell (for Mix Projects)
We can leverage the support of the derivation, irrespective of the build derivation, by calling the commands themselves.
# =============================================================================
# Variables
# =============================================================================
NIX_TEMPLATES := "$(CURDIR)/nix-templates"
TARGET := "$(PREFIX)"
PROJECT_NAME := thorndyke
NIXPKGS=../nixpkgs
NIX_PATH=nixpkgs=$(NIXPKGS)
NIX_SHELL=nix-shell -I "$(NIX_PATH)" --pure
# =============================================================================
# Rules
# =============================================================================
.PHONY= all test clean repl shell build test analyze configure install \
test-nix-install publish plt analyze
all: build
guard-%:
@ if [ "${${*}}" == "" ]; then \
echo "Environment variable $* not set"; \
exit 1; \
fi
clean:
rm -rf _build
rm -rf .cache
repl:
$(NIX_SHELL) --run "iex -pa './_build/prod/lib/*/ebin'"
shell:
$(NIX_SHELL)
configure:
$(NIX_SHELL) --command 'eval "$$configurePhase"'
build: configure
$(NIX_SHELL) --command 'eval "$$buildPhase"'
install:
$(NIX_SHELL) --command 'eval "$$installPhase"'
test:
$(NIX_SHELL) --command 'mix test --no-start --no-deps-check'
plt:
$(NIX_SHELL) --run "mix dialyzer.plt --no-deps-check"
analyze: build plt
$(NIX_SHELL) --run "mix dialyzer --no-compile"
Using a shell.nix as described (see ) should just work. Aside from test, plt, and analyze, the Make targets work just fine for all of the build derivations.
Generating Packages from Hex with hex2nix
Updating the Hex package set requires hex2nix. Given the path to the Erlang modules (usually pkgs/development/erlang-modules), it will dump a file called hex-packages.nix, containing all the packages that use a recognized build system in Hex. It can't be determined, however, whether every package is buildable.
To make life easier for our users, try to build every Hex package and remove those that fail. To do that, simply run the following command in the root of your nixpkgs repository:
$ nix-build -A beamPackages
That will attempt to build every package in beamPackages. Then manually remove those that fail. Hopefully, someone will improve hex2nix in the future to automate the process.