diff --git a/pkgs/racket-doc/scribblings/raco/api.scrbl b/pkgs/racket-doc/scribblings/raco/api.scrbl index 91a6bf0424..109a5eb7b8 100644 --- a/pkgs/racket-doc/scribblings/raco/api.scrbl +++ b/pkgs/racket-doc/scribblings/raco/api.scrbl @@ -105,19 +105,30 @@ collection. The following fields are used: @item{@indexed-racket[name] : The name of the collection as a string, used only for status and error reporting.} - @item{@indexed-racket[compile-omit-paths] : A list of immediate file - and directory paths that should not be compiled. Alternatively, - this field's value is @racket['all], which is equivalent to - specifying all files and directories in the collection (to - effectively ignore the collection for + @item{@indexed-racket[compile-omit-paths] : Either a list of paths + and @tech[#:doc reference-doc]{regexp values} or @racket['all]. + In a list, a path is treated as a file that should not be + compiled or a directory whose files should not be compiled and + whose @filepath{info.rkt} files should be ignored by @exec{raco + setup}; the paths are relative to the collection (i.e., + directory containing the @filepath{info.rkt} file) and can + refer to files and directories in subcollections that are that + are represented by subdirectories. A regexp in the list is + matched against file and directory paths relative to the + collection (so, for example, start a regexp with @litchar{^} to + match only paths in the immediate collection and not in + subcollections) to exclude those files and directories from + compilation and @exec{raco setup}. The value @racket['all] is + equivalent to specifying all files and directories in the + collection (to effectively ignore the collection for compilation). Automatically omitted files and directories are @filepath{compiled}, @filepath{doc}, and those whose names start with @litchar{.}. - Files that are required by other files, however, are always - compiled in the process of compiling the requiring file---even - when the required file is listed with this field or when the - field's value is @racket['all].} + Files that are required by other files are always compiled in + the process of compiling the requiring file---even when the + required file is listed with this field or when the field's + value is @racket['all].} @item{@indexed-racket[compile-omit-files] : A list of filenames (without directory paths) that are not compiled, in addition to the @@ -141,7 +152,8 @@ collection. The following fields are used: ] @history[#:changed "6.3" @elem{Added support for @racket[compile-include-files].} - #:changed "7.8.0.8" @elem{Changed ``starts with'' for @racket[skip-path] to include an exact match.}]} + #:changed "7.8.0.8" @elem{Changed ``starts with'' for @racket[skip-path] to include an exact match.} + #:changed "8.1.0.5" @elem{Added support for regexps in @racket[compile-omit-paths].}]} @defproc[(compile-directory-zos [path path-string?] diff --git a/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/info.rkt b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/info.rkt new file mode 100644 index 0000000000..47faaa9704 --- /dev/null +++ b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/info.rkt @@ -0,0 +1,3 @@ +#lang info + +(define compile-omit-paths '("not-me" #rx"either")) diff --git a/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/1 b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/1 new file mode 100644 index 0000000000..5626abf0f7 --- /dev/null +++ b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/1 @@ -0,0 +1 @@ +one diff --git a/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/a.scrbl b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/a.scrbl new file mode 100644 index 0000000000..14fb57234b --- /dev/null +++ b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/a.scrbl @@ -0,0 +1,3 @@ +#lang scribble/base + +@title{A} diff --git a/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/info.rkt b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/info.rkt new file mode 100644 index 0000000000..b09487c76b --- /dev/null +++ b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/info.rkt @@ -0,0 +1,8 @@ +#lang info + +(define scribblings '(("a.scrbl"))) + +(define racket-launcher-names '("x")) +(define racket-launcher-libraries '("x.rkt")) + +(define copy-shared-files '("1")) diff --git a/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/x.rkt b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/x.rkt new file mode 100644 index 0000000000..7bc35af1c4 --- /dev/null +++ b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/neither/x.rkt @@ -0,0 +1 @@ +#lang racket/base diff --git a/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/1 b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/1 new file mode 100644 index 0000000000..5626abf0f7 --- /dev/null +++ b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/1 @@ -0,0 +1 @@ +one diff --git a/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/a.scrbl b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/a.scrbl new file mode 100644 index 0000000000..14fb57234b --- /dev/null +++ b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/a.scrbl @@ -0,0 +1,3 @@ +#lang scribble/base + +@title{A} diff --git a/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/info.rkt b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/info.rkt new file mode 100644 index 0000000000..b09487c76b --- /dev/null +++ b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/info.rkt @@ -0,0 +1,8 @@ +#lang info + +(define scribblings '(("a.scrbl"))) + +(define racket-launcher-names '("x")) +(define racket-launcher-libraries '("x.rkt")) + +(define copy-shared-files '("1")) diff --git a/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/x.rkt b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/x.rkt new file mode 100644 index 0000000000..7bc35af1c4 --- /dev/null +++ b/pkgs/racket-test/tests/pkg/test-pkgs/pkg-add-excl/not-me/x.rkt @@ -0,0 +1 @@ +#lang racket/base diff --git a/pkgs/racket-test/tests/pkg/tests-conflicts.rkt b/pkgs/racket-test/tests/pkg/tests-conflicts.rkt index c7305de4bf..89f1e5c0f2 100644 --- a/pkgs/racket-test/tests/pkg/tests-conflicts.rkt +++ b/pkgs/racket-test/tests/pkg/tests-conflicts.rkt @@ -127,6 +127,12 @@ (with-fake-root (shelly-begin $ (~a "raco pkg install --copy test-pkgs/pkg-add-base " c) =exit> 0)))) + (shelly-case + "no doc conflict with excluded subcollections" + (for ([c '("test-pkgs/pkg-add-excl")]) + (with-fake-root + (shelly-begin + $ (~a "raco pkg install --copy --strict-doc-conflicts test-pkgs/pkg-add-base " c) =exit> 0)))) (putenv "PLT_PKG_NOSETUP" "") (with-fake-root (shelly-case diff --git a/racket/collects/pkg/private/addl-installs.rkt b/racket/collects/pkg/private/addl-installs.rkt index 8029e88b89..22b2af6d79 100644 --- a/racket/collects/pkg/private/addl-installs.rkt +++ b/racket/collects/pkg/private/addl-installs.rkt @@ -25,34 +25,56 @@ #:system-library-subpath [sys-lib-subpath #f]) (define single-collect (pkg-single-collection dir #:name pkg-name #:namespace metadata-ns)) - (let loop ([s (set)] [f dir] [top? #t] [omits (set)]) + ;; In this loop `omits` is a set of paths built on `dir`, and `prefix+rxes` + ;; is a set of regular expressions to continue using as we recur down; + ;; each regular expression is matched relative to the directory where it was + ;; introduced, so we have to build up a prefix to use with each regexp + (let loop ([s (set)] [f-rel dir] [wrt #f] [top? #t] [omits (set)] [prefix+rxs '()]) + (define f (if wrt (build-path wrt f-rel) f-rel)) (cond - [(and (directory-exists? f) - (not (set-member? omits (simplify-path f)))) - (define i (get-pkg-info f metadata-ns)) - (define omit-paths (if i - (i 'compile-omit-paths (lambda () null)) - null)) - (cond - [(eq? omit-paths 'all) - s] - [else - (define omit-files (if i - (i 'compile-omit-files (lambda () null)) - null)) - (define new-s - (if (and i (or single-collect (not top?))) - (set-union (extract-additional-installs i sys-type sys-lib-subpath) - s) - s)) - (define new-omits - (set-union - omits - (for/set ([i (in-list (append omit-paths omit-files))]) - (simplify-path (build-path f i))))) - (for/fold ([s new-s]) ([f (directory-list f #:build? #t)]) - (loop s f #f new-omits))])] - [else s]))) + [(and (directory-exists? f) + (let ([sf (simplify-path f)]) + (and (not (set-member? omits sf)) + (not (for/or ([prefix+rx (in-list prefix+rxs)]) + (define prefix (car prefix+rx)) + (regexp-match? (cdr prefix+rx) (if (eq? prefix 'same) + f-rel + (build-path prefix f-rel)))))))) + (define i (get-pkg-info f metadata-ns)) + (define omit-paths (if i + (i 'compile-omit-paths (lambda () null)) + null)) + (cond + [(eq? omit-paths 'all) + s] + [else + (define omit-files (if i + (i 'compile-omit-files (lambda () null)) + null)) + (define new-s + (if (and i (or single-collect (not top?))) + (set-union (extract-additional-installs i sys-type sys-lib-subpath) + s) + s)) + (define new-omits + (set-union omits + (for/set ([i (in-list (append omit-paths omit-files))] + #:unless (regexp? i)) + (simplify-path (build-path f i))))) + (define new-prefix+rxs + (append (for/list ([i (in-list (append omit-paths omit-files))] + #:when (regexp? i)) + (cons 'same i)) + ;; add to prefix for rxs accumulated so far + (for/list ([prefix+rx (in-list prefix+rxs)]) + (define prefix (car prefix+rx)) + (cons (if (eq? prefix 'same) + f-rel + (build-path prefix f-rel)) + (cdr prefix+rx))))) + (for/fold ([s new-s]) ([sub-f (directory-list f)]) + (loop s sub-f f #f new-omits new-prefix+rxs))])] + [else s]))) (define (extract-additional-installs i sys-type sys-lib-subpath) (define (extract-documents i) diff --git a/racket/collects/setup/private/omitted-paths.rkt b/racket/collects/setup/private/omitted-paths.rkt index ee4affd089..90b494aaf8 100644 --- a/racket/collects/setup/private/omitted-paths.rkt +++ b/racket/collects/setup/private/omitted-paths.rkt @@ -44,44 +44,84 @@ (and omit-doc? (equal? "doc" str)) (regexp-match? #rx"^[.]" str)))) +;; accumulated omissions is a list of +;; exploded paths plus a list of (cons prefix-path regexp) +(struct omits (exploded-paths prefix+rxs)) + +;; returns 'all or an `omits` (define (compute-omitted dir accumulated implicit-omit? get-info/full) (define info (or (get-info/full dir) (lambda _ '()))) (define explicit (let ([omit (info 'compile-omit-paths (lambda () '()))]) (if (eq? 'all omit) - 'all - (map (lambda (e) (explode-path (simplify-path e #f))) - ;; for backward compatibility - (append omit (info 'compile-omit-files (lambda () '()))))))) + 'all + (map (lambda (e) (if (regexp? e) + e + (explode-path (simplify-path e #f)))) + ;; for backward compatibility + (append omit (info 'compile-omit-files (lambda () '()))))))) (cond [(or (eq? 'all explicit) (memq 'same explicit)) 'all] [(findf (lambda (e) - (or (null? e) (not (path? (car e))) (absolute-path? (car e)))) + (and (not (regexp? e)) + (or (null? e) (not (path? (car e))) (absolute-path? (car e))))) explicit) => (lambda (bad) (error 'compile-omit-paths "bad entry value in info file: ~e" (apply build-path bad)))] - [else (append explicit - (map list (filter implicit-omit? (directory-list dir))) - accumulated)])) + [else + (define explicit-paths (filter pair? explicit)) + (define rxes (filter regexp? explicit)) + (omits + (append explicit-paths + (map list (filter (lambda (p) + (or (implicit-omit? p) + (for/or ([rx (in-list rxes)]) + (regexp-match? rx p)) + (for/or ([prefix+rx (in-list (omits-prefix+rxs accumulated))]) + (regexp-match? (cdr prefix+rx) + (build-path (car prefix+rx) p))))) + (directory-list dir))) + (omits-exploded-paths accumulated)) + (append (map (lambda (rx) (cons 'same rx)) rxes) + (omits-prefix+rxs accumulated)))])) (define (accumulate-omitted get-info/full rsubs root t omit-doc?) (define dir (apply build-path root)) (define implicit? (implicit-omit? omit-doc?)) (let loop ([rsubs rsubs]) (if (null? rsubs) - (compute-omitted dir '() implicit? get-info/full) + (compute-omitted dir (omits '() '()) implicit? get-info/full) (with-memo t rsubs - (let ([acc (loop (cdr rsubs))]) - (if (or (eq? 'all acc) (member (list (car rsubs)) acc)) - 'all - (compute-omitted (apply build-path dir (reverse rsubs)) - (for/list ([up acc] - #:when (equal? (car up) (car rsubs))) - ;; must have non-null cdr: see `member' check - (cdr up)) - implicit? - get-info/full))))))) + (let ([acc (loop (cdr rsubs))] + [rsub (car rsubs)]) + (cond + [(or (eq? 'all acc) + ;; if the nest subdirectory is omitted, it's 'all from + ;; the perspective of the subdirectory or any even more + ;; nested directory: + (member (list rsub) (omits-exploded-paths acc))) + 'all] + [else + ;; keep paths from enclosing directory that apply to + ;; nested directory, and strip off the nested directory element + (define acc-exploded-paths (for/list ([up (omits-exploded-paths acc)] + #:when (equal? (car up) (car rsubs))) + ;; must have non-null cdr: see `member' check + (cdr up))) + ;; extend prefix of each prefix+rx accumulated from the + ;; enclosing directory + (define acc-prefix+rxes (map (lambda (prefix+rx) + (define prefix (car prefix+rx)) + (cons (if (eq? prefix 'same) + rsub + (build-path prefix rsub)) + (cdr prefix+rx))) + (omits-prefix+rxs acc))) + (compute-omitted (apply build-path dir (reverse rsubs)) + (omits acc-exploded-paths acc-prefix+rxes) + implicit? + get-info/full)])))))) (define (omitted-paths* dir get-info/full root-dir) (unless (and (path-string? dir) (complete-path? dir) (directory-exists? dir)) @@ -97,13 +137,15 @@ #t)) (force roots)))] [r (and r (apply accumulate-omitted get-info/full r))]) - (unless r (error 'omitted-paths "given directory path is not in any collection root: ~e" dir)) (if (eq? 'all r) - r - (filter-map (lambda (x) (and (null? (cdr x)) (car x))) r)))) + r + ;; get paths for the immediate directory only; that is, drop + ;; any exploded path that has more than one element: + (filter-map (lambda (x) (and (null? (cdr x)) (car x))) + (omits-exploded-paths r))))) (define omitted-paths-memo (make-hash))