feat(tvix/eval): teach builtins.toXML context

XmlEmitter gains a NixContext field, and `write_typed_value` extends it
with all context elements present in the passed value.

Once all serialization is done, a into_context() function returns the
collected context, so we can construct a NixString with context.

Tests for this live in tvix-glue, as we use builtins.derivation, which
is not present in the tvix-eval crate.

Fixes b/398.

Change-Id: I85feaaa17b753885f8a017a54e419ec4e602af21
Reviewed-on: https://cl.tvl.fyi/c/depot/+/11704
Tested-by: BuildkiteCI
Reviewed-by: flokli <flokli@flokli.de>
Autosubmit: flokli <flokli@flokli.de>
Reviewed-by: Alyssa Ross <hi@alyssa.is>
This commit is contained in:
Florian Klink 2024-05-23 11:06:30 +02:00 committed by clbot
parent a4a313cdd2
commit ec8d79f3db
4 changed files with 53 additions and 6 deletions

View file

@ -1504,8 +1504,19 @@ mod pure_builtins {
} }
let mut buf: Vec<u8> = vec![]; let mut buf: Vec<u8> = vec![];
to_xml::value_to_xml(&mut buf, &value)?; let context = to_xml::value_to_xml(&mut buf, &value)?;
Ok(buf.into())
Ok((
buf,
// FUTUREWORK: We have a distinction between an empty context, and
// no context at all. Fix this.
if !context.is_empty() {
Some(Box::new(context))
} else {
None
},
)
.into())
} }
#[builtin("placeholder")] #[builtin("placeholder")]

View file

@ -6,11 +6,12 @@ use bstr::ByteSlice;
use std::borrow::Cow; use std::borrow::Cow;
use std::{io::Write, rc::Rc}; use std::{io::Write, rc::Rc};
use crate::{ErrorKind, Value}; use crate::{ErrorKind, NixContext, NixContextElement, Value};
/// Recursively serialise a value to XML. The value *must* have been /// Recursively serialise a value to XML. The value *must* have been
/// deep-forced before being passed to this function. /// deep-forced before being passed to this function.
pub fn value_to_xml<W: Write>(mut writer: W, value: &Value) -> Result<(), ErrorKind> { /// On success, returns the NixContext.
pub fn value_to_xml<W: Write>(mut writer: W, value: &Value) -> Result<NixContext, ErrorKind> {
// Write a literal document declaration, using C++-Nix-style // Write a literal document declaration, using C++-Nix-style
// single quotes. // single quotes.
writeln!(writer, "<?xml version='1.0' encoding='utf-8'?>")?; writeln!(writer, "<?xml version='1.0' encoding='utf-8'?>")?;
@ -21,7 +22,7 @@ pub fn value_to_xml<W: Write>(mut writer: W, value: &Value) -> Result<(), ErrorK
value_variant_to_xml(&mut emitter, value)?; value_variant_to_xml(&mut emitter, value)?;
emitter.write_closing_tag("expr")?; emitter.write_closing_tag("expr")?;
Ok(()) Ok(emitter.into_context())
} }
fn write_typed_value<W: Write, V: ToString>( fn write_typed_value<W: Write, V: ToString>(
@ -45,7 +46,12 @@ fn value_variant_to_xml<W: Write>(w: &mut XmlEmitter<W>, value: &Value) -> Resul
Value::Bool(b) => return write_typed_value(w, "bool", b), Value::Bool(b) => return write_typed_value(w, "bool", b),
Value::Integer(i) => return write_typed_value(w, "int", i), Value::Integer(i) => return write_typed_value(w, "int", i),
Value::Float(f) => return write_typed_value(w, "float", f), Value::Float(f) => return write_typed_value(w, "float", f),
Value::String(s) => return write_typed_value(w, "string", s.to_str()?), Value::String(s) => {
if let Some(context) = s.context() {
w.extend_context(context.iter().cloned());
}
return write_typed_value(w, "string", s.to_str()?);
}
Value::Path(p) => return write_typed_value(w, "path", p.to_string_lossy()), Value::Path(p) => return write_typed_value(w, "path", p.to_string_lossy()),
Value::List(list) => { Value::List(list) => {
@ -137,6 +143,7 @@ struct XmlEmitter<W> {
/// The current indentation /// The current indentation
cur_indent: usize, cur_indent: usize,
writer: W, writer: W,
context: NixContext,
} }
impl<W: Write> XmlEmitter<W> { impl<W: Write> XmlEmitter<W> {
@ -144,6 +151,7 @@ impl<W: Write> XmlEmitter<W> {
XmlEmitter { XmlEmitter {
cur_indent: 0, cur_indent: 0,
writer, writer,
context: Default::default(),
} }
} }
@ -245,6 +253,19 @@ impl<W: Write> XmlEmitter<W> {
_ => None, _ => None,
} }
} }
/// Extends the existing context with more context elements.
fn extend_context<T>(&mut self, iter: T)
where
T: IntoIterator<Item = NixContextElement>,
{
self.context.extend(iter)
}
/// Consumes [Self] and returns the [NixContext] collected.
fn into_context(self) -> NixContext {
self.context
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -0,0 +1 @@
[ { "/nix/store/y1s2fiq89v2h9vkb38w508ir20dwv6v2-test.drv" = { allOutputs = true; }; } false ]

View file

@ -0,0 +1,14 @@
[
# builtins.toXML retains context where there is.
(builtins.getContext (builtins.toXML {
inherit (derivation {
name = "test";
builder = "/bin/sh";
system = builtins.currentSystem;
}) drvPath;
}))
# this should have no context.
(builtins.hasContext
(builtins.toXML { }))
]