git.delta.rocks / jrsonnet / refs/commits / 94ece5cae749

difftreelog

source

crates/nix-eval/src/value.rs7.7 KiBsourcehistory
1use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc};23use better_command::NixHandler;4use serde::{Serialize, de::DeserializeOwned};56use crate::{Error, NixBuildBatch, NixSession, Result, macros::NixExprBuilder, nix_go};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	Merge(NixExprBuilder),19}20impl Index {21	pub fn var(v: impl AsRef<str>) -> Self {22		let v = v.as_ref();23		assert!(24			!(v.contains('.') | v.contains(' ')),25			"bad variable name: {v}"26		);27		Self::Var(v.to_owned())28	}29	pub fn attr(v: impl AsRef<str>) -> Self {30		Self::String(v.as_ref().to_owned())31	}32	#[allow(dead_code)]33	pub fn apply(v: impl Serialize) -> Self {34		let serialized = nixlike::serialize(v).expect("invalid value for apply");35		Self::Apply(serialized.trim_end().to_owned())36	}37}38impl fmt::Display for Index {39	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {40		match self {41			Index::Var(v) => {42				write!(f, "{v}")43			}44			Index::String(k) => {45				let v = nixlike::format_identifier(k.as_str());46				write!(f, ".{v}")47			}48			Index::Apply(_) => {49				write!(f, "<apply>(...)")50			}51			Index::Expr(e) => {52				write!(f, "[{}]", e.out)53			}54			Index::ExprApply(_) => {55				write!(f, "<apply>(...)")56			}57			Index::Pipe(e) => {58				write!(f, "<map>({})", e.out)59			}60			Index::Merge(e) => {61				write!(f, "//({})", e.out)62			}63		}64	}65}66impl fmt::Debug for Index {67	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {68		write!(f, "{self}")69	}70}71struct PathDisplay<'i>(&'i [Index]);72impl fmt::Display for PathDisplay<'_> {73	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {74		if !matches!(self.0.first(), Some(Index::Var(_))) {75			write!(f, "<unknown>")?;76		}77		for i in self.0 {78			write!(f, "{i}")?;79		}80		Ok(())81	}82}83struct ValueInner {84	full_path: Vec<Index>,85	session: NixSession,86	value: u32,87}88#[derive(Clone)]89pub struct Value(Arc<ValueInner>);90impl Value {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: vec![],95			session,96			value: 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, query: &str) -> Result<Self> {103		// TODO: Verify that query is a valid variable name104		let vid = session.0.lock().await.execute_assign(query).await?;105		Ok(Self(Arc::new(ValueInner {106			full_path: vec![Index::Var(query.to_owned())],107			session,108			value: vid,109		})))110	}111	pub async fn select(&self, name: impl IntoIterator<Item = Index>) -> Result<Self> {112		let mut used_fields = Vec::new();113		let name = name.into_iter();114115		let mut full_path = self.0.full_path.clone();116		let mut query = self.sess_field_name();117		for v in name {118			full_path.push(v.clone());119			match v {120				Index::Var(_) => panic!("var item may only be first"),121				Index::String(s) => {122					let escaped =123						nixlike::serialize(s).expect("strings are always serialized successfully");124					query.push('.');125					query.push_str(escaped.trim());126				}127				Index::Apply(a) => {128					// In cases like `a {}.b` first `{}.b` will be evaluated, so `a {}` should be encased in `()`129					query = format!("({query} {a})");130				}131				Index::Expr(e) => {132					let index = Value::new(self.0.session.clone(), &e.out).await?;133					used_fields.push(index.clone());134					query.push('.');135					let index = format!("${{sess_field_{}}}", index.0.value);136					query.push_str(&index);137				}138				Index::ExprApply(e) => {139					let index = Value::new(self.0.session.clone(), &e.out).await?;140					used_fields.push(index.clone());141					query.push(' ');142					let index = format!("sess_field_{}", index.0.value);143					query.push_str(&index);144					query = format!("({query})");145				}146				Index::Pipe(v) => {147					let index = Value::new(self.0.session.clone(), &v.out).await?;148					used_fields.push(index.clone());149					let index = format!("sess_field_{}", index.0.value);150					query = format!("({index} {query})");151				}152				Index::Merge(v) => {153					let index = Value::new(self.0.session.clone(), &v.out).await?;154					used_fields.push(index.clone());155					let index = format!("sess_field_{}", index.0.value);156					query = format!("({query} // {index})");157				}158			}159		}160161		let vid = self162			.0163			.session164			.0165			.lock()166			.await167			.execute_assign(&query)168			.await169			.map_err(|e| e.context(self.attribute()))?;170		Ok(Self(Arc::new(ValueInner {171			full_path,172			session: self.0.session.clone(),173			value: vid,174		})))175	}176	pub async fn as_json<V: DeserializeOwned>(&self) -> Result<V> {177		let query = self.sess_field_name();178		self.0179			.session180			.0181			.lock()182			.await183			.execute_expression_to_json(&query)184			.await185			.map_err(|e| e.context(self.attribute()))186	}187	#[allow(dead_code)]188	pub async fn has_field(&self, name: &str) -> Result<bool> {189		let key = nixlike::escape_string(name);190		let query = format!("{} ? {key}", self.sess_field_name());191		self.0192			.session193			.0194			.lock()195			.await196			.execute_expression_to_json(&query)197			.await198			.map_err(|e| e.context(self.attribute()))199	}200	pub async fn list_fields(&self) -> Result<Vec<String>> {201		let query = format!("builtins.attrNames {}", self.sess_field_name());202		self.0203			.session204			.0205			.lock()206			.await207			.execute_expression_to_json(&query)208			.await209			.map_err(|e| e.context(self.attribute()))210	}211	pub async fn type_of(&self) -> Result<String> {212		let query = format!("builtins.typeOf {}", self.sess_field_name());213		self.0214			.session215			.0216			.lock()217			.await218			.execute_expression_to_json(&query)219			.await220			.map_err(|e| e.context(self.attribute()))221	}222	#[allow(dead_code)]223	pub async fn import(&self) -> Result<Self> {224		let import = Self::new(self.0.session.clone(), "import").await?;225		Ok(nix_go!(self | import))226	}227	fn sess_field_name(&self) -> String {228		format!("sess_field_{}", self.0.value)229	}230	pub async fn build_maybe_batch(231		&self,232		batch: Option<NixBuildBatch>,233	) -> Result<HashMap<String, PathBuf>> {234		if let Some(batch) = batch {235			batch.submit(self.clone()).await236		} else {237			self.build().await238		}239	}240	pub async fn build(&self) -> Result<HashMap<String, PathBuf>> {241		let query = format!(":b {}", self.sess_field_name());242		let vid = self243			.0244			.session245			.0246			.lock()247			.await248			.execute_expression_raw(&query, &mut NixHandler::default())249			.await?;250		if vid.is_empty() {251			return Err(Error::BuildFailed {252				attribute: self.attribute(),253				error: "build produced no output".to_owned(),254			});255		}256		let Some(vid) = vid.strip_prefix("This derivation produced the following outputs:\n")257		else {258			return Err(Error::BuildFailed {259				attribute: self.attribute(),260				error: format!("failed to parse output: {vid}"),261			});262		};263		let outputs = vid264			.split('\n')265			.filter(|v| !v.is_empty())266			.map(|v| v.split_once(" -> ").expect("unexpected build output"))267			.map(|(a, b)| (a.trim_start().to_owned(), PathBuf::from(b)))268			.collect();269		Ok(outputs)270	}271	/// Weakly convert string-like types (derivation/path/string) to string272	pub async fn to_string_weak(&self) -> Result<String> {273		let query = format!("\"${{{}}}\"", self.sess_field_name());274		let vid: String = self275			.0276			.session277			.0278			.lock()279			.await280			.execute_expression_to_json(&query)281			.await?;282		Ok(vid)283	}284285	fn attribute(&self) -> String {286		PathDisplay(&self.0.full_path).to_string()287	}288289	pub(crate) fn session(&self) -> NixSession {290		self.0.session.clone()291	}292293	pub(crate) fn session_field_id(&self) -> u32 {294		self.0.value295	}296}297impl Drop for ValueInner {298	fn drop(&mut self) {299		if let Ok(mut lock) = self.session.0.try_lock() {300			lock.free_list.push(self.value)301		}302		// Leaked303	}304}