git.delta.rocks / jrsonnet / refs/commits / 064468b85673

difftreelog

source

crates/nix-eval/src/value.rs8.0 KiBsourcehistory
1use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc};23use better_command::NixHandler;4use serde::{de::DeserializeOwned, Serialize};56use crate::{macros::NixExprBuilder, nix_go, Error, NixBuildBatch, NixSession, Result};78#[derive(Clone)]9pub enum Index {10	Var(String),11	String(String),12	#[allow(dead_code)]13	Apply(String),14	#[allow(dead_code)]15	Expr(NixExprBuilder),16	ExprApply(NixExprBuilder),17	Pipe(NixExprBuilder),18}19impl Index {20	pub fn var(v: impl AsRef<str>) -> Self {21		let v = v.as_ref();22		assert!(23			!(v.contains('.') | v.contains(' ')),24			"bad variable name: {v}"25		);26		Self::Var(v.to_owned())27	}28	pub fn attr(v: impl AsRef<str>) -> Self {29		Self::String(v.as_ref().to_owned())30	}31	#[allow(dead_code)]32	pub fn apply(v: impl Serialize) -> Self {33		let serialized = nixlike::serialize(v).expect("invalid value for apply");34		Self::Apply(serialized.trim_end().to_owned())35	}36}37impl fmt::Display for Index {38	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {39		match self {40			Index::Var(v) => {41				write!(f, "{v}")42			}43			Index::String(k) => {44				let v = nixlike::format_identifier(k.as_str());45				write!(f, ".{v}")46			}47			Index::Apply(_) => {48				write!(f, "<apply>(...)")49			}50			Index::Expr(e) => {51				write!(f, "[{}]", e.out)52			}53			Index::ExprApply(_) => {54				write!(f, "<apply>(...)")55			}56			Index::Pipe(e) => {57				write!(f, "<map>({})", e.out)58			}59		}60	}61}62impl fmt::Debug for Index {63	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {64		write!(f, "{self}")65	}66}67struct PathDisplay<'i>(&'i [Index]);68impl fmt::Display for PathDisplay<'_> {69	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {70		for i in self.0 {71			write!(f, "{i}")?;72		}73		Ok(())74	}75}76struct ValueInner {77	full_path: Option<Vec<Index>>,78	session: NixSession,79	value: Option<u32>,80}81#[derive(Clone)]82pub struct Value(Arc<ValueInner>);83impl Value {84	fn root(session: NixSession) -> Self {85		Self(Arc::new(ValueInner {86			full_path: Some(vec![]),87			session,88			value: None,89		}))90	}91	async fn new(session: NixSession, query: &str) -> Result<Self> {92		let vid = session.0.lock().await.execute_assign(query).await?;93		Ok(Self(Arc::new(ValueInner {94			full_path: None,95			session,96			value: Some(vid),97		})))98	}99	/// Get a top-level binding.100	///101	/// In flake repl session, every output is exposed as top-level binding.102	pub async fn binding(session: NixSession, field: &str) -> Result<Self> {103		Self::root(session).select([Index::var(field)]).await104	}105	pub async fn select<'a>(&self, name: impl IntoIterator<Item = Index>) -> Result<Self> {106		let mut used_fields = Vec::new();107		let mut name = name.into_iter();108109		let mut full_path = self.0.full_path.clone();110		let mut query = if let Some(id) = self.0.value {111			format!("sess_field_{id}")112		} else {113			let first = name.next();114			if let Some(Index::Var(i)) = first {115				if let Some(full_path) = &mut full_path {116					full_path.push(Index::Var(i.clone()));117				}118				i.clone()119			} else {120				panic!("first path item should be variable, got {first:?}")121			}122		};123		for v in name {124			if let Some(full_path) = &mut full_path {125				full_path.push(v.clone());126			}127			match v {128				Index::Var(_) => panic!("var item may only be first"),129				Index::String(s) => {130					let escaped =131						nixlike::serialize(s).expect("strings are always serialized successfully");132					query.push('.');133					query.push_str(escaped.trim());134				}135				Index::Apply(a) => {136					// In cases like `a {}.b` first `{}.b` will be evaluated, so `a {}` should be encased in `()`137					query = format!("({query} {a})");138				}139				Index::Expr(e) => {140					let index = Value::new(self.0.session.clone(), &e.out).await?;141					used_fields.push(index.clone());142					query.push('.');143					let index = format!("${{sess_field_{}}}", index.0.value.expect("value"));144					query.push_str(&index);145				}146				Index::ExprApply(e) => {147					let index = Value::new(self.0.session.clone(), &e.out).await?;148					used_fields.push(index.clone());149					query.push(' ');150					let index = format!("sess_field_{}", index.0.value.expect("value"));151					query.push_str(&index);152					query = format!("({query})");153				}154				Index::Pipe(v) => {155					let index = Value::new(self.0.session.clone(), &v.out).await?;156					used_fields.push(index.clone());157					let index = format!("sess_field_{}", index.0.value.expect("value"));158					query = format!("({index} {query})");159				}160			}161		}162163		let vid = self164			.0165			.session166			.0167			.lock()168			.await169			.execute_assign(&query)170			.await171			.map_err(|e| e.context(self.attribute()))?;172		Ok(Self(Arc::new(ValueInner {173			full_path,174			session: self.0.session.clone(),175			value: Some(vid),176		})))177	}178	pub async fn as_json<V: DeserializeOwned>(&self) -> Result<V> {179		let id = self.0.value.expect("can't serialize root field");180		let query = format!("sess_field_{id}");181		self.0182			.session183			.0184			.lock()185			.await186			.execute_expression_to_json(&query)187			.await188			.map_err(|e| e.context(self.attribute()))189	}190	#[allow(dead_code)]191	pub async fn has_field(&self, name: &str) -> Result<bool> {192		let id = self.0.value.expect("can't list root fields");193		let key = nixlike::escape_string(name);194		let query = format!("sess_field_{id} ? {key}");195		self.0196			.session197			.0198			.lock()199			.await200			.execute_expression_to_json(&query)201			.await202			.map_err(|e| e.context(self.attribute()))203	}204	pub async fn list_fields(&self) -> Result<Vec<String>> {205		let id = self.0.value.expect("can't list root fields");206		let query = format!("builtins.attrNames sess_field_{id}");207		self.0208			.session209			.0210			.lock()211			.await212			.execute_expression_to_json(&query)213			.await214			.map_err(|e| e.context(self.attribute()))215	}216	pub async fn type_of(&self) -> Result<String> {217		let id = self.0.value.expect("can't list root fields");218		let query = format!("builtins.typeOf sess_field_{id}");219		self.0220			.session221			.0222			.lock()223			.await224			.execute_expression_to_json(&query)225			.await226			.map_err(|e| e.context(self.attribute()))227	}228	#[allow(dead_code)]229	pub async fn import(&self) -> Result<Self> {230		let import = Self::new(self.0.session.clone(), "import").await?;231		Ok(nix_go!(self | import))232	}233	pub async fn build_maybe_batch(234		&self,235		batch: Option<NixBuildBatch>,236	) -> Result<HashMap<String, PathBuf>> {237		if let Some(batch) = batch {238			batch.submit(self.clone()).await239		} else {240			self.build().await241		}242	}243	pub async fn build(&self) -> Result<HashMap<String, PathBuf>> {244		let id = self.0.value.expect("can't use build on not-value");245		let query = format!(":b sess_field_{id}");246		let vid = self247			.0248			.session249			.0250			.lock()251			.await252			.execute_expression_raw(&query, &mut NixHandler::default())253			.await?;254		if vid.is_empty() {255			return Err(Error::BuildFailed {256				attribute: self.attribute(),257				error: "build produced no output".to_owned(),258			});259		}260		let Some(vid) = vid.strip_prefix("This derivation produced the following outputs:\n")261		else {262			return Err(Error::BuildFailed {263				attribute: self.attribute(),264				error: format!("failed to parse output: {vid}"),265			});266		};267		let outputs = vid268			.split('\n')269			.filter(|v| !v.is_empty())270			.map(|v| v.split_once(" -> ").expect("unexpected build output"))271			.map(|(a, b)| (a.trim_start().to_owned(), PathBuf::from(b)))272			.collect();273		Ok(outputs)274	}275	/// Weakly convert string-like types (derivation/path/string) to string276	pub async fn to_string_weak(&self) -> Result<String> {277		let id = self.0.value.expect("can't use build on not-value");278		let query = format!("\"${{sess_field_{id}}}\"");279		let vid: String = self280			.0281			.session282			.0283			.lock()284			.await285			.execute_expression_to_json(&query)286			.await?;287		Ok(vid)288	}289290	fn attribute(&self) -> String {291		if let Some(full_path) = &self.0.full_path {292			PathDisplay(full_path).to_string()293		} else {294			"<root>".to_owned()295		}296	}297298	pub(crate) fn session(&self) -> NixSession {299		self.0.session.clone()300	}301302	pub(crate) fn session_field_id(&self) -> u32 {303		self.0.value.expect("not root")304	}305}306impl Drop for ValueInner {307	fn drop(&mut self) {308		if let Some(id) = self.value {309			if let Ok(mut lock) = self.session.0.try_lock() {310				lock.free_list.push(id)311			}312			// Leaked313		}314	}315}