This extends the calling convention for nint in a non-breaking way: If the called script returns an attribute set instead of a string the following is done: * If the attributes `stdout` and/or `stderr` exist, their content (which must be a string currently) is written to the respective output. * If the attribute `exit` exists, nint will exit with the given exit code. Must be a number that can be converted to an `i32`. If it's missing, nint will exit without indicating an error. Change-Id: I209cf178fee3d970fdea3b26e4049e944af47457 Reviewed-on: https://cl.tvl.fyi/c/depot/+/3547 Tested-by: BuildkiteCI Reviewed-by: tazjin <mail@tazj.in>
		
			
				
	
	
		
			156 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
extern crate serde_json;
 | 
						|
 | 
						|
use serde_json::Value;
 | 
						|
use std::ffi::OsString;
 | 
						|
use std::os::unix::ffi::{OsStringExt, OsStrExt};
 | 
						|
use std::io::{Error, ErrorKind, Write, stdout, stderr};
 | 
						|
use std::process::Command;
 | 
						|
use std::convert::{TryFrom};
 | 
						|
 | 
						|
fn render_nix_string(s: &OsString) -> OsString {
 | 
						|
    let mut rendered = Vec::new();
 | 
						|
 | 
						|
    rendered.extend(b"\"");
 | 
						|
 | 
						|
    for b in s.as_os_str().as_bytes() {
 | 
						|
        match char::from(*b) {
 | 
						|
            '\"' => rendered.extend(b"\\\""),
 | 
						|
            '\\' => rendered.extend(b"\\\\"),
 | 
						|
            '$'  => rendered.extend(b"\\$"),
 | 
						|
            _    => rendered.push(*b),
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    rendered.extend(b"\"");
 | 
						|
 | 
						|
    OsString::from_vec(rendered)
 | 
						|
}
 | 
						|
 | 
						|
fn render_nix_list(arr: &[OsString]) -> OsString {
 | 
						|
    let mut rendered = Vec::new();
 | 
						|
 | 
						|
    rendered.extend(b"[ ");
 | 
						|
 | 
						|
    for el in arr {
 | 
						|
        rendered.extend(render_nix_string(el).as_os_str().as_bytes());
 | 
						|
        rendered.extend(b" ");
 | 
						|
    }
 | 
						|
 | 
						|
    rendered.extend(b"]");
 | 
						|
 | 
						|
    OsString::from_vec(rendered)
 | 
						|
}
 | 
						|
 | 
						|
/// Slightly overkill helper macro which takes a `Map<String, Value>` obtained
 | 
						|
/// from `Value::Object` and an output name (`stderr` or `stdout`) as an
 | 
						|
/// identifier. If a value exists for the given output in the object it gets
 | 
						|
/// written to the appropriate output.
 | 
						|
macro_rules! handle_set_output {
 | 
						|
    ($map_name:ident, $output_name:ident) => {
 | 
						|
        match $map_name.get(stringify!($output_name)) {
 | 
						|
            Some(Value::String(s)) =>
 | 
						|
                $output_name().write_all(s.as_bytes()),
 | 
						|
            Some(_) => Err(
 | 
						|
                Error::new(
 | 
						|
                    ErrorKind::Other,
 | 
						|
                    format!("Attribute {} must be a string!", stringify!($output_name)),
 | 
						|
                )
 | 
						|
            ),
 | 
						|
            None => Ok(()),
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
fn main() -> std::io::Result<()> {
 | 
						|
    let mut nix_args = Vec::new();
 | 
						|
 | 
						|
    let mut args = std::env::args_os().into_iter();
 | 
						|
    let mut in_args = true;
 | 
						|
 | 
						|
    let mut argv: Vec<OsString> = Vec::new();
 | 
						|
 | 
						|
    // skip argv[0]
 | 
						|
    args.next();
 | 
						|
 | 
						|
    loop {
 | 
						|
        let arg = match args.next() {
 | 
						|
            Some(a) => a,
 | 
						|
            None => break,
 | 
						|
        };
 | 
						|
 | 
						|
        if !arg.to_str().map(|s| s.starts_with("-")).unwrap_or(false) {
 | 
						|
            in_args = false;
 | 
						|
        }
 | 
						|
 | 
						|
        if in_args {
 | 
						|
            match(arg.to_str()) {
 | 
						|
                Some("--arg") | Some("--argstr") => {
 | 
						|
                    nix_args.push(arg);
 | 
						|
                    nix_args.push(args.next().unwrap());
 | 
						|
                    nix_args.push(args.next().unwrap());
 | 
						|
                    Ok(())
 | 
						|
                }
 | 
						|
                _ => Err(Error::new(ErrorKind::Other, "unknown argument")),
 | 
						|
            }?
 | 
						|
        } else {
 | 
						|
            argv.push(arg);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if argv.len() < 1 {
 | 
						|
        Err(Error::new(ErrorKind::Other, "missing argv"))
 | 
						|
    } else {
 | 
						|
        let cd = std::env::current_dir()?.into_os_string();
 | 
						|
 | 
						|
        nix_args.push(OsString::from("--arg"));
 | 
						|
        nix_args.push(OsString::from("currentDir"));
 | 
						|
        nix_args.push(cd);
 | 
						|
 | 
						|
        nix_args.push(OsString::from("--arg"));
 | 
						|
        nix_args.push(OsString::from("argv"));
 | 
						|
        nix_args.push(render_nix_list(&argv[..]));
 | 
						|
 | 
						|
        nix_args.push(OsString::from("--eval"));
 | 
						|
        nix_args.push(OsString::from("--strict"));
 | 
						|
        nix_args.push(OsString::from("--json"));
 | 
						|
 | 
						|
        nix_args.push(argv[0].clone());
 | 
						|
 | 
						|
        let run = Command::new("nix-instantiate")
 | 
						|
                          .args(nix_args)
 | 
						|
                          .output()?;
 | 
						|
 | 
						|
        match serde_json::from_slice(&run.stdout[..]) {
 | 
						|
            Ok(Value::String(s)) => stdout().write_all(s.as_bytes()),
 | 
						|
            Ok(Value::Object(m)) => {
 | 
						|
                handle_set_output!(m, stdout)?;
 | 
						|
                handle_set_output!(m, stderr)?;
 | 
						|
 | 
						|
                match m.get("exit") {
 | 
						|
                    Some(Value::Number(n)) => {
 | 
						|
                        let code = n.as_i64().and_then(|v| i32::try_from(v).ok());
 | 
						|
 | 
						|
                        match code {
 | 
						|
                            Some(i) => std::process::exit(i),
 | 
						|
                            None => Err(
 | 
						|
                                Error::new(
 | 
						|
                                    ErrorKind::Other,
 | 
						|
                                    "Attribute exit is not an i32"
 | 
						|
                                )
 | 
						|
                            ),
 | 
						|
                        }
 | 
						|
                    },
 | 
						|
                    Some(_) => Err(
 | 
						|
                        Error::new(ErrorKind::Other, "exit must be a number")
 | 
						|
                    ),
 | 
						|
                    None => Ok(()),
 | 
						|
                }
 | 
						|
            },
 | 
						|
            Ok(_) => Err(Error::new(ErrorKind::Other, "output must be a string or an object")),
 | 
						|
            _ => {
 | 
						|
                stderr().write_all(&run.stderr[..]);
 | 
						|
                Err(Error::new(ErrorKind::Other, "internal nix error"))
 | 
						|
            },
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |