raco pkg: add trash directory for removed/updated package installations

Packages that are installed as other than a link are not meant to be
edited, but work can get lost if a package is edited and then removed
or updated. Avoid that work loss by moving removed or updated packages
to a trash folder.

By default, the trash folder holds up to 512 packages for up to 48
hours. To disable the trash folder (for a given scope), use

 raco pkg config --set max-trash-packages 0

(I expect that some variant of Greenspun's rule predicted the eventual
inclusion of "backup" management in the package system.)
This commit is contained in:
Matthew Flatt 2014-12-08 14:35:47 -07:00
parent 4bcdb9a3a3
commit 06c82877db
15 changed files with 325 additions and 41 deletions

View File

@ -45,6 +45,8 @@ to the @exec{raco pkg} sub-subcommands.
@history[#:added "6.0.17"]} @history[#:added "6.0.17"]}
@defthing[pkg-archive-command procedure?]{Implements @command-ref{archive}. @defthing[pkg-archive-command procedure?]{Implements @command-ref{archive}.
@history[#:added "6.1.0.8"]} @history[#:added "6.1.0.8"]}
@defthing[pkg-empty-trash-command procedure?]{Implements @command-ref{empty-trash}.
@history[#:added "6.1.1.6"]}
@include-section["lib.scrbl"] @include-section["lib.scrbl"]
@include-section["path.scrbl"] @include-section["path.scrbl"]

View File

@ -91,6 +91,18 @@ a parameter's value is @racket[#f], then the user's configuration is
used.} used.}
@deftogether[(
@defparam[current-pkg-trash-max-packages max-packages (or/c #f real?)]
@defparam[current-pkg-trash-max-seconds max-seconds (or/c #f real?)]
)]{
Parameters that determine the trash-directory limits. If
a parameter's value is @racket[#f], then the user's configuration is
used.
@history[#:added "6.1.1.6"]}
@defproc[(pkg-directory [name string?] @defproc[(pkg-directory [name string?]
[#:cache cache (or/c #f (and/c hash? (not/c immutable?))) #f]) [#:cache cache (or/c #f (and/c hash? (not/c immutable?))) #f])
(or/c path-string? #f)]{ (or/c path-string? #f)]{
@ -234,7 +246,8 @@ is true, error messages may suggest specific command-line flags for
[#:ignore-checksums? ignore-checksums? boolean? #f] [#:ignore-checksums? ignore-checksums? boolean? #f]
[#:strict-doc-conflicts? strict-doc-conflicts? boolean? #f] [#:strict-doc-conflicts? strict-doc-conflicts? boolean? #f]
[#:use-cache? use-cache? boolean? #t] [#:use-cache? use-cache? boolean? #t]
[#:quiet? boolean? quiet? #f] [#:quiet? quiet? boolean? #f]
[#:use-trash? use-trash? boolean? #f]
[#:from-command-line? from-command-line? boolean? #f] [#:from-command-line? from-command-line? boolean? #f]
[#:strip strip (or/c #f 'source 'binary 'binary-lib) #f] [#:strip strip (or/c #f 'source 'binary 'binary-lib) #f]
[#:force-strip? force-string? boolean? #f] [#:force-strip? force-string? boolean? #f]
@ -268,7 +281,11 @@ port, unless @racket[quiet?] is true.
If @racket[from-command-line?] is true, error messages may suggest If @racket[from-command-line?] is true, error messages may suggest
specific command-line flags for @command-ref{install}. specific command-line flags for @command-ref{install}.
The package lock must be held; see @racket[with-pkg-lock].} The package lock must be held; see @racket[with-pkg-lock].
@history[#:changed "6.1.1.5" @elem{Added the @racket[#:multi-clone-mode]
and @racket[#:infer-clone-from-dir?] arguments.}
#:changed "6.1.1.6" @elem{Added the @racket[#:use-trash?] argument.}]}
@defproc[(pkg-update [sources (listof (or/c string? pkg-desc?))] @defproc[(pkg-update [sources (listof (or/c string? pkg-desc?))]
@ -281,7 +298,8 @@ The package lock must be held; see @racket[with-pkg-lock].}
[#:ignore-checksums? ignore-checksums? boolean? #f] [#:ignore-checksums? ignore-checksums? boolean? #f]
[#:strict-doc-conflicts? strict-doc-conflicts? boolean? #f] [#:strict-doc-conflicts? strict-doc-conflicts? boolean? #f]
[#:use-cache? use-cache? quiet? #t] [#:use-cache? use-cache? quiet? #t]
[#:quiet? boolean? quiet? #f] [#:quiet? quiet? boolean? #f]
[#:use-trash? boolean? use-trash? #f]
[#:from-command-line? from-command-line? boolean? #f] [#:from-command-line? from-command-line? boolean? #f]
[#:strip strip (or/c #f 'source 'binary 'binary-lib) #f] [#:strip strip (or/c #f 'source 'binary 'binary-lib) #f]
[#:force-strip? force-string? boolean? #f] [#:force-strip? force-string? boolean? #f]
@ -318,14 +336,19 @@ corresponds to an existing repository-clone installation.
If @racket[from-command-line?] is true, error messages may suggest If @racket[from-command-line?] is true, error messages may suggest
specific command-line flags for @command-ref{update}. specific command-line flags for @command-ref{update}.
The package lock must be held; see @racket[with-pkg-lock].} The package lock must be held; see @racket[with-pkg-lock].
@history[#:changed "6.1.1.5" @elem{Added the @racket[#:multi-clone-mode]
and @racket[#:infer-clone-from-dir?] arguments.}
#:changed "6.1.1.6" @elem{Added the @racket[#:use-trash?] argument.}]}
@defproc[(pkg-remove [names (listof string?)] @defproc[(pkg-remove [names (listof string?)]
[#:demote? demote? boolean? #f] [#:demote? demote? boolean? #f]
[#:auto? auto? boolean? #f] [#:auto? auto? boolean? #f]
[#:force? force? boolean? #f] [#:force? force? boolean? #f]
[#:quiet? boolean? quiet? #f] [#:quiet? quiet? boolean? #f]
[#:use-trash? boolean? use-trash? #f]
[#:from-command-line? from-command-line? boolean? #f]) [#:from-command-line? from-command-line? boolean? #f])
(or/c 'skip (or/c 'skip
#f #f
@ -339,7 +362,9 @@ Implements @racket[pkg-remove-command]. The result is the same as for
If @racket[from-command-line?] is true, error messages may suggest If @racket[from-command-line?] is true, error messages may suggest
specific command-line flags for @command-ref{remove}. specific command-line flags for @command-ref{remove}.
The package lock must be held; see @racket[with-pkg-lock].} The package lock must be held; see @racket[with-pkg-lock].
@history[#:changed "6.1.1.6" @elem{Added the @racket[#:use-trash?] argument.}]}
@defproc[(pkg-new [name path-string?]) @defproc[(pkg-new [name path-string?])
@ -374,7 +399,7 @@ The package lock must be held to allow reads; see
[#:use-cache? use-cache? boolean? #t] [#:use-cache? use-cache? boolean? #t]
[#:ignore-checksums? ignore-checksums? boolean? #f] [#:ignore-checksums? ignore-checksums? boolean? #f]
[#:strict-doc-conflicts? strict-doc-conflicts? boolean? #f] [#:strict-doc-conflicts? strict-doc-conflicts? boolean? #f]
[#:quiet? boolean? quiet? #f] [#:quiet? quiet? boolean? #f]
[#:from-command-line? from-command-line? boolean? #f] [#:from-command-line? from-command-line? boolean? #f]
[#:strip strip (or/c #f 'source 'binary 'binary-lib) #f] [#:strip strip (or/c #f 'source 'binary 'binary-lib) #f]
[#:force-strip? force-string? boolean? #f]) [#:force-strip? force-string? boolean? #f])
@ -471,6 +496,15 @@ would allow archiving to continue for other packages.
@history[#:added "6.1.0.8"]} @history[#:added "6.1.0.8"]}
@defproc[(pkg-empty-trash [#:list? show-list? boolean? #f]
[#:quiet? quiet? boolean? #t])
void?]{
Implements @racket[pkg-empty-trash].
@history[#:added "6.1.1.6"]}
@defproc[(pkg-catalog-update-local [#:catalogs catalogs (listof string?) (pkg-config-catalogs)] @defproc[(pkg-catalog-update-local [#:catalogs catalogs (listof string?) (pkg-config-catalogs)]
[#:catalog-file catalog-file path-string? (current-pkg-catalog-file)] [#:catalog-file catalog-file path-string? (current-pkg-catalog-file)]
[#:quiet? quiet? boolean? #f] [#:quiet? quiet? boolean? #f]

View File

@ -573,11 +573,14 @@ sub-commands.
@item{@DFlag{batch} --- Disables @deftech{interactive mode}, suppressing potential prompts for a user @item{@DFlag{batch} --- Disables @deftech{interactive mode}, suppressing potential prompts for a user
(e.g., about package dependencies or clone sharing).} (e.g., about package dependencies or clone sharing).}
@item{@DFlag{no-trash} --- Refrain from moving updated or removed packages to a trash folder.}
@item{@DFlag{fail-fast} --- Breaks @exec{raco setup} as soon as any error is encountered.} @item{@DFlag{fail-fast} --- Breaks @exec{raco setup} as soon as any error is encountered.}
] ]
@history[#:changed "6.1.1.5" @elem{Added the @DFlag{batch}, @DFlag{clone}, and @history[#:changed "6.1.1.5" @elem{Added the @DFlag{batch}, @DFlag{clone}, and
@DFlag{multi-clone} flags.}]} @DFlag{multi-clone} flags.}
#:changed "6.1.1.6" @elem{Added the @DFlag{no-trash} flag.}]}
@subcommand{@command/toc{update} @nonterm{option} ... @nonterm{pkg-source} ... @subcommand{@command/toc{update} @nonterm{option} ... @nonterm{pkg-source} ...
@ -671,12 +674,14 @@ the given @nonterm{pkg-source}s.
@item{@DFlag{no-setup} --- Same as for @command-ref{install}.} @item{@DFlag{no-setup} --- Same as for @command-ref{install}.}
@item{@DFlag{jobs} @nonterm{n} or @Flag{j} @nonterm{n} --- Same as for @command-ref{install}.} @item{@DFlag{jobs} @nonterm{n} or @Flag{j} @nonterm{n} --- Same as for @command-ref{install}.}
@item{@DFlag{batch} --- Same as for @command-ref{install}.} @item{@DFlag{batch} --- Same as for @command-ref{install}.}
@item{@DFlag{no-trash} --- Same as for @command-ref{install}.}
] ]
@history[#:changed "6.1.1.5" @elem{Added the @DFlag{batch}, @DFlag{clone}, and @history[#:changed "6.1.1.5" @elem{Added the @DFlag{batch}, @DFlag{clone}, and
@DFlag{multi-clone} flags, and @DFlag{multi-clone} flags, and
added update of enclosing package added update of enclosing package
when no arguments are provided.}]} when no arguments are provided.}
#:changed "6.1.1.6" @elem{Added the @DFlag{no-trash} flag.}]}
@subcommand{@command/toc{remove} @nonterm{option} ... @nonterm{pkg} ... @subcommand{@command/toc{remove} @nonterm{option} ... @nonterm{pkg} ...
--- Attempts to remove the given packages. By default, if a package is the dependency --- Attempts to remove the given packages. By default, if a package is the dependency
@ -705,9 +710,11 @@ the given @nonterm{pkg}s.
@item{@DFlag{no-setup} --- Same as for @command-ref{install}.} @item{@DFlag{no-setup} --- Same as for @command-ref{install}.}
@item{@DFlag{jobs} @nonterm{n} or @Flag{j} @nonterm{n} --- Same as for @command-ref{install}.} @item{@DFlag{jobs} @nonterm{n} or @Flag{j} @nonterm{n} --- Same as for @command-ref{install}.}
@item{@DFlag{batch} --- Same as for @command-ref{install}.} @item{@DFlag{batch} --- Same as for @command-ref{install}.}
@item{@DFlag{no-trash} --- Same as for @command-ref{install}.}
] ]
@history[#:changed "6.1.1.5" @elem{Added the @DFlag{batch} flag.}]} @history[#:changed "6.1.1.5" @elem{Added the @DFlag{batch} flag.}
#:changed "6.1.1.6" @elem{Added the @DFlag{no-trash} flag.}]}
@subcommand{@command/toc{new} @nonterm{pkg} --- @subcommand{@command/toc{new} @nonterm{pkg} ---
@ -856,8 +863,16 @@ for @nonterm{key}.
documentation; an empty string, which is the default, disables documentation; an empty string, which is the default, disables
the URL so that the local filesystem is used. This key can be the URL so that the local filesystem is used. This key can be
set only in @exec{installation} scope.} set only in @exec{installation} scope.}
@item{@exec{trash-max-packages} --- A limit on the number of package implementations
that are kept in a trash folder when the package is removed or updated.}
@item{@exec{trash-max-seconds} --- A limit on the time since a package is removed or
updated that its implementation is kept in the trash folder. Package implementations are
removed from a trash folder only when another package is potentially added
to the trash folder or @command-ref{empty-trash} is used.}
] ]
}
@history[#:changed "6.1.1.6" @elem{Added @exec{trash-max-packages} and @exec{trash-max-seconds}.}]}
@subcommand{@command/toc{catalog-show} @nonterm{option} ... @nonterm{package-name} ... @subcommand{@command/toc{catalog-show} @nonterm{option} ... @nonterm{package-name} ...
--- Consults @tech{package catalogs} for a package (that is not necessarily installed) --- Consults @tech{package catalogs} for a package (that is not necessarily installed)
@ -961,12 +976,35 @@ for @nonterm{key}.
@item{@DFlag{exclude} @nonterm{pkg} --- Omits the specified @nonterm{pkg} from the @item{@DFlag{exclude} @nonterm{pkg} --- Omits the specified @nonterm{pkg} from the
resulting catalog. This also causes the dependencies of @nonterm{pkg} to be resulting catalog. This also causes the dependencies of @nonterm{pkg} to be
omitted if @DFlag{include-deps} is specified. This flag can be provided multiple times.} omitted if @DFlag{include-deps} is specified. This flag can be provided multiple times.}
@item{@DFlag{relative} --- Write package sources to @nonterm{dest-catalog} in relative-path form.} @item{@DFlag{relative} --- Writes package sources to @nonterm{dest-catalog} in relative-path form.}
] ]
@history[#:added "6.1.0.8"] @history[#:added "6.1.0.8"]
} }
@subcommand{@command/toc{empty-trash} @nonterm{option} ...
--- Removes or lists package implementations that were previously removed or updated and
are currently in the trash directory
for the specified @tech{package scope}. The @exec{trash-max-packages} and
@exec{trash-max-seconds} configuration keys (see @command-ref{config}) control
how many packages are kept in the trash directory and for how long.
The @exec{empty-trash} sub-command accepts
the following @nonterm{option}s:
@itemlist[
@item{@DFlag{scope} @nonterm{scope} --- Selects a @tech{package scope}, the same as for @command-ref{install}.}
@item{@Flag{i} or @DFlag{installation} --- Shorthand for @exec{--scope installation}.}
@item{@Flag{u} or @DFlag{user} --- Shorthand for @exec{--scope user}.}
@item{@DFlag{scope-dir} @nonterm{dir} --- Same as for @command-ref{install}.}
@item{@DFlag{list} or @Flag{l} --- Shows the trash directory path and its content, instead of
removing the current content.}
]
@history[#:added "6.1.1.6"]
}
@; ---------------------------------------- @; ----------------------------------------
@section[#:tag "metadata"]{Package Metadata} @section[#:tag "metadata"]{Package Metadata}

View File

@ -49,6 +49,7 @@
"update-deps" "update-deps"
"update-auto" "update-auto"
"scope" "scope"
"trash"
"migrate" "migrate"
"versions" "versions"
"platform" "platform"

View File

@ -17,7 +17,7 @@
$ "raco pkg show -l -u -a -d" =stdout> #rx"Package\\[\\*=auto\\] +Checksum +Source +Directory\npkg-test1 +[a-f0-9]+ .*pkg-test1\"\n" $ "raco pkg show -l -u -a -d" =stdout> #rx"Package\\[\\*=auto\\] +Checksum +Source +Directory\npkg-test1 +[a-f0-9]+ .*pkg-test1\"\n"
$ "racket -e '(require pkg-test1)'" =exit> 0 $ "racket -e '(require pkg-test1)'" =exit> 0
$ "racket -e '(file-or-directory-permissions (collection-path \"pkg-test1\") #o500)'" $ "racket -e '(file-or-directory-permissions (collection-path \"pkg-test1\") #o500)'"
$ "raco pkg remove pkg-test1" =exit> 1 $ "raco pkg remove --no-trash pkg-test1" =exit> 1
$ "racket -e '(require pkg-test1)'" =exit> 1) $ "racket -e '(require pkg-test1)'" =exit> 1)
(shelly-case (shelly-case

View File

@ -53,7 +53,7 @@
$ "raco pkg install -u --copy test-pkgs/pkg-test1" $ "raco pkg install -u --copy test-pkgs/pkg-test1"
$ "racket -l pkg-test1" =stdout> #rx"main loaded" $ "racket -l pkg-test1" =stdout> #rx"main loaded"
$ "raco pkg install -i --copy test-pkgs/pkg-test1" =exit> 1 =stderr> #rx"packages in different scopes conflict" $ "raco pkg install -i --copy test-pkgs/pkg-test1" =exit> 1 =stderr> #rx"packages in different scopes conflict"
$ "raco pkg remove pkg-test1" =stdout> "Removing pkg-test1\n") $ "raco pkg remove --no-trash pkg-test1" =stdout> "Removing pkg-test1\n")
(shelly-case (shelly-case
"override conflist, installation first" "override conflist, installation first"
@ -62,7 +62,7 @@
$ "raco pkg install -u --name pkg-test1 --copy test-pkgs/pkg-test1-v2" =exit> 1 $ "raco pkg install -u --name pkg-test1 --copy test-pkgs/pkg-test1-v2" =exit> 1
$ "raco pkg install --force -u --name pkg-test1 --copy test-pkgs/pkg-test1-v2" $ "raco pkg install --force -u --name pkg-test1 --copy test-pkgs/pkg-test1-v2"
$ "racket -l racket/base -l pkg-test1/number -e '(number)'" =stdout> "2\n" $ "racket -l racket/base -l pkg-test1/number -e '(number)'" =stdout> "2\n"
$ "raco pkg remove pkg-test1" =stdout> "Removing pkg-test1\n" $ "raco pkg remove --no-trash pkg-test1" =stdout> "Removing pkg-test1\n"
$ "racket -l racket/base -l pkg-test1/number -e '(number)'" =stdout> "1\n" $ "racket -l racket/base -l pkg-test1/number -e '(number)'" =stdout> "1\n"
$ "raco pkg remove pkg-test1" =stdout> #rx"Inferred package scope: installation") $ "raco pkg remove pkg-test1" =stdout> #rx"Inferred package scope: installation")
@ -73,7 +73,7 @@
$ "raco pkg install -i --name pkg-test1 --copy test-pkgs/pkg-test1-v2" =exit> 1 $ "raco pkg install -i --name pkg-test1 --copy test-pkgs/pkg-test1-v2" =exit> 1
$ "raco pkg install --force -i --name pkg-test1 --copy test-pkgs/pkg-test1-v2" $ "raco pkg install --force -i --name pkg-test1 --copy test-pkgs/pkg-test1-v2"
$ "racket -l racket/base -l pkg-test1/number -e '(number)'" =stdout> "1\n" $ "racket -l racket/base -l pkg-test1/number -e '(number)'" =stdout> "1\n"
$ "raco pkg remove pkg-test1" =stdout> "Removing pkg-test1\n" $ "raco pkg remove --no-trash pkg-test1" =stdout> "Removing pkg-test1\n"
$ "racket -l racket/base -l pkg-test1/number -e '(number)'" =stdout> "2\n" $ "racket -l racket/base -l pkg-test1/number -e '(number)'" =stdout> "2\n"
$ "raco pkg remove pkg-test1" =stdout> #rx"Inferred package scope: installation") $ "raco pkg remove pkg-test1" =stdout> #rx"Inferred package scope: installation")

View File

@ -0,0 +1,53 @@
#lang racket/base
(require "util.rkt"
"shelly.rkt")
(this-test-is-run-by-the-main-test)
(pkg-tests
(with-fake-root
(shelly-begin
(define (check-empty)
(shelly-begin
$ "raco pkg empty-trash -l" =stdout> #rx" \\[(none|does not exist)\\]\n"))
(define (add-remove)
(shelly-begin
$ "raco pkg install test-pkgs/pkg-test1.zip"
$ "raco pkg remove pkg-test1"))
(define (check-single)
(shelly-begin
$ "raco pkg empty-trash -l" =stdout> #rx"Content:\n [0-9]+-0-pkg-test1\n$"))
(define (check-double)
(shelly-begin
$ "raco pkg empty-trash -l" =stdout> #rx"Content:\n [0-9]+-0-pkg-test1\n [0-9]*-.-pkg-test1\n$"))
$ "raco pkg install test-pkgs/pkg-test1.zip"
$ "raco pkg remove --no-trash pkg-test1"
(check-empty)
(add-remove)
(check-single)
(add-remove)
(check-double)
$ "raco pkg empty-trash"
(check-empty)
$ "raco pkg config --set trash-max-packages 1"
(add-remove)
(check-single)
(add-remove)
(check-single)
$ "raco pkg config --set trash-max-packages 10"
(add-remove)
(check-double)
$ "raco pkg config --set trash-max-seconds 0"
(add-remove)
(check-single))))

View File

@ -25,7 +25,8 @@
"private/catalog-update.rkt" "private/catalog-update.rkt"
"private/catalog-archive.rkt" "private/catalog-archive.rkt"
"private/suggestions.rkt" "private/suggestions.rkt"
"private/archive.rkt") "private/archive.rkt"
"private/trash.rkt")
(define dep-behavior/c (define dep-behavior/c
(or/c #f 'fail 'force 'search-ask 'search-auto)) (or/c #f 'fail 'force 'search-ask 'search-auto))
@ -62,6 +63,10 @@
(parameter/c (or/c #f real?))] (parameter/c (or/c #f real?))]
[current-pkg-download-cache-max-bytes [current-pkg-download-cache-max-bytes
(parameter/c (or/c #f real?))] (parameter/c (or/c #f real?))]
[current-pkg-trash-max-packages
(parameter/c (or/c #f real?))]
[current-pkg-trash-max-seconds
(parameter/c (or/c #f real?))]
[pkg-directory [pkg-directory
(->* (string?) (->* (string?)
(#:cache (or/c #f (and/c hash? (not/c immutable?)))) (#:cache (or/c #f (and/c hash? (not/c immutable?))))
@ -98,6 +103,7 @@
#:update-deps? boolean? #:update-deps? boolean?
#:update-implies? boolean? #:update-implies? boolean?
#:quiet? boolean? #:quiet? boolean?
#:use-trash? boolean?
#:from-command-line? boolean? #:from-command-line? boolean?
#:all-platforms? boolean? #:all-platforms? boolean?
#:force? boolean? #:force? boolean?
@ -116,6 +122,7 @@
(#:auto? boolean? (#:auto? boolean?
#:force? boolean? #:force? boolean?
#:quiet? boolean? #:quiet? boolean?
#:use-trash? boolean?
#:from-command-line? boolean? #:from-command-line? boolean?
#:demote? boolean?) #:demote? boolean?)
(or/c #f 'skip (listof (or/c path-string? (non-empty-listof path-string?)))))] (or/c #f 'skip (listof (or/c path-string? (non-empty-listof path-string?)))))]
@ -137,6 +144,7 @@
#:use-cache? boolean? #:use-cache? boolean?
#:skip-installed? boolean? #:skip-installed? boolean?
#:quiet? boolean? #:quiet? boolean?
#:use-trash? boolean?
#:from-command-line? boolean? #:from-command-line? boolean?
#:strip (or/c #f 'source 'binary 'binary-lib) #:strip (or/c #f 'source 'binary 'binary-lib)
#:force-strip? boolean? #:force-strip? boolean?
@ -186,6 +194,11 @@
#:quiet? boolean? #:quiet? boolean?
#:package-exn-handler (string? exn:fail? . -> . any)) #:package-exn-handler (string? exn:fail? . -> . any))
void?)] void?)]
[pkg-empty-trash
(->* ()
(#:list? boolean?
#:quiet? boolean?)
void)]
[default-pkg-scope [default-pkg-scope
(-> package-scope/c)] (-> package-scope/c)]
[installed-pkg-names [installed-pkg-names

View File

@ -134,6 +134,7 @@
(syntax-case stx () (syntax-case stx ()
[(_ #:scope-flags (scope-flags ...) [(_ #:scope-flags (scope-flags ...)
#:job-flags (job-flags ...) #:job-flags (job-flags ...)
#:trash-flags (trash-flags ...)
#:catalog-flags (catalog-flags ...) #:catalog-flags (catalog-flags ...)
#:install-type-flags (install-type-flags ...) #:install-type-flags (install-type-flags ...)
#:install-dep-flags (install-dep-flags ...) #:install-dep-flags (install-dep-flags ...)
@ -146,6 +147,7 @@
#:install-copy-checks (install-copy-checks ...)) #:install-copy-checks (install-copy-checks ...))
(with-syntax ([([scope-flags ...] (with-syntax ([([scope-flags ...]
[job-flags ...] [job-flags ...]
[trash-flags ...]
[catalog-flags ...] [catalog-flags ...]
[install-type-flags ...] [install-type-flags ...]
[(install-dep-flags ... (dep-desc ...))] [(install-dep-flags ... (dep-desc ...))]
@ -157,6 +159,7 @@
[install-copy-checks ...]) [install-copy-checks ...])
(syntax-local-introduce #'([scope-flags ...] (syntax-local-introduce #'([scope-flags ...]
[job-flags ...] [job-flags ...]
[trash-flags ...]
[catalog-flags ...] [catalog-flags ...]
[install-type-flags ...] [install-type-flags ...]
[install-dep-flags ...] [install-dep-flags ...]
@ -194,6 +197,7 @@
install-force-flags ... install-force-flags ...
install-clone-flags ... install-clone-flags ...
job-flags ... job-flags ...
trash-flags ...
[#:bool fail-fast () ("Break `raco setup' when it discovers an error")] [#:bool fail-fast () ("Break `raco setup' when it discovers an error")]
#:args pkg-source #:args pkg-source
install-copy-defns ... install-copy-defns ...
@ -250,6 +254,7 @@
'fail 'fail
'ask)) 'ask))
#:link-dirs? link-dirs? #:link-dirs? link-dirs?
#:use-trash? (not no-trash)
(for/list ([p (in-list sources)]) (for/list ([p (in-list sources)])
(pkg-desc p a-type* name checksum #f (pkg-desc p a-type* name checksum #f
#:path (and (eq? a-type* 'clone) #:path (and (eq? a-type* 'clone)
@ -279,6 +284,7 @@
install-force-flags ... install-force-flags ...
install-clone-flags ... install-clone-flags ...
job-flags ... job-flags ...
trash-flags ...
#:args pkg-source #:args pkg-source
install-copy-defns ... install-copy-defns ...
(let ([pkg-source (let ([pkg-source
@ -342,7 +348,8 @@
'fail 'fail
'ask)) 'ask))
#:link-dirs? link-dirs? #:link-dirs? link-dirs?
#:infer-clone-from-dir? (not (or link static-link copy)))))) #:infer-clone-from-dir? (not (or link static-link copy))
#:use-trash? (not no-trash)))))
(setup "updated" no-setup #f setup-collects jobs))))] (setup "updated" no-setup #f setup-collects jobs))))]
;; ---------------------------------------- ;; ----------------------------------------
[remove [remove
@ -355,6 +362,7 @@
scope-flags ... scope-flags ...
#:once-each #:once-each
job-flags ... job-flags ...
trash-flags ...
#:args pkg #:args pkg
(call-with-package-scope (call-with-package-scope
'remove 'remove
@ -366,7 +374,8 @@
#:from-command-line? #t #:from-command-line? #t
#:demote? demote #:demote? demote
#:auto? auto #:auto? auto
#:force? force))) #:force? force
#:use-trash? (not no-trash))))
(setup "removed" no-setup #f setup-collects jobs)))] (setup "removed" no-setup #f setup-collects jobs)))]
;; ---------------------------------------- ;; ----------------------------------------
[new [new
@ -597,7 +606,21 @@
(cons pkg pkgs) (cons pkg pkgs)
#:include-deps? include-deps #:include-deps? include-deps
#:exclude (exclude-list) #:exclude (exclude-list)
#:relative-sources? relative))]))])) #:relative-sources? relative))]
;; ----------------------------------------
[empty-trash
"Delete old package installations from the trash directory"
#:once-any
scope-flags ...
#:once-each
[#:bool list ("-l") "Show trash content without emptying"]
#:args ()
(call-with-package-scope
'empty-trash
scope scope-dir installation user #f #f #f #f
(lambda ()
(pkg-empty-trash #:list? list
#:quiet? #f)))]))]))
(make-commands (make-commands
#:scope-flags #:scope-flags
([(#:sym scope [installation user] #f) scope () ([(#:sym scope [installation user] #f) scope ()
@ -611,6 +634,8 @@
([#:bool no-setup () ("Don't `raco setup' after changing packages (usually a bad idea)")] ([#:bool no-setup () ("Don't `raco setup' after changing packages (usually a bad idea)")]
[(#:num n #f) jobs ("-j") "Setup with <n> parallel jobs"] [(#:num n #f) jobs ("-j") "Setup with <n> parallel jobs"]
[#:bool batch () ("Disable interactive mode and all prompts")]) [#:bool batch () ("Disable interactive mode and all prompts")])
#:trash-flags
([#:bool no-trash () ("Delete uninstalled/updated, instead of moving to a trash folder")])
#:catalog-flags #:catalog-flags
([(#:str catalog #f) catalog () "Use <catalog> instead of configured catalogs"]) ([(#:str catalog #f) catalog () "Use <catalog> instead of configured catalogs"])
#:install-type-flags #:install-type-flags

View File

@ -109,12 +109,13 @@
[(sub-path? < p d) [(sub-path? < p d)
;; Under the installation mode's package directory. ;; Under the installation mode's package directory.
;; We assume that no one else writes there, so the ;; We assume that no one else writes there, so the
;; next path element is the package name (or the package ;; next path element is the package name, the package
;; name followed by "+<n>") ;; name followed by "+<n>", or ".trash"
(define len (length d)) (define len (length d))
(define pkg-name (path-element->string (list-ref p len))) (define pkg-name (path-element->string (list-ref p len)))
(if (regexp-match? #rx"pkgs[.]rktd" pkg-name) (if (or (regexp-match? #rx"pkgs[.]rktd" pkg-name)
(values #f #f #f #f) ; don't count the database as a package (regexp-match? #rx"[.]trash" pkg-name))
(values #f #f #f #f) ; don't count the database or trash can as a package
(values (if (regexp-match? #rx"[+]" pkg-name) ; +<n> used as an alternate path, sometimes (values (if (regexp-match? #rx"[+]" pkg-name) ; +<n> used as an alternate path, sometimes
(regexp-replace #rx"[+].*$" pkg-name "") (regexp-replace #rx"[+].*$" pkg-name "")
pkg-name) pkg-name)

View File

@ -26,6 +26,13 @@
(or (current-pkg-download-cache-max-bytes) (or (current-pkg-download-cache-max-bytes)
(read-pkg-cfg/def 'download-cache-max-bytes))) (read-pkg-cfg/def 'download-cache-max-bytes)))
(define (get-trash-max-packages)
(or (current-pkg-trash-max-packages)
(read-pkg-cfg/def 'trash-max-packages)))
(define (get-trash-max-seconds)
(or (current-pkg-trash-max-seconds)
(read-pkg-cfg/def 'trash-max-seconds)))
(define (read-pkg-cfg/def k) (define (read-pkg-cfg/def k)
;; Lock is held for the current scope, but if ;; Lock is held for the current scope, but if
;; the key is not found in the current scope, ;; the key is not found in the current scope,
@ -42,6 +49,8 @@
"download-cache")] "download-cache")]
['download-cache-max-files 1024] ['download-cache-max-files 1024]
['download-cache-max-bytes (* 64 1024 1024)] ['download-cache-max-bytes (* 64 1024 1024)]
['trash-max-packages 512]
['trash-max-seconds (* 60 60 24 2)] ; 2 days
[_ #f])) [_ #f]))
(define c (read-pkg-file-hash (pkg-config-file))) (define c (read-pkg-file-hash (pkg-config-file)))
(define v (hash-ref c k 'none)) (define v (hash-ref c k 'none))
@ -113,7 +122,9 @@
"download-cache-max-files" "download-cache-max-files"
"download-cache-max-bytes" "download-cache-max-bytes"
"download-cache-dir" "download-cache-dir"
"doc-open-url"))) "doc-open-url"
"trash-max-packages"
"trash-max-seconds")))
(pkg-error (~a "missing value for config key\n" (pkg-error (~a "missing value for config key\n"
" config key: ~a") " config key: ~a")
key)] key)]
@ -122,7 +133,10 @@
"name" "name"
"download-cache-max-files" "download-cache-max-files"
"download-cache-max-bytes" "download-cache-max-bytes"
"download-cache-dir")) "download-cache-dir"
"doc-open-url"
"trash-max-packages"
"trash-max-seconds"))
val val
another-val another-val
more-vals) more-vals)
@ -155,7 +169,9 @@
(path->string (path->string
(path->complete-path val))))] (path->complete-path val))))]
[(list (and key (or "download-cache-max-files" [(list (and key (or "download-cache-max-files"
"download-cache-max-bytes")) "download-cache-max-bytes"
"trash-max-packages"
"trash-max-seconds"))
val) val)
(unless (real? (string->number val)) (unless (real? (string->number val))
(pkg-error (~a "invalid value for config key\n" (pkg-error (~a "invalid value for config key\n"
@ -187,7 +203,9 @@
(printf "~a~a\n" indent (read-pkg-cfg/def 'installation-name))] (printf "~a~a\n" indent (read-pkg-cfg/def 'installation-name))]
[(or "download-cache-dir" [(or "download-cache-dir"
"download-cache-max-files" "download-cache-max-files"
"download-cache-max-bytes") "download-cache-max-bytes"
"trash-max-packages"
"trash-max-seconds")
(printf "~a~a\n" indent (read-pkg-cfg/def (string->symbol key)))] (printf "~a~a\n" indent (read-pkg-cfg/def (string->symbol key)))]
["doc-open-url" ["doc-open-url"
(printf "~a~a\n" indent (or (read-pkg-cfg/def 'doc-open-url) ""))] (printf "~a~a\n" indent (or (read-pkg-cfg/def 'doc-open-url) ""))]
@ -207,7 +225,9 @@
"default-scope" "default-scope"
"download-cache-dir" "download-cache-dir"
"download-cache-max-files" "download-cache-max-files"
"download-cache-max-bytes"))]) "download-cache-max-bytes"
"trash-max-packages"
"trash-max-seconds"))])
(printf "~a:\n" key) (printf "~a:\n" key)
(show (list key) " "))] (show (list key) " "))]
[_ (show key+vals "")])])) [_ (show key+vals "")])]))

View File

@ -135,6 +135,7 @@
#:force? force? #:force? force?
#:all-platforms? all-platforms? #:all-platforms? all-platforms?
#:quiet? quiet? #:quiet? quiet?
#:use-trash? use-trash?
#:from-command-line? from-command-line? #:from-command-line? from-command-line?
#:conversation conversation #:conversation conversation
#:strip strip-mode #:strip strip-mode
@ -424,7 +425,7 @@
(let () (let ()
(define (continue conversation) (define (continue conversation)
(raise (vector #t infos pkg-name update-pkgs (raise (vector #t infos pkg-name update-pkgs
(λ () (for-each (compose (remove-package quiet?) pkg-desc-name) update-pkgs)) (λ () (for-each (compose (remove-package quiet? use-trash?) pkg-desc-name) update-pkgs))
conversation conversation
clone-info))) clone-info)))
(match (if (andmap (lambda (dep) (set-member? implies (pkg-desc-name dep))) (match (if (andmap (lambda (dep) (set-member? implies (pkg-desc-name dep)))
@ -526,7 +527,7 @@
#:from-command-line? from-command-line? #:from-command-line? from-command-line?
#:link-dirs? link-dirs?) #:link-dirs? link-dirs?)
update-pkgs)]) update-pkgs)])
(λ () (for-each (compose (remove-package quiet?) pkg-desc-name) to-update)))) (λ () (for-each (compose (remove-package quiet? use-trash?) pkg-desc-name) to-update))))
(match this-dep-behavior (match this-dep-behavior
['fail ['fail
(clean!) (clean!)
@ -776,6 +777,7 @@
#:catalog-lookup-cache [catalog-lookup-cache (make-hash)] #:catalog-lookup-cache [catalog-lookup-cache (make-hash)]
#:updating? [updating? #f] #:updating? [updating? #f]
#:quiet? [quiet? #f] #:quiet? [quiet? #f]
#:use-trash? [use-trash? #f]
#:from-command-line? [from-command-line? #f] #:from-command-line? [from-command-line? #f]
#:conversation [conversation #f] #:conversation [conversation #f]
#:strip [strip-mode #f] #:strip [strip-mode #f]
@ -785,9 +787,9 @@
#:multi-clone-behavior [old-clone-behavior 'fail] #:multi-clone-behavior [old-clone-behavior 'fail]
#:repo-descs [old-repo-descs (initial-repo-descs #:repo-descs [old-repo-descs (initial-repo-descs
(read-pkg-db) (read-pkg-db)
(if quiet? void printf))] (if quiet? void printf/flush))]
#:convert-to-non-clone? [convert-to-non-clone? #f]) #:convert-to-non-clone? [convert-to-non-clone? #f])
(define download-printf (if quiet? void printf)) (define download-printf (if quiet? void printf/flush))
(define descs (define descs
(map (convert-clone-name-to-clone-repo/install catalog-lookup-cache (map (convert-clone-name-to-clone-repo/install catalog-lookup-cache
@ -833,6 +835,9 @@
#:catalog-lookup-cache catalog-lookup-cache #:catalog-lookup-cache catalog-lookup-cache
#:pre-succeed (lambda () (pre-succeed) (more-pre-succeed)) #:pre-succeed (lambda () (pre-succeed) (more-pre-succeed))
#:updating? updating? #:updating? updating?
#:quiet? quiet?
#:use-trash? use-trash?
#:from-command-line? from-command-line?
#:conversation conv #:conversation conv
#:strip strip-mode #:strip strip-mode
#:force-strip? force-strip? #:force-strip? force-strip?
@ -858,11 +863,12 @@
#:catalog-lookup-cache catalog-lookup-cache #:catalog-lookup-cache catalog-lookup-cache
#:pre-succeed (λ () #:pre-succeed (λ ()
(for ([pkg-name (in-hash-keys extra-updating)]) (for ([pkg-name (in-hash-keys extra-updating)])
((remove-package quiet?) pkg-name)) ((remove-package quiet? use-trash?) pkg-name))
(pre-succeed)) (pre-succeed))
#:updating? updating? #:updating? updating?
#:extra-updating extra-updating #:extra-updating extra-updating
#:quiet? quiet? #:quiet? quiet?
#:use-trash? use-trash?
#:from-command-line? from-command-line? #:from-command-line? from-command-line?
#:conversation conversation #:conversation conversation
#:strip strip-mode #:strip strip-mode
@ -1093,6 +1099,7 @@
#:update-deps? [update-deps? #f] #:update-deps? [update-deps? #f]
#:update-implies? [update-implies? #t] #:update-implies? [update-implies? #t]
#:quiet? [quiet? #f] #:quiet? [quiet? #f]
#:use-trash? [use-trash? #f]
#:from-command-line? [from-command-line? #f] #:from-command-line? [from-command-line? #f]
#:strip [strip-mode #f] #:strip [strip-mode #f]
#:force-strip? [force-strip? #f] #:force-strip? [force-strip? #f]
@ -1100,7 +1107,7 @@
#:infer-clone-from-dir? [infer-clone-from-dir? #f] #:infer-clone-from-dir? [infer-clone-from-dir? #f]
#:lookup-for-clone? [lookup-for-clone? #f] #:lookup-for-clone? [lookup-for-clone? #f]
#:multi-clone-behavior [clone-behavior 'fail]) #:multi-clone-behavior [clone-behavior 'fail])
(define download-printf (if quiet? void printf)) (define download-printf (if quiet? void printf/flush))
(define metadata-ns (make-metadata-namespace)) (define metadata-ns (make-metadata-namespace))
(define db (read-pkg-db)) (define db (read-pkg-db))
(define all-mode? (and all? (empty? in-pkgs))) (define all-mode? (and all? (empty? in-pkgs)))
@ -1157,13 +1164,14 @@
(flush-output)) (flush-output))
(pkg-install (pkg-install
#:updating? #t #:updating? #t
#:pre-succeed (λ () (for-each (compose (remove-package quiet?) pkg-desc-name) to-update)) #:pre-succeed (λ () (for-each (compose (remove-package quiet? use-trash?) pkg-desc-name) to-update))
#:dep-behavior dep-behavior #:dep-behavior dep-behavior
#:update-deps? update-deps? #:update-deps? update-deps?
#:update-implies? update-implies? #:update-implies? update-implies?
#:update-cache update-cache #:update-cache update-cache
#:catalog-lookup-cache catalog-lookup-cache #:catalog-lookup-cache catalog-lookup-cache
#:quiet? quiet? #:quiet? quiet?
#:use-trash? use-trash?
#:from-command-line? from-command-line? #:from-command-line? from-command-line?
#:strip strip-mode #:strip strip-mode
#:force-strip? force-strip? #:force-strip? force-strip?

View File

@ -27,3 +27,8 @@
(define current-pkg-download-cache-max-bytes (define current-pkg-download-cache-max-bytes
(make-parameter #f)) (make-parameter #f))
(define current-pkg-trash-max-packages
(make-parameter #f))
(define current-pkg-trash-max-seconds
(make-parameter #f))

View File

@ -10,7 +10,8 @@
"collects.rkt" "collects.rkt"
"params.rkt" "params.rkt"
"print.rkt" "print.rkt"
"get-info.rkt") "get-info.rkt"
"trash.rkt")
(provide remove-package (provide remove-package
pkg-remove) pkg-remove)
@ -24,7 +25,7 @@
(printf/flush "Demoting ~a to auto-installed\n" pkg-name)) (printf/flush "Demoting ~a to auto-installed\n" pkg-name))
(update-pkg-db! pkg-name (update-auto pi #t))))) (update-pkg-db! pkg-name (update-auto pi #t)))))
(define ((remove-package quiet?) pkg-name) (define ((remove-package quiet? use-trash?) pkg-name)
(unless quiet? (unless quiet?
(printf/flush "Removing ~a\n" pkg-name)) (printf/flush "Removing ~a\n" pkg-name))
(define db (read-pkg-db)) (define db (read-pkg-db))
@ -48,8 +49,14 @@
#:user? user? #:user? user?
#:file (scope->links-file scope) #:file (scope->links-file scope)
#:root? (not (sc-pkg-info? pi))) #:root? (not (sc-pkg-info? pi)))
(delete-directory/files pkg-dir)])) (cond
[(and use-trash?
(select-trash-dest pkg-name))
=> (lambda (trash-dest)
(printf/flush "Moving ~a to trash: ~a\n" pkg-name trash-dest)
(rename-file-or-directory pkg-dir trash-dest))]
[else
(delete-directory/files pkg-dir)])]))
(define (pkg-remove given-pkgs (define (pkg-remove given-pkgs
@ -57,6 +64,7 @@
#:force? [force? #f] #:force? [force? #f]
#:auto? [auto? #f] #:auto? [auto? #f]
#:quiet? [quiet? #f] #:quiet? [quiet? #f]
#:use-trash? [use-trash? #f]
#:from-command-line? [from-command-line? #f]) #:from-command-line? [from-command-line? #f])
(define db (read-pkg-db)) (define db (read-pkg-db))
(define all-pkgs (define all-pkgs
@ -134,7 +142,7 @@
(set->list (set-subtract (list->set in-pkgs) (set->list (set-subtract (list->set in-pkgs)
(list->set remove-pkgs))))) (list->set remove-pkgs)))))
(for-each (remove-package quiet?) (for-each (remove-package quiet? use-trash?)
remove-pkgs) remove-pkgs)
(cond (cond

View File

@ -0,0 +1,76 @@
#lang racket/base
(require racket/list
racket/file
"config.rkt"
"dirs.rkt"
"print.rkt"
"params.rkt")
(provide select-trash-dest
pkg-empty-trash)
(define (find-trash-dir) (build-path (pkg-dir #f) ".trash"))
(define (select-trash-dest pkg-name
#:remove-printf [remove-printf void])
(define max-packages (get-trash-max-packages))
(define max-seconds (get-trash-max-seconds))
(define trash-dir (find-trash-dir))
(make-directory* trash-dir)
(define (delete-trash f)
(define p (build-path trash-dir f))
(remove-printf "Removing trash directory ~a\n" p)
(log-pkg-info "Removing trash directory ~a" p)
(delete-directory/files p))
(define l (if (directory-exists? trash-dir)
(directory-list trash-dir)
null))
(let loop ([l l])
(cond
[(and (pair? l)
((length l) . > . (sub1 max-packages)))
(delete-trash (car l))
(loop (cdr l))]
[(and (pair? l)
(let ([m (regexp-match #rx"^([0-9]+)-" (car l))])
(and m
((string->number (cadr m)) . < . (- (current-seconds)
max-seconds)))))
(delete-trash (car l))
(loop (cdr l))]
[(max-packages . < . 1) #f]
[(not pkg-name) #f]
[else
(let loop ([n 0])
(define name (format "~a-~a-~a" (current-seconds) n pkg-name))
(define p (build-path trash-dir name))
(if (or (directory-exists? p)
(file-exists? p)
(link-exists? p))
(loop (add1 n))
p))])))
(define (pkg-empty-trash #:list? [show-list? #f]
#:quiet? [quiet? #t])
(cond
[show-list?
(define trash-dir (find-trash-dir))
(printf "Trash directory for ~s scope:\n ~a\nContent:\n"
(current-pkg-scope)
trash-dir)
(define exists? (directory-exists? trash-dir))
(define l (if exists?
(directory-list trash-dir)
null))
(cond
[(not exists?)
(printf " [does not exist]\n")]
[(null? l)
(printf " [none]\n")]
[else
(for ([i (in-list l)])
(printf " ~a\n" i))])]
[else
(parameterize ([current-pkg-trash-max-packages 0])
(select-trash-dest #t #:remove-printf (if quiet? void printf/flush)))
(void)]))