Skip to content

Commit

Permalink
Merge pull request #1651 from cachix/direnv-feats
Browse files Browse the repository at this point in the history
direnv: create upgrade path for direnvrc and fix more bugs
  • Loading branch information
domenkozar authored Dec 30, 2024
2 parents 36df08e + 042a95f commit 1cd2afa
Show file tree
Hide file tree
Showing 15 changed files with 261 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .envrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ set -euo pipefail
nix build --print-out-paths --accept-flake-config || echo "nix build failed, using previous build"
PATH_add "result/bin"

# External users should use `source_url` to load this file
# External users should eval `devenv direnvrc` or use `source_url` to load this file
source_env ./direnvrc

use devenv
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions devenv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ tracing-subscriber.workspace = true
which.workspace = true
whoami.workspace = true
xdg.workspace = true
once_cell = "1.20.2"
2 changes: 1 addition & 1 deletion devenv/init/.envrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export DIRENV_WARN_TIMEOUT=20s

source_url "https://raw.githubusercontent.com/cachix/devenv/82c0147677e510b247d8b9165c54f73d32dfd899/direnvrc" "sha256-7u4iDd1nZpxL4tCzmPG0dQgC5V+/44Ba+tHkPob1v2k="
eval "$(devenv direnvrc)"

use devenv
9 changes: 7 additions & 2 deletions devenv/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ pub enum Commands {
Repl {},

#[command(
about = "Deletes previous shell generations. See http://devenv.sh/garbage-collection"
about = "Deletes previous shell generations. See https://devenv.sh/garbage-collection"
)]
Gc {},

Expand All @@ -274,8 +274,13 @@ pub enum Commands {
attributes: Vec<String>,
},

#[command(
about = "Print a direnvrc that adds devenv support to direnv. See https://devenv.sh/automatic-shell-activation."
)]
Direnvrc,

#[command(about = "Print the version of devenv.")]
Version {},
Version,

#[clap(hide = true)]
Assemble,
Expand Down
21 changes: 19 additions & 2 deletions devenv/src/devenv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use include_dir::{include_dir, Dir};
use miette::{bail, Result};
use nix::sys::signal;
use nix::unistd::Pid;
use once_cell::sync::Lazy;
use serde::Deserialize;
use sha2::Digest;
use std::collections::HashMap;
Expand All @@ -22,6 +23,20 @@ const FLAKE_TMPL: &str = include_str!("flake.tmpl.nix");
const REQUIRED_FILES: [&str; 4] = ["devenv.nix", "devenv.yaml", ".envrc", ".gitignore"];
const EXISTING_REQUIRED_FILES: [&str; 1] = [".gitignore"];
const PROJECT_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/init");
pub static DIRENVRC: Lazy<String> = Lazy::new(|| {
include_str!("../../direnvrc").replace(
"DEVENV_DIRENVRC_ROLLING_UPGRADE=0",
"DEVENV_DIRENVRC_ROLLING_UPGRADE=1",
)
});
pub static DIRENVRC_VERSION: Lazy<u8> = Lazy::new(|| {
DIRENVRC
.lines()
.find(|line| line.contains("export DEVENV_DIRENVRC_VERSION"))
.map(|line| line.split('=').last().unwrap().trim())
.and_then(|version| version.parse().ok())
.unwrap_or(0)
});
// project vars
const DEVENV_FLAKE: &str = ".devenv.flake.nix";

Expand Down Expand Up @@ -817,6 +832,7 @@ impl Devenv {
devenv_tmpdir = \"{}\";
devenv_runtime = \"{}\";
devenv_istesting = {};
devenv_direnvrc_latest_version = {};
",
crate_version!(),
self.global_options.system,
Expand All @@ -829,7 +845,8 @@ impl Devenv {
.unwrap_or_else(|| "null".to_string()),
self.devenv_tmp,
self.devenv_runtime.display(),
is_testing
is_testing,
DIRENVRC_VERSION.to_string()
);
let flake = FLAKE_TMPL.replace("__DEVENV_VARS__", &vars);
let flake_path = self.devenv_root.join(DEVENV_FLAKE);
Expand All @@ -852,7 +869,7 @@ impl Devenv {
let span = tracing::info_span!("building_shell", devenv.user_message = "Building shell",);
let env = self.nix.dev_env(json, &gc_root).instrument(span).await?;

std::fs::write(
fs::write(
self.devenv_dotfile.join("input-paths.txt"),
env.paths
.iter()
Expand Down
5 changes: 5 additions & 0 deletions devenv/src/flake.tmpl.nix
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@
container.isBuilding = pkgs.lib.mkForce true;
containers.${container_name}.isBuilding = true;
})
({ options, ... }: {
config.devenv = pkgs.lib.optionalAttrs (builtins.hasAttr "direnvrcLatestVersion" options.devenv) {
direnvrcLatestVersion = devenv_direnvrc_latest_version;
};
})
] ++ (map importModule (devenv.imports or [ ])) ++ [
./devenv.nix
(devenv.devenv or { })
Expand Down
2 changes: 1 addition & 1 deletion devenv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ mod devenv;
pub mod log;

pub use cli::{default_system, GlobalOptions};
pub use devenv::{Devenv, DevenvOptions};
pub use devenv::{Devenv, DevenvOptions, DIRENVRC, DIRENVRC_VERSION};
pub use devenv_tasks as tasks;
10 changes: 7 additions & 3 deletions devenv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ async fn main() -> Result<()> {
};

let command = match cli.command {
None => return print_version(),
Some(Commands::Version { .. }) => return print_version(),
None | Some(Commands::Version) => return print_version(),
Some(Commands::Direnvrc) => {
print!("{}", devenv::DIRENVRC.to_string());
return Ok(());
}
Some(cmd) => cmd,
};

Expand Down Expand Up @@ -156,6 +159,7 @@ async fn main() -> Result<()> {
config::write_json_schema();
Ok(())
}
Commands::Version {} => unreachable!(),
Commands::Direnvrc => unreachable!(),
Commands::Version => unreachable!(),
}
}
98 changes: 81 additions & 17 deletions direnvrc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ _nix_direnv_preflight () {
if [[ ! -d "$layout_dir" ]]; then
mkdir -p "$layout_dir"
fi

export DEVENV_DIRENVRC_VERSION=1
export DEVENV_DIRENVRC_ROLLING_UPGRADE=0
}

_nix_export_or_unset() {
Expand All @@ -42,6 +45,11 @@ _nix_export_or_unset() {
_nix_import_env() {
local profile_rc=$1

# Note which environments are active, but make sure we don't repeat them
if [[ ! "''${DIRENV_ACTIVE-}" =~ (^|:)"$PWD"(:|$) ]]; then
export DIRENV_ACTIVE="$PWD:''${DIRENV_ACTIVE-}"
fi

local old_nix_build_top=${NIX_BUILD_TOP:-__UNSET__}
local old_tmp=${TMP:-__UNSET__}
local old_tmpdir=${TMPDIR:-__UNSET__}
Expand All @@ -50,7 +58,7 @@ _nix_import_env() {
local old_xdg_data_dirs=${XDG_DATA_DIRS:-}
eval "$(< "$profile_rc")"
# `nix print-dev-env` will create a temporary directory and use it as TMPDIR
# We cannot rely on this directory being availble at all times,
# We cannot rely on this directory being available at all times,
# as it may be garbage collected.
# Instead - just remove it immediately.
# Use recursive & force as it may not be empty.
Expand Down Expand Up @@ -78,7 +86,10 @@ _nix_import_env() {
_nix_argsum_suffix() {
local out checksum content

content=$(cat "$@" 2>/dev/null)
args=("$@")
sorted_args=$(printf "%s\n" "${args[@]}" | sort -u)

content=$(cat "${sorted_args[@]}" 2>/dev/null)

if has sha1sum; then
out=$(sha1sum <<< "$content")
Expand All @@ -94,44 +105,87 @@ _nix_argsum_suffix() {
}

nix_direnv_watch_file() {
log_error "nix_direnv_watch_file is deprecated. Use watch_file instead."
watch_file "$@"
nix_watches+=("$@")
}

_nix_direnv_watches() {
local -n _watches=$1
if [[ -z ${DIRENV_WATCHES-} ]]; then
return
fi
while IFS= read -r line; do
local regex='"[Pp]ath": "(.+)"$'
if [[ $line =~ $regex ]]; then
local path="${BASH_REMATCH[1]}"
if [[ $path == "${XDG_DATA_HOME:-${HOME:-/var/empty}/.local/share}/direnv/allow/"* ]]; then
continue
fi
# expand new lines and other json escapes
# shellcheck disable=2059
path=$(printf "$path")
_watches+=("$path")
fi
done < <($direnv show_dump "${DIRENV_WATCHES}")
}

_devenv_watches() {
local path=$1
local -n _watches=$2
if [[ -f "$path" ]]; then
while IFS= read -r file; do
file=$(printf "$file")
_watches+=("$file")
done < "$path"
fi
}

_devenv_profile_rc() {
local -n _watches=$1
local layout_dir profile
layout_dir=$(direnv_layout_dir)
profile="${layout_dir}/devenv-profile$(_nix_argsum_suffix "${_watches[@]}")"
echo "${profile}.rc"
}

use_devenv() {
_nix_direnv_preflight

flake_expr="${1:-.}"
flake_dir="${flake_expr%#*}"
env_deps_path="$flake_dir/.devenv/input-paths.txt"

local files_to_watch
files_to_watch=(".envrc" "$HOME/.direnvrc" "$HOME/.config/direnv/direnvrc")
local default_watches
default_watches=(".envrc" "$HOME/.direnvrc" "$HOME/.config/direnv/direnvrc")

if [[ -d "$flake_dir" ]]; then
files_to_watch+=("$flake_dir/devenv.nix" "$flake_dir/devenv.lock" "$flake_dir/devenv.yaml" "$flake_dir/devenv.local.nix")
default_watches+=("$flake_dir/devenv.nix" "$flake_dir/devenv.lock" "$flake_dir/devenv.yaml" "$flake_dir/devenv.local.nix")

if [[ -f "$flake_dir/devenv.yaml" ]]; then
if ! devenv assemble; then
log_error "$(devenv version) failed to parse devenv.yaml, make sure to use version 0.6 or newer and fix the errors above."
exit 0
fi

if [[ -f "$flake_dir/.devenv/input-paths.txt" ]]; then
for file in $(cat "$flake_dir/.devenv/input-paths.txt"); do
files_to_watch+=("$file")
done
fi
fi
fi

nix_direnv_watch_file "${files_to_watch[@]}"
# Watch the default files
watch_file "${default_watches[@]}"

local layout_dir profile_rc
layout_dir=$(direnv_layout_dir)
profile_rc="${layout_dir}/devenv-profile$(_nix_argsum_suffix "${files_to_watch[@]}").rc"
# Fetch and watch files that affect the env
local env_watches
_devenv_watches "$env_deps_path" env_watches
watch_file "${env_watches[@]}"

# Fetch all files that direnv is currently watching
local watches
_nix_direnv_watches watches

profile_rc=$(_devenv_profile_rc watches)

local need_update=0
local file=
for file in "${nix_watches[@]}"; do
for file in "${watches[@]}"; do
if [[ "$file" -nt "$profile_rc" ]]; then
need_update=1
log_status "$file changed, reloading"
Expand All @@ -148,6 +202,16 @@ use_devenv() {
exit 0
fi

# Re-watch files that affect the env
local env_watches
_devenv_watches "$env_deps_path" env_watches
watch_file "${env_watches[@]}"

# Fetch the final watches and compute the new profile_rc
local watches
_nix_direnv_watches watches
profile_rc=$(_devenv_profile_rc watches)

echo "$env" > "$profile_rc"
log_status "updated devenv shell cache"
_nix_import_env "$profile_rc"
Expand Down
Loading

0 comments on commit 1cd2afa

Please sign in to comment.