From b47c1857b5b5df31c65585de80a1fc6b501b44e1 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Sun, 14 Apr 2013 15:43:22 -0600 Subject: [PATCH] raco pkg: support platform-specific package dependencies A platform-specific dependency is useful for triggering installation of a platform-specific library only on the platform where its needed. --- collects/pkg/lib.rkt | 60 ++++++++++++++-- collects/pkg/scribblings/pkg.scrbl | 60 ++++++++++++---- .../tests/pkg/test-pkgs/pkg-w-one/info.rkt | 2 +- collects/tests/pkg/test.rkt | 1 + collects/tests/pkg/tests-platform.rkt | 71 +++++++++++++++++++ collects/tests/pkg/util.rkt | 1 + 6 files changed, 173 insertions(+), 22 deletions(-) create mode 100644 collects/tests/pkg/tests-platform.rkt diff --git a/collects/pkg/lib.rkt b/collects/pkg/lib.rkt index 41484485b3..f73a016565 100644 --- a/collects/pkg/lib.rkt +++ b/collects/pkg/lib.rkt @@ -172,7 +172,28 @@ (and (list? dep) (= 2 (length dep)) (package-source? (car dep)) - (version? (cadr dep)))))) + (version? (cadr dep))) + (and (list? dep) + ((length dep) . >= . 1) + (odd? (length dep)) + (package-source? (car dep)) + (let loop ([saw (hash)] [dep (cdr dep)]) + (cond + [(null? dep) #t] + [(hash-ref saw (car dep) #f) #f] + [else + (define kw (car dep)) + (define val (cadr dep)) + (and + (cond + [(eq? kw '#:version) (version? val)] + [(eq? kw '#:platform) + (or (string? val) + (regexp? val) + (memq val '(unix windows macosx)))] + [else #f]) + (loop (hash-set saw (car dep) #t) + (cddr dep)))])))))) (pkg-error (~a "invalid `deps' specification\n" " specification: ~e") deps))) @@ -187,9 +208,30 @@ (car dep))) (define (dependency->version dep) - (if (string? dep) - #f - (cadr dep))) + (cond + [(string? dep) #f] + [(keyword? (cadr dep)) + (dependency-lookup '#:version dep)] + [else (cadr dep)])) + +(define (dependency-lookup kw dep) + (cond + [(string? dep) #f] + [(keyword? (cadr dep)) + (define p (member kw (cdr dep))) + (and p (cadr p))] + [else #f])) + +(define (dependency-this-platform? dep) + (define p (dependency-lookup '#:platform dep)) + (if p + (if (symbol? p) + (eq? p (system-type)) + (let ([s (path->string (system-library-subpath #f))]) + (if (regexp? p) + (regexp-match? p s) + (equal? p s)))) + #t)) (define (with-package-lock* read-only? t) (define d (pkg-dir)) @@ -892,6 +934,7 @@ (filter-not (λ (dep) (define name (dependency->name dep)) (or (equal? name "racket") + (not (dependency-this-platform? dep)) (hash-ref simultaneous-installs name #f) (hash-has-key? db name))) deps))) @@ -948,8 +991,11 @@ (filter-map (λ (dep) (define name (dependency->name dep)) (define req-vers (dependency->version dep)) + (define this-platform? (dependency-this-platform? dep)) (define-values (inst-vers* can-try-update?) (cond + [(not this-platform?) + (values #f #f)] [(not req-vers) (values #f #f)] [(equal? name "racket") @@ -964,7 +1010,8 @@ (values (get-metadata metadata-ns (package-directory name) 'version (lambda () "0.0")) #t)])) - (define inst-vers (if (and req-vers + (define inst-vers (if (and this-platform? + req-vers (not (and (string? inst-vers*) (valid-version? inst-vers*)))) (begin @@ -974,7 +1021,8 @@ inst-vers*) "0.0") inst-vers*)) - (and req-vers + (and this-platform? + req-vers ((version->integer req-vers) . > . (version->integer inst-vers)) diff --git a/collects/pkg/scribblings/pkg.scrbl b/collects/pkg/scribblings/pkg.scrbl index 291a0874cb..aa7734e303 100644 --- a/collects/pkg/scribblings/pkg.scrbl +++ b/collects/pkg/scribblings/pkg.scrbl @@ -632,7 +632,15 @@ Package metadata, including dependencies on other packages, is reported by an @filepath{info.rkt} module within the package. This module must be implemented in the @racketmodname[setup/infotab] language. -The following fields are used by the package manager: +For example, a basic @filepath{info.rkt} file might be + +@codeblock{ +#lang setup/infotab +(define version "1.0") +(define deps (list _package-source-string ...)) +} + +The following @filepath{info.rkt} fields are used by the package manager: @itemlist[ @@ -640,9 +648,42 @@ The following fields are used by the package manager: @tech{version} of a package is @racket["0.0"].} @item{@racketidfont{deps} --- a list of dependencies, where each - dependency is either a @tech{package source} strings or a list - containing a @tech{package source} string and a - @tech{version} string. + dependency has one of the following forms: + + @itemlist[ + + @item{A string for a @tech{package source}.} + + @item{A list of the form + @racketblock[(list _package-source-string + _keyword-and-spec ...)] + where each @racket[_keyword-and-spec] has a + distinct keyword in the form + @racketgrammar*[#:literals (quote) + [keyword-and-spec + (code:line '#:version version-string) + (code:line '#:platform platform-spec)] + [platform-spec string symbol regexp]] + + A @racket[_version-string] specifies a lower bound + on an acceptable @tech{version} of the needed package. + + A @racket[_platform-spec] indicates that the dependency + applies only for platforms with a matching result from + @racket[(system-type)] when @racket[_platforms-spec] is + a symbol or @racket[(path->string + (system-library-subpath #f))] when + @racket[_platform-spec] is a regular expression. For + example, platform-specific binaries can be placed into + their own packages, with one separate package and one + dependency for each supported platform.} + + @item{A list of the form + @racketblock[(list _package-source-string _version-string)] + which is deprecated and equivalent to + @racketblock[(list _package-source-string '#:version _version-string)]} + + ] Each elements of the @racketidfont{deps} list determines a dependency on the @tech{package} whose name is inferred from @@ -651,9 +692,6 @@ The following fields are used by the package manager: indicates where to get the package if needed to satisfy the dependency. - When provided, a @tech{version} string specifies a lower bound - on an acceptable @tech{version} of the package. - Use the package name @racket["racket"] to specify a dependency on the version of the Racket installation.} @@ -667,14 +705,6 @@ The following fields are used by the package manager: ] -For example, a basic @filepath{info.rkt} file might be - -@codeblock{ -#lang setup/infotab -(define version "1.0") -(define deps (list _package-source-string ...)) -} - @; ---------------------------------------- @section{@|Planet1| Compatibility} diff --git a/collects/tests/pkg/test-pkgs/pkg-w-one/info.rkt b/collects/tests/pkg/test-pkgs/pkg-w-one/info.rkt index 26320f8054..ff5320ffd5 100644 --- a/collects/tests/pkg/test-pkgs/pkg-w-one/info.rkt +++ b/collects/tests/pkg/test-pkgs/pkg-w-one/info.rkt @@ -1,4 +1,4 @@ #lang setup/infotab -(define deps '(("pkg-v" "2.0") +(define deps '(("pkg-v" #:version "2.0") ("racket" "5.3.1.10"))) diff --git a/collects/tests/pkg/test.rkt b/collects/tests/pkg/test.rkt index 0022064cb8..e02cab4f47 100644 --- a/collects/tests/pkg/test.rkt +++ b/collects/tests/pkg/test.rkt @@ -52,5 +52,6 @@ "update-deps" "update-auto" "versions" + "platform" "raco" "indexes") diff --git a/collects/tests/pkg/tests-platform.rkt b/collects/tests/pkg/tests-platform.rkt new file mode 100644 index 0000000000..eea6b31632 --- /dev/null +++ b/collects/tests/pkg/tests-platform.rkt @@ -0,0 +1,71 @@ +#lang racket/base +(require rackunit + racket/file + racket/format + pkg/util + (prefix-in db: pkg/pnr-db) + "shelly.rkt" + "util.rkt") + +(pkg-tests + (shelly-begin + (define pkgs-dir (make-temporary-file "~a-pkgs" 'directory)) + (define db (build-path pkgs-dir "pnr.sqlite")) + (define pkg-x-dir (build-path pkgs-dir "pkg-x")) + + (make-directory* pkg-x-dir) + (call-with-output-file* + (build-path pkg-x-dir "info.rkt") + (lambda (o) + (displayln "#lang setup/infotab" o) + (write `(define deps '(("pkg-x-windows" #:platform windows) + ("pkg-x-unix" #:platform unix) + ("pkg-x-macosx" #:platform macosx) + ("pkg-x-platform1" + #:platform + ,(path->string (system-library-subpath #f))) + ("pkg-x-platform2" #:platform #rx".") + ("pkg-x-platform-no" #:platform #rx"no such platform"))) + o))) + + (parameterize ([db:current-pkg-index-file db]) + (db:set-indexes! '("local")) + (db:set-pkgs! "local" + '("pkg-x" "pkg-x-windows" "pkg-x-unix" "pkg-x-macosx" + "pkg-x-platform1" "pkg-x-platform2"))) + + (define (create-package name) + (define pkg-name name) + (define dir (build-path pkgs-dir pkg-name)) + (make-directory* dir) + (define coll-dir (build-path dir name)) + (make-directory* coll-dir) + (call-with-output-file* + (build-path coll-dir "main.rkt") + (lambda (o) + (displayln "#lang racket/base" o))) + (parameterize ([db:current-pkg-index-file db]) + (db:set-pkg! pkg-name "local" "author@place" (path->string dir) "123456" ""))) + + (create-package "pkg-x") + (create-package "pkg-x-unix") + (create-package "pkg-x-windows") + (create-package "pkg-x-macosx") + (create-package "pkg-x-platform1") + (create-package "pkg-x-platform2") + + (with-fake-root + (shelly-begin + $ (~a "raco pkg config --set indexes file://" (path->string db)) + $ "racket -e '(require pkg-x)'" =exit> 1 + $ "raco pkg install --deps search-auto pkg-x" =exit> 0 + $ "racket -e '(require pkg-x)'" =exit> 0 + $ (~a "racket -e '(require pkg-x-" (system-type) ")'") =exit> 0 + $ "racket -e '(require pkg-x-platform1)'" =exit> 0 + $ "racket -e '(require pkg-x-platform2)'" =exit> 0 + $ "racket -e '(require pkg-x-platform-no)'" =exit> 1 + $ "raco pkg remove pkg-x")) + + (delete-directory/files pkgs-dir))) + + diff --git a/collects/tests/pkg/util.rkt b/collects/tests/pkg/util.rkt index ee00470185..eaf14c17cd 100644 --- a/collects/tests/pkg/util.rkt +++ b/collects/tests/pkg/util.rkt @@ -140,6 +140,7 @@ (file->string "test-pkgs/pkg-test2.zip.CHECKSUM") 'source "http://localhost:9999/pkg-test2.zip")) + (hash-set! *index-ht-2* "pkg-test2-snd" (hasheq 'checksum (file->string "test-pkgs/pkg-test2.zip.CHECKSUM")