summary refs log tree commit diff
path: root/maintainers
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2010-07-04 21:11:19 +0000
committerLudovic Courtès <ludo@gnu.org>2010-07-04 21:11:19 +0000
commitc83cb10f2b919f2505c9e1eb1b8f73ebc37b8144 (patch)
tree5253f5005a04d899aa115496cf9dc9b00c6f704c /maintainers
parentc3484e59e967891fd39b72c3affa62d2d2d5ccd2 (diff)
downloadnixpkgs-c83cb10f2b919f2505c9e1eb1b8f73ebc37b8144.tar
nixpkgs-c83cb10f2b919f2505c9e1eb1b8f73ebc37b8144.tar.gz
nixpkgs-c83cb10f2b919f2505c9e1eb1b8f73ebc37b8144.tar.bz2
nixpkgs-c83cb10f2b919f2505c9e1eb1b8f73ebc37b8144.tar.lz
nixpkgs-c83cb10f2b919f2505c9e1eb1b8f73ebc37b8144.tar.xz
nixpkgs-c83cb10f2b919f2505c9e1eb1b8f73ebc37b8144.tar.zst
nixpkgs-c83cb10f2b919f2505c9e1eb1b8f73ebc37b8144.zip
gnupdate: Turn into a module.
* maintainers/scripts/gnu/gnupdate.scm: Rename to...
* maintainers/scripts/gnu/gnupdate: ... this.  Use `define-module' and
  the orthodox way of doing things.
  (main): Rename to...
  (gnupdate): ... this.

svn path=/nixpkgs/trunk/; revision=22466
Diffstat (limited to 'maintainers')
-rwxr-xr-xmaintainers/scripts/gnu/gnupdate836
-rw-r--r--maintainers/scripts/gnu/gnupdate.scm828
2 files changed, 834 insertions, 830 deletions
diff --git a/maintainers/scripts/gnu/gnupdate b/maintainers/scripts/gnu/gnupdate
index 741ce7e63ee..c08dad7e604 100755
--- a/maintainers/scripts/gnu/gnupdate
+++ b/maintainers/scripts/gnu/gnupdate
@@ -1,4 +1,836 @@
 #!/bin/sh
+# This is actually -*- mode: scheme; coding: utf-8; -*- text.
+main='(module-ref (resolve-module '\''(gnupdate)) '\'gnupdate')'
+exec ${GUILE-guile} -L "$PWD" -l "$0"    \
+         -c "(apply $main (command-line))" "$@"
+!#
+;;; GNUpdate -- Update GNU packages in Nixpkgs.
+;;; Copyright (C) 2010  Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; This program is free software: you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation, either version 3 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-exec "${GUILE:-guile}" $GUILE_FLAGS -L . -l gnupdate.scm \
-  -e '(apply main (cdr (command-line)))' -- "$@"
+(cond-expand (guile-2 #t)
+             (else (error "GNU Guile 2.0 is required")))
+
+(define-module (gnupdate)
+  #:use-module (sxml ssax)
+  #:use-module (ice-9 popen)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 regex)
+  #:use-module (ice-9 vlist)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-9)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:use-module (system foreign)
+  #:use-module (rnrs bytevectors)
+  #:export (gnupdate))
+
+
+;;;
+;;; SNix.
+;;;
+
+(define-record-type <location>
+  (make-location file line column)
+  location?
+  (file          location-file)
+  (line          location-line)
+  (column        location-column))
+
+(define (->loc line column path)
+  (and line column path
+       (make-location path (string->number line) (string->number column))))
+
+;; XXX: Hack to add missing exports from `(sxml ssax)' as of 1.9.10.
+(let ((ssax (resolve-module '(sxml ssax))))
+  (for-each (lambda (sym)
+              (module-add! (current-module) sym
+                           (module-variable ssax sym)))
+            '(ssax:warn ssax:skip-pi nl)))
+
+;; Nix object types visible in the XML output of `nix-instantiate' and
+;; mapping to S-expressions (we map to sexps, not records, so that we
+;; can do pattern matching):
+;;
+;;   at               (at varpat attrspat)
+;;   attr             (attribute loc name value)
+;;   attrs            (attribute-set attributes)
+;;   attrspat         (attribute-set-pattern patterns)
+;;   bool             #f|#t
+;;   derivation       (derivation drv-path out-path attributes)
+;;   ellipsis         '...
+;;   expr             (snix loc body ...)
+;;   function         (function loc at|attrspat|varpat)
+;;   int              int
+;;   list             list
+;;   null             'null
+;;   path             string
+;;   string           string
+;;   unevaluated      'unevaluated
+;;   varpat           (varpat name)
+;;
+;; Initially ATTRIBUTES in `derivation' and `attribute-set' was a promise;
+;; however, handling `repeated' nodes makes it impossible to do anything
+;; lazily because the whole SXML tree has to be traversed to maintain the
+;; list of known derivations.
+
+(define (xml-element->snix elem attributes body derivations)
+  ;; Return an SNix element corresponding to XML element ELEM.
+
+  (define (loc)
+    (->loc (assq-ref attributes 'line)
+           (assq-ref attributes 'column)
+           (assq-ref attributes 'path)))
+
+  (case elem
+    ((at)
+     (values `(at ,(car body) ,(cadr body)) derivations))
+    ((attr)
+     (let ((name (assq-ref attributes 'name)))
+       (cond ((null? body)
+              (values `(attribute-pattern ,name) derivations))
+             ((and (pair? body) (null? (cdr body)))
+              (values `(attribute ,(loc) ,name ,(car body))
+                      derivations))
+             (else
+              (error "invalid attribute body" name (loc) body)))))
+    ((attrs)
+     (values `(attribute-set ,(reverse body)) derivations))
+    ((attrspat)
+     (values `(attribute-set-pattern ,body) derivations))
+    ((bool)
+     (values (string-ci=? "true" (assq-ref attributes 'value))
+             derivations))
+    ((derivation)
+     (let ((drv-path (assq-ref attributes 'drvPath))
+           (out-path (assq-ref attributes 'outPath)))
+       (if (equal? body '(repeated))
+           (let ((body (vhash-assoc drv-path derivations)))
+             (if (pair? body)
+                 (values `(derivation ,drv-path ,out-path ,(cdr body))
+                         derivations)
+                 (error "no previous occurrence of derivation"
+                        drv-path)))
+           (values `(derivation ,drv-path ,out-path ,body)
+                   (vhash-cons drv-path body derivations)))))
+    ((ellipsis)
+     (values '... derivations))
+    ((expr)
+     (values `(snix ,(loc) ,@body) derivations))
+    ((function)
+     (values `(function ,(loc) ,body) derivations))
+    ((int)
+     (values (string->number (assq-ref attributes 'value))
+             derivations))
+    ((list)
+     (values body derivations))
+    ((null)
+     (values 'null derivations))
+    ((path)
+     (values (assq-ref attributes 'value) derivations))
+    ((repeated)
+     (values 'repeated derivations))
+    ((string)
+     (values (assq-ref attributes 'value) derivations))
+    ((unevaluated)
+     (values 'unevaluated derivations))
+    ((varpat)
+     (values `(varpat ,(assq-ref attributes 'name)) derivations))
+    (else (error "unhandled Nix XML element" elem))))
+
+(define xml->snix
+  ;; Return the SNix represention of TREE, an SXML tree as returned by
+  ;; parsing the XML output of `nix-instantiate' on Nixpkgs.
+  (let ((parse
+         (ssax:make-parser NEW-LEVEL-SEED
+                           (lambda (elem-gi attributes namespaces expected-content
+                                    seed)
+                             (cons '() (cdr seed)))
+
+                           FINISH-ELEMENT
+                           (lambda (elem-gi attributes namespaces parent-seed
+                                            seed)
+                             (let ((snix        (car seed))
+                                   (derivations (cdr seed)))
+                               (let-values (((snix derivations)
+                                             (xml-element->snix elem-gi
+                                                                attributes
+                                                                snix
+                                                                derivations)))
+                                 (cons (cons snix (car parent-seed))
+                                       derivations))))
+
+                           CHAR-DATA-HANDLER
+                           (lambda (string1 string2 seed)
+                             ;; Discard inter-node strings, which are blanks.
+                             seed))))
+    (lambda (port)
+      ;; Discard the second value returned by the parser (the derivation
+      ;; vhash).
+      (caar (parse port (cons '() vlist-null))))))
+
+(define (call-with-package snix proc)
+  (match snix
+    (('attribute _ (and attribute-name (? string?))
+                 ('derivation _ _ body))
+     ;; Ugly pattern matching.
+     (let ((meta
+            (any (lambda (attr)
+                   (match attr
+                     (('attribute _ "meta" ('attribute-set metas)) metas)
+                     (_ #f)))
+                 body))
+           (package-name
+            (any (lambda (attr)
+                   (match attr
+                     (('attribute _ "name" (and name (? string?)))
+                      name)
+                     (_ #f)))
+                 body))
+           (location
+            (any (lambda (attr)
+                   (match attr
+                     (('attribute loc "name" (? string?))
+                      loc)
+                     (_ #f)))
+                 body))
+           (src
+            (any (lambda (attr)
+                   (match attr
+                     (('attribute _ "src" src)
+                      src)
+                     (_ #f)))
+                 body)))
+       (proc attribute-name package-name location meta src)))))
+
+(define (call-with-src snix proc)
+  ;; Assume SNIX contains the SNix expression for the value of an `src'
+  ;; attribute, as returned by `call-with-package', and call PROC with the
+  ;; relevant SRC information, or #f if SNIX doesn't match.
+  (match snix
+    (('derivation _ _ body)
+     (let ((name
+            (any (lambda (attr)
+                   (match attr
+                     (('attribute _ "name" (and name (? string?)))
+                      name)
+                     (_ #f)))
+                 body))
+           (output-hash
+            (any (lambda (attr)
+                   (match attr
+                     (('attribute _ "outputHash" (and hash (? string?)))
+                      hash)
+                     (_ #f)))
+                 body))
+           (urls
+            (any (lambda (attr)
+                   (match attr
+                     (('attribute _ "urls" (and urls (? pair?)))
+                      urls)
+                     (_ #f)))
+                 body)))
+       (proc name output-hash urls)))
+    (_ (proc #f #f #f))))
+
+(define (src->values snix)
+  (call-with-src snix values))
+
+(define (attribute-value attribute)
+  ;; Return the value of ATTRIBUTE.
+  (match attribute
+    (('attribute _ _ value) value)))
+
+(define (derivation-source derivation)
+  ;; Return the "src" attribute of DERIVATION or #f if not found.
+  (match derivation
+    (('derivation _ _ (attributes ...))
+     (find-attribute-by-name "src" attributes))))
+
+(define (derivation-output-path derivation)
+  ;; Return the output path of DERIVATION.
+  (match derivation
+    (('derivation _ out-path _)
+     out-path)
+    (_ #f)))
+
+(define (source-output-path src)
+  ;; Return the output path of SRC, the "src" attribute of a derivation.
+  (derivation-output-path (attribute-value src)))
+
+(define (derivation-source-output-path derivation)
+  ;; Return the output path of the "src" attribute of DERIVATION or #f if
+  ;; DERIVATION lacks an "src" attribute.
+  (and=> (derivation-source derivation) source-output-path))
+
+(define (open-nixpkgs nixpkgs)
+  (let ((script  (string-append nixpkgs
+                                "/maintainers/scripts/eval-release.nix")))
+    (open-pipe* OPEN_READ "nix-instantiate"
+                "--strict" "--eval-only" "--xml"
+                script)))
+
+(define (nix-prefetch-url url)
+  ;; Download URL in the Nix store and return the base32-encoded SHA256 hash
+  ;; of the file at URL
+  (let* ((pipe (open-pipe* OPEN_READ "nix-prefetch-url" url))
+         (hash (read-line pipe)))
+    (close-pipe pipe)
+    (if (eof-object? hash)
+        (values #f #f)
+        (let* ((pipe (open-pipe* OPEN_READ "nix-store" "--print-fixed-path"
+                                 "sha256" hash (basename url)))
+               (path (read-line pipe)))
+          (if (eof-object? path)
+              (values #f #f)
+              (values (string-trim-both hash) (string-trim-both path)))))))
+
+(define (update-nix-expression file
+                               old-version old-hash
+                               new-version new-hash)
+  ;; Modify FILE in-place.  Ugly: we call out to sed(1).
+  (let ((cmd (format #f "sed -i \"~a\" -e 's/~A/~a/g ; s/~A/~A/g'"
+                     file
+                     (regexp-quote old-version) new-version
+                     old-hash
+                     (or new-hash "new hash not available, check the log"))))
+    (format #t "running `~A'...~%" cmd)
+    (system cmd)))
+
+(define (find-attribute-by-name name attributes)
+  ;; Return attribute NAME in ATTRIBUTES, a list of SNix attributes, or #f if
+  ;; NAME cannot be found.
+  (find (lambda (a)
+          (match a
+            (('attribute _ (? (cut string=? <> name)) _)
+             a)
+            (_ #f)))
+        attributes))
+
+(define (find-package-by-attribute-name name packages)
+  ;; Return the package bound to attribute NAME in PACKAGES, a list of
+  ;; packages (SNix attributes), or #f if NAME cannot be found.
+  (find (lambda (package)
+          (match package
+            (('attribute _ (? (cut string=? <> name))
+                         ('derivation _ _ _))
+             package)
+            (_ #f)))
+        packages))
+
+(define (stdenv-package packages)
+  ;; Return the `stdenv' package from PACKAGES, a list of SNix attributes.
+  (find-package-by-attribute-name "stdenv" packages))
+
+(define (package-requisites package)
+  ;; Return the list of derivations required to build PACKAGE (including that
+  ;; of PACKAGE) by recurring into its derivation attributes.
+  (let loop ((snix   package)
+             (result '()))
+    (match snix
+      (('attribute _ _ body)
+       (loop body result))
+      (('derivation _ out-path body)
+       (if (any (lambda (d)
+                  (match d
+                    (('derivation _ (? (cut string=? out-path <>)) _) #t)
+                    (_ #f)))
+                result)
+           result
+           (loop body (cons snix result))))
+      ((things ...)
+       (fold loop result things))
+      (_ result))))
+
+(define (package-source-output-path package)
+  ;; Return the output path of the "src" derivation of PACKAGE.
+  (derivation-source-output-path (attribute-value package)))
+
+
+;;;
+;;; FTP client.
+;;;
+
+(define-record-type <ftp-connection>
+  (%make-ftp-connection socket addrinfo)
+  ftp-connection?
+  (socket    ftp-connection-socket)
+  (addrinfo  ftp-connection-addrinfo))
+
+(define %ftp-ready-rx
+  (make-regexp "^([0-9]{3}) (.+)$"))
+
+(define (%ftp-listen port)
+  (let loop ((line (read-line port)))
+    (cond ((eof-object? line) (values line #f))
+          ((regexp-exec %ftp-ready-rx line)
+           =>
+           (lambda (match)
+             (values (string->number (match:substring match 1))
+                     (match:substring match 2))))
+          (else
+           (loop (read-line port))))))
+
+(define (%ftp-command command expected-code port)
+  (format port "~A~A~A" command (string #\return) (string #\newline))
+  (let-values (((code message) (%ftp-listen port)))
+    (if (eqv? code expected-code)
+        message
+        (throw 'ftp-error port command code message))))
+
+(define (%ftp-login user pass port)
+  (let ((command (string-append "USER " user (string #\newline))))
+    (display command port)
+    (let-values (((code message) (%ftp-listen port)))
+      (case code
+        ((230)  #t)
+        ((331) (%ftp-command (string-append "PASS " pass) 230 port))
+        (else  (throw 'ftp-error port command code message))))))
+
+(define (ftp-open host)
+  (catch 'getaddrinfo-error
+    (lambda ()
+      (let* ((ai (car (getaddrinfo host "ftp")))
+             (s  (socket (addrinfo:fam ai) (addrinfo:socktype ai)
+                         (addrinfo:protocol ai))))
+        (connect s (addrinfo:addr ai))
+        (setvbuf s _IOLBF)
+        (let-values (((code message) (%ftp-listen s)))
+          (if (eqv? code 220)
+              (begin
+                ;(%ftp-command "OPTS UTF8 ON" 200 s)
+                (%ftp-login "anonymous" "ludo@example.com" s)
+                (%make-ftp-connection s ai))
+              (begin
+                (format (current-error-port) "FTP to `~a' failed: ~A: ~A~%"
+                        host code message)
+                (close s)
+                #f)))))
+    (lambda (key errcode)
+      (format (current-error-port) "failed to resolve `~a': ~a~%"
+              host (gai-strerror errcode))
+      #f)))
+
+(define (ftp-close conn)
+  (close (ftp-connection-socket conn)))
+
+(define (ftp-chdir conn dir)
+  (%ftp-command (string-append "CWD " dir) 250
+                (ftp-connection-socket conn)))
+
+(define (ftp-pasv conn)
+  (define %pasv-rx
+    (make-regexp "([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)"))
+
+  (let ((message (%ftp-command "PASV" 227 (ftp-connection-socket conn))))
+    (cond ((regexp-exec %pasv-rx message)
+           =>
+           (lambda (match)
+             (+ (* (string->number (match:substring match 5)) 256)
+                (string->number (match:substring match 6)))))
+          (else
+           (throw 'ftp-error conn "PASV" 227 message)))))
+
+
+(define* (ftp-list conn #:optional directory)
+  (define (address-with-port sa port)
+    (let ((fam  (sockaddr:fam sa))
+          (addr (sockaddr:addr sa)))
+      (cond ((= fam AF_INET)
+             (make-socket-address fam addr port))
+            ((= fam AF_INET6)
+             (make-socket-address fam addr port
+                                  (sockaddr:flowinfo sa)
+                                  (sockaddr:scopeid sa)))
+            (else #f))))
+
+  (if directory
+      (ftp-chdir conn directory))
+
+  (let* ((port (ftp-pasv conn))
+         (ai   (ftp-connection-addrinfo conn))
+         (s    (socket (addrinfo:fam ai) (addrinfo:socktype ai)
+                       (addrinfo:protocol ai))))
+    (connect s (address-with-port (addrinfo:addr ai) port))
+    (setvbuf s _IOLBF)
+
+    (dynamic-wind
+      (lambda () #t)
+      (lambda ()
+        (%ftp-command "LIST" 150 (ftp-connection-socket conn))
+
+        (let loop ((line   (read-line s))
+                   (result '()))
+          (cond ((eof-object? line) (reverse result))
+                ((regexp-exec %ftp-ready-rx line)
+                 =>
+                 (lambda (match)
+                   (let ((code (string->number (match:substring match 1))))
+                     (if (= 126 code)
+                         (reverse result)
+                         (throw 'ftp-error conn "LIST" code)))))
+                (else
+                 (loop (read-line s)
+                       (let ((file (car (reverse (string-tokenize line)))))
+                         (cons file result)))))))
+      (lambda ()
+        (close s)
+        (let-values (((code message) (%ftp-listen (ftp-connection-socket conn))))
+          (or (eqv? code 226)
+              (throw 'ftp-error conn "LIST" code message)))))))
+
+
+;;;
+;;; GNU.
+;;;
+
+(define %ignored-package-attributes
+  ;; Attribute name of packages to be ignored.
+  '("bash" "bashReal" "bashInteractive" ;; the full versioned name is incorrect
+    "autoconf213"
+    "automake17x"
+    "automake19x"
+    "automake110x"
+    "automake" ;; = 1.10.x
+    "bison1875"
+    "bison23"
+    "bison" ;; = 2.3
+    "emacs22"
+    "emacsSnapshot"
+    "gcc295"
+    "gcc33"
+    "gcc34"
+    "gcc40"
+    "gcc41"
+    "gcc42"
+    "gcc43"
+    "gcc44"
+    "gcc45"
+    "glibc25"
+    "glibc27"
+    "glibc29"
+    "guile_1_9"
+    ))
+
+(define (gnu? package)
+  ;; Return true if PACKAGE (a snix expression) is a GNU package (according
+  ;; to a simple heuristic.)  Otherwise return #f.
+  (match package
+    (('attribute _ _ ('derivation _ _ body))
+     (any (lambda (attr)
+            (match attr
+              (('attribute _ "meta" ('attribute-set metas))
+               (any (lambda (attr)
+                      (match attr
+                        (('attribute _ "description" value)
+                         (string-prefix? "GNU" value))
+                        (('attribute _ "homepage" value)
+                         (string-contains value "www.gnu.org"))
+                        (_ #f)))
+                    metas))
+              (_ #f)))
+          body))
+    (_ #f)))
+
+(define (gnu-packages packages)
+  (fold (lambda (package gnu)
+          (match package
+            (('attribute _ "emacs23Packages" emacs-packages)
+             ;; XXX: Should prepend `emacs23Packages.' to attribute names.
+             (append (gnu-packages emacs-packages) gnu))
+            (('attribute _ attribute-name ('derivation _ _ body))
+             (if (member attribute-name %ignored-package-attributes)
+                 gnu
+                 (if (gnu? package)
+                     (cons package gnu)
+                     gnu)))
+            (_ gnu)))
+        '()
+        packages))
+
+(define (ftp-server/directory project)
+  (define quirks
+    '(("commoncpp2"   "ftp.gnu.org"   "/gnu/commoncpp" #f)
+      ("libgcrypt"    "ftp.gnupg.org" "/gcrypt" #t)
+      ("libgpg-error" "ftp.gnupg.org" "/gcrypt" #t)
+      ("gnupg"        "ftp.gnupg.org" "/gcrypt" #t)
+      ("gnu-ghostscript" "ftp.gnu.org"  "/ghostscript" #f)
+      ("grub"         "alpha.gnu.org" "/gnu" #t)
+      ("GNUnet"       "ftp.gnu.org" "/gnu/gnunet" #f)
+      ("mit-scheme"   "ftp.gnu.org" "/gnu/mit-scheme/stable.pkg")
+      ("icecat"       "ftp.gnu.org" "/gnu/gnuzilla" #f)
+      ("TeXmacs"      "ftp.texmacs.org" "/TeXmacs/targz" #f)))
+
+  (let ((quirk (assoc project quirks)))
+    (match quirk
+      ((_ server directory subdir?)
+       (values server (if (not subdir?)
+                          directory
+                          (string-append directory "/" project))))
+      (_
+       (values "ftp.gnu.org" (string-append "/gnu/" project))))))
+
+(define (nixpkgs->gnu-name project)
+  (define quirks
+    '(("gcc-wrapper" . "gcc")
+      ("ghostscript" . "gnu-ghostscript") ;; ../ghostscript/gnu-ghoscript-X.Y.tar.gz
+      ("gnum4"       . "m4")
+      ("gnugrep"     . "grep")
+      ("gnused"      . "sed")
+      ("gnutar"      . "tar")
+      ("gnunet"      . "GNUnet") ;; ftp.gnu.org/gnu/gnunet/GNUnet-x.y.tar.gz
+      ("mitscheme"   . "mit-scheme")
+      ("texmacs"     . "TeXmacs")))
+
+  (or (assoc-ref quirks project) project))
+
+(define (releases project)
+  ;; TODO: Handle project release trees like that of IceCat and MyServer.
+  (define release-rx
+    (make-regexp (string-append "^" project "-[0-9].*\\.tar\\.")))
+
+  (catch #t
+    (lambda ()
+      (let-values (((server directory) (ftp-server/directory project)))
+        (let* ((conn  (ftp-open server))
+               (files (ftp-list conn directory)))
+          (ftp-close conn)
+          (map (lambda (tarball)
+                 (let ((end (string-contains tarball ".tar")))
+                   (substring tarball 0 end)))
+
+               ;; Filter out signatures, deltas, and files which are potentially
+               ;; not releases of PROJECT (e.g., in /gnu/guile, filter out
+               ;; guile-oops and guile-www).
+               (filter (lambda (file)
+                         (and (not (string-suffix? ".sig" file))
+                              (regexp-exec release-rx file)))
+                       files)))))
+    (lambda (key subr message . args)
+      (format (current-error-port)
+              "failed to get release list for `~A': ~A ~A~%"
+              project message args)
+      '())))
+
+(define version-string>?
+  (let ((strverscmp
+         (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
+                        (error "could not find `strverscmp' (from GNU libc)"))))
+           (make-foreign-function int sym (list '* '*))))
+        (string->null-terminated-utf8
+         (lambda (s)
+           (let* ((utf8 (string->utf8 s))
+                  (len  (bytevector-length utf8))
+                  (nts  (make-bytevector (+ len 1))))
+             (bytevector-copy! utf8 0 nts 0 len)
+             (bytevector-u8-set! nts len 0)
+             nts))))
+    (lambda (a b)
+      (let ((a (bytevector->foreign (string->null-terminated-utf8 a)))
+            (b (bytevector->foreign (string->null-terminated-utf8 b))))
+        (> (strverscmp a b) 0)))))
+
+(define (latest-release project)
+  ;; Return "FOO-X.Y" or #f.
+  (let ((releases (releases project)))
+    (and (not (null? releases))
+         (fold (lambda (release latest)
+                 (if (version-string>? release latest)
+                     release
+                     latest))
+               ""
+               releases))))
+
+(define (package/version name+version)
+  (let ((hyphen (string-rindex name+version #\-)))
+    (if (not hyphen)
+        (values name+version #f)
+        (let ((name    (substring name+version 0 hyphen))
+              (version (substring name+version (+ hyphen 1)
+                                  (string-length name+version))))
+          (values name version)))))
+
+(define (file-extension file)
+  (let ((dot (string-rindex file #\.)))
+    (and dot (substring file (+ 1 dot) (string-length file)))))
+
+(define (packages-to-update gnu-packages)
+  (fold (lambda (pkg result)
+          (call-with-package pkg
+            (lambda (attribute name+version location meta src)
+              (let-values (((name old-version)
+                            (package/version name+version)))
+                (let ((latest (latest-release (nixpkgs->gnu-name name))))
+                  (cond ((not latest)
+                         (format #t "~A [unknown latest version]~%"
+                                 name+version)
+                         result)
+                        ((string=? name+version latest)
+                         (format #t "~A [up to date]~%" name+version)
+                         result)
+                        (else
+                         (let-values (((project new-version)
+                                       (package/version latest))
+                                      ((old-name old-hash old-urls)
+                                       (src->values src)))
+                           (format #t "~A -> ~A [~A]~%" name+version latest
+                                   (and (pair? old-urls) (car old-urls)))
+                           (let* ((url      (and (pair? old-urls)
+                                                 (car old-urls)))
+                                  (new-hash (fetch-gnu project new-version
+                                                       (if url
+                                                           (file-extension url)
+                                                           "gz"))))
+                             (cons (list name attribute
+                                         old-version old-hash
+                                         new-version new-hash
+                                         location)
+                                   result))))))))))
+        '()
+        gnu-packages))
+
+(define (fetch-gnu project version archive-type)
+  (let-values (((server directory)
+                (ftp-server/directory project)))
+    (let* ((base    (string-append project "-" version ".tar." archive-type))
+           (url     (string-append "ftp://" server "/" directory "/" base))
+           (sig     (string-append base ".sig"))
+           (sig-url (string-append url ".sig")))
+      (let-values (((hash path) (nix-prefetch-url url)))
+        (pk 'prefetch-url url hash path)
+        (and hash path
+             (begin
+               (false-if-exception (delete-file sig))
+               (system* "wget" sig-url)
+               (if (file-exists? sig)
+                   (let ((ret (system* "gpg" "--verify" sig path)))
+                     (false-if-exception (delete-file sig))
+                     (if (and ret (= 0 (status:exit-val ret)))
+                         hash
+                         (begin
+                           (format (current-error-port)
+                                   "signature verification failed for `~a'~%"
+                                   base)
+                           (format (current-error-port)
+                                   "(could be because the public key is not in your keyring)~%")
+                           #f)))
+                   (begin
+                     (format (current-error-port)
+                             "no signature for `~a'~%" base)
+                     hash))))))))
+
+
+;;;
+;;; Main program.
+;;;
+
+(define %options
+  ;; Specifications of the command-line options.
+  (list (option '(#\h "help") #f #f
+                (lambda (opt name arg result)
+                  (format #t "Usage: gnupdate [OPTIONS...]~%")
+                  (format #t "GNUpdate -- update Nix expressions of GNU packages in Nixpkgs~%")
+                  (format #t "~%")
+                  (format #t "  -x, --xml=FILE      Read XML output of `nix-instantiate'~%")
+                  (format #t "                      from FILE.~%")
+                  (format #t "  -s, --select=SET    Update only packages from SET, which may~%")
+                  (format #t "                      be either `all',`stdenv', or `non-stdenv'.~%")
+                  (format #t "  -d, --dry-run       Don't actually update Nix expressions~%")
+                  (format #t "  -h, --help          Give this help list.~%~%")
+                  (format #t "Report bugs to <ludo@gnu.org>~%")
+                  (exit 0)))
+        (option '(#\s "select") #t #f
+                (lambda (opt name arg result)
+                  (cond ((string-ci=? arg "stdenv")
+                         (alist-cons 'filter 'stdenv result))
+                        ((string-ci=? arg "non-stdenv")
+                         (alist-cons 'filter 'non-stdenv result))
+                        ((string-ci=? arg "all")
+                         (alist-cons 'filter #f result))
+                        (else
+                         (format (current-error-port)
+                                 "~A: unrecognized selection type~%"
+                                 arg)
+                         (exit 1)))))
+
+        (option '(#\d "dry-run") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'dry-run #t result)))
+
+        (option '(#\x "xml") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'xml-file arg result)))))
+
+(define (gnupdate . args)
+  ;; Assume Nixpkgs is under $NIXPKGS or ~/src/nixpkgs.
+  (let* ((opts      (args-fold (cdr args) %options
+                               (lambda (opt name arg result)
+                                 (error "unrecognized option `~A'" name))
+                               (lambda (operand result)
+                                 (error "extraneous argument `~A'" operand))
+                               '()))
+         (home      (getenv "HOME"))
+         (path      (or (getenv "NIXPKGS")
+                        (string-append home "/src/nixpkgs")))
+         (snix      (begin
+                      (format (current-error-port) "parsing XML...~%")
+                      (xml->snix
+                       (or (and=> (assoc-ref opts 'xml-file) open-input-file)
+                           (open-nixpkgs path)))))
+         (packages  (match snix
+                      (('snix _ ('attribute-set attributes))
+                       attributes)
+                      (_ #f)))
+         (stdenv    (delay
+                      ;; The source tarballs that make up stdenv.
+                      (filter-map derivation-source-output-path
+                                  (package-requisites (stdenv-package packages)))))
+         (gnu       (gnu-packages packages))
+         (gnu*      (case (assoc-ref opts 'filter)
+                      ;; Filter out packages that are/aren't in `stdenv'.  To
+                      ;; do that reliably, we check whether their "src"
+                      ;; derivation is a requisite of stdenv.
+                      ((stdenv)
+                       (filter (lambda (p)
+                                 (member (package-source-output-path p)
+                                         (force stdenv)))
+                               gnu))
+                      ((non-stdenv)
+                       (filter (lambda (p)
+                                 (not (member (package-source-output-path p)
+                                              (force stdenv))))
+                               gnu))
+                      (else gnu)))
+         (updates   (packages-to-update gnu*)))
+
+    (format #t "~%~A packages to update...~%" (length updates))
+    (for-each (lambda (update)
+                (match update
+                  ((name attribute
+                    old-version old-hash
+                    new-version new-hash
+                    location)
+                   (if (assoc-ref opts 'dry-run)
+                       (format #t "`~a' would be updated from ~a to ~a (~a -> ~a)~%"
+                               name old-version new-version
+                               old-hash new-hash)
+                       (update-nix-expression (location-file location)
+                                              old-version old-hash
+                                              new-version new-hash)))
+                  (_ #f)))
+              updates)
+    #t))
diff --git a/maintainers/scripts/gnu/gnupdate.scm b/maintainers/scripts/gnu/gnupdate.scm
deleted file mode 100644
index 9b3e7342066..00000000000
--- a/maintainers/scripts/gnu/gnupdate.scm
+++ /dev/null
@@ -1,828 +0,0 @@
-;;; GNUpdate -- Update GNU packages in Nixpkgs.     -*- coding: utf-8; -*-
-;;; Copyright (C) 2010  Ludovic Courtès <ludo@gnu.org>
-;;;
-;;; This program is free software: you can redistribute it and/or modify
-;;; it under the terms of the GNU General Public License as published by
-;;; the Free Software Foundation, either version 3 of the License, or
-;;; (at your option) any later version.
-;;;
-;;; This program is distributed in the hope that it will be useful,
-;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-;;; GNU General Public License for more details.
-;;;
-;;; You should have received a copy of the GNU General Public License
-;;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-(cond-expand (guile-2 #t)
-             (else (error "GNU Guile 2.0 is required")))
-
-(use-modules (sxml ssax)
-             (ice-9 popen)
-             (ice-9 match)
-             (ice-9 rdelim)
-             (ice-9 regex)
-             (ice-9 vlist)
-             (srfi srfi-1)
-             (srfi srfi-9)
-             (srfi srfi-11)
-             (srfi srfi-26)
-             (srfi srfi-37)
-             (system foreign)
-             (rnrs bytevectors))
-
-
-;;;
-;;; SNix.
-;;;
-
-(define-record-type <location>
-  (make-location file line column)
-  location?
-  (file          location-file)
-  (line          location-line)
-  (column        location-column))
-
-(define (->loc line column path)
-  (and line column path
-       (make-location path (string->number line) (string->number column))))
-
-;; XXX: Hack to add missing exports from `(sxml ssax)' as of 1.9.10.
-(let ((ssax (resolve-module '(sxml ssax))))
-  (for-each (lambda (sym)
-              (module-add! (current-module) sym
-                           (module-variable ssax sym)))
-            '(ssax:warn ssax:skip-pi nl)))
-
-;; Nix object types visible in the XML output of `nix-instantiate' and
-;; mapping to S-expressions (we map to sexps, not records, so that we
-;; can do pattern matching):
-;;
-;;   at               (at varpat attrspat)
-;;   attr             (attribute loc name value)
-;;   attrs            (attribute-set attributes)
-;;   attrspat         (attribute-set-pattern patterns)
-;;   bool             #f|#t
-;;   derivation       (derivation drv-path out-path attributes)
-;;   ellipsis         '...
-;;   expr             (snix loc body ...)
-;;   function         (function loc at|attrspat|varpat)
-;;   int              int
-;;   list             list
-;;   null             'null
-;;   path             string
-;;   string           string
-;;   unevaluated      'unevaluated
-;;   varpat           (varpat name)
-;;
-;; Initially ATTRIBUTES in `derivation' and `attribute-set' was a promise;
-;; however, handling `repeated' nodes makes it impossible to do anything
-;; lazily because the whole SXML tree has to be traversed to maintain the
-;; list of known derivations.
-
-(define (xml-element->snix elem attributes body derivations)
-  ;; Return an SNix element corresponding to XML element ELEM.
-
-  (define (loc)
-    (->loc (assq-ref attributes 'line)
-           (assq-ref attributes 'column)
-           (assq-ref attributes 'path)))
-
-  (case elem
-    ((at)
-     (values `(at ,(car body) ,(cadr body)) derivations))
-    ((attr)
-     (let ((name (assq-ref attributes 'name)))
-       (cond ((null? body)
-              (values `(attribute-pattern ,name) derivations))
-             ((and (pair? body) (null? (cdr body)))
-              (values `(attribute ,(loc) ,name ,(car body))
-                      derivations))
-             (else
-              (error "invalid attribute body" name (loc) body)))))
-    ((attrs)
-     (values `(attribute-set ,(reverse body)) derivations))
-    ((attrspat)
-     (values `(attribute-set-pattern ,body) derivations))
-    ((bool)
-     (values (string-ci=? "true" (assq-ref attributes 'value))
-             derivations))
-    ((derivation)
-     (let ((drv-path (assq-ref attributes 'drvPath))
-           (out-path (assq-ref attributes 'outPath)))
-       (if (equal? body '(repeated))
-           (let ((body (vhash-assoc drv-path derivations)))
-             (if (pair? body)
-                 (values `(derivation ,drv-path ,out-path ,(cdr body))
-                         derivations)
-                 (error "no previous occurrence of derivation"
-                        drv-path)))
-           (values `(derivation ,drv-path ,out-path ,body)
-                   (vhash-cons drv-path body derivations)))))
-    ((ellipsis)
-     (values '... derivations))
-    ((expr)
-     (values `(snix ,(loc) ,@body) derivations))
-    ((function)
-     (values `(function ,(loc) ,body) derivations))
-    ((int)
-     (values (string->number (assq-ref attributes 'value))
-             derivations))
-    ((list)
-     (values body derivations))
-    ((null)
-     (values 'null derivations))
-    ((path)
-     (values (assq-ref attributes 'value) derivations))
-    ((repeated)
-     (values 'repeated derivations))
-    ((string)
-     (values (assq-ref attributes 'value) derivations))
-    ((unevaluated)
-     (values 'unevaluated derivations))
-    ((varpat)
-     (values `(varpat ,(assq-ref attributes 'name)) derivations))
-    (else (error "unhandled Nix XML element" elem))))
-
-(define xml->snix
-  ;; Return the SNix represention of TREE, an SXML tree as returned by
-  ;; parsing the XML output of `nix-instantiate' on Nixpkgs.
-  (let ((parse
-         (ssax:make-parser NEW-LEVEL-SEED
-                           (lambda (elem-gi attributes namespaces expected-content
-                                    seed)
-                             (cons '() (cdr seed)))
-
-                           FINISH-ELEMENT
-                           (lambda (elem-gi attributes namespaces parent-seed
-                                            seed)
-                             (let ((snix        (car seed))
-                                   (derivations (cdr seed)))
-                               (let-values (((snix derivations)
-                                             (xml-element->snix elem-gi
-                                                                attributes
-                                                                snix
-                                                                derivations)))
-                                 (cons (cons snix (car parent-seed))
-                                       derivations))))
-
-                           CHAR-DATA-HANDLER
-                           (lambda (string1 string2 seed)
-                             ;; Discard inter-node strings, which are blanks.
-                             seed))))
-    (lambda (port)
-      ;; Discard the second value returned by the parser (the derivation
-      ;; vhash).
-      (caar (parse port (cons '() vlist-null))))))
-
-(define (call-with-package snix proc)
-  (match snix
-    (('attribute _ (and attribute-name (? string?))
-                 ('derivation _ _ body))
-     ;; Ugly pattern matching.
-     (let ((meta
-            (any (lambda (attr)
-                   (match attr
-                     (('attribute _ "meta" ('attribute-set metas)) metas)
-                     (_ #f)))
-                 body))
-           (package-name
-            (any (lambda (attr)
-                   (match attr
-                     (('attribute _ "name" (and name (? string?)))
-                      name)
-                     (_ #f)))
-                 body))
-           (location
-            (any (lambda (attr)
-                   (match attr
-                     (('attribute loc "name" (? string?))
-                      loc)
-                     (_ #f)))
-                 body))
-           (src
-            (any (lambda (attr)
-                   (match attr
-                     (('attribute _ "src" src)
-                      src)
-                     (_ #f)))
-                 body)))
-       (proc attribute-name package-name location meta src)))))
-
-(define (call-with-src snix proc)
-  ;; Assume SNIX contains the SNix expression for the value of an `src'
-  ;; attribute, as returned by `call-with-package', and call PROC with the
-  ;; relevant SRC information, or #f if SNIX doesn't match.
-  (match snix
-    (('derivation _ _ body)
-     (let ((name
-            (any (lambda (attr)
-                   (match attr
-                     (('attribute _ "name" (and name (? string?)))
-                      name)
-                     (_ #f)))
-                 body))
-           (output-hash
-            (any (lambda (attr)
-                   (match attr
-                     (('attribute _ "outputHash" (and hash (? string?)))
-                      hash)
-                     (_ #f)))
-                 body))
-           (urls
-            (any (lambda (attr)
-                   (match attr
-                     (('attribute _ "urls" (and urls (? pair?)))
-                      urls)
-                     (_ #f)))
-                 body)))
-       (proc name output-hash urls)))
-    (_ (proc #f #f #f))))
-
-(define (src->values snix)
-  (call-with-src snix values))
-
-(define (attribute-value attribute)
-  ;; Return the value of ATTRIBUTE.
-  (match attribute
-    (('attribute _ _ value) value)))
-
-(define (derivation-source derivation)
-  ;; Return the "src" attribute of DERIVATION or #f if not found.
-  (match derivation
-    (('derivation _ _ (attributes ...))
-     (find-attribute-by-name "src" attributes))))
-
-(define (derivation-output-path derivation)
-  ;; Return the output path of DERIVATION.
-  (match derivation
-    (('derivation _ out-path _)
-     out-path)
-    (_ #f)))
-
-(define (source-output-path src)
-  ;; Return the output path of SRC, the "src" attribute of a derivation.
-  (derivation-output-path (attribute-value src)))
-
-(define (derivation-source-output-path derivation)
-  ;; Return the output path of the "src" attribute of DERIVATION or #f if
-  ;; DERIVATION lacks an "src" attribute.
-  (and=> (derivation-source derivation) source-output-path))
-
-(define (open-nixpkgs nixpkgs)
-  (let ((script  (string-append nixpkgs
-                                "/maintainers/scripts/eval-release.nix")))
-    (open-pipe* OPEN_READ "nix-instantiate"
-                "--strict" "--eval-only" "--xml"
-                script)))
-
-(define (nix-prefetch-url url)
-  ;; Download URL in the Nix store and return the base32-encoded SHA256 hash
-  ;; of the file at URL
-  (let* ((pipe (open-pipe* OPEN_READ "nix-prefetch-url" url))
-         (hash (read-line pipe)))
-    (close-pipe pipe)
-    (if (eof-object? hash)
-        (values #f #f)
-        (let* ((pipe (open-pipe* OPEN_READ "nix-store" "--print-fixed-path"
-                                 "sha256" hash (basename url)))
-               (path (read-line pipe)))
-          (if (eof-object? path)
-              (values #f #f)
-              (values (string-trim-both hash) (string-trim-both path)))))))
-
-(define (update-nix-expression file
-                               old-version old-hash
-                               new-version new-hash)
-  ;; Modify FILE in-place.  Ugly: we call out to sed(1).
-  (let ((cmd (format #f "sed -i \"~a\" -e 's/~A/~a/g ; s/~A/~A/g'"
-                     file
-                     (regexp-quote old-version) new-version
-                     old-hash
-                     (or new-hash "new hash not available, check the log"))))
-    (format #t "running `~A'...~%" cmd)
-    (system cmd)))
-
-(define (find-attribute-by-name name attributes)
-  ;; Return attribute NAME in ATTRIBUTES, a list of SNix attributes, or #f if
-  ;; NAME cannot be found.
-  (find (lambda (a)
-          (match a
-            (('attribute _ (? (cut string=? <> name)) _)
-             a)
-            (_ #f)))
-        attributes))
-
-(define (find-package-by-attribute-name name packages)
-  ;; Return the package bound to attribute NAME in PACKAGES, a list of
-  ;; packages (SNix attributes), or #f if NAME cannot be found.
-  (find (lambda (package)
-          (match package
-            (('attribute _ (? (cut string=? <> name))
-                         ('derivation _ _ _))
-             package)
-            (_ #f)))
-        packages))
-
-(define (stdenv-package packages)
-  ;; Return the `stdenv' package from PACKAGES, a list of SNix attributes.
-  (find-package-by-attribute-name "stdenv" packages))
-
-(define (package-requisites package)
-  ;; Return the list of derivations required to build PACKAGE (including that
-  ;; of PACKAGE) by recurring into its derivation attributes.
-  (let loop ((snix   package)
-             (result '()))
-    (match snix
-      (('attribute _ _ body)
-       (loop body result))
-      (('derivation _ out-path body)
-       (if (any (lambda (d)
-                  (match d
-                    (('derivation _ (? (cut string=? out-path <>)) _) #t)
-                    (_ #f)))
-                result)
-           result
-           (loop body (cons snix result))))
-      ((things ...)
-       (fold loop result things))
-      (_ result))))
-
-(define (package-source-output-path package)
-  ;; Return the output path of the "src" derivation of PACKAGE.
-  (derivation-source-output-path (attribute-value package)))
-
-
-;;;
-;;; FTP client.
-;;;
-
-(define-record-type <ftp-connection>
-  (%make-ftp-connection socket addrinfo)
-  ftp-connection?
-  (socket    ftp-connection-socket)
-  (addrinfo  ftp-connection-addrinfo))
-
-(define %ftp-ready-rx
-  (make-regexp "^([0-9]{3}) (.+)$"))
-
-(define (%ftp-listen port)
-  (let loop ((line (read-line port)))
-    (cond ((eof-object? line) (values line #f))
-          ((regexp-exec %ftp-ready-rx line)
-           =>
-           (lambda (match)
-             (values (string->number (match:substring match 1))
-                     (match:substring match 2))))
-          (else
-           (loop (read-line port))))))
-
-(define (%ftp-command command expected-code port)
-  (format port "~A~A~A" command (string #\return) (string #\newline))
-  (let-values (((code message) (%ftp-listen port)))
-    (if (eqv? code expected-code)
-        message
-        (throw 'ftp-error port command code message))))
-
-(define (%ftp-login user pass port)
-  (let ((command (string-append "USER " user (string #\newline))))
-    (display command port)
-    (let-values (((code message) (%ftp-listen port)))
-      (case code
-        ((230)  #t)
-        ((331) (%ftp-command (string-append "PASS " pass) 230 port))
-        (else  (throw 'ftp-error port command code message))))))
-
-(define (ftp-open host)
-  (catch 'getaddrinfo-error
-    (lambda ()
-      (let* ((ai (car (getaddrinfo host "ftp")))
-             (s  (socket (addrinfo:fam ai) (addrinfo:socktype ai)
-                         (addrinfo:protocol ai))))
-        (connect s (addrinfo:addr ai))
-        (setvbuf s _IOLBF)
-        (let-values (((code message) (%ftp-listen s)))
-          (if (eqv? code 220)
-              (begin
-                ;(%ftp-command "OPTS UTF8 ON" 200 s)
-                (%ftp-login "anonymous" "ludo@example.com" s)
-                (%make-ftp-connection s ai))
-              (begin
-                (format (current-error-port) "FTP to `~a' failed: ~A: ~A~%"
-                        host code message)
-                (close s)
-                #f)))))
-    (lambda (key errcode)
-      (format (current-error-port) "failed to resolve `~a': ~a~%"
-              host (gai-strerror errcode))
-      #f)))
-
-(define (ftp-close conn)
-  (close (ftp-connection-socket conn)))
-
-(define (ftp-chdir conn dir)
-  (%ftp-command (string-append "CWD " dir) 250
-                (ftp-connection-socket conn)))
-
-(define (ftp-pasv conn)
-  (define %pasv-rx
-    (make-regexp "([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)"))
-
-  (let ((message (%ftp-command "PASV" 227 (ftp-connection-socket conn))))
-    (cond ((regexp-exec %pasv-rx message)
-           =>
-           (lambda (match)
-             (+ (* (string->number (match:substring match 5)) 256)
-                (string->number (match:substring match 6)))))
-          (else
-           (throw 'ftp-error conn "PASV" 227 message)))))
-
-
-(define* (ftp-list conn #:optional directory)
-  (define (address-with-port sa port)
-    (let ((fam  (sockaddr:fam sa))
-          (addr (sockaddr:addr sa)))
-      (cond ((= fam AF_INET)
-             (make-socket-address fam addr port))
-            ((= fam AF_INET6)
-             (make-socket-address fam addr port
-                                  (sockaddr:flowinfo sa)
-                                  (sockaddr:scopeid sa)))
-            (else #f))))
-
-  (if directory
-      (ftp-chdir conn directory))
-
-  (let* ((port (ftp-pasv conn))
-         (ai   (ftp-connection-addrinfo conn))
-         (s    (socket (addrinfo:fam ai) (addrinfo:socktype ai)
-                       (addrinfo:protocol ai))))
-    (connect s (address-with-port (addrinfo:addr ai) port))
-    (setvbuf s _IOLBF)
-
-    (dynamic-wind
-      (lambda () #t)
-      (lambda ()
-        (%ftp-command "LIST" 150 (ftp-connection-socket conn))
-
-        (let loop ((line   (read-line s))
-                   (result '()))
-          (cond ((eof-object? line) (reverse result))
-                ((regexp-exec %ftp-ready-rx line)
-                 =>
-                 (lambda (match)
-                   (let ((code (string->number (match:substring match 1))))
-                     (if (= 126 code)
-                         (reverse result)
-                         (throw 'ftp-error conn "LIST" code)))))
-                (else
-                 (loop (read-line s)
-                       (let ((file (car (reverse (string-tokenize line)))))
-                         (cons file result)))))))
-      (lambda ()
-        (close s)
-        (let-values (((code message) (%ftp-listen (ftp-connection-socket conn))))
-          (or (eqv? code 226)
-              (throw 'ftp-error conn "LIST" code message)))))))
-
-
-;;;
-;;; GNU.
-;;;
-
-(define %ignored-package-attributes
-  ;; Attribute name of packages to be ignored.
-  '("bash" "bashReal" "bashInteractive" ;; the full versioned name is incorrect
-    "autoconf213"
-    "automake17x"
-    "automake19x"
-    "automake110x"
-    "automake" ;; = 1.10.x
-    "bison1875"
-    "bison23"
-    "bison" ;; = 2.3
-    "emacs22"
-    "emacsSnapshot"
-    "gcc295"
-    "gcc33"
-    "gcc34"
-    "gcc40"
-    "gcc41"
-    "gcc42"
-    "gcc43"
-    "gcc44"
-    "gcc45"
-    "glibc25"
-    "glibc27"
-    "glibc29"
-    "guile_1_9"
-    ))
-
-(define (gnu? package)
-  ;; Return true if PACKAGE (a snix expression) is a GNU package (according
-  ;; to a simple heuristic.)  Otherwise return #f.
-  (match package
-    (('attribute _ _ ('derivation _ _ body))
-     (any (lambda (attr)
-            (match attr
-              (('attribute _ "meta" ('attribute-set metas))
-               (any (lambda (attr)
-                      (match attr
-                        (('attribute _ "description" value)
-                         (string-prefix? "GNU" value))
-                        (('attribute _ "homepage" value)
-                         (string-contains value "www.gnu.org"))
-                        (_ #f)))
-                    metas))
-              (_ #f)))
-          body))
-    (_ #f)))
-
-(define (gnu-packages packages)
-  (fold (lambda (package gnu)
-          (match package
-            (('attribute _ "emacs23Packages" emacs-packages)
-             ;; XXX: Should prepend `emacs23Packages.' to attribute names.
-             (append (gnu-packages emacs-packages) gnu))
-            (('attribute _ attribute-name ('derivation _ _ body))
-             (if (member attribute-name %ignored-package-attributes)
-                 gnu
-                 (if (gnu? package)
-                     (cons package gnu)
-                     gnu)))
-            (_ gnu)))
-        '()
-        packages))
-
-(define (ftp-server/directory project)
-  (define quirks
-    '(("commoncpp2"   "ftp.gnu.org"   "/gnu/commoncpp" #f)
-      ("libgcrypt"    "ftp.gnupg.org" "/gcrypt" #t)
-      ("libgpg-error" "ftp.gnupg.org" "/gcrypt" #t)
-      ("gnupg"        "ftp.gnupg.org" "/gcrypt" #t)
-      ("gnu-ghostscript" "ftp.gnu.org"  "/ghostscript" #f)
-      ("grub"         "alpha.gnu.org" "/gnu" #t)
-      ("GNUnet"       "ftp.gnu.org" "/gnu/gnunet" #f)
-      ("mit-scheme"   "ftp.gnu.org" "/gnu/mit-scheme/stable.pkg")
-      ("icecat"       "ftp.gnu.org" "/gnu/gnuzilla" #f)
-      ("TeXmacs"      "ftp.texmacs.org" "/TeXmacs/targz" #f)))
-
-  (let ((quirk (assoc project quirks)))
-    (match quirk
-      ((_ server directory subdir?)
-       (values server (if (not subdir?)
-                          directory
-                          (string-append directory "/" project))))
-      (_
-       (values "ftp.gnu.org" (string-append "/gnu/" project))))))
-
-(define (nixpkgs->gnu-name project)
-  (define quirks
-    '(("gcc-wrapper" . "gcc")
-      ("ghostscript" . "gnu-ghostscript") ;; ../ghostscript/gnu-ghoscript-X.Y.tar.gz
-      ("gnum4"       . "m4")
-      ("gnugrep"     . "grep")
-      ("gnused"      . "sed")
-      ("gnutar"      . "tar")
-      ("gnunet"      . "GNUnet") ;; ftp.gnu.org/gnu/gnunet/GNUnet-x.y.tar.gz
-      ("mitscheme"   . "mit-scheme")
-      ("texmacs"     . "TeXmacs")))
-
-  (or (assoc-ref quirks project) project))
-
-(define (releases project)
-  ;; TODO: Handle project release trees like that of IceCat and MyServer.
-  (define release-rx
-    (make-regexp (string-append "^" project "-[0-9].*\\.tar\\.")))
-
-  (catch #t
-    (lambda ()
-      (let-values (((server directory) (ftp-server/directory project)))
-        (let* ((conn  (ftp-open server))
-               (files (ftp-list conn directory)))
-          (ftp-close conn)
-          (map (lambda (tarball)
-                 (let ((end (string-contains tarball ".tar")))
-                   (substring tarball 0 end)))
-
-               ;; Filter out signatures, deltas, and files which are potentially
-               ;; not releases of PROJECT (e.g., in /gnu/guile, filter out
-               ;; guile-oops and guile-www).
-               (filter (lambda (file)
-                         (and (not (string-suffix? ".sig" file))
-                              (regexp-exec release-rx file)))
-                       files)))))
-    (lambda (key subr message . args)
-      (format (current-error-port)
-              "failed to get release list for `~A': ~A ~A~%"
-              project message args)
-      '())))
-
-(define version-string>?
-  (let ((strverscmp
-         (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
-                        (error "could not find `strverscmp' (from GNU libc)"))))
-           (make-foreign-function int sym (list '* '*))))
-        (string->null-terminated-utf8
-         (lambda (s)
-           (let* ((utf8 (string->utf8 s))
-                  (len  (bytevector-length utf8))
-                  (nts  (make-bytevector (+ len 1))))
-             (bytevector-copy! utf8 0 nts 0 len)
-             (bytevector-u8-set! nts len 0)
-             nts))))
-    (lambda (a b)
-      (let ((a (bytevector->foreign (string->null-terminated-utf8 a)))
-            (b (bytevector->foreign (string->null-terminated-utf8 b))))
-        (> (strverscmp a b) 0)))))
-
-(define (latest-release project)
-  ;; Return "FOO-X.Y" or #f.
-  (let ((releases (releases project)))
-    (and (not (null? releases))
-         (fold (lambda (release latest)
-                 (if (version-string>? release latest)
-                     release
-                     latest))
-               ""
-               releases))))
-
-(define (package/version name+version)
-  (let ((hyphen (string-rindex name+version #\-)))
-    (if (not hyphen)
-        (values name+version #f)
-        (let ((name    (substring name+version 0 hyphen))
-              (version (substring name+version (+ hyphen 1)
-                                  (string-length name+version))))
-          (values name version)))))
-
-(define (file-extension file)
-  (let ((dot (string-rindex file #\.)))
-    (and dot (substring file (+ 1 dot) (string-length file)))))
-
-(define (packages-to-update gnu-packages)
-  (fold (lambda (pkg result)
-          (call-with-package pkg
-            (lambda (attribute name+version location meta src)
-              (let-values (((name old-version)
-                            (package/version name+version)))
-                (let ((latest (latest-release (nixpkgs->gnu-name name))))
-                  (cond ((not latest)
-                         (format #t "~A [unknown latest version]~%"
-                                 name+version)
-                         result)
-                        ((string=? name+version latest)
-                         (format #t "~A [up to date]~%" name+version)
-                         result)
-                        (else
-                         (let-values (((project new-version)
-                                       (package/version latest))
-                                      ((old-name old-hash old-urls)
-                                       (src->values src)))
-                           (format #t "~A -> ~A [~A]~%" name+version latest
-                                   (and (pair? old-urls) (car old-urls)))
-                           (let* ((url      (and (pair? old-urls)
-                                                 (car old-urls)))
-                                  (new-hash (fetch-gnu project new-version
-                                                       (if url
-                                                           (file-extension url)
-                                                           "gz"))))
-                             (cons (list name attribute
-                                         old-version old-hash
-                                         new-version new-hash
-                                         location)
-                                   result))))))))))
-        '()
-        gnu-packages))
-
-(define (fetch-gnu project version archive-type)
-  (let-values (((server directory)
-                (ftp-server/directory project)))
-    (let* ((base    (string-append project "-" version ".tar." archive-type))
-           (url     (string-append "ftp://" server "/" directory "/" base))
-           (sig     (string-append base ".sig"))
-           (sig-url (string-append url ".sig")))
-      (let-values (((hash path) (nix-prefetch-url url)))
-        (pk 'prefetch-url url hash path)
-        (and hash path
-             (begin
-               (false-if-exception (delete-file sig))
-               (system* "wget" sig-url)
-               (if (file-exists? sig)
-                   (let ((ret (system* "gpg" "--verify" sig path)))
-                     (false-if-exception (delete-file sig))
-                     (if (and ret (= 0 (status:exit-val ret)))
-                         hash
-                         (begin
-                           (format (current-error-port)
-                                   "signature verification failed for `~a'~%"
-                                   base)
-                           (format (current-error-port)
-                                   "(could be because the public key is not in your keyring)~%")
-                           #f)))
-                   (begin
-                     (format (current-error-port)
-                             "no signature for `~a'~%" base)
-                     hash))))))))
-
-
-;;;
-;;; Main program.
-;;;
-
-(define %options
-  ;; Specifications of the command-line options.
-  (list (option '(#\h "help") #f #f
-                (lambda (opt name arg result)
-                  (format #t "Usage: gnupdate [OPTIONS...]~%")
-                  (format #t "GNUpdate -- update Nix expressions of GNU packages in Nixpkgs~%")
-                  (format #t "~%")
-                  (format #t "  -x, --xml=FILE      Read XML output of `nix-instantiate'~%")
-                  (format #t "                      from FILE.~%")
-                  (format #t "  -s, --select=SET    Update only packages from SET, which may~%")
-                  (format #t "                      be either `all',`stdenv', or `non-stdenv'.~%")
-                  (format #t "  -d, --dry-run       Don't actually update Nix expressions~%")
-                  (format #t "  -h, --help          Give this help list.~%~%")
-                  (format #t "Report bugs to <ludo@gnu.org>~%")
-                  (exit 0)))
-        (option '(#\s "select") #t #f
-                (lambda (opt name arg result)
-                  (cond ((string-ci=? arg "stdenv")
-                         (alist-cons 'filter 'stdenv result))
-                        ((string-ci=? arg "non-stdenv")
-                         (alist-cons 'filter 'non-stdenv result))
-                        ((string-ci=? arg "all")
-                         (alist-cons 'filter #f result))
-                        (else
-                         (format (current-error-port)
-                                 "~A: unrecognized selection type~%"
-                                 arg)
-                         (exit 1)))))
-
-        (option '(#\d "dry-run") #f #f
-                (lambda (opt name arg result)
-                  (alist-cons 'dry-run #t result)))
-
-        (option '(#\x "xml") #t #f
-                (lambda (opt name arg result)
-                  (alist-cons 'xml-file arg result)))))
-
-(define-public (main . args)
-  ;; Assume Nixpkgs is under $NIXPKGS or ~/src/nixpkgs.
-  (let* ((opts      (args-fold args %options
-                               (lambda (opt name arg result)
-                                 (error "unrecognized option `~A'" name))
-                               (lambda (operand result)
-                                 (error "extraneous argument `~A'" operand))
-                               '()))
-         (home      (getenv "HOME"))
-         (path      (or (getenv "NIXPKGS")
-                        (string-append home "/src/nixpkgs")))
-         (snix      (begin
-                      (format (current-error-port) "parsing XML...~%")
-                      (xml->snix
-                       (or (and=> (assoc-ref opts 'xml-file) open-input-file)
-                           (open-nixpkgs path)))))
-         (packages  (match snix
-                      (('snix _ ('attribute-set attributes))
-                       attributes)
-                      (_ #f)))
-         (stdenv    (delay
-                      ;; The source tarballs that make up stdenv.
-                      (filter-map derivation-source-output-path
-                                  (package-requisites (stdenv-package packages)))))
-         (gnu       (gnu-packages packages))
-         (gnu*      (case (assoc-ref opts 'filter)
-                      ;; Filter out packages that are/aren't in `stdenv'.  To
-                      ;; do that reliably, we check whether their "src"
-                      ;; derivation is a requisite of stdenv.
-                      ((stdenv)
-                       (filter (lambda (p)
-                                 (member (package-source-output-path p)
-                                         (force stdenv)))
-                               gnu))
-                      ((non-stdenv)
-                       (filter (lambda (p)
-                                 (not (member (package-source-output-path p)
-                                              (force stdenv))))
-                               gnu))
-                      (else gnu)))
-         (updates   (packages-to-update gnu*)))
-
-    (format #t "~%~A packages to update...~%" (length updates))
-    (for-each (lambda (update)
-                (match update
-                  ((name attribute
-                    old-version old-hash
-                    new-version new-hash
-                    location)
-                   (if (assoc-ref opts 'dry-run)
-                       (format #t "`~a' would be updated from ~a to ~a (~a -> ~a)~%"
-                               name old-version new-version
-                               old-hash new-hash)
-                       (update-nix-expression (location-file location)
-                                              old-version old-hash
-                                              new-version new-hash)))
-                  (_ #f)))
-              updates)
-    #t))