123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- #!/usr/bin/env -S guile --no-auto-compile -e main
- !#
- (use-modules (gnu packages)
- (gnu packages python)
- (guix diagnostics)
- (guix graph)
- (guix monads)
- (guix scripts graph)
- (guix scripts style)
- (guix store)
- (guix packages)
- (guix build utils)
- (guix utils)
- (ice-9 match)
- (ice-9 popen)
- (ice-9 pretty-print)
- (ice-9 textual-ports)
- (srfi srfi-1)
- (srfi srfi-2)
- (srfi srfi-26))
- ;;; Commentary:
- ;;;
- ;;; Find all the leaf (0 dependents) Python 2 packages, delete them,
- ;;; and de-register any "python2-variant" property that referred to
- ;;; them from a Python 3 same-named package.
- (define (all-packages)
- (fold-packages (lambda (package result)
- (cons package result))
- '()))
- (define (list-dependents package)
- "List the packages dependents on PACKAGE."
- (with-store store
- (run-with-store store
- (mlet* %store-monad ((edges (node-back-edges
- %bag-node-type
- (package-closure (all-packages)))))
- (return (node-transitive-edges (list package) edges))))))
- (define (locate-package-via-git name)
- "Return the location object corresponding to package NAME, searched via git."
- (let* ((input-pipe (open-pipe* OPEN_READ
- "git" "grep" "-n" "--column"
- (format #f "^(define-public ~a$" name)))
- (output (get-string-all input-pipe)) ;file:line:column:match
- (exit-val (status:exit-val (close-pipe input-pipe))))
- (case exit-val
- ((0)
- (let ((components (string-split output #\:)))
- (location
- (first components) ;file
- (string->number (second components)) ;1-indexed line
- ;; FIXME: Comment discrepancy in (guix diagnostics), which
- ;; says the column is 0-indexed.
- (and=> (string->number (third components)) 1-)))) ;0-indexed column
- ((1) #f) ;no match
- (else (error "git grep failed with status" exit-val)))))
- (define (delete-package-field field sexp)
- "Delete FIELD (a symbol) from a package SEXP."
- (match sexp
- ;; TODO: Can we come up with a general pattern here?
- (('define-public name ('package (fields ...) ...))
- `(define-public ,name
- (package
- ,@(alist-delete field fields))))
- (('define-public name ('package/inherit parent (fields ...) ...))
- `(define-public ,name
- (package/inherit ,parent
- ,@(alist-delete field fields))))
- (('define-public name ('hidden-package ('package (fields ...) ...)))
- `(define-public ,name
- (hidden-package
- (package ,@(alist-delete field fields)))))
- (('define-public name ('hidden-package
- ('package/inherit parent (fields ...) ...)))
- `(define-public ,name
- (hidden-package
- (package/inherit ,parent
- ,@(alist-delete field fields)))))))
- ;;; Potential improvement suggested by daviid (to try).
- ;; (define (delete-package-field field sexp)
- ;; "Delete FIELD (a symbol) from a package SEXP."
- ;; (match sexp
- ;; ;; TODO: Can we come up with a general pattern here?
- ;; (('define-public name package-def ...)
- ;; `(define-public ,name
- ;; ,(update-package-def package-def field)))))
- ;; (define (update-package-def package-def field)
- ;; (match package-def
- ;; (('package (fields ...) ...)
- ;; `(package
- ;; ,@(alist-delete field fields)))
- ;; (('package/inherit parent (fields ...) ...)
- ;; `(package/inherit ,parent
- ;; ,@(alist-delete field fields)))
- ;; (('hidden-package ('package (fields ...) ...))
- ;; `(hidden-package
- ;; (package ,@(alist-delete field fields))))
- ;; (('hidden-package ('package/inherit parent (fields ...) ...))
- ;; `(hidden-package (package/inherit ,parent
- ;; ,@(alist-delete field fields))))))
- (define (delete-package-property-helper property fields)
- "Return a patched copy of a package fields."
- (let* ((properties (assoc-ref fields 'properties))
- (new-properties (sexp-filter (remove-node property) properties)))
- (alist-replace 'properties new-properties fields)))
- (define (delete-package-property property sexp)
- "Delete a specific PROPERTY (a symbol) from a package SEXP."
- (match sexp
- (('define-public name ('package (fields ...) ...))
- `(define-public ,name
- (package ,@(delete-package-property-helper property fields))))
- (('define-public name ('package/inherit parent (fields ...) ...))
- `(define-public ,name
- (package/inherit ,parent
- ,@(delete-package-property-helper property fields))))
- (('define-public name ('hidden-package ('package (fields ...) ...)))
- `(define-public ,name
- (hidden-package
- (package ,@(delete-package-property-helper property fields)))))
- (('define-public name ('hidden-package
- ('package/inherit parent (fields ...) ...)))
- `(define-public ,name
- (hidden-package
- (package/inherit ,parent
- ,@(delete-package-property-helper property fields)))))))
- (define (delete-leaf-python-package package)
- "Delete the package, and de-register any variant property."
- (with-directory-excursion (%guix-source-root-directory)
- (let* ((name (package-name package))
- (py3name (string-replace-substring name "python2" "python"))
- ;; XXX: Due to https://issues.guix.gnu.org/55139, and also
- ;; the fact that we are editing the sources at run time
- ;; without re-evaluating the modules, we rely on git to
- ;; locate the package definition.
- (source-properties (and=> (locate-package-via-git name)
- location->source-properties)))
- (format #t "Deleting package ~s...~%" name)
- ;; Note: the line is decremented by one so the blank line
- ;; separating the package definitions is also removed.
- (delete-expression (alist-replace
- 'line (1- (assoc-ref source-properties 'line))
- source-properties))
- ;; Check if a same-named Python 3 package had a python2-variant
- ;; entry for it.
- (and-let* ((py3package (match (find-packages-by-name py3name)
- (() #f)
- ((p p* ...)
- p)))
- (properties (package-properties py3package))
- (python2-variant (and=> (assoc-ref properties 'python2-variant)
- force))
- (source-properties (and=> (locate-package-via-git py3name)
- location->source-properties)))
- (when (and (eq? python2-variant package))
- (format #t "Removing 'python2-variant' property from package ~a...~%"
- py3name)
- (edit-expression
- source-properties
- (lambda (expr)
- (let ((sexp (call-with-input-string expr read)))
- (string-trim ;remove added \n
- (call-with-output-string
- (lambda (port)
- (pretty-print-with-comments
- port
- (if (= 1 (length properties))
- (delete-package-field 'properties sexp)
- (delete-package-property 'python2-variant sexp)))))))))
- #t)))))
- (define (main _)
- (define skipped-packages
- (fold-packages
- (lambda (package lst)
- (let ((python-arg (and=> (member #:python
- (package-arguments package))
- second)))
- (if (eq? python-2 python-arg)
- (let* ((dependents (list-dependents package))
- (name (package-name package))
- (dependent-count (length dependents)))
- (case dependent-count
- ((0)
- (delete-leaf-python-package package)
- (format #t "Committing '~a' package removal...~%" name)
- (with-directory-excursion (%guix-source-root-directory)
- (invoke "./etc/committer.scm"))
- lst)
- (else
- (format #t "Skipping package '~a' with ~a dependents...~%"
- name dependent-count)
- (cons (list name dependent-count) lst))))
- lst)))
- '()))
- (newline)
- (format #t "Python 2 purge complete.~%")
- (format #t "~a packages were kept due to having dependents:~%"
- (length skipped-packages))
- ;; Sort the skipped packages by descending number of dependencies.
- (define skipped-packages/sorted (sort skipped-packages
- (lambda (x y)
- (> (second x) (second y)))))
- (pretty-print-table (cons (list "Packages" "Dependencies")
- skipped-packages/sorted)))
- ;;;
- ;;; Sexp-munging procedures.
- ;;;
- ;;; sexp-filter and removed-node co-authored by daviid; thank you!
- (define (sexp-filter proc sexp)
- "Filter SEXP recursively using PROC."
- (match sexp
- (() sexp)
- (((a rest))
- (cons (sexp-filter proc (car sexp))
- (sexp-filter proc (cdr sexp))))
- ((a rest)
- (cons a (list (proc rest))))))
- (define (remove-node key)
- (lambda (props)
- (filter-map (lambda (prop)
- (match prop
- ((k . v)
- (and (not (eq? k key)) prop))))
- props)))
|