refactor(nix/*): drop yants and consumers, and some more
Change-Id: I96ab5890518c7bb0d4a676adbad20e4c49699b63
This commit is contained in:
parent
001556aa30
commit
cff6575948
33 changed files with 11 additions and 2414 deletions
|
|
@ -1,51 +0,0 @@
|
||||||
{ lib, pkgs, depot, ... }:
|
|
||||||
|
|
||||||
# Takes a derivation and a list of binary names
|
|
||||||
# and returns an attribute set of `name -> path`.
|
|
||||||
# The list can also contain renames in the form of
|
|
||||||
# `{ use, as }`, which goes `as -> usePath`.
|
|
||||||
#
|
|
||||||
# It is usually used to construct an attrset `bins`
|
|
||||||
# containing all the binaries required in a file,
|
|
||||||
# similar to a simple import system.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# bins = getBins pkgs.hello [ "hello" ]
|
|
||||||
# // getBins pkgs.coreutils [ "printf" "ln" "echo" ]
|
|
||||||
# // getBins pkgs.execline
|
|
||||||
# [ { use = "if"; as = "execlineIf" } ]
|
|
||||||
# // getBins pkgs.s6-portable-utils
|
|
||||||
# [ { use = "s6-test"; as = "test" }
|
|
||||||
# { use = "s6-cat"; as = "cat" }
|
|
||||||
# ];
|
|
||||||
#
|
|
||||||
# provides
|
|
||||||
# bins.{hello,printf,ln,echo,execlineIf,test,cat}
|
|
||||||
#
|
|
||||||
|
|
||||||
let
|
|
||||||
getBins = drv: xs:
|
|
||||||
let
|
|
||||||
f = x:
|
|
||||||
# TODO(Profpatsch): typecheck
|
|
||||||
let x' = if builtins.isString x then { use = x; as = x; } else x;
|
|
||||||
in {
|
|
||||||
name = x'.as;
|
|
||||||
value = "${lib.getBin drv}/bin/${x'.use}";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
builtins.listToAttrs (builtins.map f xs);
|
|
||||||
|
|
||||||
|
|
||||||
tests = import ./tests.nix {
|
|
||||||
inherit getBins;
|
|
||||||
inherit (depot.nix) writeScriptBin;
|
|
||||||
inherit (depot.nix.runTestsuite) assertEq it runTestsuite;
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
__functor = _: getBins;
|
|
||||||
inherit tests;
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
{ writeScriptBin, assertEq, it, runTestsuite, getBins }:
|
|
||||||
|
|
||||||
let
|
|
||||||
drv = writeScriptBin "hello" "it’s me";
|
|
||||||
drv2 = writeScriptBin "goodbye" "tschau";
|
|
||||||
|
|
||||||
bins = getBins drv [
|
|
||||||
"hello"
|
|
||||||
{ use = "hello"; as = "also-hello"; }
|
|
||||||
]
|
|
||||||
// getBins drv2 [ "goodbye" ]
|
|
||||||
;
|
|
||||||
|
|
||||||
simple = it "path is equal to the executable name" [
|
|
||||||
(assertEq "path"
|
|
||||||
bins.hello
|
|
||||||
"${drv}/bin/hello")
|
|
||||||
(assertEq "content"
|
|
||||||
(builtins.readFile bins.hello)
|
|
||||||
"it’s me")
|
|
||||||
];
|
|
||||||
|
|
||||||
useAs = it "use/as can be used to rename attributes" [
|
|
||||||
(assertEq "path"
|
|
||||||
bins.also-hello
|
|
||||||
"${drv}/bin/hello")
|
|
||||||
];
|
|
||||||
|
|
||||||
secondDrv = it "by merging attrsets you can build up bins" [
|
|
||||||
(assertEq "path"
|
|
||||||
bins.goodbye
|
|
||||||
"${drv2}/bin/goodbye")
|
|
||||||
];
|
|
||||||
|
|
||||||
in
|
|
||||||
runTestsuite "getBins" [
|
|
||||||
simple
|
|
||||||
useAs
|
|
||||||
secondDrv
|
|
||||||
]
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
{ lib, depot, ... }:
|
|
||||||
/*
|
|
||||||
JSON Merge-Patch for nix
|
|
||||||
Spec: https://tools.ietf.org/html/rfc7396
|
|
||||||
|
|
||||||
An algorithm for changing and removing fields in nested objects.
|
|
||||||
|
|
||||||
For example, given the following original document:
|
|
||||||
|
|
||||||
{
|
|
||||||
a = "b";
|
|
||||||
c = {
|
|
||||||
d = "e";
|
|
||||||
f = "g";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Changing the value of `a` and removing `f` can be achieved by merging the patch
|
|
||||||
|
|
||||||
{
|
|
||||||
a = "z";
|
|
||||||
c.f = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
which results in
|
|
||||||
|
|
||||||
{
|
|
||||||
a = "z";
|
|
||||||
c = {
|
|
||||||
d = "e";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Pseudo-code:
|
|
||||||
define MergePatch(Target, Patch):
|
|
||||||
if Patch is an Object:
|
|
||||||
if Target is not an Object:
|
|
||||||
Target = {} # Ignore the contents and set it to an empty Object
|
|
||||||
for each Name/Value pair in Patch:
|
|
||||||
if Value is null:
|
|
||||||
if Name exists in Target:
|
|
||||||
remove the Name/Value pair from Target
|
|
||||||
else:
|
|
||||||
Target[Name] = MergePatch(Target[Name], Value)
|
|
||||||
return Target
|
|
||||||
else:
|
|
||||||
return Patch
|
|
||||||
*/
|
|
||||||
|
|
||||||
let
|
|
||||||
foldlAttrs = op: init: attrs:
|
|
||||||
lib.foldl' op init
|
|
||||||
(lib.mapAttrsToList lib.nameValuePair attrs);
|
|
||||||
|
|
||||||
mergePatch = target: patch:
|
|
||||||
if lib.isAttrs patch
|
|
||||||
then
|
|
||||||
let target' = if lib.isAttrs target then target else { };
|
|
||||||
in foldlAttrs
|
|
||||||
(acc: patchEl:
|
|
||||||
if patchEl.value == null
|
|
||||||
then removeAttrs acc [ patchEl.name ]
|
|
||||||
else acc // {
|
|
||||||
${patchEl.name} =
|
|
||||||
mergePatch
|
|
||||||
(acc.${patchEl.name} or "unnused")
|
|
||||||
patchEl.value;
|
|
||||||
})
|
|
||||||
target'
|
|
||||||
patch
|
|
||||||
else patch;
|
|
||||||
|
|
||||||
inherit (depot.nix.runTestsuite)
|
|
||||||
runTestsuite
|
|
||||||
it
|
|
||||||
assertEq
|
|
||||||
;
|
|
||||||
|
|
||||||
tests =
|
|
||||||
let
|
|
||||||
# example target from the RFC
|
|
||||||
testTarget = {
|
|
||||||
a = "b";
|
|
||||||
c = {
|
|
||||||
d = "e";
|
|
||||||
f = "g";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
# example patch from the RFC
|
|
||||||
testPatch = {
|
|
||||||
a = "z";
|
|
||||||
c.f = null;
|
|
||||||
};
|
|
||||||
emptyPatch = it "the empty patch returns the original target" [
|
|
||||||
(assertEq "id"
|
|
||||||
(mergePatch testTarget { })
|
|
||||||
testTarget)
|
|
||||||
];
|
|
||||||
nonAttrs = it "one side is a non-attrset value" [
|
|
||||||
(assertEq "target is a value means the value is replaced by the patch"
|
|
||||||
(mergePatch 42 testPatch)
|
|
||||||
(mergePatch { } testPatch))
|
|
||||||
(assertEq "patch is a value means it replaces target alltogether"
|
|
||||||
(mergePatch testTarget 42)
|
|
||||||
42)
|
|
||||||
];
|
|
||||||
rfcExamples = it "the examples from the RFC" [
|
|
||||||
(assertEq "a subset is deleted and overwritten"
|
|
||||||
(mergePatch testTarget testPatch)
|
|
||||||
{
|
|
||||||
a = "z";
|
|
||||||
c = {
|
|
||||||
d = "e";
|
|
||||||
};
|
|
||||||
})
|
|
||||||
(assertEq "a more complicated example from the example section"
|
|
||||||
(mergePatch
|
|
||||||
{
|
|
||||||
title = "Goodbye!";
|
|
||||||
author = {
|
|
||||||
givenName = "John";
|
|
||||||
familyName = "Doe";
|
|
||||||
};
|
|
||||||
tags = [ "example" "sample" ];
|
|
||||||
content = "This will be unchanged";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
title = "Hello!";
|
|
||||||
phoneNumber = "+01-123-456-7890";
|
|
||||||
author.familyName = null;
|
|
||||||
tags = [ "example" ];
|
|
||||||
})
|
|
||||||
{
|
|
||||||
title = "Hello!";
|
|
||||||
phoneNumber = "+01-123-456-7890";
|
|
||||||
author = {
|
|
||||||
givenName = "John";
|
|
||||||
};
|
|
||||||
tags = [ "example" ];
|
|
||||||
content = "This will be unchanged";
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
rfcTests =
|
|
||||||
let
|
|
||||||
r = index: target: patch: res:
|
|
||||||
(assertEq "test number ${toString index}"
|
|
||||||
(mergePatch target patch)
|
|
||||||
res);
|
|
||||||
in
|
|
||||||
it "the test suite from the RFC" [
|
|
||||||
(r 1 { "a" = "b"; } { "a" = "c"; } { "a" = "c"; })
|
|
||||||
(r 2 { "a" = "b"; } { "b" = "c"; } { "a" = "b"; "b" = "c"; })
|
|
||||||
(r 3 { "a" = "b"; } { "a" = null; } { })
|
|
||||||
(r 4 { "a" = "b"; "b" = "c"; }
|
|
||||||
{ "a" = null; }
|
|
||||||
{ "b" = "c"; })
|
|
||||||
(r 5 { "a" = [ "b" ]; } { "a" = "c"; } { "a" = "c"; })
|
|
||||||
(r 6 { "a" = "c"; } { "a" = [ "b" ]; } { "a" = [ "b" ]; })
|
|
||||||
(r 7 { "a" = { "b" = "c"; }; }
|
|
||||||
{ "a" = { "b" = "d"; "c" = null; }; }
|
|
||||||
{ "a" = { "b" = "d"; }; })
|
|
||||||
(r 8 { "a" = [{ "b" = "c"; }]; }
|
|
||||||
{ "a" = [ 1 ]; }
|
|
||||||
{ "a" = [ 1 ]; })
|
|
||||||
(r 9 [ "a" "b" ] [ "c" "d" ] [ "c" "d" ])
|
|
||||||
(r 10 { "a" = "b"; } [ "c" ] [ "c" ])
|
|
||||||
(r 11 { "a" = "foo"; } null null)
|
|
||||||
(r 12 { "a" = "foo"; } "bar" "bar")
|
|
||||||
(r 13 { "e" = null; } { "a" = 1; } { "e" = null; "a" = 1; })
|
|
||||||
(r 14 [ 1 2 ]
|
|
||||||
{ "a" = "b"; "c" = null; }
|
|
||||||
{ "a" = "b"; })
|
|
||||||
(r 15 { }
|
|
||||||
{ "a" = { "bb" = { "ccc" = null; }; }; }
|
|
||||||
{ "a" = { "bb" = { }; }; })
|
|
||||||
];
|
|
||||||
|
|
||||||
in
|
|
||||||
runTestsuite "mergePatch" [
|
|
||||||
emptyPatch
|
|
||||||
nonAttrs
|
|
||||||
rfcExamples
|
|
||||||
rfcTests
|
|
||||||
];
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
__functor = _: mergePatch;
|
|
||||||
|
|
||||||
inherit tests;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
raitobezarius
|
|
||||||
|
|
@ -1,93 +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 must either be
|
|
||||||
|
|
||||||
* 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
|
|
||||||
|
|
||||||
```
|
|
||||||
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 <nixpkgs/lib>;
|
|
||||||
|
|
||||||
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"
|
|
||||||
```
|
|
||||||
|
|
@ -1,16 +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)
|
|
||||||
149
nix/nint/nint.rs
149
nix/nint/nint.rs
|
|
@ -1,149 +0,0 @@
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
use serde_json::Value;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::ffi::OsString;
|
|
||||||
use std::io::{stderr, stdout, Error, ErrorKind, Write};
|
|
||||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
{ depot, lib, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit (depot.nix.runTestsuite)
|
|
||||||
runTestsuite
|
|
||||||
it
|
|
||||||
assertEq
|
|
||||||
assertThrows
|
|
||||||
;
|
|
||||||
|
|
||||||
tree-ex = depot.nix.readTree {
|
|
||||||
path = ./test-example;
|
|
||||||
args = { };
|
|
||||||
};
|
|
||||||
|
|
||||||
example = it "corresponds to the README example" [
|
|
||||||
(assertEq "third_party attrset"
|
|
||||||
(lib.isAttrs tree-ex.third_party
|
|
||||||
&& (! lib.isDerivation tree-ex.third_party))
|
|
||||||
true)
|
|
||||||
(assertEq "third_party attrset other attribute"
|
|
||||||
tree-ex.third_party.favouriteColour
|
|
||||||
"orange")
|
|
||||||
(assertEq "rustpkgs attrset aho-corasick"
|
|
||||||
tree-ex.third_party.rustpkgs.aho-corasick
|
|
||||||
"aho-corasick")
|
|
||||||
(assertEq "rustpkgs attrset serde"
|
|
||||||
tree-ex.third_party.rustpkgs.serde
|
|
||||||
"serde")
|
|
||||||
(assertEq "tools cheddear"
|
|
||||||
"cheddar"
|
|
||||||
tree-ex.tools.cheddar)
|
|
||||||
(assertEq "tools roquefort"
|
|
||||||
tree-ex.tools.roquefort
|
|
||||||
"roquefort")
|
|
||||||
];
|
|
||||||
|
|
||||||
tree-tl = depot.nix.readTree {
|
|
||||||
path = ./test-tree-traversal;
|
|
||||||
args = { };
|
|
||||||
};
|
|
||||||
|
|
||||||
traversal-logic = it "corresponds to the traversal logic in the README" [
|
|
||||||
(assertEq "skip-tree/a is read"
|
|
||||||
tree-tl.skip-tree.a
|
|
||||||
"a is read normally")
|
|
||||||
(assertEq "skip-tree does not contain b"
|
|
||||||
(builtins.attrNames tree-tl.skip-tree)
|
|
||||||
[ "__readTree" "__readTreeChildren" "a" ])
|
|
||||||
(assertEq "skip-tree children list does not contain b"
|
|
||||||
tree-tl.skip-tree.__readTreeChildren
|
|
||||||
[ "a" ])
|
|
||||||
|
|
||||||
(assertEq "skip subtree default.nix is read"
|
|
||||||
tree-tl.skip-subtree.but
|
|
||||||
"the default.nix is still read")
|
|
||||||
(assertEq "skip subtree a/default.nix is skipped"
|
|
||||||
(tree-tl.skip-subtree ? a)
|
|
||||||
false)
|
|
||||||
(assertEq "skip subtree b/c.nix is skipped"
|
|
||||||
(tree-tl.skip-subtree ? b)
|
|
||||||
false)
|
|
||||||
(assertEq "skip subtree a/default.nix would be read without .skip-subtree"
|
|
||||||
(tree-tl.no-skip-subtree.a)
|
|
||||||
"am I subtree yet?")
|
|
||||||
(assertEq "skip subtree b/c.nix would be read without .skip-subtree"
|
|
||||||
(tree-tl.no-skip-subtree.b.c)
|
|
||||||
"cool")
|
|
||||||
|
|
||||||
(assertEq "default.nix attrset is merged with siblings"
|
|
||||||
tree-tl.default-nix.no
|
|
||||||
"siblings should be read")
|
|
||||||
(assertEq "default.nix means sibling isn’t read"
|
|
||||||
(tree-tl.default-nix ? sibling)
|
|
||||||
false)
|
|
||||||
(assertEq "default.nix means subdirs are still read and merged into default.nix"
|
|
||||||
(tree-tl.default-nix.subdir.a)
|
|
||||||
"but I’m picked up")
|
|
||||||
|
|
||||||
(assertEq "default.nix can be not an attrset"
|
|
||||||
tree-tl.default-nix.no-merge
|
|
||||||
"I’m not merged with any children")
|
|
||||||
(assertEq "default.nix is not an attrset -> children are not merged"
|
|
||||||
(tree-tl.default-nix.no-merge ? subdir)
|
|
||||||
false)
|
|
||||||
|
|
||||||
(assertEq "default.nix can contain a derivation"
|
|
||||||
(lib.isDerivation tree-tl.default-nix.can-be-drv)
|
|
||||||
true)
|
|
||||||
(assertEq "Even if default.nix is a derivation, children are traversed and merged"
|
|
||||||
tree-tl.default-nix.can-be-drv.subdir.a
|
|
||||||
"Picked up through the drv")
|
|
||||||
(assertEq "default.nix drv is not changed by readTree"
|
|
||||||
tree-tl.default-nix.can-be-drv
|
|
||||||
(import ./test-tree-traversal/default-nix/can-be-drv/default.nix { }))
|
|
||||||
];
|
|
||||||
|
|
||||||
# these each call readTree themselves because the throws have to happen inside assertThrows
|
|
||||||
wrong = it "cannot read these files and will complain" [
|
|
||||||
(assertThrows "this file is not a function"
|
|
||||||
(depot.nix.readTree {
|
|
||||||
path = ./test-wrong-not-a-function;
|
|
||||||
args = { };
|
|
||||||
}).not-a-function)
|
|
||||||
# can’t test for that, assertThrows can’t catch this error
|
|
||||||
# (assertThrows "this file is a function but doesn’t have dots"
|
|
||||||
# (depot.nix.readTree {} ./test-wrong-no-dots).no-dots-in-function)
|
|
||||||
];
|
|
||||||
|
|
||||||
read-markers = depot.nix.readTree {
|
|
||||||
path = ./test-marker;
|
|
||||||
args = { };
|
|
||||||
};
|
|
||||||
|
|
||||||
assertMarkerByPath = path:
|
|
||||||
assertEq "${lib.concatStringsSep "." path} is marked correctly"
|
|
||||||
(lib.getAttrFromPath path read-markers).__readTree
|
|
||||||
path;
|
|
||||||
|
|
||||||
markers = it "marks nodes correctly" [
|
|
||||||
(assertMarkerByPath [ "directory-marked" ])
|
|
||||||
(assertMarkerByPath [ "directory-marked" "nested" ])
|
|
||||||
(assertMarkerByPath [ "file-children" "one" ])
|
|
||||||
(assertMarkerByPath [ "file-children" "two" ])
|
|
||||||
(assertEq "nix file children are marked correctly"
|
|
||||||
read-markers.file-children.__readTreeChildren [ "one" "two" ])
|
|
||||||
(assertEq "directory children are marked correctly"
|
|
||||||
read-markers.directory-marked.__readTreeChildren [ "nested" ])
|
|
||||||
(assertEq "absence of children is marked"
|
|
||||||
read-markers.directory-marked.nested.__readTreeChildren [ ])
|
|
||||||
];
|
|
||||||
|
|
||||||
in
|
|
||||||
runTestsuite "readTree" [
|
|
||||||
example
|
|
||||||
traversal-logic
|
|
||||||
wrong
|
|
||||||
markers
|
|
||||||
]
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
{ depot, pkgs, lib, ... }:
|
|
||||||
let
|
|
||||||
runExecline = import ./runExecline.nix {
|
|
||||||
inherit (pkgs) stdenv;
|
|
||||||
inherit (depot.nix) escapeExecline getBins;
|
|
||||||
inherit pkgs lib;
|
|
||||||
};
|
|
||||||
|
|
||||||
runExeclineLocal = name: args: execline:
|
|
||||||
runExecline name
|
|
||||||
(args // {
|
|
||||||
derivationArgs = args.derivationArgs or { } // {
|
|
||||||
preferLocalBuild = true;
|
|
||||||
allowSubstitutes = false;
|
|
||||||
};
|
|
||||||
})
|
|
||||||
execline;
|
|
||||||
|
|
||||||
tests = import ./tests.nix {
|
|
||||||
inherit runExecline runExeclineLocal;
|
|
||||||
inherit (depot.nix) getBins writeScript;
|
|
||||||
inherit (pkgs) stdenv coreutils;
|
|
||||||
inherit pkgs;
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
__functor = _: runExecline;
|
|
||||||
local = runExeclineLocal;
|
|
||||||
inherit tests;
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
{ pkgs, stdenv, lib, getBins, escapeExecline }:
|
|
||||||
|
|
||||||
# runExecline is a primitive building block
|
|
||||||
# for writing non-kitchen sink builders.
|
|
||||||
#
|
|
||||||
# It’s conceptually similar to `runCommand`,
|
|
||||||
# but instead of concatenating bash scripts left
|
|
||||||
# and right, it actually *uses* the features of
|
|
||||||
# `derivation`, passing things to `args`
|
|
||||||
# and making it possible to overwrite the `builder`
|
|
||||||
# in a sensible manner.
|
|
||||||
#
|
|
||||||
# Additionally, it provides a way to pass a nix string
|
|
||||||
# to `stdin` of the build script.
|
|
||||||
#
|
|
||||||
# Similar to //nix/writeExecline, the passed script is
|
|
||||||
# not a string, but a nested list of nix lists
|
|
||||||
# representing execline blocks. Escaping is
|
|
||||||
# done by the implementation, the user can just use
|
|
||||||
# normal nix strings.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# runExecline "my-drv" { stdin = "hi!"; } [
|
|
||||||
# "importas" "out" "out"
|
|
||||||
# # this pipes stdout of s6-cat to $out
|
|
||||||
# # and s6-cat redirects from stdin to stdout
|
|
||||||
# "redirfd" "-w" "1" "$out" bins.s6-cat
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
# which creates a derivation with "hi!" in $out.
|
|
||||||
#
|
|
||||||
# See ./tests.nix for more examples.
|
|
||||||
|
|
||||||
|
|
||||||
let
|
|
||||||
bins = getBins pkgs.execline [
|
|
||||||
"execlineb"
|
|
||||||
{ use = "if"; as = "execlineIf"; }
|
|
||||||
"redirfd"
|
|
||||||
"importas"
|
|
||||||
"exec"
|
|
||||||
]
|
|
||||||
// getBins pkgs.s6-portable-utils [
|
|
||||||
"s6-cat"
|
|
||||||
"s6-grep"
|
|
||||||
"s6-touch"
|
|
||||||
"s6-test"
|
|
||||||
"s6-chmod"
|
|
||||||
];
|
|
||||||
|
|
||||||
in
|
|
||||||
|
|
||||||
# TODO: move name into the attrset
|
|
||||||
name:
|
|
||||||
{
|
|
||||||
# a string to pass as stdin to the execline script
|
|
||||||
stdin ? ""
|
|
||||||
# a program wrapping the acutal execline invocation;
|
|
||||||
# should be in Bernstein-chaining style
|
|
||||||
, builderWrapper ? bins.exec
|
|
||||||
# additional arguments to pass to the derivation
|
|
||||||
, derivationArgs ? { }
|
|
||||||
}:
|
|
||||||
# the execline script as a nested list of string,
|
|
||||||
# representing the blocks;
|
|
||||||
# see docs of `escapeExecline`.
|
|
||||||
execline:
|
|
||||||
|
|
||||||
# those arguments can’t be overwritten
|
|
||||||
assert !derivationArgs ? system;
|
|
||||||
assert !derivationArgs ? name;
|
|
||||||
assert !derivationArgs ? builder;
|
|
||||||
assert !derivationArgs ? args;
|
|
||||||
|
|
||||||
derivation (derivationArgs // {
|
|
||||||
# TODO(Profpatsch): what about cross?
|
|
||||||
inherit (stdenv) system;
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
# okay, `builtins.toFile` does not accept strings
|
|
||||||
# that reference drv outputs. This means we need
|
|
||||||
# to pass the script and stdin as envvar;
|
|
||||||
# this might clash with another passed envar,
|
|
||||||
# so we give it a long & unique name
|
|
||||||
_runExeclineScript =
|
|
||||||
let
|
|
||||||
in escapeExecline execline;
|
|
||||||
_runExeclineStdin = stdin;
|
|
||||||
passAsFile = [
|
|
||||||
"_runExeclineScript"
|
|
||||||
"_runExeclineStdin"
|
|
||||||
] ++ derivationArgs.passAsFile or [ ];
|
|
||||||
|
|
||||||
# the default, exec acts as identity executable
|
|
||||||
builder = builderWrapper;
|
|
||||||
|
|
||||||
args = [
|
|
||||||
bins.importas # import script file as $script
|
|
||||||
"-ui" # drop the envvar afterwards
|
|
||||||
"script" # substitution name
|
|
||||||
"_runExeclineScriptPath" # passed script file
|
|
||||||
|
|
||||||
bins.importas # do the same for $stdin
|
|
||||||
"-ui"
|
|
||||||
"stdin"
|
|
||||||
"_runExeclineStdinPath"
|
|
||||||
|
|
||||||
bins.redirfd # now we
|
|
||||||
"-r" # read the file
|
|
||||||
"0" # into the stdin of execlineb
|
|
||||||
"$stdin" # that was given via stdin
|
|
||||||
|
|
||||||
bins.execlineb # the actual invocation
|
|
||||||
# TODO(Profpatsch): depending on the use-case, -S0 might not be enough
|
|
||||||
# in all use-cases, then a wrapper for execlineb arguments
|
|
||||||
# should be added (-P, -S, -s).
|
|
||||||
"-S0" # set $@ inside the execline script
|
|
||||||
"-W" # die on syntax error
|
|
||||||
"$script" # substituted by importas
|
|
||||||
];
|
|
||||||
})
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, runExecline
|
|
||||||
, runExeclineLocal
|
|
||||||
, getBins
|
|
||||||
, writeScript
|
|
||||||
# https://www.mail-archive.com/skaware@list.skarnet.org/msg01256.html
|
|
||||||
, coreutils
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
|
|
||||||
bins = getBins coreutils [ "mv" ]
|
|
||||||
// getBins pkgs.execline [
|
|
||||||
"execlineb"
|
|
||||||
{ use = "if"; as = "execlineIf"; }
|
|
||||||
"redirfd"
|
|
||||||
"importas"
|
|
||||||
]
|
|
||||||
// getBins pkgs.s6-portable-utils [
|
|
||||||
"s6-chmod"
|
|
||||||
"s6-grep"
|
|
||||||
"s6-touch"
|
|
||||||
"s6-cat"
|
|
||||||
"s6-test"
|
|
||||||
];
|
|
||||||
|
|
||||||
# execline block of depth 1
|
|
||||||
block = args: builtins.map (arg: " ${arg}") args ++ [ "" ];
|
|
||||||
|
|
||||||
# derivation that tests whether a given line exists
|
|
||||||
# in the given file. Does not use runExecline, because
|
|
||||||
# that should be tested after all.
|
|
||||||
fileHasLine = line: file: derivation {
|
|
||||||
name = "run-execline-test-file-${file.name}-has-line";
|
|
||||||
inherit (stdenv) system;
|
|
||||||
builder = bins.execlineIf;
|
|
||||||
args =
|
|
||||||
(block [
|
|
||||||
bins.redirfd
|
|
||||||
"-r"
|
|
||||||
"0"
|
|
||||||
file # read file to stdin
|
|
||||||
bins.s6-grep
|
|
||||||
"-F"
|
|
||||||
"-q"
|
|
||||||
line # and grep for the line
|
|
||||||
])
|
|
||||||
++ [
|
|
||||||
# if the block succeeded, touch $out
|
|
||||||
bins.importas
|
|
||||||
"-ui"
|
|
||||||
"out"
|
|
||||||
"out"
|
|
||||||
bins.s6-touch
|
|
||||||
"$out"
|
|
||||||
];
|
|
||||||
preferLocalBuild = true;
|
|
||||||
allowSubstitutes = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
# basic test that touches out
|
|
||||||
basic = runExeclineLocal "run-execline-test-basic"
|
|
||||||
{ } [
|
|
||||||
"importas"
|
|
||||||
"-ui"
|
|
||||||
"out"
|
|
||||||
"out"
|
|
||||||
"${bins.s6-touch}"
|
|
||||||
"$out"
|
|
||||||
];
|
|
||||||
|
|
||||||
# whether the stdin argument works as intended
|
|
||||||
stdin = fileHasLine "foo" (runExeclineLocal "run-execline-test-stdin"
|
|
||||||
{
|
|
||||||
stdin = "foo\nbar\nfoo";
|
|
||||||
} [
|
|
||||||
"importas"
|
|
||||||
"-ui"
|
|
||||||
"out"
|
|
||||||
"out"
|
|
||||||
# this pipes stdout of s6-cat to $out
|
|
||||||
# and s6-cat redirects from stdin to stdout
|
|
||||||
"redirfd"
|
|
||||||
"-w"
|
|
||||||
"1"
|
|
||||||
"$out"
|
|
||||||
bins.s6-cat
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
wrapWithVar = runExeclineLocal "run-execline-test-wrap-with-var"
|
|
||||||
{
|
|
||||||
builderWrapper = writeScript "var-wrapper" ''
|
|
||||||
#!${bins.execlineb} -S0
|
|
||||||
export myvar myvalue $@
|
|
||||||
'';
|
|
||||||
} [
|
|
||||||
"importas"
|
|
||||||
"-ui"
|
|
||||||
"v"
|
|
||||||
"myvar"
|
|
||||||
"if"
|
|
||||||
[ bins.s6-test "myvalue" "=" "$v" ]
|
|
||||||
"importas"
|
|
||||||
"out"
|
|
||||||
"out"
|
|
||||||
bins.s6-touch
|
|
||||||
"$out"
|
|
||||||
];
|
|
||||||
|
|
||||||
in
|
|
||||||
[
|
|
||||||
basic
|
|
||||||
stdin
|
|
||||||
wrapWithVar
|
|
||||||
]
|
|
||||||
|
|
@ -1,199 +0,0 @@
|
||||||
{ lib, pkgs, depot, ... }:
|
|
||||||
|
|
||||||
# Run a nix testsuite.
|
|
||||||
#
|
|
||||||
# The tests are simple assertions on the nix level,
|
|
||||||
# and can use derivation outputs if IfD is enabled.
|
|
||||||
#
|
|
||||||
# You build a testsuite by bundling assertions into
|
|
||||||
# “it”s and then bundling the “it”s into a testsuite.
|
|
||||||
#
|
|
||||||
# Running the testsuite will abort evaluation if
|
|
||||||
# any assertion fails.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# runTestsuite "myFancyTestsuite" [
|
|
||||||
# (it "does an assertion" [
|
|
||||||
# (assertEq "42 is equal to 42" "42" "42")
|
|
||||||
# (assertEq "also 23" 23 23)
|
|
||||||
# ])
|
|
||||||
# (it "frmbls the brlbr" [
|
|
||||||
# (assertEq true false)
|
|
||||||
# ])
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
# will fail the second it group because true is not false.
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit (depot.nix.yants)
|
|
||||||
sum
|
|
||||||
struct
|
|
||||||
string
|
|
||||||
any
|
|
||||||
defun
|
|
||||||
list
|
|
||||||
drv
|
|
||||||
bool
|
|
||||||
;
|
|
||||||
|
|
||||||
bins = depot.nix.getBins pkgs.coreutils [ "printf" ]
|
|
||||||
// depot.nix.getBins pkgs.s6-portable-utils [ "s6-touch" "s6-false" "s6-cat" ];
|
|
||||||
|
|
||||||
# Returns true if the given expression throws when `deepSeq`-ed
|
|
||||||
throws = expr:
|
|
||||||
!(builtins.tryEval (builtins.deepSeq expr { })).success;
|
|
||||||
|
|
||||||
# rewrite the builtins.partition result
|
|
||||||
# to use `ok` and `err` instead of `right` and `wrong`.
|
|
||||||
partitionTests = pred: xs:
|
|
||||||
let res = builtins.partition pred xs;
|
|
||||||
in {
|
|
||||||
ok = res.right;
|
|
||||||
err = res.wrong;
|
|
||||||
};
|
|
||||||
|
|
||||||
AssertErrorContext =
|
|
||||||
sum "AssertErrorContext" {
|
|
||||||
not-equal = struct "not-equal" {
|
|
||||||
left = any;
|
|
||||||
right = any;
|
|
||||||
};
|
|
||||||
should-throw = struct "should-throw" {
|
|
||||||
expr = any;
|
|
||||||
};
|
|
||||||
unexpected-throw = struct "unexpected-throw" { };
|
|
||||||
};
|
|
||||||
|
|
||||||
# The result of an assert,
|
|
||||||
# either it’s true (yep) or false (nope).
|
|
||||||
# If it's nope we return an additional context
|
|
||||||
# attribute which gives details on the failure
|
|
||||||
# depending on the type of assert performed.
|
|
||||||
AssertResult =
|
|
||||||
sum "AssertResult" {
|
|
||||||
yep = struct "yep" {
|
|
||||||
test = string;
|
|
||||||
};
|
|
||||||
nope = struct "nope" {
|
|
||||||
test = string;
|
|
||||||
context = AssertErrorContext;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Result of an it. An it is a bunch of asserts
|
|
||||||
# bundled up with a good description of what is tested.
|
|
||||||
ItResult =
|
|
||||||
struct "ItResult" {
|
|
||||||
it-desc = string;
|
|
||||||
asserts = list AssertResult;
|
|
||||||
};
|
|
||||||
|
|
||||||
# If the given boolean is true return a positive AssertResult.
|
|
||||||
# If the given boolean is false return a negative AssertResult
|
|
||||||
# with the provided AssertErrorContext describing the failure.
|
|
||||||
#
|
|
||||||
# This function is intended as a generic assert to implement
|
|
||||||
# more assert types and is not exposed to the user.
|
|
||||||
assertBoolContext = defun [ AssertErrorContext string bool AssertResult ]
|
|
||||||
(context: desc: res:
|
|
||||||
if res
|
|
||||||
then { yep = { test = desc; }; }
|
|
||||||
else {
|
|
||||||
nope = {
|
|
||||||
test = desc;
|
|
||||||
inherit context;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
# assert that left and right values are equal
|
|
||||||
assertEq = defun [ string any any AssertResult ]
|
|
||||||
(desc: left: right:
|
|
||||||
let
|
|
||||||
context = { not-equal = { inherit left right; }; };
|
|
||||||
in
|
|
||||||
assertBoolContext context desc (left == right));
|
|
||||||
|
|
||||||
# assert that the expression throws when `deepSeq`-ed
|
|
||||||
assertThrows = defun [ string any AssertResult ]
|
|
||||||
(desc: expr:
|
|
||||||
let
|
|
||||||
context = { should-throw = { inherit expr; }; };
|
|
||||||
in
|
|
||||||
assertBoolContext context desc (throws expr));
|
|
||||||
|
|
||||||
# assert that the expression does not throw when `deepSeq`-ed
|
|
||||||
assertDoesNotThrow = defun [ string any AssertResult ]
|
|
||||||
(desc: expr:
|
|
||||||
assertBoolContext { unexpected-throw = { }; } desc (!(throws expr)));
|
|
||||||
|
|
||||||
# Annotate a bunch of asserts with a descriptive name
|
|
||||||
it = desc: asserts: {
|
|
||||||
it-desc = desc;
|
|
||||||
inherit asserts;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Run a bunch of its and check whether all asserts are yep.
|
|
||||||
# If not, abort evaluation with `throw`
|
|
||||||
# and print the result of the test suite.
|
|
||||||
#
|
|
||||||
# Takes a test suite name as first argument.
|
|
||||||
runTestsuite = defun [ string (list ItResult) drv ]
|
|
||||||
(name: itResults:
|
|
||||||
let
|
|
||||||
goodAss = ass: AssertResult.match ass {
|
|
||||||
yep = _: true;
|
|
||||||
nope = _: false;
|
|
||||||
};
|
|
||||||
res = partitionTests
|
|
||||||
(it:
|
|
||||||
(partitionTests goodAss it.asserts).err == [ ]
|
|
||||||
)
|
|
||||||
itResults;
|
|
||||||
prettyRes = lib.generators.toPretty { } res;
|
|
||||||
in
|
|
||||||
if res.err == [ ]
|
|
||||||
then
|
|
||||||
depot.nix.runExecline.local "testsuite-${name}-successful" { } [
|
|
||||||
"importas"
|
|
||||||
"out"
|
|
||||||
"out"
|
|
||||||
# force derivation to rebuild if test case list changes
|
|
||||||
"ifelse"
|
|
||||||
[ bins.s6-false ]
|
|
||||||
[
|
|
||||||
bins.printf
|
|
||||||
""
|
|
||||||
(builtins.hashString "sha512" prettyRes)
|
|
||||||
]
|
|
||||||
"if"
|
|
||||||
[ bins.printf "%s\n" "testsuite ${name} successful!" ]
|
|
||||||
bins.s6-touch
|
|
||||||
"$out"
|
|
||||||
]
|
|
||||||
else
|
|
||||||
depot.nix.runExecline.local "testsuite-${name}-failed"
|
|
||||||
{
|
|
||||||
stdin = prettyRes + "\n";
|
|
||||||
} [
|
|
||||||
"importas"
|
|
||||||
"out"
|
|
||||||
"out"
|
|
||||||
"if"
|
|
||||||
[ bins.printf "%s\n" "testsuite ${name} failed!" ]
|
|
||||||
"if"
|
|
||||||
[ bins.s6-cat ]
|
|
||||||
"exit"
|
|
||||||
"1"
|
|
||||||
]);
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit
|
|
||||||
assertEq
|
|
||||||
assertThrows
|
|
||||||
assertDoesNotThrow
|
|
||||||
it
|
|
||||||
runTestsuite
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
{ depot, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit (depot.nix.runTestsuite)
|
|
||||||
runTestsuite
|
|
||||||
it
|
|
||||||
assertEq
|
|
||||||
;
|
|
||||||
|
|
||||||
inherit (depot.nix.stateMonad)
|
|
||||||
pure
|
|
||||||
run
|
|
||||||
join
|
|
||||||
fmap
|
|
||||||
bind
|
|
||||||
get
|
|
||||||
set
|
|
||||||
modify
|
|
||||||
after
|
|
||||||
for_
|
|
||||||
getAttr
|
|
||||||
setAttr
|
|
||||||
modifyAttr
|
|
||||||
;
|
|
||||||
|
|
||||||
runStateIndependent = run (throw "This should never be evaluated!");
|
|
||||||
in
|
|
||||||
|
|
||||||
runTestsuite "stateMonad" [
|
|
||||||
(it "behaves correctly independent of state" [
|
|
||||||
(assertEq "pure" (runStateIndependent (pure 21)) 21)
|
|
||||||
(assertEq "join pure" (runStateIndependent (join (pure (pure 42)))) 42)
|
|
||||||
(assertEq "fmap pure" (runStateIndependent (fmap (builtins.mul 2) (pure 21))) 42)
|
|
||||||
(assertEq "bind pure" (runStateIndependent (bind (pure 12) (x: pure x))) 12)
|
|
||||||
])
|
|
||||||
(it "behaves correctly with an integer state" [
|
|
||||||
(assertEq "get" (run 42 get) 42)
|
|
||||||
(assertEq "after set get" (run 21 (after (set 42) get)) 42)
|
|
||||||
(assertEq "after modify get" (run 21 (after (modify (builtins.mul 2)) get)) 42)
|
|
||||||
(assertEq "fmap get" (run 40 (fmap (builtins.add 2) get)) 42)
|
|
||||||
(assertEq "stateful sum list"
|
|
||||||
(run 0 (after
|
|
||||||
(for_
|
|
||||||
[
|
|
||||||
15
|
|
||||||
12
|
|
||||||
10
|
|
||||||
5
|
|
||||||
]
|
|
||||||
(x: modify (builtins.add x)))
|
|
||||||
get))
|
|
||||||
42)
|
|
||||||
])
|
|
||||||
(it "behaves correctly with an attr set state" [
|
|
||||||
(assertEq "getAttr" (run { foo = 42; } (getAttr "foo")) 42)
|
|
||||||
(assertEq "after setAttr getAttr"
|
|
||||||
(run { foo = 21; } (after (setAttr "foo" 42) (getAttr "foo")))
|
|
||||||
42)
|
|
||||||
(assertEq "after modifyAttr getAttr"
|
|
||||||
(run { foo = 10.5; }
|
|
||||||
(after
|
|
||||||
(modifyAttr "foo" (builtins.mul 4))
|
|
||||||
(getAttr "foo")))
|
|
||||||
42)
|
|
||||||
(assertEq "fmap getAttr"
|
|
||||||
(run { foo = 21; } (fmap (builtins.mul 2) (getAttr "foo")))
|
|
||||||
42)
|
|
||||||
(assertEq "after setAttr to insert getAttr"
|
|
||||||
(run { } (after (setAttr "foo" 42) (getAttr "foo")))
|
|
||||||
42)
|
|
||||||
(assertEq "insert permutations"
|
|
||||||
(run
|
|
||||||
{
|
|
||||||
a = 2;
|
|
||||||
b = 3;
|
|
||||||
c = 5;
|
|
||||||
}
|
|
||||||
(after
|
|
||||||
(bind get
|
|
||||||
(state:
|
|
||||||
let
|
|
||||||
names = builtins.attrNames state;
|
|
||||||
in
|
|
||||||
for_ names (name1:
|
|
||||||
for_ names (name2:
|
|
||||||
# this is of course a bit silly, but making it more cumbersome
|
|
||||||
# makes sure the test exercises more of the code.
|
|
||||||
(bind (getAttr name1)
|
|
||||||
(value1:
|
|
||||||
(bind (getAttr name2)
|
|
||||||
(value2:
|
|
||||||
setAttr "${name1}_${name2}" (value1 * value2)))))))))
|
|
||||||
get))
|
|
||||||
{
|
|
||||||
a = 2;
|
|
||||||
b = 3;
|
|
||||||
c = 5;
|
|
||||||
a_a = 4;
|
|
||||||
a_b = 6;
|
|
||||||
a_c = 10;
|
|
||||||
b_a = 6;
|
|
||||||
b_b = 9;
|
|
||||||
b_c = 15;
|
|
||||||
c_c = 25;
|
|
||||||
c_a = 10;
|
|
||||||
c_b = 15;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
])
|
|
||||||
]
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
{ depot, lib, verifyTag, discr, discrDef, match, matchLam }:
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit (depot.nix.runTestsuite)
|
|
||||||
runTestsuite
|
|
||||||
assertEq
|
|
||||||
assertThrows
|
|
||||||
it
|
|
||||||
;
|
|
||||||
|
|
||||||
isTag-test = it "checks whether something is a tag" [
|
|
||||||
(assertEq "is Tag"
|
|
||||||
(verifyTag { foo = "bar"; })
|
|
||||||
{
|
|
||||||
isTag = true;
|
|
||||||
name = "foo";
|
|
||||||
val = "bar";
|
|
||||||
errmsg = null;
|
|
||||||
})
|
|
||||||
(assertEq "is not Tag"
|
|
||||||
(removeAttrs (verifyTag { foo = "bar"; baz = 42; }) [ "errmsg" ])
|
|
||||||
{
|
|
||||||
isTag = false;
|
|
||||||
name = null;
|
|
||||||
val = null;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
discr-test = it "can discr things" [
|
|
||||||
(assertEq "id"
|
|
||||||
(discr [
|
|
||||||
{ a = lib.const true; }
|
|
||||||
] "x")
|
|
||||||
{ a = "x"; })
|
|
||||||
(assertEq "bools here, ints there"
|
|
||||||
(discr [
|
|
||||||
{ bool = lib.isBool; }
|
|
||||||
{ int = lib.isInt; }
|
|
||||||
] 25)
|
|
||||||
{ int = 25; })
|
|
||||||
(assertEq "bools here, ints there 2"
|
|
||||||
(discr [
|
|
||||||
{ bool = lib.isBool; }
|
|
||||||
{ int = lib.isInt; }
|
|
||||||
]
|
|
||||||
true)
|
|
||||||
{ bool = true; })
|
|
||||||
(assertEq "fallback to default"
|
|
||||||
(discrDef "def" [
|
|
||||||
{ bool = lib.isBool; }
|
|
||||||
{ int = lib.isInt; }
|
|
||||||
] "foo")
|
|
||||||
{ def = "foo"; })
|
|
||||||
(assertThrows "throws failing to match"
|
|
||||||
(discr [
|
|
||||||
{ fish = x: x == 42; }
|
|
||||||
] 21))
|
|
||||||
];
|
|
||||||
|
|
||||||
match-test = it "can match things" [
|
|
||||||
(assertEq "match example"
|
|
||||||
(
|
|
||||||
let
|
|
||||||
success = { res = 42; };
|
|
||||||
failure = { err = "no answer"; };
|
|
||||||
matcher = {
|
|
||||||
res = i: i + 1;
|
|
||||||
err = _: 0;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
one = match success matcher;
|
|
||||||
two = match failure matcher;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
{
|
|
||||||
one = 43;
|
|
||||||
two = 0;
|
|
||||||
})
|
|
||||||
(assertEq "matchLam & pipe"
|
|
||||||
(lib.pipe { foo = 42; } [
|
|
||||||
(matchLam {
|
|
||||||
foo = i: if i < 23 then { small = i; } else { big = i; };
|
|
||||||
bar = _: { small = 5; };
|
|
||||||
})
|
|
||||||
(matchLam {
|
|
||||||
small = i: "yay it was small";
|
|
||||||
big = i: "whoo it was big!";
|
|
||||||
})
|
|
||||||
])
|
|
||||||
"whoo it was big!")
|
|
||||||
];
|
|
||||||
|
|
||||||
in
|
|
||||||
runTestsuite "tag" [
|
|
||||||
isTag-test
|
|
||||||
discr-test
|
|
||||||
match-test
|
|
||||||
]
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
{ depot, lib, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit (depot.nix.runTestsuite)
|
|
||||||
runTestsuite
|
|
||||||
it
|
|
||||||
assertEq
|
|
||||||
assertThrows
|
|
||||||
assertDoesNotThrow
|
|
||||||
;
|
|
||||||
|
|
||||||
inherit (depot.nix.utils)
|
|
||||||
isDirectory
|
|
||||||
isRegularFile
|
|
||||||
isSymlink
|
|
||||||
storePathName
|
|
||||||
;
|
|
||||||
|
|
||||||
assertUtilsPred = msg: act: exp: [
|
|
||||||
(assertDoesNotThrow "${msg} does not throw" act)
|
|
||||||
(assertEq msg (builtins.tryEval act).value exp)
|
|
||||||
];
|
|
||||||
|
|
||||||
pathPredicates = it "judges paths correctly" (lib.flatten [
|
|
||||||
# isDirectory
|
|
||||||
(assertUtilsPred "directory isDirectory"
|
|
||||||
(isDirectory ./directory)
|
|
||||||
true)
|
|
||||||
(assertUtilsPred "symlink not isDirectory"
|
|
||||||
(isDirectory ./symlink-directory)
|
|
||||||
false)
|
|
||||||
(assertUtilsPred "file not isDirectory"
|
|
||||||
(isDirectory ./directory/file)
|
|
||||||
false)
|
|
||||||
# isRegularFile
|
|
||||||
(assertUtilsPred "file isRegularFile"
|
|
||||||
(isRegularFile ./directory/file)
|
|
||||||
true)
|
|
||||||
(assertUtilsPred "symlink not isRegularFile"
|
|
||||||
(isRegularFile ./symlink-file)
|
|
||||||
false)
|
|
||||||
(assertUtilsPred "directory not isRegularFile"
|
|
||||||
(isRegularFile ./directory)
|
|
||||||
false)
|
|
||||||
# isSymlink
|
|
||||||
(assertUtilsPred "symlink to file isSymlink"
|
|
||||||
(isSymlink ./symlink-file)
|
|
||||||
true)
|
|
||||||
(assertUtilsPred "symlink to directory isSymlink"
|
|
||||||
(isSymlink ./symlink-directory)
|
|
||||||
true)
|
|
||||||
(assertUtilsPred "symlink to symlink isSymlink"
|
|
||||||
(isSymlink ./symlink-symlink-file)
|
|
||||||
true)
|
|
||||||
(assertUtilsPred "symlink to missing file isSymlink"
|
|
||||||
(isSymlink ./missing)
|
|
||||||
true)
|
|
||||||
(assertUtilsPred "directory not isSymlink"
|
|
||||||
(isSymlink ./directory)
|
|
||||||
false)
|
|
||||||
(assertUtilsPred "file not isSymlink"
|
|
||||||
(isSymlink ./directory/file)
|
|
||||||
false)
|
|
||||||
# missing files throw
|
|
||||||
(assertThrows "isDirectory throws on missing file"
|
|
||||||
(isDirectory ./does-not-exist))
|
|
||||||
(assertThrows "isRegularFile throws on missing file"
|
|
||||||
(isRegularFile ./does-not-exist))
|
|
||||||
(assertThrows "isSymlink throws on missing file"
|
|
||||||
(isSymlink ./does-not-exist))
|
|
||||||
]);
|
|
||||||
|
|
||||||
magratheaStorePath =
|
|
||||||
builtins.unsafeDiscardStringContext depot.tools.magrathea.outPath;
|
|
||||||
|
|
||||||
cleanedSource = lib.cleanSource ./.;
|
|
||||||
|
|
||||||
storePathNameTests = it "correctly gets the basename of a store path" [
|
|
||||||
(assertEq "base name of a derivation"
|
|
||||||
(storePathName depot.tools.magrathea)
|
|
||||||
depot.tools.magrathea.name)
|
|
||||||
(assertEq "base name of a store path string"
|
|
||||||
(storePathName magratheaStorePath)
|
|
||||||
depot.tools.magrathea.name)
|
|
||||||
(assertEq "base name of a path within a store path"
|
|
||||||
(storePathName "${magratheaStorePath}/bin/mg") "mg")
|
|
||||||
(assertEq "base name of a path"
|
|
||||||
(storePathName ../default.nix) "default.nix")
|
|
||||||
(assertEq "base name of a cleanSourced path"
|
|
||||||
(storePathName cleanedSource)
|
|
||||||
cleanedSource.name)
|
|
||||||
];
|
|
||||||
in
|
|
||||||
|
|
||||||
runTestsuite "nix.utils" [
|
|
||||||
pathPredicates
|
|
||||||
storePathNameTests
|
|
||||||
]
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{ depot, pkgs, ... }:
|
|
||||||
|
|
||||||
{ name, src, deps ? (_: [ ]), emacs ? pkgs.emacs-nox }:
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit (pkgs) emacsPackages emacsPackagesFor;
|
|
||||||
inherit (builtins) isString toFile;
|
|
||||||
|
|
||||||
finalEmacs = (emacsPackagesFor emacs).emacsWithPackages deps;
|
|
||||||
|
|
||||||
srcFile =
|
|
||||||
if isString src
|
|
||||||
then toFile "${name}.el" src
|
|
||||||
else src;
|
|
||||||
|
|
||||||
in
|
|
||||||
depot.nix.writeScriptBin name ''
|
|
||||||
#!/bin/sh
|
|
||||||
${finalEmacs}/bin/emacs --batch --no-site-file --script ${srcFile} $@
|
|
||||||
''
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
{ pkgs, depot, ... }:
|
|
||||||
|
|
||||||
# Write an execline script, represented as nested nix lists.
|
|
||||||
# Everything is escaped correctly.
|
|
||||||
# https://skarnet.org/software/execline/
|
|
||||||
|
|
||||||
# TODO(Profpatsch) upstream into nixpkgs
|
|
||||||
|
|
||||||
name:
|
|
||||||
{
|
|
||||||
# "var": substitute readNArgs variables and start $@
|
|
||||||
# from the (readNArgs+1)th argument
|
|
||||||
# "var-full": substitute readNArgs variables and start $@ from $0
|
|
||||||
# "env": don’t substitute, set # and 0…n environment vaariables, where n=$#
|
|
||||||
# "none": don’t substitute or set any positional arguments
|
|
||||||
# "env-no-push": like "env", but bypass the push-phase. Not recommended.
|
|
||||||
argMode ? "var"
|
|
||||||
, # Number of arguments to be substituted as variables (passed to "var"/"-s" or "var-full"/"-S"
|
|
||||||
readNArgs ? 0
|
|
||||||
,
|
|
||||||
}:
|
|
||||||
# Nested list of lists of commands.
|
|
||||||
# Inner lists are translated to execline blocks.
|
|
||||||
argList:
|
|
||||||
|
|
||||||
let
|
|
||||||
env =
|
|
||||||
if argMode == "var" then "s${toString readNArgs}"
|
|
||||||
else if argMode == "var-full" then "S${toString readNArgs}"
|
|
||||||
else if argMode == "env" then ""
|
|
||||||
else if argMode == "none" then "P"
|
|
||||||
else if argMode == "env-no-push" then "p"
|
|
||||||
else abort ''"${toString argMode}" is not a valid argMode, use one of "var", "var-full", "env", "none", "env-no-push".'';
|
|
||||||
|
|
||||||
in
|
|
||||||
depot.nix.writeScript name ''
|
|
||||||
#!${pkgs.execline}/bin/execlineb -W${env}
|
|
||||||
${depot.nix.escapeExecline argList}
|
|
||||||
''
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
{ pkgs, depot, ... }:
|
|
||||||
|
|
||||||
# Write the given string to $out
|
|
||||||
# and make it executable.
|
|
||||||
|
|
||||||
let
|
|
||||||
bins = depot.nix.getBins pkgs.s6-portable-utils [
|
|
||||||
"s6-cat"
|
|
||||||
"s6-chmod"
|
|
||||||
];
|
|
||||||
|
|
||||||
in
|
|
||||||
name:
|
|
||||||
# string of the executable script that is put in $out
|
|
||||||
script:
|
|
||||||
|
|
||||||
depot.nix.runExecline name
|
|
||||||
{
|
|
||||||
stdin = script;
|
|
||||||
derivationArgs = {
|
|
||||||
preferLocalBuild = true;
|
|
||||||
allowSubstitutes = false;
|
|
||||||
};
|
|
||||||
} [
|
|
||||||
"importas"
|
|
||||||
"out"
|
|
||||||
"out"
|
|
||||||
# this pipes stdout of s6-cat to $out
|
|
||||||
# and s6-cat redirects from stdin to stdout
|
|
||||||
"if"
|
|
||||||
[ "redirfd" "-w" "1" "$out" bins.s6-cat ]
|
|
||||||
bins.s6-chmod
|
|
||||||
"0755"
|
|
||||||
"$out"
|
|
||||||
]
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
{ depot, ... }:
|
|
||||||
|
|
||||||
# Like writeScript,
|
|
||||||
# but put the script into `$out/bin/${name}`.
|
|
||||||
|
|
||||||
name:
|
|
||||||
script:
|
|
||||||
|
|
||||||
depot.nix.binify {
|
|
||||||
exe = (depot.nix.writeScript name script);
|
|
||||||
inherit name;
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
{ depot, pkgs, lib, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
bins = depot.nix.getBins pkgs.s6-portable-utils [ "s6-ln" "s6-ls" "s6-touch" ]
|
|
||||||
;
|
|
||||||
|
|
||||||
linkTo = name: path: depot.nix.runExecline.local name { } [
|
|
||||||
"importas"
|
|
||||||
"out"
|
|
||||||
"out"
|
|
||||||
bins.s6-ln
|
|
||||||
"-s"
|
|
||||||
path
|
|
||||||
"$out"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Build a rust executable, $out is the executable.
|
|
||||||
rustSimple = args@{ name, ... }: src:
|
|
||||||
linkTo name "${rustSimpleBin args src}/bin/${name}";
|
|
||||||
|
|
||||||
# Like `rustSimple`, but put the binary in `$out/bin/`.
|
|
||||||
rustSimpleBin =
|
|
||||||
{ name
|
|
||||||
, dependencies ? [ ]
|
|
||||||
, doCheck ? true
|
|
||||||
}: src:
|
|
||||||
(if doCheck then testRustSimple else pkgs.lib.id)
|
|
||||||
(pkgs.buildRustCrate ({
|
|
||||||
pname = name;
|
|
||||||
version = "1.0.0";
|
|
||||||
crateName = name;
|
|
||||||
crateBin = [ name ];
|
|
||||||
dependencies = dependencies;
|
|
||||||
src = pkgs.runCommandLocal "write-main.rs"
|
|
||||||
{
|
|
||||||
src = src;
|
|
||||||
passAsFile = [ "src" ];
|
|
||||||
} ''
|
|
||||||
mkdir -p $out/src/bin
|
|
||||||
cp "$srcPath" $out/src/bin/${name}.rs
|
|
||||||
find $out
|
|
||||||
'';
|
|
||||||
}));
|
|
||||||
|
|
||||||
# Build a rust library, that can be used as dependency to `rustSimple`.
|
|
||||||
# Wrapper around `pkgs.buildRustCrate`, takes all its arguments.
|
|
||||||
rustSimpleLib =
|
|
||||||
{ name
|
|
||||||
, dependencies ? [ ]
|
|
||||||
, doCheck ? true
|
|
||||||
,
|
|
||||||
}: src:
|
|
||||||
(if doCheck then testRustSimple else pkgs.lib.id)
|
|
||||||
(pkgs.buildRustCrate ({
|
|
||||||
pname = name;
|
|
||||||
version = "1.0.0";
|
|
||||||
crateName = name;
|
|
||||||
dependencies = dependencies;
|
|
||||||
src = pkgs.runCommandLocal "write-lib.rs"
|
|
||||||
{
|
|
||||||
src = src;
|
|
||||||
passAsFile = [ "src" ];
|
|
||||||
} ''
|
|
||||||
mkdir -p $out/src
|
|
||||||
cp "$srcPath" $out/src/lib.rs
|
|
||||||
find $out
|
|
||||||
'';
|
|
||||||
}));
|
|
||||||
|
|
||||||
/* Takes a `buildRustCrate` derivation as an input,
|
|
||||||
* builds it with `{ buildTests = true; }` and runs
|
|
||||||
* all tests found in its `tests` dir. If they are
|
|
||||||
* all successful, `$out` will point to the crate
|
|
||||||
* built with `{ buildTests = false; }`, otherwise
|
|
||||||
* it will fail to build.
|
|
||||||
*
|
|
||||||
* See also `nix.drvSeqL` which is used to implement
|
|
||||||
* this behavior.
|
|
||||||
*/
|
|
||||||
testRustSimple = rustDrv:
|
|
||||||
let
|
|
||||||
crate = buildTests: rustDrv.override { inherit buildTests; };
|
|
||||||
tests = depot.nix.runExecline.local "${rustDrv.name}-tests-run" { } [
|
|
||||||
"importas"
|
|
||||||
"out"
|
|
||||||
"out"
|
|
||||||
"if"
|
|
||||||
[
|
|
||||||
"pipeline"
|
|
||||||
[ bins.s6-ls "${crate true}/tests" ]
|
|
||||||
"forstdin"
|
|
||||||
"-o0"
|
|
||||||
"test"
|
|
||||||
"importas"
|
|
||||||
"test"
|
|
||||||
"test"
|
|
||||||
"${crate true}/tests/$test"
|
|
||||||
]
|
|
||||||
bins.s6-touch
|
|
||||||
"$out"
|
|
||||||
];
|
|
||||||
in
|
|
||||||
depot.nix.drvSeqL [ tests ] (crate false);
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit
|
|
||||||
rustSimple
|
|
||||||
rustSimpleBin
|
|
||||||
rustSimpleLib
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
{ depot, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit (depot.nix.writers)
|
|
||||||
rustSimple
|
|
||||||
rustSimpleLib
|
|
||||||
rustSimpleBin
|
|
||||||
;
|
|
||||||
|
|
||||||
inherit (pkgs)
|
|
||||||
coreutils
|
|
||||||
;
|
|
||||||
|
|
||||||
run = drv: depot.nix.runExecline.local "run-${drv.name}" { } [
|
|
||||||
"if"
|
|
||||||
[ drv ]
|
|
||||||
"importas"
|
|
||||||
"out"
|
|
||||||
"out"
|
|
||||||
"${coreutils}/bin/touch"
|
|
||||||
"$out"
|
|
||||||
];
|
|
||||||
|
|
||||||
rustTransitiveLib = rustSimpleLib
|
|
||||||
{
|
|
||||||
name = "transitive";
|
|
||||||
} ''
|
|
||||||
pub fn transitive(s: &str) -> String {
|
|
||||||
let mut new = s.to_string();
|
|
||||||
new.push_str(" 1 2 3");
|
|
||||||
new
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_transitive() {
|
|
||||||
assert_eq!(transitive("foo").as_str(), "foo 1 2 3")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
|
|
||||||
rustTestLib = rustSimpleLib
|
|
||||||
{
|
|
||||||
name = "test_lib";
|
|
||||||
dependencies = [ rustTransitiveLib ];
|
|
||||||
} ''
|
|
||||||
extern crate transitive;
|
|
||||||
use transitive::{transitive};
|
|
||||||
pub fn test() -> String {
|
|
||||||
transitive("test")
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
|
|
||||||
rustWithLib = run (rustSimple
|
|
||||||
{
|
|
||||||
name = "rust-with-lib";
|
|
||||||
dependencies = [ rustTestLib ];
|
|
||||||
} ''
|
|
||||||
extern crate test_lib;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
assert_eq!(test_lib::test(), String::from("test 1 2 3"));
|
|
||||||
}
|
|
||||||
'');
|
|
||||||
|
|
||||||
|
|
||||||
in
|
|
||||||
depot.nix.readTree.drvTargets {
|
|
||||||
inherit
|
|
||||||
rustTransitiveLib
|
|
||||||
rustWithLib
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
yants
|
|
||||||
=====
|
|
||||||
|
|
||||||
This is a tiny type-checker for data in Nix, written in Nix.
|
|
||||||
|
|
||||||
# Features
|
|
||||||
|
|
||||||
* Checking of primitive types (`int`, `string` etc.)
|
|
||||||
* Checking polymorphic types (`option`, `list`, `either`)
|
|
||||||
* Defining & checking struct/record types
|
|
||||||
* Defining & matching enum types
|
|
||||||
* Defining & matching sum types
|
|
||||||
* Defining function signatures (including curried functions)
|
|
||||||
* Types are composable! `option string`! `list (either int (option float))`!
|
|
||||||
* Type errors also compose!
|
|
||||||
|
|
||||||
Currently lacking:
|
|
||||||
|
|
||||||
* Any kind of inference
|
|
||||||
* Convenient syntax for attribute-set function signatures
|
|
||||||
|
|
||||||
## Primitives & simple polymorphism
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Structs
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Nested structs!
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Enums!
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Functions!
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
Yants can be imported from its `default.nix`. A single attribute (`lib`) can be
|
|
||||||
passed, which will otherwise be imported from `<nixpkgs>`.
|
|
||||||
|
|
||||||
TIP: You do not need to clone the entire TVL repository to use Yants!
|
|
||||||
You can clone just this project through josh: `git clone
|
|
||||||
https://code.tvl.fyi/depot.git:/nix/yants.git`
|
|
||||||
|
|
||||||
Examples for the most common import methods would be:
|
|
||||||
|
|
||||||
1. Import into scope with `with`:
|
|
||||||
```nix
|
|
||||||
with (import ./default.nix {});
|
|
||||||
# ... Nix code that uses yants ...
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Import as a named variable:
|
|
||||||
```nix
|
|
||||||
let yants = import ./default.nix {};
|
|
||||||
in yants.string "foo" # or other uses ...
|
|
||||||
````
|
|
||||||
|
|
||||||
3. Overlay into `pkgs.lib`:
|
|
||||||
```nix
|
|
||||||
# wherever you import your package set (e.g. from <nixpkgs>):
|
|
||||||
import <nixpkgs> {
|
|
||||||
overlays = [
|
|
||||||
(self: super: {
|
|
||||||
lib = super.lib // { yants = import ./default.nix { inherit (super) lib; }; };
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
# yants now lives at lib.yants, besides the other library functions!
|
|
||||||
```
|
|
||||||
|
|
||||||
Please see my [Nix one-pager](https://github.com/tazjin/nix-1p) for more generic
|
|
||||||
information about the Nix language and what the above constructs mean.
|
|
||||||
|
|
||||||
# Stability
|
|
||||||
|
|
||||||
The current API of Yants is **not yet** considered stable, but it works fine and
|
|
||||||
should continue to do so even if used at an older version.
|
|
||||||
|
|
||||||
Yants' tests use Nix versions above 2.2 - compatibility with older versions is
|
|
||||||
not guaranteed.
|
|
||||||
|
|
@ -1,368 +0,0 @@
|
||||||
# Copyright 2019 Google LLC
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Provides a "type-system" for Nix that provides various primitive &
|
|
||||||
# polymorphic types as well as the ability to define & check records.
|
|
||||||
#
|
|
||||||
# All types (should) compose as expected.
|
|
||||||
|
|
||||||
{ lib ? (import <nixpkgs> { }).lib, ... }:
|
|
||||||
|
|
||||||
with builtins; let
|
|
||||||
prettyPrint = lib.generators.toPretty { };
|
|
||||||
|
|
||||||
# typedef' :: struct {
|
|
||||||
# name = string;
|
|
||||||
# checkType = function; (a -> result)
|
|
||||||
# checkToBool = option function; (result -> bool)
|
|
||||||
# toError = option function; (a -> result -> string)
|
|
||||||
# def = option any;
|
|
||||||
# match = option function;
|
|
||||||
# } -> type
|
|
||||||
# -> (a -> b)
|
|
||||||
# -> (b -> bool)
|
|
||||||
# -> (a -> b -> string)
|
|
||||||
# -> type
|
|
||||||
#
|
|
||||||
# This function creates an attribute set that acts as a type.
|
|
||||||
#
|
|
||||||
# It receives a type name, a function that is used to perform a
|
|
||||||
# check on an arbitrary value, a function that can translate the
|
|
||||||
# return of that check to a boolean that informs whether the value
|
|
||||||
# is type-conformant, and a function that can construct error
|
|
||||||
# messages from the check result.
|
|
||||||
#
|
|
||||||
# This function is the low-level primitive used to create types. For
|
|
||||||
# many cases the higher-level 'typedef' function is more appropriate.
|
|
||||||
typedef' =
|
|
||||||
{ name
|
|
||||||
, checkType
|
|
||||||
, checkToBool ? (result: result.ok)
|
|
||||||
, toError ? (_: result: result.err)
|
|
||||||
, def ? null
|
|
||||||
, match ? null
|
|
||||||
}: {
|
|
||||||
inherit name checkToBool toError;
|
|
||||||
|
|
||||||
# check :: a -> bool
|
|
||||||
#
|
|
||||||
# This function is used to determine whether a given type is
|
|
||||||
# conformant.
|
|
||||||
check = value: checkToBool (checkType value);
|
|
||||||
|
|
||||||
# checkType :: a -> struct { ok = bool; err = option string; }
|
|
||||||
#
|
|
||||||
# This function checks whether the passed value is type conformant
|
|
||||||
# and returns an optional type error string otherwise.
|
|
||||||
inherit checkType;
|
|
||||||
|
|
||||||
# __functor :: a -> a
|
|
||||||
#
|
|
||||||
# This function checks whether the passed value is type conformant
|
|
||||||
# and throws an error if it is not.
|
|
||||||
#
|
|
||||||
# The name of this function is a special attribute in Nix that
|
|
||||||
# makes it possible to execute a type attribute set like a normal
|
|
||||||
# function.
|
|
||||||
__functor = self: value:
|
|
||||||
let result = self.checkType value;
|
|
||||||
in if checkToBool result then value
|
|
||||||
else throw (toError value result);
|
|
||||||
};
|
|
||||||
|
|
||||||
typeError = type: val:
|
|
||||||
"expected type '${type}', but value '${prettyPrint val}' is of type '${typeOf val}'";
|
|
||||||
|
|
||||||
# typedef :: string -> (a -> bool) -> type
|
|
||||||
#
|
|
||||||
# typedef is the simplified version of typedef' which uses a default
|
|
||||||
# error message constructor.
|
|
||||||
typedef = name: check: typedef' {
|
|
||||||
inherit name;
|
|
||||||
checkType = v:
|
|
||||||
let res = check v;
|
|
||||||
in {
|
|
||||||
ok = res;
|
|
||||||
} // (lib.optionalAttrs (!res) {
|
|
||||||
err = typeError name v;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
checkEach = name: t: l: foldl'
|
|
||||||
(acc: e:
|
|
||||||
let
|
|
||||||
res = t.checkType e;
|
|
||||||
isT = t.checkToBool res;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
ok = acc.ok && isT;
|
|
||||||
err =
|
|
||||||
if isT
|
|
||||||
then acc.err
|
|
||||||
else acc.err + "${prettyPrint e}: ${t.toError e res}\n";
|
|
||||||
})
|
|
||||||
{ ok = true; err = "expected type ${name}, but found:\n"; }
|
|
||||||
l;
|
|
||||||
in
|
|
||||||
lib.fix (self: {
|
|
||||||
# Primitive types
|
|
||||||
any = typedef "any" (_: true);
|
|
||||||
unit = typedef "unit" (v: v == { });
|
|
||||||
int = typedef "int" isInt;
|
|
||||||
bool = typedef "bool" isBool;
|
|
||||||
float = typedef "float" isFloat;
|
|
||||||
string = typedef "string" isString;
|
|
||||||
path = typedef "path" (x: typeOf x == "path");
|
|
||||||
drv = typedef "derivation" (x: isAttrs x && x ? "type" && x.type == "derivation");
|
|
||||||
function = typedef "function" (x: isFunction x || (isAttrs x && x ? "__functor"
|
|
||||||
&& isFunction x.__functor));
|
|
||||||
|
|
||||||
# Type for types themselves. Useful when defining polymorphic types.
|
|
||||||
type = typedef "type" (x:
|
|
||||||
isAttrs x
|
|
||||||
&& hasAttr "name" x && self.string.check x.name
|
|
||||||
&& hasAttr "checkType" x && self.function.check x.checkType
|
|
||||||
&& hasAttr "checkToBool" x && self.function.check x.checkToBool
|
|
||||||
&& hasAttr "toError" x && self.function.check x.toError
|
|
||||||
);
|
|
||||||
|
|
||||||
# Polymorphic types
|
|
||||||
option = t: typedef' rec {
|
|
||||||
name = "option<${t.name}>";
|
|
||||||
checkType = v:
|
|
||||||
let res = t.checkType v;
|
|
||||||
in {
|
|
||||||
ok = isNull v || (self.type t).checkToBool res;
|
|
||||||
err = "expected type ${name}, but value does not conform to '${t.name}': "
|
|
||||||
+ t.toError v res;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
eitherN = tn: typedef "either<${concatStringsSep ", " (map (x: x.name) tn)}>"
|
|
||||||
(x: any (t: (self.type t).check x) tn);
|
|
||||||
|
|
||||||
either = t1: t2: self.eitherN [ t1 t2 ];
|
|
||||||
|
|
||||||
list = t: typedef' rec {
|
|
||||||
name = "list<${t.name}>";
|
|
||||||
|
|
||||||
checkType = v:
|
|
||||||
if isList v
|
|
||||||
then checkEach name (self.type t) v
|
|
||||||
else {
|
|
||||||
ok = false;
|
|
||||||
err = typeError name v;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs = t: typedef' rec {
|
|
||||||
name = "attrs<${t.name}>";
|
|
||||||
|
|
||||||
checkType = v:
|
|
||||||
if isAttrs v
|
|
||||||
then checkEach name (self.type t) (attrValues v)
|
|
||||||
else {
|
|
||||||
ok = false;
|
|
||||||
err = typeError name v;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Structs / record types
|
|
||||||
#
|
|
||||||
# Checks that all fields match their declared types, no optional
|
|
||||||
# fields are missing and no unexpected fields occur in the struct.
|
|
||||||
#
|
|
||||||
# Anonymous structs are supported (e.g. for nesting) by omitting the
|
|
||||||
# name.
|
|
||||||
#
|
|
||||||
# TODO: Support open records?
|
|
||||||
struct =
|
|
||||||
# Struct checking is more involved than the simpler types above.
|
|
||||||
# To make the actual type definition more readable, several
|
|
||||||
# helpers are defined below.
|
|
||||||
let
|
|
||||||
# checkField checks an individual field of the struct against
|
|
||||||
# its definition and creates a typecheck result. These results
|
|
||||||
# are aggregated during the actual checking.
|
|
||||||
checkField = def: name: value:
|
|
||||||
let result = def.checkType value; in rec {
|
|
||||||
ok = def.checkToBool result;
|
|
||||||
err =
|
|
||||||
if !ok && isNull value
|
|
||||||
then "missing required ${def.name} field '${name}'\n"
|
|
||||||
else "field '${name}': ${def.toError value result}\n";
|
|
||||||
};
|
|
||||||
|
|
||||||
# checkExtraneous determines whether a (closed) struct contains
|
|
||||||
# any fields that are not part of the definition.
|
|
||||||
checkExtraneous = def: has: acc:
|
|
||||||
if (length has) == 0 then acc
|
|
||||||
else if (hasAttr (head has) def)
|
|
||||||
then checkExtraneous def (tail has) acc
|
|
||||||
else
|
|
||||||
checkExtraneous def (tail has) {
|
|
||||||
ok = false;
|
|
||||||
err = acc.err + "unexpected struct field '${head has}'\n";
|
|
||||||
};
|
|
||||||
|
|
||||||
# checkStruct combines all structure checks and creates one
|
|
||||||
# typecheck result from them
|
|
||||||
checkStruct = def: value:
|
|
||||||
let
|
|
||||||
init = { ok = true; err = ""; };
|
|
||||||
extraneous = checkExtraneous def (attrNames value) init;
|
|
||||||
|
|
||||||
checkedFields = map
|
|
||||||
(n:
|
|
||||||
let v = if hasAttr n value then value."${n}" else null;
|
|
||||||
in checkField def."${n}" n v)
|
|
||||||
(attrNames def);
|
|
||||||
|
|
||||||
combined = foldl'
|
|
||||||
(acc: res: {
|
|
||||||
ok = acc.ok && res.ok;
|
|
||||||
err = if !res.ok then acc.err + res.err else acc.err;
|
|
||||||
})
|
|
||||||
init
|
|
||||||
checkedFields;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
ok = combined.ok && extraneous.ok;
|
|
||||||
err = combined.err + extraneous.err;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct' = name: def: typedef' {
|
|
||||||
inherit name def;
|
|
||||||
checkType = value:
|
|
||||||
if isAttrs value
|
|
||||||
then (checkStruct (self.attrs self.type def) value)
|
|
||||||
else { ok = false; err = typeError name value; };
|
|
||||||
|
|
||||||
toError = _: result: "expected '${name}'-struct, but found:\n" + result.err;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
arg: if isString arg then (struct' arg) else (struct' "anon" arg);
|
|
||||||
|
|
||||||
# Enums & pattern matching
|
|
||||||
enum =
|
|
||||||
let
|
|
||||||
plain = name: def: typedef' {
|
|
||||||
inherit name def;
|
|
||||||
|
|
||||||
checkType = (x: isString x && elem x def);
|
|
||||||
checkToBool = x: x;
|
|
||||||
toError = value: _: "'${prettyPrint value} is not a member of enum ${name}";
|
|
||||||
};
|
|
||||||
enum' = name: def: lib.fix (e: (plain name def) // {
|
|
||||||
match = x: actions: deepSeq (map e (attrNames actions)) (
|
|
||||||
let
|
|
||||||
actionKeys = attrNames actions;
|
|
||||||
missing = foldl' (m: k: if (elem k actionKeys) then m else m ++ [ k ]) [ ] def;
|
|
||||||
in
|
|
||||||
if (length missing) > 0
|
|
||||||
then throw "Missing match action for members: ${prettyPrint missing}"
|
|
||||||
else actions."${e x}"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
in
|
|
||||||
arg: if isString arg then (enum' arg) else (enum' "anon" arg);
|
|
||||||
|
|
||||||
# Sum types
|
|
||||||
#
|
|
||||||
# The representation of a sum type is an attribute set with only one
|
|
||||||
# value, where the key of the value denotes the variant of the type.
|
|
||||||
sum =
|
|
||||||
let
|
|
||||||
plain = name: def: typedef' {
|
|
||||||
inherit name def;
|
|
||||||
checkType = (x:
|
|
||||||
let variant = elemAt (attrNames x) 0;
|
|
||||||
in if isAttrs x && length (attrNames x) == 1 && hasAttr variant def
|
|
||||||
then
|
|
||||||
let
|
|
||||||
t = def."${variant}";
|
|
||||||
v = x."${variant}";
|
|
||||||
res = t.checkType v;
|
|
||||||
in
|
|
||||||
if t.checkToBool res
|
|
||||||
then { ok = true; }
|
|
||||||
else {
|
|
||||||
ok = false;
|
|
||||||
err = "while checking '${name}' variant '${variant}': "
|
|
||||||
+ t.toError v res;
|
|
||||||
}
|
|
||||||
else { ok = false; err = typeError name x; }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
sum' = name: def: lib.fix (s: (plain name def) // {
|
|
||||||
match = x: actions:
|
|
||||||
let
|
|
||||||
variant = deepSeq (s x) (elemAt (attrNames x) 0);
|
|
||||||
actionKeys = attrNames actions;
|
|
||||||
defKeys = attrNames def;
|
|
||||||
missing = foldl' (m: k: if (elem k actionKeys) then m else m ++ [ k ]) [ ] defKeys;
|
|
||||||
in
|
|
||||||
if (length missing) > 0
|
|
||||||
then throw "Missing match action for variants: ${prettyPrint missing}"
|
|
||||||
else actions."${variant}" x."${variant}";
|
|
||||||
});
|
|
||||||
in
|
|
||||||
arg: if isString arg then (sum' arg) else (sum' "anon" arg);
|
|
||||||
|
|
||||||
# Typed function definitions
|
|
||||||
#
|
|
||||||
# These definitions wrap the supplied function in type-checking
|
|
||||||
# forms that are evaluated when the function is called.
|
|
||||||
#
|
|
||||||
# Note that typed functions themselves are not types and can not be
|
|
||||||
# used to check values for conformity.
|
|
||||||
defun =
|
|
||||||
let
|
|
||||||
mkFunc = sig: f: {
|
|
||||||
inherit sig;
|
|
||||||
__toString = self: foldl' (s: t: "${s} -> ${t.name}")
|
|
||||||
"λ :: ${(head self.sig).name}"
|
|
||||||
(tail self.sig);
|
|
||||||
__functor = _: f;
|
|
||||||
};
|
|
||||||
|
|
||||||
defun' = sig: func:
|
|
||||||
if length sig > 2
|
|
||||||
then mkFunc sig (x: defun' (tail sig) (func ((head sig) x)))
|
|
||||||
else mkFunc sig (x: ((head (tail sig)) (func ((head sig) x))));
|
|
||||||
|
|
||||||
in
|
|
||||||
sig: func:
|
|
||||||
if length sig < 2
|
|
||||||
then (throw "Signature must at least have two types (a -> b)")
|
|
||||||
else defun' sig func;
|
|
||||||
|
|
||||||
# Restricting types
|
|
||||||
#
|
|
||||||
# `restrict` wraps a type `t`, and uses a predicate `pred` to further
|
|
||||||
# restrict the values, giving the restriction a descriptive `name`.
|
|
||||||
#
|
|
||||||
# First, the wrapped type definition is checked (e.g. int) and then the
|
|
||||||
# value is checked with the predicate, so the predicate can already
|
|
||||||
# depend on the value being of the wrapped type.
|
|
||||||
restrict = name: pred: t:
|
|
||||||
let restriction = "${t.name}[${name}]"; in typedef' {
|
|
||||||
name = restriction;
|
|
||||||
checkType = v:
|
|
||||||
let res = t.checkType v;
|
|
||||||
in
|
|
||||||
if !(t.checkToBool res)
|
|
||||||
then res
|
|
||||||
else
|
|
||||||
let
|
|
||||||
iok = pred v;
|
|
||||||
in
|
|
||||||
if isBool iok then {
|
|
||||||
ok = iok;
|
|
||||||
err = "${prettyPrint v} does not conform to restriction '${restriction}'";
|
|
||||||
} else
|
|
||||||
# use throw here to avoid spamming the build log
|
|
||||||
throw "restriction '${restriction}' predicate returned unexpected value '${prettyPrint iok}' instead of boolean";
|
|
||||||
};
|
|
||||||
|
|
||||||
})
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB |
|
|
@ -1,158 +0,0 @@
|
||||||
{ depot, pkgs, ... }:
|
|
||||||
|
|
||||||
with depot.nix.yants;
|
|
||||||
|
|
||||||
# Note: Derivations are not included in the tests below as they cause
|
|
||||||
# issues with deepSeq.
|
|
||||||
|
|
||||||
let
|
|
||||||
|
|
||||||
inherit (depot.nix.runTestsuite)
|
|
||||||
runTestsuite
|
|
||||||
it
|
|
||||||
assertEq
|
|
||||||
assertThrows
|
|
||||||
assertDoesNotThrow
|
|
||||||
;
|
|
||||||
|
|
||||||
# this derivation won't throw if evaluated with deepSeq
|
|
||||||
# unlike most things even remotely related with nixpkgs
|
|
||||||
trivialDerivation = derivation {
|
|
||||||
name = "trivial-derivation";
|
|
||||||
inherit (pkgs.stdenv) system;
|
|
||||||
builder = "/bin/sh";
|
|
||||||
args = [ "-c" "echo hello > $out" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
testPrimitives = it "checks that all primitive types match" [
|
|
||||||
(assertDoesNotThrow "unit type" (unit { }))
|
|
||||||
(assertDoesNotThrow "int type" (int 15))
|
|
||||||
(assertDoesNotThrow "bool type" (bool false))
|
|
||||||
(assertDoesNotThrow "float type" (float 13.37))
|
|
||||||
(assertDoesNotThrow "string type" (string "Hello!"))
|
|
||||||
(assertDoesNotThrow "function type" (function (x: x * 2)))
|
|
||||||
(assertDoesNotThrow "path type" (path /nix))
|
|
||||||
(assertDoesNotThrow "derivation type" (drv trivialDerivation))
|
|
||||||
];
|
|
||||||
|
|
||||||
testPoly = it "checks that polymorphic types work as intended" [
|
|
||||||
(assertDoesNotThrow "option type" (option int null))
|
|
||||||
(assertDoesNotThrow "list type" (list string [ "foo" "bar" ]))
|
|
||||||
(assertDoesNotThrow "either type" (either int float 42))
|
|
||||||
];
|
|
||||||
|
|
||||||
# Test that structures work as planned.
|
|
||||||
person = struct "person" {
|
|
||||||
name = string;
|
|
||||||
age = int;
|
|
||||||
|
|
||||||
contact = option (struct {
|
|
||||||
email = string;
|
|
||||||
phone = option string;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
testStruct = it "checks that structures work as intended" [
|
|
||||||
(assertDoesNotThrow "person struct" (person {
|
|
||||||
name = "Brynhjulf";
|
|
||||||
age = 42;
|
|
||||||
contact.email = "brynhjulf@yants.nix";
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
|
|
||||||
# Test enum definitions & matching
|
|
||||||
colour = enum "colour" [ "red" "blue" "green" ];
|
|
||||||
colourMatcher = {
|
|
||||||
red = "It is in fact red!";
|
|
||||||
blue = "It should not be blue!";
|
|
||||||
green = "It should not be green!";
|
|
||||||
};
|
|
||||||
|
|
||||||
testEnum = it "checks enum definitions and matching" [
|
|
||||||
(assertEq "enum is matched correctly"
|
|
||||||
"It is in fact red!"
|
|
||||||
(colour.match "red" colourMatcher))
|
|
||||||
(assertThrows "out of bounds enum fails"
|
|
||||||
(colour.match "alpha" (colourMatcher // {
|
|
||||||
alpha = "This should never happen";
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
# Test sum type definitions
|
|
||||||
creature = sum "creature" {
|
|
||||||
human = struct {
|
|
||||||
name = string;
|
|
||||||
age = option int;
|
|
||||||
};
|
|
||||||
|
|
||||||
pet = enum "pet" [ "dog" "lizard" "cat" ];
|
|
||||||
};
|
|
||||||
some-human = creature {
|
|
||||||
human = {
|
|
||||||
name = "Brynhjulf";
|
|
||||||
age = 42;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testSum = it "checks sum types definitions and matching" [
|
|
||||||
(assertDoesNotThrow "creature sum type" some-human)
|
|
||||||
(assertEq "sum type is matched correctly"
|
|
||||||
"It's a human named Brynhjulf"
|
|
||||||
(creature.match some-human {
|
|
||||||
human = v: "It's a human named ${v.name}";
|
|
||||||
pet = v: "It's not supposed to be a pet!";
|
|
||||||
})
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
# Test curried function definitions
|
|
||||||
func = defun [ string int string ]
|
|
||||||
(name: age: "${name} is ${toString age} years old");
|
|
||||||
|
|
||||||
testFunctions = it "checks function definitions" [
|
|
||||||
(assertDoesNotThrow "function application" (func "Brynhjulf" 42))
|
|
||||||
];
|
|
||||||
|
|
||||||
# Test that all types are types.
|
|
||||||
assertIsType = name: t:
|
|
||||||
assertDoesNotThrow "${name} is a type" (type t);
|
|
||||||
testTypes = it "checks that all types are types" [
|
|
||||||
(assertIsType "any" any)
|
|
||||||
(assertIsType "bool" bool)
|
|
||||||
(assertIsType "drv" drv)
|
|
||||||
(assertIsType "float" float)
|
|
||||||
(assertIsType "int" int)
|
|
||||||
(assertIsType "string" string)
|
|
||||||
(assertIsType "path" path)
|
|
||||||
|
|
||||||
(assertIsType "attrs int" (attrs int))
|
|
||||||
(assertIsType "eitherN [ ... ]" (eitherN [ int string bool ]))
|
|
||||||
(assertIsType "either int string" (either int string))
|
|
||||||
(assertIsType "enum [ ... ]" (enum [ "foo" "bar" ]))
|
|
||||||
(assertIsType "list string" (list string))
|
|
||||||
(assertIsType "option int" (option int))
|
|
||||||
(assertIsType "option (list string)" (option (list string)))
|
|
||||||
(assertIsType "struct { ... }" (struct { a = int; b = option string; }))
|
|
||||||
(assertIsType "sum { ... }" (sum { a = int; b = option string; }))
|
|
||||||
];
|
|
||||||
|
|
||||||
testRestrict = it "checks restrict types" [
|
|
||||||
(assertDoesNotThrow "< 42" ((restrict "< 42" (i: i < 42) int) 25))
|
|
||||||
(assertDoesNotThrow "list length < 3"
|
|
||||||
((restrict "not too long" (l: builtins.length l < 3) (list int)) [ 1 2 ]))
|
|
||||||
(assertDoesNotThrow "list eq 5"
|
|
||||||
(list (restrict "eq 5" (v: v == 5) any) [ 5 5 5 ]))
|
|
||||||
];
|
|
||||||
|
|
||||||
in
|
|
||||||
runTestsuite "yants" [
|
|
||||||
testPrimitives
|
|
||||||
testPoly
|
|
||||||
testStruct
|
|
||||||
testEnum
|
|
||||||
testSum
|
|
||||||
testFunctions
|
|
||||||
testTypes
|
|
||||||
testRestrict
|
|
||||||
]
|
|
||||||
|
|
@ -1,29 +1,12 @@
|
||||||
# Expose secrets as part of the tree, making it possible to validate
|
# Expose secrets as part of the tree, exposing their paths at eval time.
|
||||||
# their paths at eval time.
|
|
||||||
#
|
#
|
||||||
# Note that encrypted secrets end up in the Nix store, but this is
|
# Note that encrypted secrets end up in the Nix store, but this is
|
||||||
# fine since they're publicly available anyways.
|
# fine since they're publicly available anyways.
|
||||||
{ depot, lib, ... }:
|
{ depot, lib, ... }:
|
||||||
|
|
||||||
let
|
(
|
||||||
inherit (depot.nix.yants)
|
path: secrets:
|
||||||
attrs
|
|
||||||
any
|
|
||||||
either
|
|
||||||
defun
|
|
||||||
list
|
|
||||||
path
|
|
||||||
restrict
|
|
||||||
string
|
|
||||||
struct
|
|
||||||
;
|
|
||||||
ssh-pubkey = restrict "SSH pubkey" (lib.hasPrefix "ssh-") string;
|
|
||||||
age-pubkey = restrict "age pubkey" (lib.hasPrefix "age") string;
|
|
||||||
agenixSecret = struct "agenixSecret" { publicKeys = list (either age-pubkey ssh-pubkey); };
|
|
||||||
in
|
|
||||||
|
|
||||||
defun [ path (attrs agenixSecret) (attrs any) ]
|
|
||||||
(path: secrets:
|
|
||||||
depot.nix.readTree.drvTargets
|
depot.nix.readTree.drvTargets
|
||||||
# Import each secret into the Nix store
|
# Import each secret into the Nix store
|
||||||
(builtins.mapAttrs (name: _: "${path}/${name}") secrets))
|
(builtins.mapAttrs (name: _: "${path}/${name}") secrets)
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
# This file is read by agenix standalone, to know which audiences to (re)encrypt secrets to.
|
||||||
|
|
||||||
let
|
let
|
||||||
raito = [
|
raito = [
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICaw9ihTG7ucB8P38XdalEWev8+q96e2yNm4B+/I9IJp"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICaw9ihTG7ucB8P38XdalEWev8+q96e2yNm4B+/I9IJp"
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
{ depot, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
bins = depot.nix.getBins pkgs.coreutils [ "printf" ];
|
|
||||||
|
|
||||||
# printf(1), but redirect to stderr
|
|
||||||
in
|
|
||||||
depot.nix.writeExecline "eprintf" { } [
|
|
||||||
"fdmove"
|
|
||||||
"-c"
|
|
||||||
"1"
|
|
||||||
"2"
|
|
||||||
bins.printf
|
|
||||||
"$@"
|
|
||||||
]
|
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
{ depot, pkgs, lib, ... }:
|
{ depot, pkgs, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
bins =
|
|
||||||
depot.nix.getBins pkgs.cargo-audit [ "cargo-audit" ]
|
|
||||||
// depot.nix.getBins pkgs.jq [ "jq" ]
|
|
||||||
// depot.nix.getBins pkgs.findutils [ "find" ]
|
|
||||||
// depot.nix.getBins pkgs.gnused [ "sed" ]
|
|
||||||
;
|
|
||||||
|
|
||||||
our-crates = lib.filter (v: v ? outPath)
|
our-crates = lib.filter (v: v ? outPath)
|
||||||
(builtins.attrValues depot.third_party.rust-crates);
|
(builtins.attrValues depot.third_party.rust-crates);
|
||||||
|
|
||||||
|
|
@ -36,10 +28,10 @@ let
|
||||||
exit 100
|
exit 100
|
||||||
fi
|
fi
|
||||||
|
|
||||||
"${bins.cargo-audit}" audit --json --no-fetch \
|
${pkgs.cargo-audit}/bin/cargo-audit audit --json --no-fetch \
|
||||||
--db "${depot.third_party.rustsec-advisory-db}" \
|
--db "${depot.third_party.rustsec-advisory-db}" \
|
||||||
--file "$2" \
|
--file "$2" \
|
||||||
| "${bins.jq}" --raw-output --join-output \
|
| ${pkgs.jq}/bin/jq --raw-output --join-output \
|
||||||
--from-file "${./format-audit-result.jq}" \
|
--from-file "${./format-audit-result.jq}" \
|
||||||
--arg maintainers "''${4:-}" \
|
--arg maintainers "''${4:-}" \
|
||||||
--argjson checklist "''${3:-false}" \
|
--argjson checklist "''${3:-false}" \
|
||||||
|
|
@ -56,9 +48,9 @@ let
|
||||||
|
|
||||||
# Find prints the found lockfiles as <DEPOT ROOT>\t<LOCKFILE DIR>\t<LOCKFILE PATH>\0
|
# Find prints the found lockfiles as <DEPOT ROOT>\t<LOCKFILE DIR>\t<LOCKFILE PATH>\0
|
||||||
while IFS=$'\t' read -r -d $'\0' entryPoint dir lockFile; do
|
while IFS=$'\t' read -r -d $'\0' entryPoint dir lockFile; do
|
||||||
label="$(printf '%s' "$dir" | "${bins.sed}" "s|^$entryPoint|/|")"
|
label="$(printf '%s' "$dir" | ${pkgs.gnused}/bin/sed "s|^$entryPoint|/|")"
|
||||||
"${lock-file-report}" "$label" "$lockFile" || status=1
|
"${lock-file-report}" "$label" "$lockFile" || status=1
|
||||||
done < <("${bins.find}" "$root" -type f -name Cargo.lock -printf '%H\t%h\t%p\0' )
|
done < <(${pkgs.findutils}/bin/find "$root" -type f -name Cargo.lock -printf '%H\t%h\t%p\0' )
|
||||||
|
|
||||||
exit $status
|
exit $status
|
||||||
'';
|
'';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue