diff --git a/pkgs/racket-doc/pkg/scribblings/pkg.scrbl b/pkgs/racket-doc/pkg/scribblings/pkg.scrbl index eb9ada0875..bd9f3c86a5 100644 --- a/pkgs/racket-doc/pkg/scribblings/pkg.scrbl +++ b/pkgs/racket-doc/pkg/scribblings/pkg.scrbl @@ -398,9 +398,13 @@ all users of the Racket installation. A directory path can be used as a @tech{package scope}, in which case package operations affect the set of packages installations in the -directory. An installation can be configured to include the -directory in its search path for installed packages (see -@secref["config-file" #:doc raco-doc]). +directory. An installation can be configured to include the directory +in its search path for installed packages (see @secref["config-file" +#:doc raco-doc]). When a directory path is used as a @tech{package +scope}, operations such as dependency checking will use all paths in +the configured search path starting with the one that is designed as a +@tech{package scope}; if the designated path is not in the configured +search path, then the dierctory by itself is used as the search path. Conflict checking disallows installation of the same or conflicting package in different scopes, but if such a configuration is forced, diff --git a/pkgs/racket-doc/scribblings/raco/config.scrbl b/pkgs/racket-doc/scribblings/raco/config.scrbl index 17fe02a107..c45d9bd654 100644 --- a/pkgs/racket-doc/scribblings/raco/config.scrbl +++ b/pkgs/racket-doc/scribblings/raco/config.scrbl @@ -78,10 +78,22 @@ directory}: "pkg/scribblings/pkg.scrbl")]{package scope}. It defaults to @filepath{pkgs} in the main shared-file directory.} - @item{@indexed-racket['pkgs-search-dirs] --- like - @racket['lib-search-dirs], but for packages in @exec{installation} - @tech[#:doc '(lib "pkg/scribblings/pkg.scrbl")]{package - scope}.} + @item{@indexed-racket['pkgs-search-dirs] --- similar to + @racket['lib-search-dirs], but for packages in roughly + @exec{installation} @tech[#:doc '(lib + "pkg/scribblings/pkg.scrbl")]{package scope}. More precisely, a + @racket[#f] value in the list is replaced with the directory + specified by @racket['pkgs-dir], and that point in the search + list corresponds to @exec{installation} scope. Paths before or + after a @racket[#f] value in the list can be selected as a + scopes to start searches at that path's point in the list. + Directories listed in @racket['pkgs-search-dirs] typically oblige + a corresponding entry in @racket['links-search-files], where + the corresponding entry is @filepath{links.rktd} within the + directory. + + @history[#:changed "7.0.0.19" @elem{Adapt the package-search path in + a general way for a directory scope.}]} @item{@indexed-racket['bin-dir] --- a path, string, or byte string for the installation's directory containing executables. It defaults to a diff --git a/pkgs/racket-test/tests/pkg/test-scope-add.rkt b/pkgs/racket-test/tests/pkg/test-scope-add.rkt new file mode 100644 index 0000000000..02e39b84bb --- /dev/null +++ b/pkgs/racket-test/tests/pkg/test-scope-add.rkt @@ -0,0 +1,27 @@ +#lang racket/base +(require racket/cmdline + setup/dirs) + +;; This module is meant to be run via "tests-scope.rkt". It adds the +;; given paths to the package-search list, and it adds "links.rktd" in +;; those paths to the links-search list. + +(command-line + #:args + path + (unless (null? path) + (let ([paths path] + [file (build-path (find-config-dir) "config.rktd")]) + (define ht (call-with-input-file* file read)) + (define new-ht + (hash-set (hash-set ht + 'pkgs-search-dirs + (append paths (hash-ref ht 'pkgs-search-dirs '(#f)))) + 'links-search-files + (append (for/list ([path (in-list paths)]) + (path->string (build-path path "links.rktd"))) + (hash-ref ht 'links-search-files '(#f))))) + (call-with-output-file* + file + #:exists 'truncate/replace + (lambda (o) (write new-ht o)))))) diff --git a/pkgs/racket-test/tests/pkg/tests-scope.rkt b/pkgs/racket-test/tests/pkg/tests-scope.rkt index c92e79974f..4e8a614846 100644 --- a/pkgs/racket-test/tests/pkg/tests-scope.rkt +++ b/pkgs/racket-test/tests/pkg/tests-scope.rkt @@ -3,7 +3,8 @@ "util.rkt" pkg/lib setup/dirs - racket/format) + racket/format + racket/file) (this-test-is-run-by-the-main-test) @@ -78,4 +79,22 @@ $ "racket -l racket/base -l pkg-test1/number -e '(number)'" =stdout> "2\n" $ "raco pkg remove pkg-test1" =stdout> #rx"Inferred package scope: installation") + (initialize-catalogs) + (define alone-dir (make-temporary-file "alone~a" 'directory)) + (define in-search-dir (make-temporary-file "in-search~a" 'directory)) + (shelly-case + "directory as a package scope" + $ "raco pkg config -i --set catalogs http://localhost:9990 http://localhost:9991" + $ "raco pkg install -i pkg-test1" + $ "racket -l pkg-test1" =stdout> #rx"main loaded" + ;; won't find "base" on catalog: + $ (~a "raco pkg install --scope-dir "alone-dir" --auto pkg-test2") =exit> 1 =stderr> #rx"cannot find package.*base" + $ "racket -l pkg-test2" =exit> 1 =stderr> #rx"collection not found" + $ (~a "racket -l tests/pkg/test-scope-add "in-search-dir) + ;; will install "pkg-test2" without an extra "pkg-test1" or "base": + $ (~a "raco pkg install --scope-dir "in-search-dir" --auto pkg-test2") + $ "racket -l pkg-test2" =stdout> #rx"pkg-test2/main loaded") + (delete-directory/files alone-dir) + (delete-directory/files in-search-dir) + ))) diff --git a/racket/collects/pkg/private/pkg-db.rkt b/racket/collects/pkg/private/pkg-db.rkt index 7dc0c8e66e..6cb242cd94 100644 --- a/racket/collects/pkg/private/pkg-db.rkt +++ b/racket/collects/pkg/private/pkg-db.rkt @@ -37,20 +37,10 @@ ;; read all packages in this scope or wider (define (merge-pkg-dbs [scope (current-pkg-scope)]) - (define (merge-next-pkg-dbs scope) - (parameterize ([current-pkg-scope scope]) - (merge-pkg-dbs scope))) - (if (path? scope) - (read-pkg-db) - (case scope - [(installation) - (for*/hash ([dir (in-list (get-pkgs-search-dirs))] - [(k v) (read-pkgs-db dir)]) - (values k v))] - [(user) - (define db (read-pkgs-db 'user (current-pkg-scope-version))) - (for/fold ([ht (merge-next-pkg-dbs 'installation)]) ([(k v) (in-hash db)]) - (hash-set ht k v))]))) + (for/fold ([ht #hash()]) ([m-scope (in-list (reverse (get-scope-list scope)))]) + (define db (read-pkgs-db m-scope (current-pkg-scope-version))) + (for/fold ([ht ht]) ([(k v) (in-hash db)]) + (hash-set ht k v)))) ;; Finds the scope, in which `pkg-name' is installed; returns 'dir, ;; 'installation, a path, or #f (where #f means "not installed"). If @@ -149,19 +139,22 @@ (and (path? scope) (build-path scope "links.rktd"))) -(define (get-scope-list) +(define (get-scope-list [current-scope (current-pkg-scope)]) ;; Get a list of scopes suitable for searches with respect to ;; the current scope - (define current-scope (current-pkg-scope)) - (if (path? current-scope) - (list current-scope) - (member current-scope - (append '(user) - (let ([main (find-pkgs-dir)]) - (for/list ([d (get-pkgs-search-dirs)]) - (if (equal? d main) - 'installation - d))))))) + (or + ;; Exploit the fact that `member` returns a list starting + ;; with the found element: + (member current-scope + (append '(user) + (let ([main (find-pkgs-dir)]) + (for/list ([d (get-pkgs-search-dirs)]) + (if (equal? d main) + 'installation + d))))) + ;; In case the specified scope wasn't in the list, + ;; then make a search path that has just that scope: + (list current-scope))) (define (pkg-directory pkg-name #:cache [cache #f]) ;; Warning: takes locks individually.