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

Commit

Permalink
build_loop, builder: Use OutputPaths for explicit root names
Browse files Browse the repository at this point in the history
Use the `OutputPaths` struct to direct the creation of roots, so that
we don’t have to rely on “magical paths” anymore.

This is another step towards splitting instantiation and build.

Introduces a `DrvFile` wrapper so we can distinguish between
unrealized and realized (`StorePath`) store paths.
  • Loading branch information
Profpatsch committed Aug 28, 2019
1 parent 2c6b8a5 commit c576b95
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 22 deletions.
5 changes: 3 additions & 2 deletions src/build_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,11 @@ impl<'a> BuildLoop<'a> {

debug!("named drvs: {:#?}", build.output_paths);

// TODO: once build returns realized drvs, make those into roots
// create root for every field in OutputPaths
// TODO: remove build-0 dependency (see direnv.rs)
let output_paths = ::builder::OutputPaths {
shell_gc_root: roots.add("build-0", &build.output_paths.shell_gc_root)?,
// TODO: bad, this magic name "shell_gc_root" should not be known here (only in roots)
shell_gc_root: roots.add("shell_gc_root", &build.output_paths.shell_gc_root)?,
};

let event = BuildResults { output_paths };
Expand Down
37 changes: 37 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use std::process::{Command, Stdio};
use std::thread;
use NixFile;

// TODO: when moving to CallOpts, you have to change the names of the roots CallOpts generates!
fn instrumented_build(
root_nix_file: &NixFile,
cas: &ContentAddressable,
Expand Down Expand Up @@ -191,6 +192,42 @@ pub struct OutputPaths<T> {
pub shell_gc_root: T,
}

/// Return the name of each `OutputPaths` attribute.
pub fn output_path_attr_names() -> OutputPaths<String> {
OutputPaths {
shell_gc_root: String::from("shell_gc_root"),
}
}

impl<T> OutputPaths<T> {
/// `map` for `OutputPaths`.
pub fn map<F, U>(self, f: F) -> OutputPaths<U>
where
F: Fn(T) -> U,
{
OutputPaths {
shell_gc_root: f(self.shell_gc_root),
}
}

/// Like `map`, but return the first `Err`.
pub fn map_res<F, U, E>(self, f: F) -> Result<OutputPaths<U>, E>
where
F: Fn(T) -> Result<U, E>,
{
Ok(OutputPaths {
shell_gc_root: f(self.shell_gc_root)?,
})
}

/// `zip` for `OutputPaths`
pub fn zip<U>(self, us: OutputPaths<U>) -> OutputPaths<(T, U)> {
OutputPaths {
shell_gc_root: (self.shell_gc_root, us.shell_gc_root),
}
}
}

/// Possible errors from an individual evaluation
#[derive(Debug)]
pub enum Error {
Expand Down
13 changes: 12 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub mod project;
pub mod socket;
pub mod watch;

use std::path::PathBuf;
use std::path::{Path, PathBuf};

// OUT_DIR and build_rev.rs are generated by cargo, see ../build.rs
include!(concat!(env!("OUT_DIR"), "/build_rev.rs"));
Expand Down Expand Up @@ -80,3 +80,14 @@ impl From<PathBuf> for NixFile {
NixFile(p)
}
}

/// A .drv file (generated by `nix-instantiate`).
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
pub struct DrvFile(PathBuf);

impl DrvFile {
/// Underlying `Path`.
pub fn as_path(&self) -> &Path {
self.0.as_ref()
}
}
10 changes: 5 additions & 5 deletions src/ops/direnv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod version;

use self::version::{DirenvVersion, MIN_DIRENV_VERSION};
use crate::ops::{ok, ok_msg, ExitError, OpResult};
use crate::project::roots::Roots;
use crate::project::Project;
use crate::socket::communicate::client;
use crate::socket::communicate::{Ping, DEFAULT_READ_TIMEOUT};
Expand All @@ -14,9 +15,6 @@ use std::process::Command;
pub fn main(project: Project) -> OpResult {
check_direnv_version()?;

let mut shell_root = project.gc_root_path.to_owned();
shell_root.push("build-0"); // !!!

// TODO: don’t start build/evaluation automatically, let the user decide
if let Ok(client) = client::ping(DEFAULT_READ_TIMEOUT).connect(
&::socket::path::SocketPath::from(::ops::get_paths()?.daemon_socket_file()),
Expand All @@ -30,7 +28,9 @@ pub fn main(project: Project) -> OpResult {
eprintln!("Uh oh, your lorri daemon is not running.");
}

if !shell_root.exists() {
let root_paths = Roots::from_project(&project).paths();

if !root_paths.all_exist() {
return Err(ExitError::errmsg(
"Please start `lorri daemon` or run `lorri watch` before using direnv integration.",
));
Expand All @@ -50,7 +50,7 @@ watch_file "$EVALUATION_ROOT"
{}
"#,
shell_root.display(),
root_paths.shell_gc_root,
include_str!("envrc.bash")
))
}
Expand Down
2 changes: 1 addition & 1 deletion src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct Project {

/// Directory in which this project’s
/// garbage collection roots are stored.
pub gc_root_path: PathBuf,
gc_root_path: PathBuf,

/// Hash of the nix file’s absolute path.
hash: String,
Expand Down
71 changes: 61 additions & 10 deletions src/project/roots.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
//! TODO
//! Handling of nix GC roots
//!
//! TODO: inline this module into `::project`
use crate::project::Project;
use builder::OutputPaths;
use nix::StorePath;
use std::env;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};

/// Roots manipulation
#[derive(Clone)]
pub struct Roots {
root_dir: PathBuf,
/// The GC root directory in the lorri user cache dir
gc_root_path: PathBuf,
id: String,
}

Expand All @@ -22,6 +26,24 @@ impl RootPath {
}
}

impl OutputPaths<RootPath> {
/// Check whether all all GC roots exist.
pub fn all_exist(&self) -> bool {
let ex = |rpath: &RootPath| rpath.0.exists();
match self {
::builder::OutputPaths { shell_gc_root } => ex(shell_gc_root),
}
}

/// Check that the shell_gc_root is a directory.
pub fn shell_gc_root_is_dir(&self) -> bool {
match self.shell_gc_root.0.metadata() {
Err(_) => false,
Ok(m) => m.is_dir(),
}
}
}

/// Proxy through the `Display` class for `PathBuf`.
impl std::fmt::Display for RootPath {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
Expand All @@ -35,25 +57,52 @@ impl Roots {
/// and ID.
pub fn from_project(project: &Project) -> Roots {
Roots {
root_dir: project.gc_root_path.to_path_buf(),
gc_root_path: project.gc_root_path.to_path_buf(),
id: project.hash().to_string(),
}
}

fn attr_name(attr: String) -> String {
format!("attr-{}", attr)
}

/// Return the filesystem paths for these roots.
pub fn paths(&self) -> OutputPaths<RootPath> {
::builder::output_path_attr_names()
.map(|attr| RootPath(self.gc_root_path.join(&Self::attr_name(attr))))
}

/// Create roots to store paths.
pub fn create_roots(
&self,
// Important: this intentionally only allows creating
// roots to `StorePath`, not to `DrvFile`, because we have
// no use case for creating GC roots for drv files.
paths: OutputPaths<StorePath>,
) -> Result<OutputPaths<RootPath>, AddRootError>
where {
::builder::output_path_attr_names()
.zip(paths)
.map_res(|(name, path)| self.add(&Self::attr_name(name), &path))
}

// TODO: rename to create_root()
/// Store a new root under name
pub fn add(&self, name: &str, store_path: &::nix::StorePath) -> Result<RootPath, AddRootError> {
let mut path = self.root_dir.clone();
pub fn add(&self, name: &str, store_path: &StorePath) -> Result<RootPath, AddRootError> {
// final path in the `self.gc_root_path` directory
let mut path = self.gc_root_path.clone();
path.push(name);

debug!("Adding root from {:?} to {:?}", store_path, path,);
debug!("Adding root from {:?} to {:?}", store_path.as_path(), path,);
std::fs::remove_file(&path).or_else(|e| AddRootError::remove(e, &path))?;

std::fs::remove_file(&path).or_else(|e| AddRootError::remove(e, &path))?;

symlink(store_path.as_path(), &path)
// the forward GC root that points from the store path to our cache gc_roots dir
std::os::unix::fs::symlink(store_path.as_path(), &path)
.map_err(|e| AddRootError::symlink(e, store_path.as_path(), &path))?;

// the reverse GC root that points from nix to our cache gc_roots dir
let mut root = if let Ok(path) = env::var("NIX_STATE_DIR") {
PathBuf::from(path)
} else {
Expand All @@ -62,7 +111,7 @@ impl Roots {
root.push("gcroots");
root.push("per-user");

// TODO: check on start
// TODO: check on start of lorri
root.push(env::var("USER").expect("env var 'USER' must be set"));

// The user directory sometimes doesn’t exist,
Expand All @@ -76,8 +125,10 @@ impl Roots {
debug!("Connecting root from {:?} to {:?}", path, root,);
std::fs::remove_file(&root).or_else(|e| AddRootError::remove(e, &root))?;

symlink(&path, &root).map_err(|e| AddRootError::symlink(e, &path, &root))?;
std::os::unix::fs::symlink(&path, &root)
.map_err(|e| AddRootError::symlink(e, &path, &root))?;

// TODO: don’t return the RootPath here
Ok(RootPath(path))
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/direnvtestcase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct DirenvTestCase {
projectdir: TempDir,
// only kept around to not delete tempdir
#[allow(dead_code)]
cachedir: TempDir,
pub cachedir: TempDir,
project: Project,
}

Expand Down
20 changes: 18 additions & 2 deletions tests/integration/trivial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,26 @@ use direnv::DirenvValue;
use direnvtestcase::DirenvTestCase;

#[test]
fn trivial() {
fn trivial() -> std::io::Result<()> {
let mut testcase = DirenvTestCase::new("basic");
testcase.evaluate().expect("Failed to build the first time");
let res = testcase.evaluate().expect("Failed to build the first time");

assert!(
res.output_paths.all_exist(),
"no build output (build-0) in {}.\nContents of {}\n{}",
res.output_paths.shell_gc_root,
testcase.cachedir.path().display(),
std::str::from_utf8(
&std::process::Command::new("ls")
.args(&["-la", "--recursive"])
.args(&[testcase.cachedir.path().as_os_str()])
.output()?
.stdout
)
.unwrap()
);

let env = testcase.get_direnv_variables();
assert_eq!(env.get_env("MARKER"), DirenvValue::Value("present"));
Ok(())
}

0 comments on commit c576b95

Please sign in to comment.