# Usage: use_nix [...] # # Load environment variables from `nix-shell`. # If you have a `default.nix` or `shell.nix` one of these will be used and # the derived environment will be stored at ./.direnv/env- # and symlink to it will be created at ./.direnv/default. # Dependencies are added to the GC roots, such that the environment remains persistent. # # The resulting environment is cached for better performance. # # To trigger switch to a different environment: # `rm -f .direnv/default` # # To derive a new environment: # `rm -rf .direnv/env-$(md5sum {shell,default}.nix 2> /dev/null | cut -c -32)` # # To remove cache: # `rm -f .direnv/dump-*` # # To remove all environments: # `rm -rf .direnv/env-*` # # To remove only old environments: # `find .direnv -name 'env-*' -and -not -name `readlink .direnv/default` -exec rm -rf {} +` # set -eo pipefail use_nix() { # define all local variables local shell f env_hash dir default wd drv dump path_backup local files_to_watch=() declare opt declare OPTARG declare OPTIND while getopts ":s:w:" opt; do case "${opt}" in s) shell="${OPTARG}" files_to_watch=("${files_to_watch[@]}" "${shell}") ;; w) files_to_watch=("${files_to_watch[@]}" "${OPTARG}") ;; :) >&2 echo "Invalid option: $OPTARG requires an argument" ;; \?) >&2 echo "Invalid option: $OPTARG" exit 1 ;; esac done shift $((OPTIND -1)) if [[ -z "${shell}" ]]; then >&2 echo "ERR: no shell was given" exit 1 fi for f in "${files_to_watch[@]}"; do if ! [[ -f "${f}" ]]; then >&2 echo "cannot watch file ${f} because it does not exist" exit 1 fi done # compute the hash of all the files that makes up the development environment env_hash="$(hashContents "${files_to_watch[@]}")" dir="$(direnv_layout_dir)" default="${dir}/default" if [[ ! -L "${default}" ]] || [[ ! -d $(readlink "${default}") ]]; then wd="${dir}/env-${env_hash}" mkdir -p "${wd}" drv="${wd}/env.drv" if [[ ! -f "${drv}" ]]; then log_status "use nix: deriving new environment" IN_NIX_SHELL=1 nix-instantiate --add-root "${drv}" --indirect "${shell}" > /dev/null nix-store -r $(nix-store --query --references "${drv}") --add-root "${wd}/dep" --indirect > /dev/null fi rm -f "${default}" ln -s $(basename "${wd}") "${default}" fi drv=$(readlink "${default}/env.drv") dump="${dir}/dump-$(hashFile ".envrc")-$(hashFile ${drv})" if [[ ! -f "${dump}" ]] || [[ "${XDG_CONFIG_DIR}/direnv/direnvrc" -nt "${dump}" ]]; then log_status "use nix: updating cache" old=$(find "${dir}" -name 'dump-*') nix-shell --pure "${drv}" --show-trace --run "$(join_args "$direnv" dump bash)" > "${dump}" rm -f ${old} fi # evaluate the dump created by nix-shell earlier, but have to merge the PATH # with the current PATH # NOTE: we eval the dump here as opposed to direnv_load it because we don't # want to persist environment variables coming from the shell at the time of # the dump. See https://github.com/direnv/direnv/issues/405 for context. path_backup="${PATH}" eval $(cat "${dump}") export PATH="${PATH}:${path_backup}" for f in "${files_to_watch[@]}"; do watch_file "${f}" done } hashContents() { if has md5sum; then cat "${@}" | md5sum | cut -c -32 elif has md5; then cat "${@}" | md5 -q fi } hashFile() { if has md5sum; then md5sum "${@}" | cut -c -32 elif has md5; then md5 -q "${@}" fi } fail() { log_error "${@}" exit 1 } validateVersion() { local version="$("${direnv}" version)" local major="$(echo "${version}" | cut -d. -f1)" local minor="$(echo "${version}" | cut -d. -f2)" local patch="$(echo "${version}" | cut -d. -f3)" if [[ "${major}" -gt 2 ]]; then return 0; fi if [[ "${major}" -eq 2 ]] && [[ "${minor}" -gt 18 ]]; then return 0; fi if [[ "${major}" -eq 2 ]] && [[ "${minor}" -eq 18 ]] && [[ "${patch}" -ge 2 ]]; then return 0; fi return 1 } if ! validateVersion; then echo "This .envrc requires direnv version 2.18.2 or above." exit 1 fi use_nix -s shell.nix