Move nix json types to extra crate
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
d662ac59a3
commit
a7986584d5
7 changed files with 934 additions and 8 deletions
|
|
@ -7,3 +7,11 @@ authors.workspace = true
|
|||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
displaydoc.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
petgraph.workspace = true
|
||||
tokio.workspace = true
|
||||
futures.workspace = true
|
||||
nix-json.workspace = true
|
||||
|
|
|
|||
|
|
@ -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:#?}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue