Developing with Elixir requires a fair amount of configuration.
You need:
- Erlang
- Elixir
- Hex package manager
And usually you want all these to be locked at a specific version.
There are several solutions out there, the most popular probably being https://github.com/asdf-vm/asdf
but for Nix fans, how do we setup a nix-shell
and do this the “nix way”?
The Nix community seems to reccomend to nixify your projects, examples exist for
- Ruby https://github.com/nix-community/bundix
- Node https://nixos.wiki/wiki/Node.js
- Elixir https://discourse.nixos.org/t/announcing-mixnix-build-elixir-projects-with-nix/2444
I’ve been using a different pattern with most of my projects to achieve this, and it works for both Ruby, Elixir and many other languages.
The trick is find the env variables that allow us to “isolate” locally the dependency installation, in our example these are MIX_HOME
and HEX_HOME
.
By setting these two variables to the local directory within the nix-shell
we allow mix
to install the packages locally instead of trying to add them to the nix-store
path (/nix...
) that is readonly.
Summing it up
Here’s the shell.nix
file that I use in my elixir projects
with import <nixpkgs> {};
let
# define packages to install with special handling for OSX
basePackages = [
gnumake
gcc
readline
openssl
zlib
libxml2
curl
libiconv
elixir_1_9
glibcLocales
nodejs-12_x
yarn
postgresql
];
inputs = basePackages
++ lib.optional stdenv.isLinux inotify-tools
++ lib.optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [
CoreFoundation
CoreServices
]);
# define shell startup command
hooks = ''
# this allows mix to work on the local directory
mkdir -p .nix-mix
mkdir -p .nix-hex
export MIX_HOME=$PWD/.nix-mix
export HEX_HOME=$PWD/.nix-hex
export PATH=$MIX_HOME/bin:$PATH
export PATH=$HEX_HOME/bin:$PATH
export LANG=en_US.UTF-8
export ERL_AFLAGS="-kernel shell_history enabled"
'';
in mkShell {
buildInputs = inputs;
shellHook = hooks;
}
save this in your project directory as shell.nix
then type nix-shell
and you will be in a bash
shell that has mix
and elixir
ready to be used!
Bonus: Add gigalixir support
https://www.gigalixir.com is one of the simplest ways to deploy Elixir applications. Similarly to Heroku, you subscribe, create an account, install the cli and you are off to the races.
Jan 2022 Update
Gigalixir is now on nixpkgs!
If you are on a recent version of nixpkgs (I tested 21.11) all you need to do is add gigalixir
to the list of packages above.
Note: the package is currently broken in nixpkgs-unstable
, afterall it is called “unstable” :)
Old install process
The problem is that our nix-shell
does not allow us to run pip install gigalixir
and install the gigalixir
command line utility.
Setting up python in nix requires a little more configuration.
First we need to add Python to our nix-shell
and ensure that our python install has the pip
package manager, a detailed explaination can be found here https://nixos.wiki/wiki/Python.
In short you can do something like this
my-python-packages = python-packages: with python-packages; [
pip
setuptools
];
python-with-my-packages = pkgs.python3.withPackages my-python-packages;
Then, like we did for Elixir, we have to force Python to
install its stuff locally, we do this by setting PIP_PREFIX
and PYTHONPATH
;
alias pip="PIP_PREFIX='$(pwd)/_build/pip_packages' \pip"
export PYTHONPATH="$(pwd)/_build/pip_packages/lib/python3.7/site-packages:$PYTHONPATH"
The resulting shell.nix
file is then as follows
with import <nixpkgs> {};
let
my-python-packages = python-packages: with python-packages; [
pip
setuptools
];
python-with-my-packages = pkgs.python3.withPackages my-python-packages;
# define packages to install with special handling for OSX
basePackages = [
gnumake
gcc
readline
openssl
zlib
libxml2
curl
libiconv
elixir_1_9
glibcLocales
nodejs-12_x
yarn
postgresql
inotify-tools
python-with-my-packages
];
inputs = basePackages
++ lib.optional stdenv.isLinux inotify-tools
++ lib.optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [
CoreFoundation
CoreServices
]);
# define shell startup command
hooks = ''
export PS1='\n\[\033[1;32m\][nix-shell:\w]($(git rev-parse --abbrev-ref HEAD))\$\[\033[0m\] '
# this allows python to work locally
alias pip="PIP_PREFIX='$(pwd)/_build/pip_packages' \pip"
export PYTHONPATH="$(pwd)/_build/pip_packages/lib/python3.7/site-packages:$PYTHONPATH"
unset SOURCE_DATE_EPOCH
# this allows mix to work on the local directory
mkdir -p .nix-mix
mkdir -p .nix-hex
export MIX_HOME=$PWD/.nix-mix
export HEX_HOME=$PWD/.nix-hex
export PATH=$MIX_HOME/bin:$PATH
export PATH=$HEX_HOME/bin:$PATH
export LANG=en_US.UTF-8
export PATH=$PATH:$(pwd)/_build/pip_packages/bin
export ERL_AFLAGS="-kernel shell_history enabled"
'';
in mkShell {
buildInputs = inputs;
shellHook = hooks;
}
At this point we can type nix-shell
and then pip install gigalixir
and we are ready to go!
Notes:
The full code can be found here in this gist
After publishing this post I found this one that presents a simpler approach and with a shorter shell configuration, I updated my notes above to use mkShell
and a nicer syntax that uses lib.optionals
to load the MacOS only packages
Another good resource I only found today is here