Skip to content

Commit

Permalink
Check that .drv dependencies present in the store
Browse files Browse the repository at this point in the history
  • Loading branch information
xzfc committed May 29, 2023
1 parent 59b11bf commit bb4c221
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 3 deletions.
17 changes: 17 additions & 0 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ blake3 = "1.3.3"
bytelines = "2.4.0"
itertools = "0.10.5"
nix = "0.26.2"
nom = "7.1.3"
once_cell = "1.17.1"
serde_json = "1.0.96"
tempfile = "3.5.0"
Expand Down
140 changes: 140 additions & 0 deletions src/drv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use nom::branch::alt;
use nom::bytes::complete::{escaped_transform, is_not, tag};
use nom::character::complete::char;
use nom::combinator::{eof, opt, value};
use nom::multi::separated_list0;
use nom::sequence::{delimited, preceded, tuple};
use nom::{AsChar, IResult, InputIter, Slice};
use std::ffi::{OsStr, OsString};
use std::fmt::Debug;
use std::fs::read;
use std::ops::RangeFrom;
use std::os::unix::prelude::OsStringExt;
use std::path::Path;

#[derive(Debug)]
#[allow(dead_code)]
struct Derivation {
outputs: Vec<(OsString, OsString, OsString, OsString)>,
input_drvs: Vec<(OsString, Vec<OsString>)>,
input_srcs: Vec<OsString>,
platform: OsString,
builder: OsString,
args: Vec<OsString>,
env: Vec<(OsString, OsString)>,
}

/// Check if .drv file is present, and all of its inputs (both .drv and their
/// outputs) are present.
pub fn derivation_is_ok<P: AsRef<OsStr>>(path: P) -> Result<(), String> {
// nix-shell doesn't create an output for the shell derivation, so we
// check it's dependencies instead.
for (drv, outputs) in load_derive(&path)?.input_drvs {
let parsed_drv = load_derive(&drv)?;
for out_name in outputs {
let name = &parsed_drv
.outputs
.iter()
.find(|(name, _, _, _)| name == &out_name)
.ok_or_else(|| {
format!(
"{}: output {:?} not found",
drv.to_string_lossy(),
out_name
)
})?
.1;
if !Path::new(&name).exists() {
return Err(format!("{}: not found", name.to_string_lossy()));
}
}
}
Ok(())
}

fn load_derive<P: AsRef<OsStr>>(path: P) -> Result<Derivation, String> {
let data = read(path.as_ref())
.map_err(|e| format!("{}: !{}", path.as_ref().to_string_lossy(), e))?;
parse_derive(&data).map(|a| a.1).map_err(|_| {
format!("{}: failed to parse", path.as_ref().to_string_lossy())
})
}

fn parse_derive(input: &[u8]) -> IResult<&[u8], Derivation> {
let (input, values) = delimited(
tag("Derive"),
tuple((
preceded(char('('), parse_list(parse_output)),
preceded(char(','), parse_list(parse_input_drv)),
preceded(char(','), parse_list(parse_string)),
preceded(char(','), parse_string),
preceded(char(','), parse_string),
preceded(char(','), parse_list(parse_string)),
preceded(char(','), parse_list(parse_env)),
)),
preceded(char(')'), eof),
)(input)?;

let result = Derivation {
outputs: values.0,
input_drvs: values.1,
input_srcs: values.2,
platform: values.3,
builder: values.4,
args: values.5,
env: values.6,
};
Ok((input, result))
}

fn parse_output(
input: &[u8],
) -> IResult<&[u8], (OsString, OsString, OsString, OsString)> {
tuple((
preceded(char('('), parse_string),
preceded(char(','), parse_string),
preceded(char(','), parse_string),
delimited(char(','), parse_string, char(')')),
))(input)
}

fn parse_input_drv(input: &[u8]) -> IResult<&[u8], (OsString, Vec<OsString>)> {
tuple((
preceded(char('('), parse_string),
delimited(char(','), parse_list(parse_string), char(')')),
))(input)
}

fn parse_env(input: &[u8]) -> IResult<&[u8], (OsString, OsString)> {
tuple((
preceded(char('('), parse_string),
delimited(char(','), parse_string, char(')')),
))(input)
}

fn parse_list<I, O, E, F>(f: F) -> impl FnMut(I) -> IResult<I, Vec<O>, E>
where
I: Clone + nom::InputLength + Slice<RangeFrom<usize>> + InputIter,
<I as InputIter>::Item: AsChar,
F: nom::Parser<I, O, E>,
E: nom::error::ParseError<I>,
{
delimited(char('['), separated_list0(char(','), f), char(']'))
}

fn parse_string(input: &[u8]) -> IResult<&[u8], OsString> {
let (input, parsed) = delimited(
char('\"'),
opt(escaped_transform(
is_not("\\\""),
'\\',
alt((
value(b"\\" as &'static [u8], char('\\')),
value(b"\"" as &'static [u8], char('"')),
value(b"\n" as &'static [u8], char('n')),
)),
)),
char('\"'),
)(input)?;
Ok((input, OsString::from_vec(parsed.unwrap_or_default())))
}
9 changes: 6 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use once_cell::sync::Lazy;
use std::collections::{BTreeMap, HashSet};
use std::env::current_dir;
use std::ffi::{OsStr, OsString};
use std::fs::{read, read_link, File};
use std::fs::{read, File};
use std::io::{Read, Write};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::prelude::OsStringExt;
Expand All @@ -22,6 +22,7 @@ use ufcs::Pipe;

mod args;
mod bash;
mod drv;
mod nix_path;
mod path_clean;
mod shebang;
Expand Down Expand Up @@ -566,8 +567,10 @@ fn check_cache(hash: &str) -> Option<BTreeMap<OsString, OsString>> {

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

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

let trace = read(trace_fname).unwrap().pipe(Trace::load);
if trace.check_for_changes() {
Expand Down
27 changes: 27 additions & 0 deletions tests/t18-invalidate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/sh
. ./lib.sh
# Check that the cache is invalidated when a dependency is deleted.

put tmp/shell.nix << 'EOF'
with import <nixpkgs> { };
let
dep = stdenv.mkDerivation {
name = "cached-nix-shell-test-inner-dep";
unpackPhase = ": | md5sum | cut -c 1-32 > $out";
};
in mkShell { inherit dep; }
EOF

run cached-nix-shell tmp/shell.nix --pure --run 'cat $dep; echo $dep > tmp/dep'
check_contains d41d8cd98f00b204e9800998ecf8427e
check_slow

run cached-nix-shell tmp/shell.nix --pure --run 'cat $dep'
check_contains d41d8cd98f00b204e9800998ecf8427e
check_fast

run nix-store --delete $(cat tmp/dep)

run cached-nix-shell tmp/shell.nix --pure --run 'cat $dep'
check_contains d41d8cd98f00b204e9800998ecf8427e
check_slow

0 comments on commit bb4c221

Please sign in to comment.