#!/usr/bin/env nix-shell #!nix-shell -i bash -p findutils -p git -p nix-update # each update job has to do an entire nix eval, which can be memory intensive; be careful when tuning this # N.B.: not all update jobs are actually independent! naive parallelism like this *will* result in # failed/partial updates, e.g.: # 1. `sane.nixpkgs-bootstrap.master` is being updated: this breaks eval of _all_ other attributes. # 2. `sane.otherPackage` is being updated: its updateScript fails to eval `sane.otherPackage.src`, # however this would eval if job 1 and 2 were serialized. # # TODO: perform parallel updates in some way that the update scripts don't interfere with eachother. PARALLELISM=8 SELF_PATH=$PWD/$0 usage() { echo "update: update rev/hash for one or more packages" echo "usage: update [options] [attr-path]" echo "" echo "options:" echo "- --dry-run" echo "- --verbose" echo "- -j (default: $PARALLELISM)" echo "" echo "examples:" echo "- update nixpkgs: update only the nixpkgs input" echo "- update sane: update every package under the 'sane' attribute" echo "- update: update everything i know how to update" exit 1 } warn() { echo "$@" } info() { echo "$@" } debug() { if [ -n "$verbose" ]; then echo "$@" fi } hasEffect() { if [ -n "$dryRun" ]; then echo "dry-run: skip $@" else eval "$@" fi } REPO_ROOT= repo_root() { if [ -z "$REPO_ROOT" ]; then REPO_ROOT=$(git -C "$(dirname SELF_PATH)" rev-parse --show-toplevel) fi echo "$REPO_ROOT" } # usage: getPkgs outVar prefix getPkgs() { local -n attrsArr="$1" local attrPrefix="$2" if [ -z "$attrPrefix" ]; then attrPrefix=sane fi # # nix-env doesn't seem to build anything when evaluating queries, # # but since i use Import From Derivation along paths which i also want to query, # # then i need to ensure those derivations are available for import. # debug "creating requisite .drv store paths" # nix-instantiate -A nix "$(repo_root)" # nix-instantiate -A nixpkgs-bootstrap.master "$(repo_root)" debug "querying packages to update as part of '$attrPrefix'" local attrs=$(nix eval --raw -f "$(repo_root)" 'updateTargets."'"$attrPrefix"'"' --apply 'builtins.concatStringsSep " "' "${nixFlags[@]}") debug "got: $attrs" attrsArr+=($attrs) } updateOnePkg() { local attrPath="$1" local updateScript=$(nix eval --raw -f "$(repo_root)" 'updateScripts."'"$attrPath"'"' "${nixFlags[@]}") if [ -z "$updateScript" ]; then warn "don't know how to update '$attrPath'" return fi # make sure everything needed to invoke the update script exists in-store local context=$(nix eval --raw -f "$(repo_root)" 'updateScripts."'"$attrPath"'"' --apply 's: builtins.concatStringsSep " " (builtins.attrNames (builtins.getContext s))' "${nixFlags[@]}") for c in $context; do debug "realizing updateScript requisite: $context" nix-store --realize "$c" "${nixFlags[@]}" || true done local workingDir="$(repo_root)/.working/update/$attrPath" rm -rf "$workingDir" mkdir -p "$workingDir" info "updating: '$attrPath'" info "working out of $workingDir" debug "$updateScript" # update scripts often write artifacts (e.g. `git-commits.txt`) to the working directory, # so change to a unique directory before running the update script to avoid interfering with any other # update scripts that might be running simultaneously. pushd "$workingDir" "$updateScript" > >(tee update.log) 2> >(tee update.stderr >&2) popd } updatePkgsInParallel() { debug "updating packages in parallel using xargs" debug "- $@" debug "- xargs -n 1 -P $PARALLELISM $0 ${scriptFlags[*]}" echo "$@" | xargs -n 1 -P "$PARALLELISM" "$0" "${scriptFlags[@]}" } scriptFlags=() nixFlags=() dryRun= toplevelsToUpdate=() verbose= parseArgs() { while [ "$#" -ne 0 ]; do local arg=$1 shift case "$arg" in (--help) usage ;; (--dry-run) scriptFlags+=(--dry-run) dryRun=1 ;; (--verbose) scriptFlags+=(--verbose) verbose=1 ;; (-j) PARALLELISM=$1 shift ;; (--*) nixFlags+=("$arg") ;; (*) toplevelsToUpdate+=("$arg") ;; esac done if [ "${#toplevelsToUpdate[@]}" -eq 0 ]; then toplevelsToUpdate=(sane) fi } parseArgs "$@" pkgsToUpdate=() for t in "${toplevelsToUpdate[@]}"; do getPkgs pkgsToUpdate "$t" done case "${#pkgsToUpdate[@]}" in (0) echo "nothing to do" ;; (1) hasEffect updateOnePkg "${pkgsToUpdate[0]}" ;; (*) hasEffect updatePkgsInParallel "${pkgsToUpdate[@]}" ;; esac