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

Commit

Permalink
builder: split nix instantiation and build
Browse files Browse the repository at this point in the history
The build is split into a two-step process:
- nix-instantiate, which is called with -vv and parses its stderr (and
  produces drv files)
- nix-build, which builds the drv file

The idea behind this is that we can’t easily write tests which use
`nix-build` (nix-in-nix problem), so we are only gonna use the first
step for tests (which is enough to check the stderr parsing stuff).

Another problem this solves is that the stderr of nix-build is now
separated from the stderr of nix-instantiate, and nix-build is not
called with -vv, meaning we can show it to the user while it’s
generated, making it possible to fix the silence problem lorri has.

Closes #126
  • Loading branch information
Profpatsch committed Aug 8, 2019
1 parent 9705063 commit c712e01
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 73 deletions.
25 changes: 14 additions & 11 deletions src/build_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::project::roots;
use crate::project::roots::Roots;
use crate::project::Project;
use crate::watch::Watch;
use crate::StorePath;
use std::collections::HashMap;
use std::sync::mpsc::Sender;

Expand Down Expand Up @@ -82,9 +83,7 @@ impl<'a> BuildLoop<'a> {
tx.send(Event::Failure(failure))
.expect("Failed to notify the results of a failed evaluation");
}
otherwise => {
otherwise.unwrap();
}
Err(BuildError::Unrecoverable(err)) => panic!("Unrecoverable error!\n{:#?}", err),
}

self.watch.wait_for_change().expect("Waiter exited");
Expand All @@ -97,9 +96,19 @@ impl<'a> BuildLoop<'a> {
/// the evaluation.
pub fn once(&mut self) -> Result<BuildResults, BuildError> {
let build = builder::run(&self.project.nix_file, &self.project.cas)?;

match build {
builder::Info::Failure(f) => Err(BuildError::Recoverable(BuildExitFailure {
log_lines: f.log_lines,
})),
builder::Info::Success(s) => self.once_(s),
}
}

fn once_(&mut self, build: builder::Success<StorePath>) -> Result<BuildResults, BuildError> {
let roots = Roots::from_project(&self.project);

let paths = build.paths;
let paths = &build.paths;
debug!("original paths: {:?}", paths.len());

let paths = reduce_paths(&paths);
Expand Down Expand Up @@ -141,13 +150,7 @@ impl<'a> BuildLoop<'a> {
// add all new (reduced) nix sources to the input source watchlist
self.watch.extend(&paths.into_iter().collect::<Vec<_>>())?;

if build.exec_result.success() {
Ok(event)
} else {
Err(BuildError::Recoverable(BuildExitFailure {
log_lines: build.log_lines,
}))
}
Ok(event)
}
}

Expand Down
86 changes: 67 additions & 19 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ use std::path::PathBuf;
use std::process::{Command, Stdio};
use {DrvFile, NixFile, StorePath};

// TODO: when moving to CallOpts, you have to change the names of the roots CallOpts generates!
fn instrumented_build(
fn instrumented_instantiation(
root_nix_file: &NixFile,
cas: &ContentAddressable,
) -> Result<Info<DrvFile>, Error> {
) -> Result<Info<OutputPaths<DrvFile>>, Error> {
// We're looking for log lines matching:
//
// copied source '...' -> '/nix/store/...'
Expand All @@ -28,13 +27,12 @@ fn instrumented_build(
// to determine which files we should setup watches on.
// Increasing verbosity by two levels via `-vv` satisfies that.

let mut cmd = Command::new("nix-build");
let mut cmd = Command::new("nix-instantiate");

let logged_evaluation_nix = cas.file_from_string(include_str!("./logged-evaluation.nix"))?;

cmd.args(&[
OsStr::new("-vv"),
OsStr::new("--no-out-link"),
OsStr::new("--argstr"),
OsStr::new("runTimeClosure"),
OsStr::new(crate::RUN_TIME_CLOSURE),
Expand Down Expand Up @@ -98,6 +96,13 @@ fn instrumented_build(
},
);

if !output.status.success() {
return Ok(Info::Failure(Failure {
exec_result: output.status,
log_lines,
}));
}

// check whether we got all required `OutputPaths`
let output_paths = match output_paths {
// programming error
Expand All @@ -118,21 +123,37 @@ fn instrumented_build(
},
};

Ok(Info {
exec_result: output.status,
Ok(Info::Success(Success {
drvs: produced_drvs,
output_paths,
paths,
log_lines,
})
}))
}

/// Builds the Nix expression in `root_nix_file`.
///
/// Instruments the nix file to gain extra information,
/// which is valuable even if the build fails.
pub fn run(root_nix_file: &NixFile, cas: &ContentAddressable) -> Result<Info<DrvFile>, Error> {
instrumented_build(root_nix_file, cas)
pub fn run(root_nix_file: &NixFile, cas: &ContentAddressable) -> Result<Info<StorePath>, Error> {
let inst_info = instrumented_instantiation(root_nix_file, cas)?;
match inst_info {
Info::Success(s) => {
let drvs = s.output_paths.clone();
// TODO: we are only using shell_gc_root here, I don’t think
// we are using the shell anywhere anymore. Then we could remove
// it from OutputPaths and simplify logged-evaluation.nix!
let realized = ::nix::CallOpts::file(drvs.shell_gc_root.as_path()).path()?;
match s {
Success { paths, .. } => Ok(Info::Success(Success {
// TODO: duplication, remove drvs in favour of output_paths
drvs: vec![realized.clone()],
output_paths: realized,
paths,
})),
}
}
Info::Failure(f) => Ok(Info::Failure(f)),
}
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -189,17 +210,24 @@ fn parse_evaluation_line(line: &OsStr) -> LogDatum {
}
}

/// The results of an individual build.
/// The results of an individual instantiation/build.
/// Even if the exit code is not 0, there is still
/// valuable information in the output, like new paths
/// to watch.
#[derive(Debug)]
pub struct Info<T> {
/// The result of executing Nix
pub exec_result: std::process::ExitStatus,
pub enum Info<T> {
/// Nix ran successfully.
Success(Success<T>),
/// Nix returned a failing status code.
Failure(Failure),
}

/// A successful Nix run.
#[derive(Debug)]
pub struct Success<T> {
/// See `OutputPaths`
pub output_paths: OutputPaths<T>,
// TODO: move back to `OutputPaths<T>`
pub output_paths: T,

// TODO: this is redundant with shell_gc_root
/// A list of the evaluation's result derivations
Expand All @@ -208,6 +236,13 @@ pub struct Info<T> {
// TODO: rename to `sources` (it’s the input sources we have to watch)
/// A list of paths examined during the evaluation
pub paths: Vec<PathBuf>,
}

/// A failing Nix run.
#[derive(Debug)]
pub struct Failure {
/// The error status code
exec_result: std::process::ExitStatus,

/// A list of stderr log lines
pub log_lines: Vec<OsString>,
Expand Down Expand Up @@ -265,15 +300,23 @@ impl<T> OutputPaths<T> {
/// Possible errors from an individual evaluation
#[derive(Debug)]
pub enum Error {
/// IO error executing nix-instantiate
Io(std::io::Error),
/// Executing nix-instantiate failed
Instantiate(std::io::Error),

/// Executing nix-build failed
Build(::nix::OnePathError),

/// Failed to spawn a log processing thread
ThreadFailure(std::boxed::Box<(dyn std::any::Any + std::marker::Send + 'static)>),
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Error {
Error::Io(e)
Error::Instantiate(e)
}
}
impl From<::nix::OnePathError> for Error {
fn from(e: ::nix::OnePathError) -> Error {
Error::Build(e)
}
}
impl From<Box<dyn Any + Send + 'static>> for Error {
Expand Down Expand Up @@ -327,4 +370,9 @@ mod tests {
))
);
}

#[test]
fn transitive_source_file_detection() -> std::io::Result<()> {
Ok(())
}
}
Loading

0 comments on commit c712e01

Please sign in to comment.