From 88e0867d362a836eca870b0efecbefa57069fad5 Mon Sep 17 00:00:00 2001 From: sternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org> Date: Thu, 18 Mar 2021 15:14:37 +0100 Subject: 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. --- pkgs/sternenseemann/default.nix | 2 + pkgs/sternenseemann/rust/default.nix | 8 ++ pkgs/sternenseemann/rust/nix-env-diff.rs | 163 +++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 pkgs/sternenseemann/rust/nix-env-diff.rs (limited to 'pkgs/sternenseemann') 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); +#[derive(Eq, PartialEq, Hash, Clone)] +struct Attr(Vec); + +type OutPathMap = HashMap; + +#[derive(Eq, PartialEq)] +enum Mode { + PrintAttrs, + PrintOutPaths, +} + +fn parse_nix_env_line( + line: &[u8] +) -> Option<(Vec, Attr)> { + // fields are separated by > 1 space + let segments = + line.split(|c| *c == 0x20) + .filter(|s| s.len() > 0) + .collect::>(); + let len = segments.len(); + + if len >= 2 { + // handle multiple outputs which are listed like this: + // ;lib=;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( + 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( + 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, Vec) = + 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(()) +} -- cgit 1.4.1