1use std::collections::{HashMap, HashSet, VecDeque};2use std::ffi::CString;34use anyhow::{Result, bail};5use serde::Deserialize;67use crate::nix_raw::{derivation_free, derivation_to_json, store_drv_from_store_path};8use crate::{copy_nix_str, with_store_context};910fn store_dir() -> Result<String> {11 let mut out = String::new();12 with_store_context(|c, store, _| unsafe {13 crate::nix_raw::store_get_storedir(c, store, Some(copy_nix_str), (&raw mut out).cast())14 })?;15 Ok(out)16}1718fn to_absolute_store_path(store_dir: &str, path: &str) -> String {19 if path.starts_with('/') {20 path.to_owned()21 } else {22 format!("{store_dir}/{path}")23 }24}2526pub struct Derivation(*mut crate::nix_raw::derivation);27unsafe impl Send for Derivation {}2829impl Derivation {30 pub fn from_path(drv_path: &str) -> Result<Self> {31 let path_c = CString::new(drv_path)?;32 let store_path = with_store_context(|c, store, _| unsafe {33 crate::nix_raw::store_parse_path(c, store, path_c.as_ptr())34 })?;35 let drv = with_store_context(|c, store, _| unsafe {36 store_drv_from_store_path(c, store, store_path)37 });38 unsafe { crate::nix_raw::store_path_free(store_path) };39 let drv = drv?;40 if drv.is_null() {41 bail!("failed to read derivation from {drv_path}");42 }43 Ok(Self(drv))44 }4546 pub fn to_json_string(&self) -> Result<String> {47 let mut out = String::new();48 with_store_context(|c, _, _| unsafe {49 derivation_to_json(c, self.0, Some(copy_nix_str), (&raw mut out).cast())50 })?;51 Ok(out)52 }5354 pub fn parsed(&self) -> Result<DrvParsed> {55 let s = self.to_json_string()?;56 Ok(serde_json::from_str(&s)?)57 }58}5960impl Drop for Derivation {61 fn drop(&mut self) {62 unsafe { derivation_free(self.0) };63 }64}6566#[derive(Debug, Deserialize)]67pub struct DrvParsed {68 pub inputs: DrvInputs,69 pub outputs: HashMap<String, serde_json::Value>,70}7172#[derive(Debug, Deserialize)]73pub struct DrvInputs {74 #[serde(default)]75 pub srcs: Vec<String>,76 #[serde(default)]77 pub drvs: HashMap<String, DrvInputEntry>,78}7980#[derive(Debug, Deserialize)]81pub struct DrvInputEntry {82 pub outputs: Vec<String>,83}8485#[derive(Debug)]86pub struct DrvGraph {87 pub root: String,88 pub nodes: HashMap<String, DrvNode>,89}9091#[derive(Debug)]92pub struct DrvNode {93 pub name: String,94 pub input_drvs: HashMap<String, Vec<String>>,95 pub input_srcs: Vec<String>,96 pub outputs: Vec<String>,97}9899impl DrvGraph {100 pub fn resolve(drv_path: &str) -> Result<Self> {101 let sd = store_dir()?;102 let root = to_absolute_store_path(&sd, drv_path);103104 let mut nodes = HashMap::new();105 let mut queue = VecDeque::new();106 let mut visited = HashSet::new();107 queue.push_back(root.clone());108 visited.insert(root.clone());109110 while let Some(path) = queue.pop_front() {111 let drv = Derivation::from_path(&path)?;112 let parsed = drv.parsed()?;113114 let input_drvs: HashMap<String, Vec<String>> = parsed115 .inputs116 .drvs117 .into_iter()118 .map(|(k, v)| (to_absolute_store_path(&sd, &k), v.outputs))119 .collect();120121 for dep_path in input_drvs.keys() {122 if visited.insert(dep_path.clone()) {123 queue.push_back(dep_path.clone());124 }125 }126127 nodes.insert(128 path.clone(),129 DrvNode {130 name: extract_drv_name(&path),131 input_drvs,132 input_srcs: parsed.inputs.srcs,133 outputs: parsed.outputs.into_keys().collect(),134 },135 );136 }137138 Ok(Self { root, nodes })139 }140}141142fn extract_drv_name(drv_path: &str) -> String {143 drv_path144 .rsplit('/')145 .next()146 .and_then(|f| f.strip_suffix(".drv"))147 .and_then(|f| f.split_once('-').map(|(_, name)| name))148 .unwrap_or(drv_path)149 .to_owned()150}