use std::collections::BTreeMap;
use std::rc::Rc;
use crate::eval_ir::{coerce_to_string_ctx, IrAttrs, IrContextElem, IrEvalError, IrValue};
use sui_compat::derivation::Derivation;
pub(crate) fn build_derivation(arg: &IrValue) -> Result<IrValue, IrEvalError> {
let forced = arg.force()?;
let input = match &forced {
IrValue::Attrs(a) => a.clone(),
other => {
return Err(IrEvalError::TypeMismatch {
expected: "set",
got: other.type_name(),
})
}
};
let name = force_attr_string(&input, "name")?;
let (drv_path, out_paths) = compute_full_drv(&input, &name)?;
build_derivation_result(&input, &name, &drv_path, &out_paths)
}
fn force_attr_string(input: &IrAttrs, key: &str) -> Result<String, IrEvalError> {
let v = input
.get(key)
.ok_or_else(|| IrEvalError::AttrNotFound(key.to_string()))?;
let forced = v.force()?;
let (s, _ctx) = coerce_to_string_ctx(&forced, true)?;
Ok(s)
}
fn optional_attr_string(input: &IrAttrs, key: &str) -> Result<Option<String>, IrEvalError> {
match input.get(key) {
None => Ok(None),
Some(v) => {
let forced = v.force()?;
let (s, _ctx) = coerce_to_string_ctx(&forced, true)?;
Ok(Some(s))
}
}
}
fn compute_full_drv(
input: &IrAttrs,
name: &str,
) -> Result<(String, BTreeMap<String, String>), IrEvalError> {
let drv = construct_derivation(input, name)?;
compute_derivation_outputs(input, name, drv)
}
fn construct_derivation(input: &IrAttrs, name: &str) -> Result<Derivation, IrEvalError> {
use crate::eval_ir::IrStringContext;
let system = force_attr_string(input, "system")?;
let mut collected_ctx = IrStringContext::new();
let builder = {
let v = input.get("builder").ok_or_else(|| {
IrEvalError::TypeError("derivation: missing required attribute 'builder'".into())
})?;
let forced = v.force()?;
let (s, ctx) = coerce_to_string_ctx(&forced, true)?;
collected_ctx.merge(&ctx);
s
};
let args_list: Vec<String> = if let Some(a) = input.get("args") {
let forced_args = a.force()?;
let list = as_list(&forced_args)?;
let mut out = Vec::with_capacity(list.len());
for item in list.iter() {
let forced = item.force()?;
let (s, ctx) = coerce_to_string_ctx(&forced, true)?;
collected_ctx.merge(&ctx);
out.push(s);
}
out
} else {
Vec::new()
};
let ignore_nulls = input
.get("__ignoreNulls")
.and_then(|v| v.force().ok())
.is_some_and(|v| matches!(v, IrValue::Bool(true)));
let structured_attrs = input
.get("__structuredAttrs")
.and_then(|v| v.force().ok())
.is_some_and(|v| matches!(v, IrValue::Bool(true)));
if structured_attrs {
return Err(IrEvalError::Unsupported("derivation-structured-attrs"));
}
let mut env_vars: BTreeMap<String, String> = BTreeMap::new();
for (k, v) in input.iter() {
if matches!(
k.as_str(),
"name" | "system" | "builder" | "args" | "__ignoreNulls" | "__impure"
| "__contentAddressed"
) {
continue;
}
let forced_v = match v.force() {
Ok(fv) => fv,
Err(_) => continue,
};
if ignore_nulls && matches!(forced_v, IrValue::Null) {
continue;
}
match coerce_to_string_ctx(&forced_v, true) {
Ok((s, ctx)) => {
collected_ctx.merge(&ctx);
env_vars.insert(k.clone(), s);
}
Err(
e @ (IrEvalError::Throw(_)
| IrEvalError::Abort(_)
| IrEvalError::AssertionFailed
| IrEvalError::Unsupported(_)
| IrEvalError::MissingBuiltin(_)),
) => return Err(e),
Err(_) => {}
}
}
env_vars.insert("name".to_string(), name.to_string());
env_vars.insert("system".to_string(), system.clone());
env_vars.insert("builder".to_string(), builder.clone());
let mut input_derivations: BTreeMap<String, Vec<String>> = BTreeMap::new();
let mut input_sources: Vec<String> = Vec::new();
for elem in collected_ctx.iter() {
match elem {
IrContextElem::Output { drv, output } => {
input_derivations
.entry(drv.clone())
.or_default()
.push(output.clone());
}
IrContextElem::Plain(p) => {
if p.starts_with("/nix/store/") && !input_sources.contains(p) {
input_sources.push(p.clone());
}
}
IrContextElem::DrvDeep(_) => {} }
}
for outs in input_derivations.values_mut() {
outs.sort();
outs.dedup();
}
Ok(Derivation {
outputs: BTreeMap::new(),
input_derivations,
input_sources,
system,
builder,
args: args_list,
env: env_vars,
})
}
fn compute_derivation_outputs(
input: &IrAttrs,
name: &str,
drv: Derivation,
) -> Result<(String, BTreeMap<String, String>), IrEvalError> {
if input.contains_key("outputHash") {
let _ = optional_attr_string(input, "outputHash")?;
return Err(IrEvalError::Unsupported("derivation-fixed-output"));
}
let outputs = parse_outputs_list(input)?;
let algo = sui_spec::derivation::load_canonical().map_err(|e| {
IrEvalError::TypeError(format!("derivation algorithm spec failed to load: {e}"))
})?;
let (drv_path, out_paths, _drv) = sui_spec::derivation::apply(&algo, drv, outputs, name)
.map_err(|e| {
IrEvalError::TypeError(format!("derivation algorithm interpreter failed: {e}"))
})?;
Ok((drv_path, out_paths))
}
fn parse_outputs_list(input: &IrAttrs) -> Result<Vec<String>, IrEvalError> {
if let Some(o) = input.get("outputs") {
let forced_o = o.force()?;
let list = as_list(&forced_o)?;
let mut out = Vec::with_capacity(list.len());
for item in list.iter() {
let forced = item.force()?;
let s = match &forced {
IrValue::Str(s, _) => (**s).clone(),
other => {
return Err(IrEvalError::TypeMismatch {
expected: "string",
got: other.type_name(),
})
}
};
out.push(s);
}
if out.is_empty() {
return Err(IrEvalError::TypeError(
"derivation: outputs list must not be empty".into(),
));
}
Ok(out)
} else {
Ok(vec!["out".to_string()])
}
}
fn build_derivation_result(
input: &IrAttrs,
name: &str,
drv_path: &str,
out_paths: &BTreeMap<String, String>,
) -> Result<IrValue, IrEvalError> {
use crate::eval_ir::IrStringContext;
let output_names = parse_outputs_list(input)?;
let primary = output_names
.first()
.cloned()
.unwrap_or_else(|| "out".to_string());
let drv_path_val = {
let mut ctx = IrStringContext::new();
ctx.add_drv_deep(drv_path.to_string());
IrValue::string_with_context(drv_path.to_string(), ctx)
};
let out_path_val = {
let p = out_paths.get(&primary).cloned().unwrap_or_default();
let mut ctx = IrStringContext::new();
ctx.add_output(drv_path.to_string(), primary.clone());
IrValue::string_with_context(p, ctx)
};
let mut result = (*input).clone();
result.insert("type".to_string(), IrValue::string("derivation"));
result.insert("drvPath".to_string(), drv_path_val.clone());
result.insert("drvAttrs".to_string(), IrValue::Attrs(Rc::new((*input).clone())));
result.insert("outPath".to_string(), out_path_val);
result.insert("outputName".to_string(), IrValue::string(primary.clone()));
let mut all_outputs: Vec<IrValue> = Vec::with_capacity(output_names.len());
for output_name in &output_names {
let mut out_attrs = IrAttrs::new();
let p = out_paths.get(output_name).cloned().unwrap_or_default();
let mut ctx = IrStringContext::new();
ctx.add_output(drv_path.to_string(), output_name.clone());
out_attrs.insert(
"outPath".to_string(),
IrValue::string_with_context(p, ctx),
);
out_attrs.insert("drvPath".to_string(), drv_path_val.clone());
out_attrs.insert("type".to_string(), IrValue::string("derivation"));
out_attrs.insert("outputName".to_string(), IrValue::string(output_name.clone()));
out_attrs.insert("name".to_string(), IrValue::string(name));
let out_val = IrValue::Attrs(Rc::new(out_attrs));
all_outputs.push(out_val.clone());
result.insert(output_name.clone(), out_val);
}
result.insert("all".to_string(), IrValue::List(Rc::new(all_outputs)));
Ok(IrValue::Attrs(Rc::new(result)))
}
fn as_list(v: &IrValue) -> Result<Rc<Vec<IrValue>>, IrEvalError> {
match v {
IrValue::List(items) => Ok(items.clone()),
other => Err(IrEvalError::TypeMismatch {
expected: "list",
got: other.type_name(),
}),
}
}