Skip to main content

tatara_pkgs/
bridge.rs

1//! `NixpkgsBridge` — expose any attribute in an already-installed nixpkgs as
2//! a tatara `Derivation`. All heavy lifting (fetch, build, cache, store) stays
3//! in Nix; we only own the typed name.
4
5use tatara_nix::derivation::{BridgeTarget, Derivation};
6
7use crate::set::{PackageLookup, PackageSet, PackageSetError};
8
9/// Bridges a list of names through to an existing Nix expression universe.
10/// By default that's `import <nixpkgs> {}`, but any expression that yields an
11/// attribute set will do (flake revisions, release.nix, a private overlay).
12pub struct NixpkgsBridge {
13    /// Nix expression evaluating to the attribute root. Default: `"import <nixpkgs> {}"`.
14    pub pkg_set: Option<String>,
15    /// Names this bridge claims to know. Empty list = "open universe" — any
16    /// name resolves. Non-empty list = closed universe (used for mirror gen).
17    pub known_names: Vec<String>,
18    /// Human label for logs.
19    pub label: String,
20}
21
22impl Default for NixpkgsBridge {
23    fn default() -> Self {
24        Self {
25            pkg_set: None,
26            known_names: vec![],
27            label: "nixpkgs-bridge".into(),
28        }
29    }
30}
31
32impl NixpkgsBridge {
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    pub fn with_pkg_set(mut self, expr: impl Into<String>) -> Self {
38        self.pkg_set = Some(expr.into());
39        self
40    }
41
42    pub fn with_names(mut self, names: Vec<String>) -> Self {
43        self.known_names = names;
44        self
45    }
46
47    pub fn with_label(mut self, label: impl Into<String>) -> Self {
48        self.label = label.into();
49        self
50    }
51
52    /// Build a `Derivation` for a name without consulting `known_names`.
53    /// Useful when you're sure the attr exists upstream.
54    pub fn derivation(&self, attr_path: impl Into<String>) -> Derivation {
55        let attr = attr_path.into();
56        Derivation {
57            name: attr.clone(),
58            version: None,
59            inputs: vec![],
60            source: Default::default(),
61            builder: Default::default(),
62            outputs: Default::default(),
63            env: vec![],
64            sandbox: Default::default(),
65            bridge: Some(BridgeTarget {
66                attr_path: attr,
67                pkg_set: self.pkg_set.clone(),
68            }),
69            nix_expr: None,
70        }
71    }
72}
73
74impl PackageSet for NixpkgsBridge {
75    fn get(&self, name: &str) -> Result<PackageLookup, PackageSetError> {
76        // Open universe: always yes.
77        if self.known_names.is_empty() {
78            return Ok(Some(self.derivation(name)));
79        }
80        // Closed universe: only names we've been given.
81        if self.known_names.iter().any(|n| n == name) {
82            Ok(Some(self.derivation(name)))
83        } else {
84            Ok(None)
85        }
86    }
87
88    fn names(&self) -> Vec<String> {
89        self.known_names.clone()
90    }
91
92    fn label(&self) -> &str {
93        &self.label
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn open_bridge_resolves_anything() {
103        let b = NixpkgsBridge::new();
104        let d = b.get("hello").unwrap().expect("should resolve");
105        assert_eq!(d.name, "hello");
106        assert!(d.bridge.is_some());
107        assert_eq!(d.bridge.as_ref().unwrap().attr_path, "hello");
108        assert!(d.bridge.as_ref().unwrap().pkg_set.is_none());
109    }
110
111    #[test]
112    fn closed_bridge_only_resolves_known_names() {
113        let b = NixpkgsBridge::new().with_names(vec!["hello".into(), "bash".into()]);
114        assert!(b.get("hello").unwrap().is_some());
115        assert!(b.get("nonexistent").unwrap().is_none());
116        assert_eq!(b.names().len(), 2);
117    }
118
119    #[test]
120    fn custom_pkg_set_carries_through() {
121        let b = NixpkgsBridge::new().with_pkg_set("import ./release.nix {}");
122        let d = b.get("mypkg").unwrap().unwrap();
123        assert_eq!(
124            d.bridge.as_ref().unwrap().pkg_set.as_deref(),
125            Some("import ./release.nix {}")
126        );
127    }
128
129    #[test]
130    fn derivation_exposes_dotted_attr_path() {
131        let b = NixpkgsBridge::new();
132        let d = b.derivation("python3Packages.requests");
133        assert_eq!(d.name, "python3Packages.requests");
134        assert_eq!(
135            d.bridge.as_ref().unwrap().attr_path,
136            "python3Packages.requests"
137        );
138    }
139}