Skip to content

Commit bb4c221

Browse files
committed
Check that .drv dependencies present in the store
1 parent 59b11bf commit bb4c221

File tree

5 files changed

+191
-3
lines changed

5 files changed

+191
-3
lines changed

Cargo.lock

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ blake3 = "1.3.3"
1313
bytelines = "2.4.0"
1414
itertools = "0.10.5"
1515
nix = "0.26.2"
16+
nom = "7.1.3"
1617
once_cell = "1.17.1"
1718
serde_json = "1.0.96"
1819
tempfile = "3.5.0"

src/drv.rs

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
use nom::branch::alt;
2+
use nom::bytes::complete::{escaped_transform, is_not, tag};
3+
use nom::character::complete::char;
4+
use nom::combinator::{eof, opt, value};
5+
use nom::multi::separated_list0;
6+
use nom::sequence::{delimited, preceded, tuple};
7+
use nom::{AsChar, IResult, InputIter, Slice};
8+
use std::ffi::{OsStr, OsString};
9+
use std::fmt::Debug;
10+
use std::fs::read;
11+
use std::ops::RangeFrom;
12+
use std::os::unix::prelude::OsStringExt;
13+
use std::path::Path;
14+
15+
#[derive(Debug)]
16+
#[allow(dead_code)]
17+
struct Derivation {
18+
outputs: Vec<(OsString, OsString, OsString, OsString)>,
19+
input_drvs: Vec<(OsString, Vec<OsString>)>,
20+
input_srcs: Vec<OsString>,
21+
platform: OsString,
22+
builder: OsString,
23+
args: Vec<OsString>,
24+
env: Vec<(OsString, OsString)>,
25+
}
26+
27+
/// Check if .drv file is present, and all of its inputs (both .drv and their
28+
/// outputs) are present.
29+
pub fn derivation_is_ok<P: AsRef<OsStr>>(path: P) -> Result<(), String> {
30+
// nix-shell doesn't create an output for the shell derivation, so we
31+
// check it's dependencies instead.
32+
for (drv, outputs) in load_derive(&path)?.input_drvs {
33+
let parsed_drv = load_derive(&drv)?;
34+
for out_name in outputs {
35+
let name = &parsed_drv
36+
.outputs
37+
.iter()
38+
.find(|(name, _, _, _)| name == &out_name)
39+
.ok_or_else(|| {
40+
format!(
41+
"{}: output {:?} not found",
42+
drv.to_string_lossy(),
43+
out_name
44+
)
45+
})?
46+
.1;
47+
if !Path::new(&name).exists() {
48+
return Err(format!("{}: not found", name.to_string_lossy()));
49+
}
50+
}
51+
}
52+
Ok(())
53+
}
54+
55+
fn load_derive<P: AsRef<OsStr>>(path: P) -> Result<Derivation, String> {
56+
let data = read(path.as_ref())
57+
.map_err(|e| format!("{}: !{}", path.as_ref().to_string_lossy(), e))?;
58+
parse_derive(&data).map(|a| a.1).map_err(|_| {
59+
format!("{}: failed to parse", path.as_ref().to_string_lossy())
60+
})
61+
}
62+
63+
fn parse_derive(input: &[u8]) -> IResult<&[u8], Derivation> {
64+
let (input, values) = delimited(
65+
tag("Derive"),
66+
tuple((
67+
preceded(char('('), parse_list(parse_output)),
68+
preceded(char(','), parse_list(parse_input_drv)),
69+
preceded(char(','), parse_list(parse_string)),
70+
preceded(char(','), parse_string),
71+
preceded(char(','), parse_string),
72+
preceded(char(','), parse_list(parse_string)),
73+
preceded(char(','), parse_list(parse_env)),
74+
)),
75+
preceded(char(')'), eof),
76+
)(input)?;
77+
78+
let result = Derivation {
79+
outputs: values.0,
80+
input_drvs: values.1,
81+
input_srcs: values.2,
82+
platform: values.3,
83+
builder: values.4,
84+
args: values.5,
85+
env: values.6,
86+
};
87+
Ok((input, result))
88+
}
89+
90+
fn parse_output(
91+
input: &[u8],
92+
) -> IResult<&[u8], (OsString, OsString, OsString, OsString)> {
93+
tuple((
94+
preceded(char('('), parse_string),
95+
preceded(char(','), parse_string),
96+
preceded(char(','), parse_string),
97+
delimited(char(','), parse_string, char(')')),
98+
))(input)
99+
}
100+
101+
fn parse_input_drv(input: &[u8]) -> IResult<&[u8], (OsString, Vec<OsString>)> {
102+
tuple((
103+
preceded(char('('), parse_string),
104+
delimited(char(','), parse_list(parse_string), char(')')),
105+
))(input)
106+
}
107+
108+
fn parse_env(input: &[u8]) -> IResult<&[u8], (OsString, OsString)> {
109+
tuple((
110+
preceded(char('('), parse_string),
111+
delimited(char(','), parse_string, char(')')),
112+
))(input)
113+
}
114+
115+
fn parse_list<I, O, E, F>(f: F) -> impl FnMut(I) -> IResult<I, Vec<O>, E>
116+
where
117+
I: Clone + nom::InputLength + Slice<RangeFrom<usize>> + InputIter,
118+
<I as InputIter>::Item: AsChar,
119+
F: nom::Parser<I, O, E>,
120+
E: nom::error::ParseError<I>,
121+
{
122+
delimited(char('['), separated_list0(char(','), f), char(']'))
123+
}
124+
125+
fn parse_string(input: &[u8]) -> IResult<&[u8], OsString> {
126+
let (input, parsed) = delimited(
127+
char('\"'),
128+
opt(escaped_transform(
129+
is_not("\\\""),
130+
'\\',
131+
alt((
132+
value(b"\\" as &'static [u8], char('\\')),
133+
value(b"\"" as &'static [u8], char('"')),
134+
value(b"\n" as &'static [u8], char('n')),
135+
)),
136+
)),
137+
char('\"'),
138+
)(input)?;
139+
Ok((input, OsString::from_vec(parsed.unwrap_or_default())))
140+
}

src/main.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use once_cell::sync::Lazy;
88
use std::collections::{BTreeMap, HashSet};
99
use std::env::current_dir;
1010
use std::ffi::{OsStr, OsString};
11-
use std::fs::{read, read_link, File};
11+
use std::fs::{read, File};
1212
use std::io::{Read, Write};
1313
use std::os::unix::ffi::OsStrExt;
1414
use std::os::unix::prelude::OsStringExt;
@@ -22,6 +22,7 @@ use ufcs::Pipe;
2222

2323
mod args;
2424
mod bash;
25+
mod drv;
2526
mod nix_path;
2627
mod path_clean;
2728
mod shebang;
@@ -566,8 +567,10 @@ fn check_cache(hash: &str) -> Option<BTreeMap<OsString, OsString>> {
566567

567568
let env = read(env_fname).unwrap().pipe(deserealize_env);
568569

569-
let drv_store_fname = read_link(drv_fname).ok()?;
570-
std::fs::metadata(drv_store_fname).ok()?;
570+
if let Err(e) = drv::derivation_is_ok(drv_fname) {
571+
eprintln!("cached-nix-shell: {}", e);
572+
return None;
573+
}
571574

572575
let trace = read(trace_fname).unwrap().pipe(Trace::load);
573576
if trace.check_for_changes() {

tests/t18-invalidate.sh

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/sh
2+
. ./lib.sh
3+
# Check that the cache is invalidated when a dependency is deleted.
4+
5+
put tmp/shell.nix << 'EOF'
6+
with import <nixpkgs> { };
7+
let
8+
dep = stdenv.mkDerivation {
9+
name = "cached-nix-shell-test-inner-dep";
10+
unpackPhase = ": | md5sum | cut -c 1-32 > $out";
11+
};
12+
in mkShell { inherit dep; }
13+
EOF
14+
15+
run cached-nix-shell tmp/shell.nix --pure --run 'cat $dep; echo $dep > tmp/dep'
16+
check_contains d41d8cd98f00b204e9800998ecf8427e
17+
check_slow
18+
19+
run cached-nix-shell tmp/shell.nix --pure --run 'cat $dep'
20+
check_contains d41d8cd98f00b204e9800998ecf8427e
21+
check_fast
22+
23+
run nix-store --delete $(cat tmp/dep)
24+
25+
run cached-nix-shell tmp/shell.nix --pure --run 'cat $dep'
26+
check_contains d41d8cd98f00b204e9800998ecf8427e
27+
check_slow

0 commit comments

Comments
 (0)