From 318d10e60875ef69132651668a249de75730d2ba Mon Sep 17 00:00:00 2001 From: sterni Date: Thu, 9 Sep 2021 18:26:03 +0200 Subject: chore(nint): move from //users/sterni to //nix Since //web/bubblegum depends on nint, we need to move it to a non user directory to conform with the policy established via cl/3434. Note that this likely doesn't mean greater stability (which isn't really implied in depot anyways), since I still would like to use a more elaborate calling convention to allow for additional useful features. Change-Id: I616f905d8df13e3363674aab69a797b0d39fdd79 Reviewed-on: https://cl.tvl.fyi/c/depot/+/3506 Tested-by: BuildkiteCI Reviewed-by: tazjin --- README.md | 2 +- default.nix | 5 -- nix/nint/OWNERS | 3 ++ nix/nint/README.md | 85 ++++++++++++++++++++++++++++++++ nix/nint/default.nix | 14 ++++++ nix/nint/nint.rs | 110 ++++++++++++++++++++++++++++++++++++++++++ users/sterni/nint/README.md | 85 -------------------------------- users/sterni/nint/default.nix | 14 ------ users/sterni/nint/nint.rs | 110 ------------------------------------------ web/bubblegum/README.md | 2 +- web/bubblegum/default.nix | 3 +- 11 files changed, 216 insertions(+), 217 deletions(-) create mode 100644 nix/nint/OWNERS create mode 100644 nix/nint/README.md create mode 100644 nix/nint/default.nix create mode 100644 nix/nint/nint.rs delete mode 100644 users/sterni/nint/README.md delete mode 100644 users/sterni/nint/default.nix delete mode 100644 users/sterni/nint/nint.rs diff --git a/README.md b/README.md index 535275b10f..5741ef8ebc 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ configuration is tracked in `//ops/{modules,machines}`. software. Currently only SBCL is supported. Lisp programs in this repository are built using this library. * `//web/bubblegum` contains a CGI-based web framework written in Nix. +* `//nix/nint`: A shebang-compatible interpreter wrapper for Nix. * `//tvix` contains initial work towards a modular architecture for Nix. * `//third_party/nix` contains [our fork][tvix] of the Nix package manager. @@ -88,7 +89,6 @@ Some examples: [tazj.in](https://tazj.in) * `//users/grfn/xanthous`: A (WIP) TUI RPG, written in Haskell. * `//users/tazjin/emacs`: tazjin's Emacs & EXWM configuration -* `//users/sterni/nint`: A shebang-compatible interpreter wrapper for Nix. * `//users/tazjin/finito`: A persistent finite-state machine library for Rust. # Licensing diff --git a/default.nix b/default.nix index 77fb4ae544..866b3fa6bf 100644 --- a/default.nix +++ b/default.nix @@ -34,11 +34,6 @@ let # TODO(tazjin): Can this one be removed somehow? [ "ops" "nixos" ] [ "ops" "machines" "all-systems" ] - - # //web/bubblegum has examples using //users/sterni, they should - # probably be in the user folder instead with a link there. - # TODO(sterni): Clean this up. - [ "web" "bubblegum" ] ] then args else args // { diff --git a/nix/nint/OWNERS b/nix/nint/OWNERS new file mode 100644 index 0000000000..f16dd105d7 --- /dev/null +++ b/nix/nint/OWNERS @@ -0,0 +1,3 @@ +inherited: true +owners: + - sterni diff --git a/nix/nint/README.md b/nix/nint/README.md new file mode 100644 index 0000000000..ddd8045f73 --- /dev/null +++ b/nix/nint/README.md @@ -0,0 +1,85 @@ +# nint — Nix INTerpreter + +`nint` is a shebang compatible interpreter for nix. It is currently +implemented as a fairly trivial wrapper around `nix-instantiate --eval`. +It allows to run nix expressions as command line tools if they conform +to the following calling convention: + +* Every nix script needs to evaluate to a function which takes an + attribute set as its single argument. Ideally a set pattern with + an ellipsis should be used. By default `nint` passes the following + arguments: + + * `currentDir`: the current working directory as a nix path + * `argv`: a list of arguments to the invokation including the + program name at `builtins.head argv`. + * Extra arguments can be manually passed as described below. + +* The return value should always be a string (throwing is also okay) + which is printed to stdout by `nint`. + +## Usage + +``` +nint [ --arg ARG VALUE … ] script.nix [ ARGS … ] +``` + +Instead of `--arg`, `--argstr` can also be used. They both work +like the flags of the same name for `nix-instantiate` and may +be specified any number of times as long as they are passed +*before* the nix expression to run. + +Below is a shebang which also passes `depot` as an argument +(note the usage of `env -S` to get around the shebang limitation +to two arguments). + +```nix +#!/usr/bin/env -S nint --arg depot /path/to/depot +``` + +## Limitations + +* No side effects except for writing to `stdout`. + +* Output is not streaming, i. e. even if the output is incrementally + calculated, nothing will be printed until the full output is available. + With plain nix strings we can't do better anyways. + +* Limited error handling for the script, no way to set the exit code etc. + +Some of these limitations may be possible to address in the future by using +an alternative nix interpreter and a more elaborate calling convention. + +## Example + +Below is a (very simple) implementation of a `ls(1)`-like program in nix: + +```nix +#!/usr/bin/env nint +{ currentDir, argv, ... }: + +let + lib = import ; + + dirs = + let + args = builtins.tail argv; + in + if args == [] + then [ currentDir ] + else args; + + makeAbsolute = p: + if builtins.isPath p + then p + else if builtins.match "^/.*" p != null + then p + else "${toString currentDir}/${p}"; +in + + lib.concatStringsSep "\n" + (lib.flatten + (builtins.map + (d: (builtins.attrNames (builtins.readDir (makeAbsolute d)))) + dirs)) + "\n" +``` diff --git a/nix/nint/default.nix b/nix/nint/default.nix new file mode 100644 index 0000000000..5cf83d15d6 --- /dev/null +++ b/nix/nint/default.nix @@ -0,0 +1,14 @@ +{ depot, pkgs, ... }: + +let + inherit (depot.nix.writers) + rustSimpleBin + ; +in + + rustSimpleBin { + name = "nint"; + dependencies = [ + depot.third_party.rust-crates.serde_json + ]; + } (builtins.readFile ./nint.rs) diff --git a/nix/nint/nint.rs b/nix/nint/nint.rs new file mode 100644 index 0000000000..3d43061285 --- /dev/null +++ b/nix/nint/nint.rs @@ -0,0 +1,110 @@ +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}; +use std::process::Command; + +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) +} + +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 = 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("--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)) => Ok(print!("{}", s)), + Ok(_) => Err(Error::new(ErrorKind::Other, "output must be a string")), + _ => { + std::io::stderr().write_all(&run.stderr[..]); + Err(Error::new(ErrorKind::Other, "internal nix error")) + }, + } + } +} diff --git a/users/sterni/nint/README.md b/users/sterni/nint/README.md deleted file mode 100644 index ddd8045f73..0000000000 --- a/users/sterni/nint/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# nint — Nix INTerpreter - -`nint` is a shebang compatible interpreter for nix. It is currently -implemented as a fairly trivial wrapper around `nix-instantiate --eval`. -It allows to run nix expressions as command line tools if they conform -to the following calling convention: - -* Every nix script needs to evaluate to a function which takes an - attribute set as its single argument. Ideally a set pattern with - an ellipsis should be used. By default `nint` passes the following - arguments: - - * `currentDir`: the current working directory as a nix path - * `argv`: a list of arguments to the invokation including the - program name at `builtins.head argv`. - * Extra arguments can be manually passed as described below. - -* The return value should always be a string (throwing is also okay) - which is printed to stdout by `nint`. - -## Usage - -``` -nint [ --arg ARG VALUE … ] script.nix [ ARGS … ] -``` - -Instead of `--arg`, `--argstr` can also be used. They both work -like the flags of the same name for `nix-instantiate` and may -be specified any number of times as long as they are passed -*before* the nix expression to run. - -Below is a shebang which also passes `depot` as an argument -(note the usage of `env -S` to get around the shebang limitation -to two arguments). - -```nix -#!/usr/bin/env -S nint --arg depot /path/to/depot -``` - -## Limitations - -* No side effects except for writing to `stdout`. - -* Output is not streaming, i. e. even if the output is incrementally - calculated, nothing will be printed until the full output is available. - With plain nix strings we can't do better anyways. - -* Limited error handling for the script, no way to set the exit code etc. - -Some of these limitations may be possible to address in the future by using -an alternative nix interpreter and a more elaborate calling convention. - -## Example - -Below is a (very simple) implementation of a `ls(1)`-like program in nix: - -```nix -#!/usr/bin/env nint -{ currentDir, argv, ... }: - -let - lib = import ; - - dirs = - let - args = builtins.tail argv; - in - if args == [] - then [ currentDir ] - else args; - - makeAbsolute = p: - if builtins.isPath p - then p - else if builtins.match "^/.*" p != null - then p - else "${toString currentDir}/${p}"; -in - - lib.concatStringsSep "\n" - (lib.flatten - (builtins.map - (d: (builtins.attrNames (builtins.readDir (makeAbsolute d)))) - dirs)) + "\n" -``` diff --git a/users/sterni/nint/default.nix b/users/sterni/nint/default.nix deleted file mode 100644 index 5cf83d15d6..0000000000 --- a/users/sterni/nint/default.nix +++ /dev/null @@ -1,14 +0,0 @@ -{ depot, pkgs, ... }: - -let - inherit (depot.nix.writers) - rustSimpleBin - ; -in - - rustSimpleBin { - name = "nint"; - dependencies = [ - depot.third_party.rust-crates.serde_json - ]; - } (builtins.readFile ./nint.rs) diff --git a/users/sterni/nint/nint.rs b/users/sterni/nint/nint.rs deleted file mode 100644 index 3d43061285..0000000000 --- a/users/sterni/nint/nint.rs +++ /dev/null @@ -1,110 +0,0 @@ -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}; -use std::process::Command; - -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) -} - -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 = 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("--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)) => Ok(print!("{}", s)), - Ok(_) => Err(Error::new(ErrorKind::Other, "output must be a string")), - _ => { - std::io::stderr().write_all(&run.stderr[..]); - Err(Error::new(ErrorKind::Other, "internal nix error")) - }, - } - } -} diff --git a/web/bubblegum/README.md b/web/bubblegum/README.md index 0e09c1c65e..48c2ba6e03 100644 --- a/web/bubblegum/README.md +++ b/web/bubblegum/README.md @@ -2,7 +2,7 @@ `bubblegum` is a CGI programming library for the Nix expression language. It provides a few helpers to make writing CGI scripts which are executable -using [//users/sterni/nint](../../users/sterni/nint/README.md) convenient. +using [//nix/nint](../../nix/nint/README.md) convenient. An example nix.cgi script looks like this (don't worry about the shebang too much, you can use `web.bubblegum.writeCGI` to set this up without diff --git a/web/bubblegum/default.nix b/web/bubblegum/default.nix index 0ad541390b..2db6f04204 100644 --- a/web/bubblegum/default.nix +++ b/web/bubblegum/default.nix @@ -7,6 +7,7 @@ let getBins utils sparseTree + nint ; minimalDepot = sparseTree depot.path [ @@ -186,7 +187,7 @@ let else "${scriptName}/${path}"; bins = getBins pkgs.coreutils [ "env" "tee" "cat" "printf" "chmod" ] - // getBins depot.users.sterni.nint [ "nint" ]; + // getBins nint [ "nint" ]; /* Type: args -> either path derivation string -> derivation */ -- cgit 1.4.1