direnv

Posted on Sat 04 May 2019 in linux

What is direnv?

direnv is a simple environment switcher for the shell that allows to load/unload environment variables depending on the directory the user is changing into. It supports various shells, including bash, zsh, tcsh, fish shell and elvish.

In the following, its basic functionality will be explained using the combination of bash and python.

Installation

Simply install direnv from the Debian repository:

sudo apt install direnv

To activate direnv for the bash, simply add the following line to the ~/.bashrc file:

eval "$(direnv hook bash)"

Make sure to reload the file again:

source ~/.bashrc

Basic Idea

In each project directory, which should be managed by direnv, place a corresponding .envrc file with the corresponding configuration.

For example, a few environment variables should be set, when entering the foo/ directory. Thus, a .envrc file in the foo/ directory is placed with the following content:

export PATH=$PWD/example/bin:$PATH
export TMPDIR=$PWD/tmp

When saving the .envrc file, the following warning will appear:

direnv: error .envrc is blocked. Run direnv allow to approve its content.

This is simply a security mechanism that avoids the automatic loading of malicious content, e.g. when checking out a foreign git repository.

To activate the .envrc file simply type:

direnv allow

After allowing the access to the file, direnv will automatically set the previously defined environment variables. The same will happen each time you will enter the foo/ directory. In contrast, when leaving the directory, the environment variables will be unset again.

Python

In the next step, the more sophisticated case of setting a Python virtual environment is discussed. There are many ways to setup a virtual environment for python, each requiring a special direnv setup. Here, the built-in venv module is used that does not need any additional software.

To this end, add the following new layout to the general direnv user configuration file ~/.direnvrc:

realpath() {
    [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
layout_python-venv() {
    local python=${1:-python3}
    [[ $# -gt 0 ]] && shift
    unset PYTHONHOME
    if [[ -n $VIRTUAL_ENV ]]; then
        VIRTUAL_ENV=$(realpath "${VIRTUAL_ENV}")
    else
        local python_version
        python_version=$("$python" -c "import platform; print(platform.python_version())")
        if [[ -z $python_version ]]; then
            log_error "Could not detect Python version"
            return 1
        fi
        VIRTUAL_ENV=$PWD/.direnv/python-venv-$python_version
    fi
    export VIRTUAL_ENV
    if [[ ! -d $VIRTUAL_ENV ]]; then
        log_status "no venv found; creating $VIRTUAL_ENV"
        "$python" -m venv "$VIRTUAL_ENV"
    fi
    PATH_add "$VIRTUAL_ENV/bin"
}

Subsequently, add the python-venv new layout to the .envrc file of the Python project:

export VIRTUAL_ENV=env
layout python-venv

After allowing the access to the .envrc file, direnv will automatically change to the virtual environment in env/ (or create it, if it is not existing yet).

In contrast, to normally changing into the virtual environment there is no prompt indicator (env) that is highlighting the change. To enable this behavior for direnv, add the following to ~/.bashrc:

show_virtual_env() {
  if [[ -n "$VIRTUAL_ENV" && -n "$DIRENV_DIR" ]]; then
    echo "($(basename $VIRTUAL_ENV))"
  fi
}
export -f show_virtual_env
PS1='$(show_virtual_env)'$PS1

Then, reload the file again:

source ~/.bashrc

After that, the familiar (env) prompt will appear, when changing into virtual environments with direnv.

For other ways to make use of direnv in combination with Python see the official wiki. Of course, similar setups can be created for other programming languages.