guix package: Add '--delete-generations'.
* guix/scripts/package.scm (switch-to-previous-generation): New function. (roll-back): Use the new function instead of 'switch-link'. (show-help): Add '--delete-generations'. (%options): Likewise. (guix-package)[process-actions]: Add 'current-generation-number', 'display-and-delete', and 'delete-generation'. Add support for '--delete-generations', and reindent the code. * tests/guix-package.sh: Test '--delete-generations'. * doc/guix.texi (Invoking guix-package): Document '--delete-generations'.
This commit is contained in:
parent
64d2e973fb
commit
b7884ca3ca
|
@ -714,6 +714,16 @@ or months by passing an integer along with the first letter of the
|
||||||
duration, e.g., @code{--list-generations=20d}.
|
duration, e.g., @code{--list-generations=20d}.
|
||||||
@end itemize
|
@end itemize
|
||||||
|
|
||||||
|
@item --delete-generations[=@var{pattern}]
|
||||||
|
@itemx -d [@var{pattern}]
|
||||||
|
Delete all generations except the current one. Note that the zeroth
|
||||||
|
generation is never deleted.
|
||||||
|
|
||||||
|
This command accepts the same patterns as @option{--list-generations}.
|
||||||
|
When @var{pattern} is specified, delete the matching generations. If
|
||||||
|
the current generation matches, it is deleted atomically, i.e., by
|
||||||
|
switching to the previous available generation.
|
||||||
|
|
||||||
@end table
|
@end table
|
||||||
|
|
||||||
@node Packages with Multiple Outputs
|
@node Packages with Multiple Outputs
|
||||||
|
|
|
@ -223,6 +223,16 @@ all of PACKAGES, a list of name/version/output/path/deps tuples."
|
||||||
|
|
||||||
(switch-symlinks generation prof)))
|
(switch-symlinks generation prof)))
|
||||||
|
|
||||||
|
(define (switch-to-previous-generation profile)
|
||||||
|
"Atomically switch PROFILE to the previous generation."
|
||||||
|
(let* ((number (generation-number profile))
|
||||||
|
(previous-number (previous-generation-number profile number))
|
||||||
|
(previous-generation (format #f "~a-~a-link"
|
||||||
|
profile previous-number)))
|
||||||
|
(format #t (_ "switching from generation ~a to ~a~%")
|
||||||
|
number previous-number)
|
||||||
|
(switch-symlinks profile previous-generation)))
|
||||||
|
|
||||||
(define (roll-back profile)
|
(define (roll-back profile)
|
||||||
"Roll back to the previous generation of PROFILE."
|
"Roll back to the previous generation of PROFILE."
|
||||||
(let* ((number (generation-number profile))
|
(let* ((number (generation-number profile))
|
||||||
|
@ -230,24 +240,18 @@ all of PACKAGES, a list of name/version/output/path/deps tuples."
|
||||||
(previous-generation (format #f "~a-~a-link"
|
(previous-generation (format #f "~a-~a-link"
|
||||||
profile previous-number))
|
profile previous-number))
|
||||||
(manifest (string-append previous-generation "/manifest")))
|
(manifest (string-append previous-generation "/manifest")))
|
||||||
|
(cond ((not (file-exists? profile)) ; invalid profile
|
||||||
(define (switch-link)
|
(leave (_ "profile '~a' does not exist~%")
|
||||||
;; Atomically switch PROFILE to the previous generation.
|
|
||||||
(format #t (_ "switching from generation ~a to ~a~%")
|
|
||||||
number previous-number)
|
|
||||||
(switch-symlinks profile previous-generation))
|
|
||||||
|
|
||||||
(cond ((not (file-exists? profile)) ; invalid profile
|
|
||||||
(leave (_ "profile `~a' does not exist~%")
|
|
||||||
profile))
|
profile))
|
||||||
((zero? number) ; empty profile
|
((zero? number) ; empty profile
|
||||||
(format (current-error-port)
|
(format (current-error-port)
|
||||||
(_ "nothing to do: already at the empty profile~%")))
|
(_ "nothing to do: already at the empty profile~%")))
|
||||||
((or (zero? previous-number) ; going to emptiness
|
((or (zero? previous-number) ; going to emptiness
|
||||||
(not (file-exists? previous-generation)))
|
(not (file-exists? previous-generation)))
|
||||||
(link-to-empty-profile previous-generation)
|
(link-to-empty-profile previous-generation)
|
||||||
(switch-link))
|
(switch-to-previous-generation profile))
|
||||||
(else (switch-link))))) ; anything else
|
(else
|
||||||
|
(switch-to-previous-generation profile))))) ; anything else
|
||||||
|
|
||||||
(define (generation-time profile number)
|
(define (generation-time profile number)
|
||||||
"Return the creation time of a generation in the UTC format."
|
"Return the creation time of a generation in the UTC format."
|
||||||
|
@ -515,6 +519,9 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
|
||||||
(display (_ "
|
(display (_ "
|
||||||
-l, --list-generations[=PATTERN]
|
-l, --list-generations[=PATTERN]
|
||||||
list generations matching PATTERN"))
|
list generations matching PATTERN"))
|
||||||
|
(display (_ "
|
||||||
|
-d, --delete-generations[=PATTERN]
|
||||||
|
delete generations matching PATTERN"))
|
||||||
(newline)
|
(newline)
|
||||||
(display (_ "
|
(display (_ "
|
||||||
-p, --profile=PROFILE use PROFILE instead of the user's default profile"))
|
-p, --profile=PROFILE use PROFILE instead of the user's default profile"))
|
||||||
|
@ -578,6 +585,10 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
|
||||||
(lambda (opt name arg result)
|
(lambda (opt name arg result)
|
||||||
(cons `(query list-generations ,(or arg ""))
|
(cons `(query list-generations ,(or arg ""))
|
||||||
result)))
|
result)))
|
||||||
|
(option '(#\d "delete-generations") #f #t
|
||||||
|
(lambda (opt name arg result)
|
||||||
|
(alist-cons 'delete-generations (or arg "")
|
||||||
|
result)))
|
||||||
(option '("search-paths") #f #f
|
(option '("search-paths") #f #f
|
||||||
(lambda (opt name arg result)
|
(lambda (opt name arg result)
|
||||||
(cons `(query search-paths) result)))
|
(cons `(query search-paths) result)))
|
||||||
|
@ -828,85 +839,146 @@ more information.~%"))
|
||||||
install))))
|
install))))
|
||||||
(_ #f)))
|
(_ #f)))
|
||||||
|
|
||||||
|
(define current-generation-number
|
||||||
|
(generation-number profile))
|
||||||
|
|
||||||
|
(define (display-and-delete number)
|
||||||
|
(let ((generation (format #f "~a-~a-link" profile number)))
|
||||||
|
(unless (zero? number)
|
||||||
|
(format #t (_ "deleting ~a~%") generation)
|
||||||
|
(delete-file generation))))
|
||||||
|
|
||||||
|
(define (delete-generation number)
|
||||||
|
(let* ((previous-number (previous-generation-number profile number))
|
||||||
|
(previous-generation (format #f "~a-~a-link"
|
||||||
|
profile previous-number)))
|
||||||
|
(cond ((zero? number)) ; do not delete generation 0
|
||||||
|
((and (= number current-generation-number)
|
||||||
|
(not (file-exists? previous-generation)))
|
||||||
|
(link-to-empty-profile previous-generation)
|
||||||
|
(switch-to-previous-generation profile)
|
||||||
|
(display-and-delete number))
|
||||||
|
((= number current-generation-number)
|
||||||
|
(roll-back profile)
|
||||||
|
(display-and-delete number))
|
||||||
|
(else
|
||||||
|
(display-and-delete number)))))
|
||||||
|
|
||||||
;; First roll back if asked to.
|
;; First roll back if asked to.
|
||||||
(if (and (assoc-ref opts 'roll-back?) (not dry-run?))
|
(cond ((and (assoc-ref opts 'roll-back?) (not dry-run?))
|
||||||
(begin
|
(begin
|
||||||
(roll-back profile)
|
(roll-back profile)
|
||||||
(process-actions (alist-delete 'roll-back? opts)))
|
(process-actions (alist-delete 'roll-back? opts))))
|
||||||
(let* ((installed (manifest-packages (profile-manifest profile)))
|
((and (assoc-ref opts 'delete-generations)
|
||||||
(upgrade-regexps (filter-map (match-lambda
|
(not dry-run?))
|
||||||
(('upgrade . regexp)
|
(filter-map
|
||||||
(make-regexp (or regexp "")))
|
(match-lambda
|
||||||
(_ #f))
|
(('delete-generations . pattern)
|
||||||
opts))
|
(cond ((not (file-exists? profile)) ; XXX: race condition
|
||||||
(upgrade (if (null? upgrade-regexps)
|
(leave (_ "profile '~a' does not exist~%")
|
||||||
'()
|
profile))
|
||||||
(let ((newest (find-newest-available-packages)))
|
((string-null? pattern)
|
||||||
(filter-map (match-lambda
|
(let ((numbers (generation-numbers profile)))
|
||||||
((name version output path _)
|
(if (equal? numbers '(0))
|
||||||
(and (any (cut regexp-exec <> name)
|
(exit 0)
|
||||||
upgrade-regexps)
|
(for-each display-and-delete
|
||||||
(upgradeable? name version path)
|
(delete current-generation-number
|
||||||
(find-package name
|
numbers)))))
|
||||||
(or output "out"))))
|
;; Do not delete the zeroth generation.
|
||||||
(_ #f))
|
((equal? 0 (string->number pattern))
|
||||||
installed))))
|
(exit 0))
|
||||||
(install (append
|
((matching-generations pattern profile)
|
||||||
upgrade
|
=>
|
||||||
(filter-map (match-lambda
|
(lambda (numbers)
|
||||||
(('install . (? package? p))
|
(if (null-list? numbers)
|
||||||
(package->tuple p))
|
(exit 1)
|
||||||
(('install . (? store-path?))
|
(for-each delete-generation numbers))))
|
||||||
#f)
|
(else
|
||||||
(('install . package)
|
(leave (_ "invalid syntax: ~a~%")
|
||||||
(find-package package))
|
pattern)))
|
||||||
(_ #f))
|
|
||||||
opts)))
|
(process-actions
|
||||||
(drv (filter-map (match-lambda
|
(alist-delete 'delete-generations opts)))
|
||||||
((name version sub-drv
|
(_ #f))
|
||||||
(? package? package)
|
opts))
|
||||||
(deps ...))
|
(else
|
||||||
(check-package-freshness package)
|
(let* ((installed (manifest-packages (profile-manifest profile)))
|
||||||
(package-derivation (%store) package))
|
(upgrade-regexps (filter-map (match-lambda
|
||||||
(_ #f))
|
(('upgrade . regexp)
|
||||||
install))
|
(make-regexp (or regexp "")))
|
||||||
(install* (append
|
(_ #f))
|
||||||
(filter-map (match-lambda
|
opts))
|
||||||
(('install . (? package? p))
|
(upgrade (if (null? upgrade-regexps)
|
||||||
#f)
|
'()
|
||||||
(('install . (? store-path? path))
|
(let ((newest (find-newest-available-packages)))
|
||||||
(let-values (((name version)
|
(filter-map
|
||||||
(package-name->name+version
|
(match-lambda
|
||||||
(store-path-package-name
|
((name version output path _)
|
||||||
path))))
|
(and (any (cut regexp-exec <> name)
|
||||||
`(,name ,version #f ,path ())))
|
upgrade-regexps)
|
||||||
(_ #f))
|
(upgradeable? name version path)
|
||||||
opts)
|
(find-package name
|
||||||
(map (lambda (tuple drv)
|
(or output "out"))))
|
||||||
(match tuple
|
(_ #f))
|
||||||
((name version sub-drv _ (deps ...))
|
installed))))
|
||||||
(let ((output-path
|
(install (append
|
||||||
(derivation->output-path
|
upgrade
|
||||||
drv sub-drv)))
|
(filter-map (match-lambda
|
||||||
`(,name ,version ,sub-drv ,output-path
|
(('install . (? package? p))
|
||||||
,(canonicalize-deps deps))))))
|
(package->tuple p))
|
||||||
install drv)))
|
(('install . (? store-path?))
|
||||||
(remove (filter-map (match-lambda
|
#f)
|
||||||
(('remove . package)
|
(('install . package)
|
||||||
package)
|
(find-package package))
|
||||||
(_ #f))
|
(_ #f))
|
||||||
opts))
|
opts)))
|
||||||
(remove* (filter-map (cut assoc <> installed) remove))
|
(drv (filter-map (match-lambda
|
||||||
(packages (append install*
|
((name version sub-drv
|
||||||
(fold (lambda (package result)
|
(? package? package)
|
||||||
(match package
|
(deps ...))
|
||||||
((name _ out _ ...)
|
(check-package-freshness package)
|
||||||
(filter (negate
|
(package-derivation (%store) package))
|
||||||
(cut same-package? <>
|
(_ #f))
|
||||||
name out))
|
install))
|
||||||
result))))
|
(install*
|
||||||
(fold alist-delete installed remove)
|
(append
|
||||||
install*))))
|
(filter-map (match-lambda
|
||||||
|
(('install . (? package? p))
|
||||||
|
#f)
|
||||||
|
(('install . (? store-path? path))
|
||||||
|
(let-values (((name version)
|
||||||
|
(package-name->name+version
|
||||||
|
(store-path-package-name
|
||||||
|
path))))
|
||||||
|
`(,name ,version #f ,path ())))
|
||||||
|
(_ #f))
|
||||||
|
opts)
|
||||||
|
(map (lambda (tuple drv)
|
||||||
|
(match tuple
|
||||||
|
((name version sub-drv _ (deps ...))
|
||||||
|
(let ((output-path
|
||||||
|
(derivation->output-path
|
||||||
|
drv sub-drv)))
|
||||||
|
`(,name ,version ,sub-drv ,output-path
|
||||||
|
,(canonicalize-deps deps))))))
|
||||||
|
install drv)))
|
||||||
|
(remove (filter-map (match-lambda
|
||||||
|
(('remove . package)
|
||||||
|
package)
|
||||||
|
(_ #f))
|
||||||
|
opts))
|
||||||
|
(remove* (filter-map (cut assoc <> installed) remove))
|
||||||
|
(packages
|
||||||
|
(append install*
|
||||||
|
(fold (lambda (package result)
|
||||||
|
(match package
|
||||||
|
((name _ out _ ...)
|
||||||
|
(filter (negate
|
||||||
|
(cut same-package? <>
|
||||||
|
name out))
|
||||||
|
result))))
|
||||||
|
(fold alist-delete installed remove)
|
||||||
|
install*))))
|
||||||
|
|
||||||
(when (equal? profile %current-profile)
|
(when (equal? profile %current-profile)
|
||||||
(ensure-default-profile))
|
(ensure-default-profile))
|
||||||
|
@ -950,7 +1022,7 @@ more information.~%"))
|
||||||
count)
|
count)
|
||||||
count)
|
count)
|
||||||
(display-search-paths packages
|
(display-search-paths packages
|
||||||
profile))))))))))
|
profile)))))))))))
|
||||||
|
|
||||||
(define (process-query opts)
|
(define (process-query opts)
|
||||||
;; Process any query specified by OPTS. Return #t when a query was
|
;; Process any query specified by OPTS. Return #t when a query was
|
||||||
|
|
|
@ -142,6 +142,17 @@ then
|
||||||
# Make sure LIBRARY_PATH gets listed by `--search-paths'.
|
# Make sure LIBRARY_PATH gets listed by `--search-paths'.
|
||||||
guix package --bootstrap -p "$profile" -i guile-bootstrap -i gcc-bootstrap
|
guix package --bootstrap -p "$profile" -i guile-bootstrap -i gcc-bootstrap
|
||||||
guix package --search-paths -p "$profile" | grep LIBRARY_PATH
|
guix package --search-paths -p "$profile" | grep LIBRARY_PATH
|
||||||
|
|
||||||
|
# Delete the third generation and check that it was actually deleted.
|
||||||
|
guix package -p "$profile" --delete-generations=3
|
||||||
|
test -z "`guix package -p "$profile" -l 3`"
|
||||||
|
|
||||||
|
# Exit with 1 when a generation does not exist.
|
||||||
|
if guix package -p "$profile" --delete-generations=42;
|
||||||
|
then false; else true; fi
|
||||||
|
|
||||||
|
# Exit with 0 when trying to delete the zeroth generation.
|
||||||
|
guix package -p "$profile" --delete-generations=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Make sure the `:' syntax works.
|
# Make sure the `:' syntax works.
|
||||||
|
|
Loading…
Reference in New Issue