about summary refs log tree commit diff
path: root/pkgs/sternenseemann
diff options
context:
space:
mode:
authorsternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org>2021-03-18 15:14:37 +0100
committersternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org>2021-03-18 15:14:37 +0100
commit88e0867d362a836eca870b0efecbefa57069fad5 (patch)
treec93f585a755eb520647218a6a950b7de9c7add10 /pkgs/sternenseemann
parent06b78c72027ed680f9e8e1e59814dc300b83f8c3 (diff)
pkgs/sternenseemann/nix-env-diff: utility for comparing nix-env evals
nix-env-diff is a tiny utility which parses and compares the output of
`nix-env -qaP --out-path`, printing all changed and added out paths (or
attributes if desired). This facilitates a simple way to determine
rebuilds or changed reverse dependencies when working on nixpkgs in a
similar way as nixpkgs-review and ofborg do it.

Both the new evaluation and the base evaluation to compare against have
to be created manually using nix-env, which in turn also allows
considering attribute sets that are normally not evaluated do to missing
`lib.recurseIntoAttrs`. As an example, here is an example building all
changed attributes in `ocaml-ng.ocamlPackages_4_12`:

```
nix-env -qaP -A ocaml-ng.ocamlPackages_4_12 --out-path -f . \
  | nix-env-diff --attrs ./base-ocamlPackages_4_12 \
  | xargs -n 1 -P 4 nix-instantiate --quiet -A \
  | xargs nix-store --realise --builders 'ssh://edwin'
```

`./base-ocamlPackages_4_12` contains the result of the `nix-env`
invocation executed on the master branch.
Diffstat (limited to 'pkgs/sternenseemann')
-rw-r--r--pkgs/sternenseemann/default.nix2
-rw-r--r--pkgs/sternenseemann/rust/default.nix8
-rw-r--r--pkgs/sternenseemann/rust/nix-env-diff.rs163
3 files changed, 173 insertions, 0 deletions
diff --git a/pkgs/sternenseemann/default.nix b/pkgs/sternenseemann/default.nix
index e0c1bb16..ce2e47cd 100644
--- a/pkgs/sternenseemann/default.nix
+++ b/pkgs/sternenseemann/default.nix
@@ -35,6 +35,7 @@ let
 
   rust = callPackage ./rust {
     inherit (profpatsch)
+      writeRustSimpleBin
       writeRustSimpleLib
       testRustSimple
       ;
@@ -84,6 +85,7 @@ lib.fix (self: {
 
   inherit (rust)
     temp
+    nix-env-diff
     ;
 
   # don't bother hydra with trivial text substitution
diff --git a/pkgs/sternenseemann/rust/default.nix b/pkgs/sternenseemann/rust/default.nix
index f79644a8..bc4e785b 100644
--- a/pkgs/sternenseemann/rust/default.nix
+++ b/pkgs/sternenseemann/rust/default.nix
@@ -1,4 +1,5 @@
 { writeRustSimpleLib
+, writeRustSimpleBin
 , testRustSimple
 }:
 
@@ -13,8 +14,15 @@ let
       };
     } ./temp.rs);
 
+  nix-env-diff = writeRustSimpleBin "nix-env-diff" {
+    meta = {
+      description = "Print changed attrs / outpath for nix-env outputs";
+    };
+  } ./nix-env-diff.rs;
+
 in {
   inherit
     temp
+    nix-env-diff
     ;
   }
diff --git a/pkgs/sternenseemann/rust/nix-env-diff.rs b/pkgs/sternenseemann/rust/nix-env-diff.rs
new file mode 100644
index 00000000..ce68aff6
--- /dev/null
+++ b/pkgs/sternenseemann/rust/nix-env-diff.rs
@@ -0,0 +1,163 @@
+use std::collections::HashMap;
+use std::env::args_os;
+use std::ffi::OsString;
+use std::fs::File;
+use std::io::{BufReader, BufRead, Read};
+use std::os::unix::ffi::OsStrExt;
+use std::process::exit;
+
+#[derive(Eq, PartialEq, Hash)]
+struct OutPath(Vec<u8>);
+#[derive(Eq, PartialEq, Hash, Clone)]
+struct Attr(Vec<u8>);
+
+type OutPathMap = HashMap<OutPath, Attr>;
+
+#[derive(Eq, PartialEq)]
+enum Mode {
+  PrintAttrs,
+  PrintOutPaths,
+}
+
+fn parse_nix_env_line(
+  line: &[u8]
+) -> Option<(Vec<OutPath>, Attr)> {
+  // fields are separated by > 1 space
+  let segments =
+    line.split(|c| *c == 0x20)
+        .filter(|s| s.len() > 0)
+        .collect::<Vec<&[u8]>>();
+  let len = segments.len();
+
+  if len >= 2 {
+    // handle multiple outputs which are listed like this:
+    // <out>;lib=<bin>;dev=<dev>
+    // where <…> are all out paths
+    let outpaths = segments[len - 1]
+      .split(|c| *c == 0x3B) // ';'
+      .map(|s| s.split(|c| *c == 0x3D).last().unwrap()) // '=', should never
+      .map(|s| OutPath(s.to_owned()))                   // panic if s non-empty
+      .collect();
+
+    Some((outpaths, Attr(segments[0].to_owned())))
+  } else {
+    None
+  }
+}
+
+fn insert_out_paths<T: Read>(
+  input: T,
+  map: &mut OutPathMap
+) -> std::io::Result<()> {
+  let mut reader = BufReader::new(input);
+
+  for line in reader.split(0x0A) {
+    match line.map(|s| parse_nix_env_line(s.as_slice()))? {
+      Some((outs, attr)) =>
+        for out in outs {
+          map.insert(out, attr.clone());
+        },
+      None => continue,
+    };
+  }
+
+  Ok(())
+}
+
+fn print_changed<T: Read>(
+  mode: Mode,
+  input: T,
+  map: &OutPathMap
+) -> std::io::Result<()> {
+  let mut reader = BufReader::new(input);
+
+  for line in reader.split(0x0A) {
+    match line.map(|s| parse_nix_env_line(s.as_slice()))? {
+      Some((outs, attr)) =>
+        for out in outs {
+          if map.get(&out).is_some() {
+            println!("{}", String::from_utf8_lossy(
+                match mode {
+                  Mode::PrintOutPaths => &out.0,
+                  Mode::PrintAttrs => &attr.0
+                }));
+
+            if mode == Mode::PrintAttrs {
+              // only need to print the attr once
+              break;
+            }
+          }
+        },
+      None => continue,
+    }
+  }
+
+  Ok(())
+}
+
+fn main() -> std::io::Result<()> {
+  fn print_usage() {
+    eprintln!(r"Usage: nix-env-diff [--attrs] BASE [ NEW ]
+
+BASE and NEW need to be both the output of a call to
+`nix-env -qaP --out-path` or similar. If NEW is omitted,
+stdin is used. `--json` is not supported because it
+doesn't contain the output paths which are crucial.
+
+nix-env-diff parses the outputs of nix-env and prints
+all output paths (or attribute paths if --attrs is
+given) that are in NEW, but not in BASE, i. e. all
+new or changed attributes / output paths. This can
+be used as a poor man's nixpkgs-review to inspect
+all potential rebuilds between two evaluations of
+nixpkgs with the added benefit that processing the
+output is easier and the nix-env parameters can
+be specified freely, so you can even get around
+`lib.dontRecurseIntoAttrs`.
+
+Options:
+
+    --attrs    Print attributes instead of out paths
+    --help     Print this message"
+    );
+  }
+
+  let mut mode = Mode::PrintOutPaths;
+  let (flags, args): (Vec<OsString>, Vec<OsString>) =
+    args_os().skip(1)
+      .partition(|a| a.as_bytes().starts_with(b"-"));
+
+  for flag in flags {
+    match flag.as_bytes() {
+      b"--attrs" => mode = Mode::PrintAttrs,
+      b"--help"  => {
+        print_usage();
+        exit(0);
+      },
+      _ => {
+        print_usage();
+        exit(100);
+      }
+    }
+  }
+
+  if args.len() < 1 {
+    print_usage();
+    exit(100);
+  }
+
+  let old = File::open(args[0].as_os_str())?;
+  let new = args.get(1)
+                .map(|p| File::open(p.as_os_str()))
+                .transpose()?;
+
+  let mut old_outpaths = HashMap::new();
+
+  insert_out_paths(old, &mut old_outpaths)?;
+  match new {
+    Some(f) => print_changed(mode, f, &old_outpaths),
+    None    => print_changed(mode, std::io::stdin(), &old_outpaths),
+  }?;
+
+  Ok(())
+}