Skip to content

Commit 3f6fd70

Browse files
authored
Merge pull request #31 from nix-community/flake-support
2 parents d9ae531 + 8a397b7 commit 3f6fd70

File tree

6 files changed

+252
-49
lines changed

6 files changed

+252
-49
lines changed

.github/workflows/test.yml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,34 @@ name: "Test"
22
on:
33
pull_request:
44
push:
5+
56
jobs:
67
tests:
7-
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
# macOS installer for nixUnstable seems broken atm
11+
#os: [ ubuntu-latest, macos-latest ]
12+
os: [ ubuntu-latest ]
13+
nix-install-url:
14+
- https://nixos.org/nix/install
15+
- https://github.com/numtide/nix-flakes-installer/releases/download/nix-2.4pre20200618_377345e/install
16+
#- https://github.com/numtide/nix-flakes-installer/releases/download/nix-3.0pre20200804_ed52cf6/install
17+
runs-on: ${{ matrix.os }}
818
steps:
919
- uses: actions/checkout@v2
1020
- uses: cachix/install-nix-action@v10
21+
with:
22+
install_url: ${{ matrix.nix-install-url }}
1123
- name: Prevent garbage collections of gcroots
1224
run:
13-
printf "keep-outputs = true\nkeep-derivations = true\n" | sudo tee -a /etc/nix/nix.conf;
25+
printf "keep-outputs = true\nkeep-derivations = true\nexperimental-features = nix-command flakes\n" | sudo tee -a /etc/nix/nix.conf;
26+
- name: Restart nix-daemon with systemctl
27+
run:
1428
sudo systemctl restart nix-daemon
29+
if: matrix.os == 'ubuntu-latest'
30+
- name: Restart nix-daemon with launchctl
31+
run:
32+
sudo launchctl kickstart -k org.nixos.nix-daemon
33+
if: matrix.os == 'macos-latest'
1534
- run:
1635
nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/nixpkgs-unstable.tar.gz ci.nix --run 'true'

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,56 @@ Then source the direnvrc from this repository in your own `.direnvrc`
104104
source $HOME/nix-direnv/direnvrc
105105
```
106106

107+
## Usage example
108+
109+
Either add `shell.nix` or a `default.nix` to the same directory:
110+
111+
``` nix
112+
# save this as shell.nix
113+
{ pkgs ? with import <nixpkgs> {}}:
114+
115+
pkgs.mkShell {
116+
nativeBuildInputs = [ pkgs.hello ];
117+
}
118+
```
119+
120+
Then add the line `use nix` to your envrc:
121+
```console
122+
$ echo "use nix" >> .envrc
123+
$ direnv allow
124+
```
125+
126+
## Experimental flakes support
127+
128+
nix-direnv also comes with a flake alternative. The code is tested and works however
129+
since future nix versions might change their api regarding this feature we cannot
130+
guarantee stability after an nix upgrade.
131+
132+
Save this file as `flake.nix`:
133+
134+
``` nix
135+
{
136+
description = "A very basic flake";
137+
# Provides abstraction to boiler-code when specifying multi-platform outputs.
138+
inputs.flake-utils.url = "github:numtide/flake-utils";
139+
outputs = { self, nixpkgs, flake-utils }:
140+
flake-utils.lib.eachDefaultSystem (system: let
141+
pkgs = nixpkgs.legacyPackages.${system};
142+
in {
143+
devShell = pkgs.mkShell {
144+
nativeBuildInputs = [ pkgs.hello ];
145+
};
146+
});
147+
}
148+
```
149+
150+
Then add `use flake` to your `.envrc`:
151+
152+
```console
153+
$ echo "use flake" >> .envrc
154+
$ direnv allow
155+
```
156+
107157
## Storing .direnv outside the project directory
108158

109159
A `.direnv` directory will be created in each `use_nix` project, which might

direnvrc

Lines changed: 120 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,122 @@
11
# shellcheck shell=bash
22

3+
_nix_import_env() {
4+
local env=$1
5+
6+
local term_backup=$TERM path_backup=$PATH
7+
if [[ -n ${TMPDIR+x} ]]; then
8+
local tmp_backup=$TMPDIR
9+
fi
10+
local impure_ssl_cert_file=${SSL_CERT_FILE:-__UNSET__}
11+
local impure_nix_ssl_cert_file=${NIX_SSL_CERT_FILE:-__UNSET__}
12+
13+
eval "$env"
14+
15+
# `nix-shell --pure` sets invalid ssl certificate paths
16+
if [[ "${SSL_CERT_FILE:-}" = /no-cert-file.crt ]]; then
17+
if [[ $impure_ssl_cert_file == __UNSET__ ]]; then
18+
unset SSL_CERT_FILE
19+
else
20+
export SSL_CERT_FILE=$impure_nix_ssl_cert_file
21+
fi
22+
fi
23+
24+
if [[ "${NIX_SSL_CERT_FILE:-}" = /no-cert-file.crt ]]; then
25+
if [[ $impure_nix_ssl_cert_file == __UNSET__ ]]; then
26+
unset NIX_SSL_CERT_FILE
27+
else
28+
export NIX_SSL_CERT_FILE=${impure_ssl_cert_file}
29+
fi
30+
fi
31+
32+
export PATH=$PATH:$path_backup TERM=$term_backup
33+
if [[ -n ${tmp_backup+x} ]]; then
34+
export TMPDIR=${tmp_backup}
35+
else
36+
unset TMPDIR
37+
fi
38+
39+
# misleading since we are in an impure shell now
40+
export IN_NIX_SHELL=impure
41+
}
42+
43+
_nix_add_gcroot() {
44+
local storepath=$1
45+
local symlink=$2
46+
47+
local stripped_pwd=${PWD/\//}
48+
local escaped_pwd=${stripped_pwd//-/--}
49+
local escaped_pwd=${escaped_pwd//\//-}
50+
ln -fs "$storepath" "$symlink"
51+
ln -fs "$symlink" "/nix/var/nix/gcroots/per-user/$USER/$escaped_pwd"
52+
}
53+
54+
use_flake() {
55+
watch_file flake.nix
56+
watch_file flake.lock
57+
58+
local profile="$(direnv_layout_dir)/flake-profile"
59+
local profile_rc="${profile}.rc"
60+
61+
if [[ ! -e "$profile" ]] || \
62+
[[ ! -e "$profile_rc" ]] || \
63+
[[ "$HOME/.direnvrc" -nt "$profile_rc" ]] || \
64+
[[ .envrc -nt "$profile_rc" ]] || \
65+
[[ flake.nix -nt "$profile_rc" ]] || \
66+
[[ flake.lock -nt "$profile_rc" ]];
67+
then
68+
local tmp_profile="$(direnv_layout_dir)/flake-profile.$$"
69+
[[ -d "$(direnv_layout_dir)" ]] || mkdir "$(direnv_layout_dir)"
70+
local tmp_profile_rc=$(nix print-dev-env --profile "$tmp_profile")
71+
drv=$(realpath "$tmp_profile")
72+
echo "$tmp_profile_rc" > "$profile_rc"
73+
rm -f "$tmp_profile" "$tmp_profile"*
74+
_nix_add_gcroot "$drv" "$profile"
75+
log_status renewed cache
76+
else
77+
log_status using cached dev shell
78+
fi
79+
80+
local old_nix_build_top=${NIX_BUILD_TOP:-__UNSET__}
81+
local old_tmp=${TMP:-__UNSET__}
82+
local old_tmpdir=${TMPDIR:-__UNSET__}
83+
local old_temp=${TEMP:-__UNSET__}
84+
local old_tempdir=${TEMPDIR:-__UNSET__}
85+
eval "$(< "$profile_rc")"
86+
# nix print-env-dev will create a temporary directory and use it a TMPDIR,
87+
# we cannot rely on this directory beeing not deleted at some point,
88+
# hence we are just removing it right away.
89+
if [[ "$NIX_BUILD_TOP" == */nix-shell.* && -d "$NIX_BUILD_TOP" ]]; then
90+
rmdir "$NIX_BUILD_TOP"
91+
fi
92+
93+
if [[ "$old_nix_build_top" == __UNSET__ ]]; then
94+
unset NIX_BUILD_TOP
95+
else
96+
export NIX_BUILD_TOP=$old_nix_build_top
97+
fi
98+
if [[ "$old_tmp" == __UNSET__ ]]; then
99+
unset TMP
100+
else
101+
export TMP=$old_temp
102+
fi
103+
if [[ "$old_tmpdir" == __UNSET__ ]]; then
104+
unset TMPDIR
105+
else
106+
export TMPDIR=$old_tmpdir
107+
fi
108+
if [[ "$old_temp" == __UNSET__ ]]; then
109+
unset TEMP
110+
else
111+
export TEMP=$old_temp
112+
fi
113+
if [[ "$old_tempdir" == __UNSET__ ]]; then
114+
unset TEMPDIR
115+
else
116+
export TEMPDIR=$old_tempdir
117+
fi
118+
}
119+
3120
use_nix() {
4121
local path direnv_dir
5122
path=$(nix-instantiate --find-file nixpkgs)
@@ -42,57 +159,20 @@ use_nix() {
42159
else
43160
log_status using cached derivation
44161
fi
45-
local term_backup=$TERM path_backup=$PATH
46-
if [[ -n ${TMPDIR+x} ]]; then
47-
local tmp_backup=$TMPDIR
48-
fi
49-
50-
local impure_ssl_cert_file=${SSL_CERT_FILE:-__UNSET__}
51-
local impure_nix_ssl_cert_file=${NIX_SSL_CERT_FILE:-__UNSET__}
52162

53163
log_status eval "$cache"
54164
read -r cache_content < "$cache"
55-
eval "$cache_content"
56-
export PATH=$PATH:$path_backup TERM=$term_backup
57-
if [[ -n ${tmp_backup+x} ]]; then
58-
export TMPDIR=${tmp_backup}
59-
else
60-
unset TMPDIR
61-
fi
62-
63-
# `nix-shell --pure` sets IN_NIX_SHELL to pure which is
64-
# misleading since we are in an impure shell now
65-
export IN_NIX_SHELL=impure
66-
67-
# `nix-shell --pure` sets invalid ssl certificate paths
68-
if [[ "${SSL_CERT_FILE:-}" = /no-cert-file.crt ]]; then
69-
if [[ ${impure_ssl_cert_file} == __UNSET__ ]]; then
70-
unset SSL_CERT_FILE
71-
else
72-
export SSL_CERT_FILE=${impure_ssl_cert_file}
73-
fi
74-
fi
75-
if [[ "${NIX_SSL_CERT_FILE:-}" = /no-cert-file.crt ]]; then
76-
if [[ ${impure_nix_ssl_cert_file} == __UNSET__ ]]; then
77-
unset NIX_SSL_CERT_FILE
78-
else
79-
export NIX_SSL_CERT_FILE=${impure_nix_ssl_cert_file}
80-
fi
81-
fi
165+
_nix_import_env "$cache_content"
82166

83167
# This part is based on https://discourse.nixos.org/t/what-is-the-best-dev-workflow-around-nix-shell/418/4
84168
if [[ "${out:-}" != "" ]] && (( update_drv )); then
85169
local drv_link="${direnv_dir}/drv" drv
86170
drv=$(nix show-derivation "$out" | grep -E -o -m1 '/nix/store/.*.drv')
87-
local stripped_pwd=${PWD/\//}
88-
local escaped_pwd=${stripped_pwd//-/--}
89-
local escaped_pwd=${escaped_pwd//\//-}
90-
ln -fs "$drv" "$drv_link"
91-
ln -fs "$drv_link" "/nix/var/nix/gcroots/per-user/$USER/$escaped_pwd"
171+
_nix_add_gcroot "$drv" "$drv_link"
92172
log_status renewed cache and derivation link
93173
fi
94174

95-
if [[ $# = 0 ]]; then
175+
if [[ "$#" == 0 ]]; then
96176
watch_file default.nix
97177
watch_file shell.nix
98178
fi

tests/test.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,31 @@ def run(cmd: List[str], **kwargs) -> subprocess.CompletedProcess:
1818
return subprocess.run(cmd, **kwargs)
1919

2020

21+
def support_flakes() -> bool:
22+
cmd = [
23+
"nix-instantiate",
24+
"--json",
25+
"--eval",
26+
"--expr",
27+
'(builtins.compareVersions "2.4" builtins.nixVersion) == 1',
28+
]
29+
proc = subprocess.run(cmd, text=True, capture_output=True, check=True)
30+
return proc.stdout != "true"
31+
32+
2133
class IntegrationTest(unittest.TestCase):
2234
def setUp(self) -> None:
2335
self.env = os.environ.copy()
2436
self.dir = TemporaryDirectory()
2537
self.env["HOME"] = str(self.dir.name)
2638
self.testenv = Path(self.dir.name).joinpath("testenv")
2739
shutil.copytree(TEST_ROOT.joinpath("testenv"), self.testenv)
28-
direnvrc = str(TEST_ROOT.parent.joinpath("direnvrc"))
40+
self.direnvrc = str(TEST_ROOT.parent.joinpath("direnvrc"))
41+
42+
def test_nix_shell(self) -> None:
2943
with open(self.testenv.joinpath(".envrc"), "w") as f:
30-
f.write(f"source {direnvrc}\n" "use nix")
44+
f.write(f"source {self.direnvrc}\n" "use nix")
3145

32-
def test_direnv(self) -> None:
3346
run(["direnv", "allow"], cwd=str(self.testenv), env=self.env, check=True)
3447

3548
run(["nix-collect-garbage"], check=True)
@@ -45,7 +58,6 @@ def test_direnv(self) -> None:
4558
self.assertEqual(out1.returncode, 0)
4659

4760
run(["nix-collect-garbage"], check=True)
48-
run(["sh", "-c", "realpath /nix/var/nix/gcroots/per-user/$USER/*"], check=True)
4961

5062
out2 = run(
5163
["direnv", "exec", str(self.testenv), "hello"],
@@ -57,6 +69,37 @@ def test_direnv(self) -> None:
5769
self.assertIn("using cached derivation", out2.stderr)
5870
self.assertEqual(out2.returncode, 0)
5971

72+
@unittest.skipUnless(support_flakes(), "requires flakes")
73+
def test_nix_flake(self) -> None:
74+
with open(self.testenv.joinpath(".envrc"), "w") as f:
75+
f.write(f"source {self.direnvrc}\n" "use flake")
76+
77+
run(["direnv", "allow"], cwd=str(self.testenv), env=self.env, check=True)
78+
79+
run(["nix-collect-garbage"], check=True)
80+
81+
out1 = run(
82+
["direnv", "exec", str(self.testenv), "hello"],
83+
env=self.env,
84+
stderr=subprocess.PIPE,
85+
text=True,
86+
)
87+
sys.stderr.write(out1.stderr)
88+
self.assertIn("renewed cache", out1.stderr)
89+
self.assertEqual(out1.returncode, 0)
90+
91+
run(["nix-collect-garbage"], check=True)
92+
93+
out2 = run(
94+
["direnv", "exec", str(self.testenv), "hello"],
95+
env=self.env,
96+
stderr=subprocess.PIPE,
97+
text=True,
98+
)
99+
sys.stderr.write(out2.stderr)
100+
self.assertIn("using cached dev shell", out2.stderr)
101+
self.assertEqual(out2.returncode, 0)
102+
60103
def tearDown(self) -> None:
61104
self.dir.cleanup()
62105

tests/testenv/flake.nix

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
description = "A very basic flake";
3+
inputs.flake-utils.url = "github:numtide/flake-utils";
4+
5+
outputs = { self, nixpkgs, flake-utils }:
6+
flake-utils.lib.eachDefaultSystem (system: {
7+
devShell = import ./shell.nix {
8+
pkgs = nixpkgs.legacyPackages.${system};
9+
};
10+
});
11+
}

tests/testenv/shell.nix

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
with import <nixpkgs> {};
2-
mkShell {
3-
nativeBuildInputs = [ hello ];
1+
{ pkgs ? import <nixpkgs> {} }:
2+
pkgs.mkShell {
3+
nativeBuildInputs = [ pkgs.hello ];
44
}

0 commit comments

Comments
 (0)