Move nix json types to extra crate

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2025-12-30 10:05:27 +01:00
parent d662ac59a3
commit a7986584d5
7 changed files with 934 additions and 8 deletions

View file

@ -1,14 +1,265 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
use std::collections::HashMap;
use std::fmt::Write;
use std::process::Stdio;
use futures::FutureExt;
use futures::StreamExt;
use futures::stream::BoxStream;
use nix_json::NixBuildLogLine;
use nix_json::RawNixDerivationInfoOutput;
use petgraph::Directed;
use petgraph::Graph;
use petgraph::prelude::NodeIndex;
use tokio::io::AsyncBufReadExt;
use tokio::process::Command;
#[derive(Debug)]
pub struct NixBuildResult {
derivation: String,
log: String,
success: bool,
}
impl NixBuildResult {
pub fn derivation(&self) -> &str {
&self.derivation
}
pub fn log(&self) -> &str {
&self.log
}
pub fn success(&self) -> bool {
self.success
}
}
#[derive(Debug)]
pub enum NixBuildError {}
pub trait NixInProgressBuild {
fn next_log_line(
&mut self,
) -> impl Future<Output = Option<Result<NixBuildLogLine, NixBuildError>>>;
}
pub trait NixBackend {
type InProgressBuild<'b>: NixInProgressBuild
where
Self: 'b;
fn start_build(
&self,
derivation: &str,
) -> impl Future<Output = Result<Self::InProgressBuild<'_>, NixBuildError>>;
fn get_needed_builds(
&self,
derivation: &str,
) -> impl Future<Output = Result<NixBuildGraph, NixBuildError>>;
}
pub struct NixCliBackend {
command_path: String,
}
pub struct NixCliBackendBuild {
child: BoxStream<'static, String>,
}
impl NixInProgressBuild for NixCliBackendBuild {
fn next_log_line(
&mut self,
) -> impl Future<Output = Option<Result<NixBuildLogLine, NixBuildError>>> {
self.child.next().map(|out| {
out.map(|line| Ok(serde_json::from_str(line.trim_start_matches("@nix ")).unwrap()))
})
}
}
impl NixBackend for NixCliBackend {
type InProgressBuild<'b> = NixCliBackendBuild;
fn start_build(
&self,
derivation: &str,
) -> impl Future<Output = Result<Self::InProgressBuild<'_>, NixBuildError>> {
async move {
let mut cmd = Command::new(&self.command_path)
.args(["build", "--log-format", "internal-json"])
.arg(derivation)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let init = cmd.stderr.take().unwrap();
Ok(NixCliBackendBuild {
child: futures::stream::unfold(
tokio::io::BufReader::new(init),
move |mut state| async move {
let mut buffer = String::new();
let num_bytes = state.read_line(&mut buffer).await.unwrap();
if num_bytes == 0 {
None
} else {
Some((std::mem::take(&mut buffer), state))
}
},
)
.boxed(),
})
}
}
fn get_needed_builds(
&self,
derivation: &str,
) -> impl Future<Output = Result<NixBuildGraph, NixBuildError>> {
async move {
let cmd = Command::new(&self.command_path)
.args(["derivation", "show", "--recursive"])
.arg(derivation)
.output()
.await
.unwrap();
let output: RawNixDerivationInfoOutput = serde_json::from_slice(&cmd.stdout).unwrap();
let mut build_graph = NixBuildGraph::default();
for (path, _info) in output.info() {
let internal_id = build_graph.dependencies.add_node(path.to_string());
build_graph.build_infos.insert(
path.to_string(),
NixBuildInfo {
internal_id,
present_in_binary_cache: false,
},
);
}
for (path, info) in output.info() {
let build_info = &build_graph.build_infos[path];
let cur_node = build_info.internal_id;
for (dep_path, _dep_info) in info.input_derivations() {
let other_node = build_graph.build_infos[dep_path].internal_id;
build_graph.dependencies.add_edge(
cur_node,
other_node,
NixBuildOutput {
output_name: String::new(),
},
);
}
}
Ok(build_graph)
}
}
}
pub struct NixBuilder<B> {
backend: B,
}
#[derive(Debug, Clone)]
pub struct NixBuildInfo {
internal_id: NodeIndex<usize>,
present_in_binary_cache: bool,
}
#[derive(Debug)]
pub struct NixBuildOutput {
output_name: String,
}
#[derive(Debug, Default)]
pub struct NixBuildGraph {
build_infos: HashMap<String, NixBuildInfo>,
dependencies: Graph<String, NixBuildOutput, Directed, usize>,
}
impl NixBuildGraph {
fn get_non_binary_builds(&self) -> impl Iterator<Item = (String, NixBuildInfo)> {
self.build_infos
.iter()
.filter(|(_, value)| !value.present_in_binary_cache)
.map(|(key, value)| (key.clone(), value.clone()))
}
}
impl<B> NixBuilder<B>
where
B: NixBackend,
{
pub async fn build(
&self,
derivation: String,
) -> Result<HashMap<String, NixBuildResult>, NixBuildError> {
let needed_builds = self.backend.get_needed_builds(&derivation).await?;
let mut started_build = self.backend.start_build(&derivation).await?;
let mut actually_built =
HashMap::from_iter(needed_builds.get_non_binary_builds().map(|(k, _v)| {
(
k.clone(),
NixBuildResult {
derivation: k,
log: String::new(),
success: false,
},
)
}));
let mut id_to_derivation = HashMap::new();
while let Some(next_log_line) = started_build.next_log_line().await {
let next_log_line = next_log_line?;
if let NixBuildLogLine::Start(start_log) = next_log_line {
if start_log.kind == Some(105) {
id_to_derivation.insert(
start_log.id,
start_log.fields[0].as_str().unwrap().to_string(),
);
}
} else if let NixBuildLogLine::Result(result) = next_log_line {
if result.kind == 101 {
if let Some(build) = actually_built.get_mut(&id_to_derivation[&result.id]) {
writeln!(&mut build.log, "{}", result.fields[0].as_str().unwrap()).unwrap()
} else {
panic!("Could not find correct id");
}
}
}
}
actually_built.retain(|_k, v| !v.log.is_empty());
Ok(actually_built)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
#[tokio::test]
async fn check_unpure() {
let builder = NixBuilder {
backend: NixCliBackend {
command_path: String::from("nix"),
},
};
let infos = builder
.build(".#checks.x86_64-linux.crate-fmt".to_string())
.await
.unwrap();
println!("Got: {infos:#?}");
}
}