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, DrvParsedOutput>,70}7172#[derive(Debug, Deserialize)]73pub struct DrvParsedOutput {74 #[serde(default)]75 pub path: Option<String>,76}7778#[derive(Debug, Deserialize)]79pub struct DrvInputs {80 #[serde(default)]81 pub srcs: Vec<String>,82 #[serde(default)]83 pub drvs: HashMap<String, DrvInputEntry>,84}8586#[derive(Debug, Deserialize)]87pub struct DrvInputEntry {88 pub outputs: Vec<String>,89}9091#[derive(Debug, Clone)]92pub struct DrvGraph {93 pub root: String,94 pub nodes: HashMap<String, DrvNode>,95}9697#[derive(Debug, Clone)]98pub struct DrvNode {99 pub name: String,100 pub input_drvs: HashMap<String, Vec<String>>,101 pub input_srcs: Vec<String>,102 103 pub outputs: HashMap<String, String>,104}105106impl DrvGraph {107 pub fn resolve(drv_path: &str) -> Result<Self> {108 let sd = store_dir()?;109 let root = to_absolute_store_path(&sd, drv_path);110111 let mut nodes = HashMap::new();112 let mut queue = VecDeque::new();113 let mut visited = HashSet::new();114 queue.push_back(root.clone());115 visited.insert(root.clone());116117 while let Some(path) = queue.pop_front() {118 let drv = Derivation::from_path(&path)?;119 let parsed = drv.parsed()?;120121 let input_drvs: HashMap<String, Vec<String>> = parsed122 .inputs123 .drvs124 .into_iter()125 .map(|(k, v)| (to_absolute_store_path(&sd, &k), v.outputs))126 .collect();127128 for dep_path in input_drvs.keys() {129 if visited.insert(dep_path.clone()) {130 queue.push_back(dep_path.clone());131 }132 }133134 let outputs: HashMap<String, String> = parsed135 .outputs136 .into_iter()137 .filter_map(|(name, out)| out.path.map(|p| (name, to_absolute_store_path(&sd, &p))))138 .collect();139140 nodes.insert(141 path.clone(),142 DrvNode {143 name: extract_drv_name(&path),144 input_drvs,145 input_srcs: parsed.inputs.srcs,146 outputs,147 },148 );149 }150151 Ok(Self { root, nodes })152 }153154 pub fn wanted_outputs(&self, root_outputs: &[String]) -> HashMap<String, Vec<String>> {155 let mut wanted: HashMap<String, HashSet<String>> = HashMap::new();156 wanted.insert(self.root.clone(), root_outputs.iter().cloned().collect());157158 let mut queue: VecDeque<String> = VecDeque::new();159 queue.push_back(self.root.clone());160 while let Some(path) = queue.pop_front() {161 let Some(node) = self.nodes.get(&path) else {162 continue;163 };164 for (dep_path, dep_outputs) in &node.input_drvs {165 let entry = wanted.entry(dep_path.clone()).or_default();166 let mut changed = false;167 for o in dep_outputs {168 if entry.insert(o.clone()) {169 changed = true;170 }171 }172 if changed {173 queue.push_back(dep_path.clone());174 }175 }176 }177178 wanted179 .into_iter()180 .map(|(k, v)| {181 let mut v: Vec<_> = v.into_iter().collect();182 v.sort();183 (k, v)184 })185 .collect()186 }187}188189fn extract_drv_name(drv_path: &str) -> String {190 drv_path191 .rsplit('/')192 .next()193 .and_then(|f| f.strip_suffix(".drv"))194 .and_then(|f| f.split_once('-').map(|(_, name)| name))195 .unwrap_or(drv_path)196 .to_owned()197}