1use std::collections::{HashMap, HashSet, VecDeque};23use anyhow::{Result, bail};4use camino::{Utf8Component, Utf8Path, Utf8PathBuf};5use serde::Deserialize;67use crate::nix_raw::{derivation_free, derivation_to_json, store_drv_from_store_path};8use crate::{Store, copy_nix_str, with_default_context};910pub struct Derivation(*mut crate::nix_raw::derivation);11unsafe impl Send for Derivation {}1213impl Derivation {14 pub fn from_path(store: &Store, drv_path: &Utf8Path) -> Result<Self> {15 let store_path = store.parse_path(drv_path)?;16 let drv = with_default_context(|c, _| unsafe {17 store_drv_from_store_path(c, store.as_ptr(), store_path.as_ptr())18 });19 let drv = drv?;20 if drv.is_null() {21 bail!("failed to read derivation from {drv_path}");22 }23 Ok(Self(drv))24 }2526 pub fn to_json_string(&self) -> Result<String> {27 let mut out = String::new();28 with_default_context(|c, _| unsafe {29 derivation_to_json(c, self.0, Some(copy_nix_str), (&raw mut out).cast())30 })?;31 Ok(out)32 }3334 pub fn parsed(&self) -> Result<DrvParsed> {35 let s = self.to_json_string()?;36 Ok(serde_json::from_str(&s)?)37 }38}3940impl Drop for Derivation {41 fn drop(&mut self) {42 unsafe { derivation_free(self.0) };43 }44}4546#[derive(Debug, Deserialize)]47pub struct DrvParsed {48 pub inputs: DrvInputs,49 pub outputs: HashMap<String, DrvParsedOutput>,50}5152#[derive(Debug, Deserialize)]53pub struct DrvParsedOutput {54 #[serde(default)]55 pub path: Option<String>,56}5758#[derive(Debug, Deserialize)]59pub struct DrvInputs {60 #[serde(default)]61 pub srcs: Vec<Utf8PathBuf>,62 #[serde(default)]63 pub drvs: HashMap<Utf8PathBuf, DrvInputEntry>,64}6566#[derive(Debug, Deserialize)]67pub struct DrvInputEntry {68 pub outputs: Vec<String>,69}7071#[derive(Debug, Clone)]72pub struct DrvGraph {73 pub root: Utf8PathBuf,74 pub nodes: HashMap<Utf8PathBuf, DrvNode>,75}7677#[derive(Debug, Clone)]78pub struct DrvNode {79 pub name: String,80 pub input_drvs: HashMap<Utf8PathBuf, Vec<String>>,81 pub input_srcs: Vec<Utf8PathBuf>,82 83 pub outputs: HashMap<String, Utf8PathBuf>,84}8586impl DrvGraph {87 pub fn resolve(store: &Store, drv_path: &Utf8Path) -> Result<Self> {88 let sd = store.store_dir()?;89 let root = sd.join(drv_path);9091 let mut nodes = HashMap::new();92 let mut queue = VecDeque::new();93 let mut visited = HashSet::new();94 queue.push_back(root.clone());95 visited.insert(root.clone());9697 while let Some(path) = queue.pop_front() {98 let drv = Derivation::from_path(store, &path)?;99 let parsed = drv.parsed()?;100101 let input_drvs: HashMap<Utf8PathBuf, Vec<String>> = parsed102 .inputs103 .drvs104 .into_iter()105 .map(|(k, v)| (sd.join(&k), v.outputs))106 .collect();107 let input_srcs: Vec<Utf8PathBuf> = parsed108 .inputs109 .srcs110 .into_iter()111 .map(|k| sd.join(&k))112 .collect();113114 for dep_path in input_drvs.keys() {115 if visited.insert(dep_path.clone()) {116 queue.push_back(dep_path.clone());117 }118 }119120 let outputs: HashMap<String, Utf8PathBuf> = parsed121 .outputs122 .into_iter()123 .filter_map(|(name, out)| out.path.map(|p| (name, sd.join(&p))))124 .collect();125126 nodes.insert(127 path.clone(),128 DrvNode {129 name: extract_drv_name(&path),130 input_drvs,131 input_srcs,132 outputs,133 },134 );135 }136137 Ok(Self { root, nodes })138 }139140 pub fn wanted_outputs(&self, root_outputs: &[String]) -> HashMap<Utf8PathBuf, Vec<String>> {141 let mut wanted: HashMap<Utf8PathBuf, HashSet<String>> = HashMap::new();142 wanted.insert(self.root.clone(), root_outputs.iter().cloned().collect());143144 let mut queue: VecDeque<Utf8PathBuf> = VecDeque::new();145 queue.push_back(self.root.clone());146 while let Some(path) = queue.pop_front() {147 let Some(node) = self.nodes.get(&path) else {148 continue;149 };150 for (dep_path, dep_outputs) in &node.input_drvs {151 let entry = wanted.entry(dep_path.clone()).or_default();152 let mut changed = false;153 for o in dep_outputs {154 if entry.insert(o.clone()) {155 changed = true;156 }157 }158 if changed {159 queue.push_back(dep_path.clone());160 }161 }162 }163164 wanted165 .into_iter()166 .map(|(k, v)| {167 let mut v: Vec<_> = v.into_iter().collect();168 v.sort();169 (k, v)170 })171 .collect()172 }173}174175pub fn extract_drv_name(drv_path: &Utf8Path) -> String {176 let comp = drv_path177 .components()178 .rev()179 .next()180 .expect("drv path is at least one component");181 let Utf8Component::Normal(n) = comp else {182 panic!("drv path is normal");183 };184185 let n = n.strip_suffix(".drv").unwrap_or(n);186187 let n = n.split_once(' ').map(|(_, n)| n).unwrap_or(n);188189 n.to_owned()190}