feat(nix/nint): accept attribute set with stdout, stderr and exit
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>
This commit is contained in:
parent
5dec982334
commit
852d059e2f
2 changed files with 57 additions and 3 deletions
|
@ -15,8 +15,16 @@ to the following calling convention:
|
||||||
program name at `builtins.head argv`.
|
program name at `builtins.head argv`.
|
||||||
* Extra arguments can be manually passed as described below.
|
* Extra arguments can be manually passed as described below.
|
||||||
|
|
||||||
* The return value should always be a string (throwing is also okay)
|
* The return value must either be
|
||||||
which is printed to stdout by `nint`.
|
|
||||||
|
* A string which is rendered to `stdout`.
|
||||||
|
|
||||||
|
* An attribute set with the following optional attributes:
|
||||||
|
|
||||||
|
* `stdout`: A string that's rendered to `stdout`
|
||||||
|
* `stderr`: A string that's rendered to `stderr`
|
||||||
|
* `exit`: A number which is used as an exit code.
|
||||||
|
If missing, nint always exits with 0 (or equivalent).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ use std::ffi::OsString;
|
||||||
use std::os::unix::ffi::{OsStringExt, OsStrExt};
|
use std::os::unix::ffi::{OsStringExt, OsStrExt};
|
||||||
use std::io::{Error, ErrorKind, Write, stdout, stderr};
|
use std::io::{Error, ErrorKind, Write, stdout, stderr};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::convert::{TryFrom};
|
||||||
|
|
||||||
fn render_nix_string(s: &OsString) -> OsString {
|
fn render_nix_string(s: &OsString) -> OsString {
|
||||||
let mut rendered = Vec::new();
|
let mut rendered = Vec::new();
|
||||||
|
@ -40,6 +41,26 @@ fn render_nix_list(arr: &[OsString]) -> OsString {
|
||||||
OsString::from_vec(rendered)
|
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<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
let mut nix_args = Vec::new();
|
let mut nix_args = Vec::new();
|
||||||
|
|
||||||
|
@ -90,6 +111,7 @@ fn main() -> std::io::Result<()> {
|
||||||
nix_args.push(render_nix_list(&argv[..]));
|
nix_args.push(render_nix_list(&argv[..]));
|
||||||
|
|
||||||
nix_args.push(OsString::from("--eval"));
|
nix_args.push(OsString::from("--eval"));
|
||||||
|
nix_args.push(OsString::from("--strict"));
|
||||||
nix_args.push(OsString::from("--json"));
|
nix_args.push(OsString::from("--json"));
|
||||||
|
|
||||||
nix_args.push(argv[0].clone());
|
nix_args.push(argv[0].clone());
|
||||||
|
@ -100,7 +122,31 @@ fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
match serde_json::from_slice(&run.stdout[..]) {
|
match serde_json::from_slice(&run.stdout[..]) {
|
||||||
Ok(Value::String(s)) => stdout().write_all(s.as_bytes()),
|
Ok(Value::String(s)) => stdout().write_all(s.as_bytes()),
|
||||||
Ok(_) => Err(Error::new(ErrorKind::Other, "output must be a string")),
|
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[..]);
|
stderr().write_all(&run.stderr[..]);
|
||||||
Err(Error::new(ErrorKind::Other, "internal nix error"))
|
Err(Error::new(ErrorKind::Other, "internal nix error"))
|
||||||
|
|
Loading…
Reference in a new issue