diff --git a/.envrc b/.envrc index 1d953f4..2bfa87c 100644 --- a/.envrc +++ b/.envrc @@ -1 +1,153 @@ -use nix +# 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 diff --git a/.gitignore b/.gitignore index 120c4f5..f5d01ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ _* src/archive.org +.direnv/