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();107108 for dep_path in input_drvs.keys() {109 if visited.insert(dep_path.clone()) {110 queue.push_back(dep_path.clone());111 }112 }113114 let outputs: HashMap<String, Utf8PathBuf> = parsed115 .outputs116 .into_iter()117 .filter_map(|(name, out)| out.path.map(|p| (name, sd.join(&p))))118 .collect();119120 nodes.insert(121 path.clone(),122 DrvNode {123 name: extract_drv_name(&path),124 input_drvs,125 input_srcs: parsed.inputs.srcs,126 outputs,127 },128 );129 }130131 Ok(Self { root, nodes })132 }133134 pub fn wanted_outputs(&self, root_outputs: &[String]) -> HashMap<Utf8PathBuf, Vec<String>> {135 let mut wanted: HashMap<Utf8PathBuf, HashSet<String>> = HashMap::new();136 wanted.insert(self.root.clone(), root_outputs.iter().cloned().collect());137138 let mut queue: VecDeque<Utf8PathBuf> = VecDeque::new();139 queue.push_back(self.root.clone());140 while let Some(path) = queue.pop_front() {141 let Some(node) = self.nodes.get(&path) else {142 continue;143 };144 for (dep_path, dep_outputs) in &node.input_drvs {145 let entry = wanted.entry(dep_path.clone()).or_default();146 let mut changed = false;147 for o in dep_outputs {148 if entry.insert(o.clone()) {149 changed = true;150 }151 }152 if changed {153 queue.push_back(dep_path.clone());154 }155 }156 }157158 wanted159 .into_iter()160 .map(|(k, v)| {161 let mut v: Vec<_> = v.into_iter().collect();162 v.sort();163 (k, v)164 })165 .collect()166 }167}168169pub fn extract_drv_name(drv_path: &Utf8Path) -> String {170 let comp = drv_path171 .components()172 .rev()173 .next()174 .expect("drv path is at least one component");175 let Utf8Component::Normal(n) = comp else {176 panic!("drv path is normal");177 };178179 let n = n.strip_suffix(".drv").unwrap_or(n);180181 let n = n.split_once(' ').map(|(_, n)| n).unwrap_or(n);182183 n.to_owned()184}