Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refact(ops/stream_events): directly construct json value #122

Open
wants to merge 1 commit into
base: canon
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 6 additions & 65 deletions src/build_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::path::PathBuf;
/// Build events that can happen.
/// Abstracting over its internal to make different serialize instances possible.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EventI<NixFile, Reason, OutputPath, BuildError> {
pub enum Event {
/// Demarks a stream of events from recent history becoming live
SectionEnd,
/// A build has started
Expand All @@ -32,7 +32,7 @@ pub enum EventI<NixFile, Reason, OutputPath, BuildError> {
/// The shell.nix file for the building project
nix_file: NixFile,
/// the output paths of the build
rooted_output_paths: OutputPath,
rooted_output_paths: builder::OutputPath,
},
/// A build command returned a failing exit status
Failure {
Expand All @@ -43,75 +43,16 @@ pub enum EventI<NixFile, Reason, OutputPath, BuildError> {
},
}

/// Builder events sent back over `BuildLoop.tx`.
pub type Event = EventI<NixFile, Reason, builder::OutputPath<project::RootPath>, BuildError>;

impl<NixFile, Reason, OutputPath, BuildError> EventI<NixFile, Reason, OutputPath, BuildError> {
/// Map over the inner types.
pub fn map<F, G, H, I, NixFile2, Reason2, OutputPaths2, BuildError2>(
self,
nix_file_f: F,
reason_f: G,
output_paths_f: H,
build_error_f: I,
) -> EventI<NixFile2, Reason2, OutputPaths2, BuildError2>
where
F: Fn(NixFile) -> NixFile2,
G: Fn(Reason) -> Reason2,
H: Fn(OutputPath) -> OutputPaths2,
I: Fn(BuildError) -> BuildError2,
{
use EventI::*;
match self {
SectionEnd => SectionEnd,
Started { nix_file, reason } => Started {
nix_file: nix_file_f(nix_file),
reason: reason_f(reason),
},
Completed {
nix_file,
rooted_output_paths,
} => Completed {
nix_file: nix_file_f(nix_file),
rooted_output_paths: output_paths_f(rooted_output_paths),
},
Failure { nix_file, failure } => Failure {
nix_file: nix_file_f(nix_file),
failure: build_error_f(failure),
},
}
}
}

/// Description of the project change that triggered a build.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ReasonI<NixFile> {
/// When a project is presented to Lorri to track, it's built for this reason.
ProjectAdded(NixFile),
/// When a ping is received.
pub enum Reason {
/// We received a ping, requesting us to reevaluate and maybe build the project
PingReceived,
/// When there is a filesystem change, the first changed file is recorded,
/// along with a count of other filesystem events.
FilesChanged(Vec<PathBuf>),
}

impl<NixFile> ReasonI<NixFile> {
/// Map over the inner types.
pub fn map<F, NixFile2>(self, nix_file_f: F) -> ReasonI<NixFile2>
where
F: Fn(NixFile) -> NixFile2,
{
use ReasonI::*;
match self {
ProjectAdded(nix_file) => ProjectAdded(nix_file_f(nix_file)),
PingReceived => PingReceived,
FilesChanged(vec) => FilesChanged(vec),
}
}
}

type Reason = ReasonI<NixFile>;

/// The BuildLoop repeatedly builds the Nix expression in
/// `project` each time a source file influencing
/// a previous build changes.
Expand Down Expand Up @@ -315,7 +256,7 @@ impl<'a> BuildLoop<'a> {
///
/// This will create GC roots and expand the file watch list for
/// the evaluation.
pub fn once(&mut self) -> Result<builder::OutputPath<project::RootPath>, BuildError> {
pub fn once(&mut self) -> Result<builder::OutputPath, BuildError> {
let nix_file = self.project.nix_file.clone();
let cas = self.project.cas.clone();
let extra_nix_options = self.extra_nix_options.clone();
Expand All @@ -331,7 +272,7 @@ impl<'a> BuildLoop<'a> {
fn handle_run_result(
&mut self,
run_result: Result<builder::RunResult, BuildError>,
) -> Result<builder::OutputPath<project::RootPath>, BuildError> {
) -> Result<builder::OutputPath, BuildError> {
let run_result = run_result?;
let paths = run_result.referenced_paths;

Expand Down
17 changes: 3 additions & 14 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use crate::cas::ContentAddressable;
use crate::nix::{options::NixOptions, StorePath};
use crate::osstrlines;
use crate::project::RootPath;
use crate::watch::WatchPathBuf;
use crate::{DrvFile, NixFile};
use regex::Regex;
Expand Down Expand Up @@ -475,21 +476,9 @@ where

/// Output path generated by `logged-evaluation.nix`
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputPath<T> {
pub struct OutputPath {
/// Shell path modified to work as a gc root
pub shell_gc_root: T,
}

impl<T> OutputPath<T> {
/// map over the inner type.
pub fn map<F, T2>(self, f: F) -> OutputPath<T2>
where
F: Fn(T) -> T2,
{
OutputPath {
shell_gc_root: f(self.shell_gc_root),
}
}
pub shell_gc_root: RootPath,
}

#[cfg(test)]
Expand Down
18 changes: 16 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ impl AbsPathBuf {
self.0.display()
}

/// Print a path to a json string, assuming it is UTF-8, converting any non-utf codeblocks to replacement characters
pub fn to_json_value(&self) -> serde_json::Value {
path_to_json_string(self.0.as_path())
}

/// Joins a path to the end of this absolute path.
/// If the path is absolute, it will replace this absolute path.
pub fn join<P: AsRef<Path>>(&self, pb: P) -> Self {
Expand Down Expand Up @@ -115,13 +120,22 @@ impl NixFile {
pub fn as_absolute_path(&self) -> &Path {
self.0.as_path()
}
}

impl NixFile {
/// `display` the path.
pub fn display(&self) -> std::path::Display {
self.0.display()
}

/// Print a path to a json string, assuming it is UTF-8, converting any non-utf codeblocks to replacement characters
pub fn to_json_value(&self) -> serde_json::Value {
path_to_json_string(self.0.as_path())
}
}

/// Print a path to a json string, assuming it is UTF-8, converting any non-utf codeblocks to replacement characters
fn path_to_json_string(p: &Path) -> serde_json::Value {
let s = p.as_os_str().to_string_lossy().into_owned();
serde_json::json!(s)
}

impl From<AbsPathBuf> for NixFile {
Expand Down
71 changes: 33 additions & 38 deletions src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ mod direnv;
pub mod error;

use crate::build_loop::BuildLoop;
use crate::build_loop::{Event, EventI, ReasonI};
use crate::build_loop::Event;
use crate::build_loop::Reason;
use crate::builder::OutputPath;
use crate::cas::ContentAddressable;
use crate::changelog;
Expand All @@ -19,6 +20,7 @@ use crate::nix::options::NixOptions;
use crate::nix::CallOpts;
use crate::ops::direnv::{DirenvVersion, MIN_DIRENV_VERSION};
use crate::ops::error::{ExitAs, ExitError, ExitErrorType};
use crate::path_to_json_string;
use crate::project::{NixGcRootUserDir, Project};
use crate::run_async::Async;
use crate::socket::path::SocketPath;
Expand All @@ -44,6 +46,7 @@ use std::{fmt::Debug, fs::remove_dir_all};
use anyhow::Context;
use crossbeam_channel as chan;

use serde_json::json;
use slog::{debug, info, warn};
use thiserror::Error;

Expand Down Expand Up @@ -598,30 +601,6 @@ impl FromStr for EventKind {
}
}

// These types are just transparent newtype wrappers to implement a different serde class and JsonEncode

/// For now use the EventI structure, in the future we might want to split it off.
/// At least it will show us that we need to change something here if we change it
/// and it relates to this interface.
#[derive(Serialize)]
#[serde(transparent)]
struct StreamEvent(EventI<StreamNixFile, StreamReason, StreamOutputPath, StreamBuildError>);

/// Nix files are encoded as strings
#[derive(Serialize)]
#[serde(transparent)]
struct StreamNixFile(String);

/// Same here, the reason contains a nix file which has to be converted to a string.
#[derive(Serialize)]
#[serde(transparent)]
struct StreamReason(ReasonI<String>);

/// And same here, OutputPaths are GcRoots and have to be converted as well.
#[derive(Serialize)]
#[serde(transparent)]
struct StreamOutputPath(OutputPath<String>);

/// Just expose the error message for now.
#[derive(Serialize)]
struct StreamBuildError {
Expand Down Expand Up @@ -676,21 +655,37 @@ pub fn stream_events(kind: EventKind, logger: &slog::Logger) -> Result<(), ExitE
}
ev => match (snapshot_done, &kind) {
(_, EventKind::All) | (false, EventKind::Snapshot) | (true, EventKind::Live) => {
fn nix_file_string(nix_file: NixFile) -> String {
nix_file.display().to_string()
}
let json: serde_json::Value = match ev {
Event::SectionEnd => json!({"SectionEnd":{}}),
Event::Started { nix_file, reason } =>
json!({
"Started": {
"nix_file": nix_file.to_json_value(),
"reason": match reason {
Reason::PingReceived => json!({"PingReceived": {}}),
Reason::FilesChanged(files) => json!({"FilesChanged": files.iter().map(|p| path_to_json_string(p)).collect::<Vec<serde_json::Value>>()})
}
}
}),
Event::Completed { nix_file, rooted_output_paths } => json!({
"Completed": {
"nix_file": nix_file.to_json_value(),
"rooted_output_paths": {
"shell_gc_root": rooted_output_paths.shell_gc_root.0.to_json_value()
}
}
}),
Event::Failure { nix_file, failure } => json!({
"Failure": {
"nix_file": nix_file.to_json_value(),
"failure": { "message": format!("{}", failure) }
}
}),
};

serde_json::to_writer(
std::io::stdout(),
&StreamEvent(ev.map(
|nix_file| StreamNixFile(nix_file_string(nix_file)),
|reason| StreamReason(reason.map(nix_file_string)),
|output_path| {
StreamOutputPath(output_path.map(|o| o.display().to_string()))
},
|build_error| StreamBuildError {
message: format!("{}", build_error),
},
)),
&json
)
.expect("couldn't serialize event");
writeln!(std::io::stdout()).expect("couldn't serialize event");
Expand Down
6 changes: 3 additions & 3 deletions src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl Project {
}

/// Return the filesystem paths for these roots.
pub fn root_paths(&self) -> OutputPath<RootPath> {
pub fn root_paths(&self) -> OutputPath {
OutputPath {
shell_gc_root: RootPath(self.shell_gc_root()),
}
Expand All @@ -105,7 +105,7 @@ impl Project {
path: RootedPath,
nix_gc_root_user_dir: NixGcRootUserDir,
logger: &slog::Logger,
) -> Result<OutputPath<RootPath>, AddRootError>
) -> Result<OutputPath, AddRootError>
where {
let store_path = &path.path;

Expand Down Expand Up @@ -158,7 +158,7 @@ impl RootPath {
}
}

impl OutputPath<RootPath> {
impl OutputPath {
/// Check whether all all GC roots exist.
pub fn all_exist(&self) -> bool {
let crate::builder::OutputPath { shell_gc_root } = self;
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/direnvtestcase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl DirenvTestCase {
}

/// Execute the build loop one time
pub fn evaluate(&mut self) -> Result<builder::OutputPath<project::RootPath>, BuildError> {
pub fn evaluate(&mut self) -> Result<builder::OutputPath, BuildError> {
let username = project::Username::from_env_var().unwrap();
BuildLoop::new(
&self.project,
Expand Down
Loading