Skip to content
This repository has been archived by the owner on Oct 15, 2022. It is now read-only.

Commit

Permalink
builder: mirror nix’s default.nix mechanism
Browse files Browse the repository at this point in the history
For a directory import like `import ./foo`, nix imports
`./foo/default.nix` but only prints `./foo` to its log output.
This commit reproduces that behaviour.

We were watching way too many files, e.g. `import <nixpkgs> {}` meant
we’d watch all of nixpkgs recursively. This should be solved now.

Fixes #58
  • Loading branch information
Profpatsch committed Sep 5, 2019
1 parent 35f8c4f commit f29ee94
Showing 1 changed file with 111 additions and 3 deletions.
114 changes: 111 additions & 3 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,21 @@ fn instrumented_instantiation(
.into_iter()
.fold((vec![], vec![]), |(mut paths, mut log_lines), result| {
match result {
LogDatum::CopiedSource(src)
| LogDatum::NixSourceFile(src)
| LogDatum::ReadFileOrDir(src) => {
LogDatum::CopiedSource(src) | LogDatum::ReadFileOrDir(src) => {
paths.push(src);
}
LogDatum::NixSourceFile(mut src) => {
// We need to emulate nix’s `default.nix` mechanism here.
// That is, if the user uses something like
// `import ./foo`
// and `foo` is a directory, nix will actually import
// `./foo/default.nix`
// but still print `./foo`.
// Since this is the only time directories are printed,
// we can just manually re-implement that behavior.
if src.is_dir() {
src.push("default.nix");
}
paths.push(src);
}
LogDatum::Text(line) => log_lines.push(line),
Expand Down Expand Up @@ -405,4 +417,100 @@ in {}
Ok(())
}

/// Helper to recursively print the contents of files of a directory tree.
/// No error handling is done.
fn pretty_print_files_in_dir(dir: &Path) -> String {
std::fs::read_dir(&dir)
.unwrap()
.map(|path| {
let p = path.unwrap().path();
format!(
"{:?}: {}\n",
p,
if !p.is_dir() {
String::from_utf8(std::fs::read(&p).unwrap()).unwrap()
} else {
"<directory>\n".to_string() + &pretty_print_files_in_dir(&p)
}
)
})
.collect::<String>()
}

// TODO: builtins.fetchTarball and the like? What happens with those?
// Are they directories and if yes, should we watch them?
/// The paths that are returned by the nix-instantiate call
/// must not contain directories, otherwise the watcher will
/// watch those recursively, which leads to a lot of wasted resources
/// and often exhausts the amount of available file handles
/// (especially on macOS).
#[test]
fn no_unnecessary_files_or_directories_watched() -> std::io::Result<()> {
let root_tmp = tempfile::tempdir()?;
let cas_tmp = tempfile::tempdir()?;
let root = root_tmp.path();
let shell = root.join("shell.nix");
std::fs::write(
&shell,
drv(
"shell",
r##"
# The `foo/default.nix` is implicitely imported
# (we only want to watch that one, not the whole directory)
foo = import ./foo;
# `dir` is imported as source directory (no `import`).
# We *do* want to watch this directory, because we need to react
# when the user updates it.
dir-as-source = ./dir;
"##,
),
)?;

// ./foo
// ./foo/default.nix
// ./foo/bar <- should not be watched
// ./foo/baz <- should be watched
// ./dir <- should be watched, because imported as source
let foo = root.join("foo");
std::fs::create_dir(&foo)?;
let dir = root.join("dir");
std::fs::create_dir(&dir)?;
let foo_default = &foo.join("default.nix");
std::fs::write(&foo_default, "import ./baz")?;
let foo_bar = &foo.join("bar");
std::fs::write(&foo_bar, "This file should not be watched")?;
let foo_baz = &foo.join("baz");
std::fs::write(&foo_baz, "\"This file should be watched\"")?;

let cas = ContentAddressable::new(cas_tmp.path().join("cas"))?;

match instrumented_instantiation(&NixFile::from(shell), &cas).unwrap() {
Info::Success(s) => {
assert!(
s.paths.iter().any(|p| p.ends_with("foo/default.nix")),
"foo/default.nix should be watched!"
);
assert!(
!s.paths.iter().any(|p| p.ends_with("foo/bar")),
"foo/bar should not be watched!"
);
assert!(
s.paths.iter().any(|p| p.ends_with("foo/baz")),
"foo/baz should be watched!"
);
assert!(
s.paths.iter().any(|p| p.ends_with("dir")),
"dir should be watched!"
);
assert!(
!s.paths.iter().any(|p| p.ends_with("foo")),
"No imported directories must exist in watched paths: {:#?}",
s.paths
);
}
Info::Failure(f) => panic!("{:#?}\n{}", f, pretty_print_files_in_dir(root)),
};

Ok(())
}
}

0 comments on commit f29ee94

Please sign in to comment.