import: cran: Add support for git repositories.

* guix/import/cran.scm (vcs-file?): New procedure.
(download): Support downloading from git.
(fetch-description): Add a clause for the 'git repository type.
(files-match-pattern?): New procedure.
(tarball-files-match-pattern?): Implement in terms of FILES-MATCH-PATTERN?.
(directory-needs-fortran?, directory-needs-zlib?,
directory-needs-pkg-config?): New procedures.
(needs-fortran?, needs-zlib?, needs-pkg-config?): Rename these procedures...
(tarball-needs-fortran?, tarball-needs-zlib?, tarball-needs-pkg-config?):
...to this, and use them.
(file-hash): New procedure.
(description->package): Handle the 'git repository type.
* guix/import/utils.scm (package->definition): Handle package expression
inside of a let.
* guix/scripts/import.scm (guix-import): Handle let expressions.
* doc/guix.texi (Invoking guix import): Document it.
This commit is contained in:
Ricardo Wurmus 2019-08-28 00:38:31 +02:00
parent ce82e8bf5b
commit ad553ec4b1
No known key found for this signature in database
GPG Key ID: 197A5888235FACAC
4 changed files with 197 additions and 72 deletions

View File

@ -8638,6 +8638,14 @@ R package:
guix import cran --archive=bioconductor GenomicRanges guix import cran --archive=bioconductor GenomicRanges
@end example @end example
Finally, you can also import R packages that have not yet been published on
CRAN or Bioconductor as long as they are in a git repository. Use
@code{--archive=git} followed by the URL of the git repository:
@example
guix import cran --archive=git https://github.com/immunogenomics/harmony
@end example
@item texlive @item texlive
@cindex TeX Live @cindex TeX Live
@cindex CTAN @cindex CTAN

View File

@ -24,6 +24,7 @@
#:use-module ((ice-9 rdelim) #:select (read-string read-line)) #:use-module ((ice-9 rdelim) #:select (read-string read-line))
#:use-module (srfi srfi-1) #:use-module (srfi srfi-1)
#:use-module (srfi srfi-2) #:use-module (srfi srfi-2)
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-26) #:use-module (srfi srfi-26)
#:use-module (srfi srfi-34) #:use-module (srfi srfi-34)
#:use-module (ice-9 receive) #:use-module (ice-9 receive)
@ -32,11 +33,13 @@
#:use-module (guix http-client) #:use-module (guix http-client)
#:use-module (gcrypt hash) #:use-module (gcrypt hash)
#:use-module (guix store) #:use-module (guix store)
#:use-module ((guix serialization) #:select (write-file))
#:use-module (guix base32) #:use-module (guix base32)
#:use-module ((guix download) #:select (download-to-store)) #:use-module ((guix download) #:select (download-to-store))
#:use-module (guix import utils) #:use-module (guix import utils)
#:use-module ((guix build utils) #:select (find-files)) #:use-module ((guix build utils) #:select (find-files))
#:use-module (guix utils) #:use-module (guix utils)
#:use-module (guix git)
#:use-module ((guix build-system r) #:select (cran-uri bioconductor-uri)) #:use-module ((guix build-system r) #:select (cran-uri bioconductor-uri))
#:use-module (guix upstream) #:use-module (guix upstream)
#:use-module (guix packages) #:use-module (guix packages)
@ -166,11 +169,25 @@ bioconductor package NAME, or #F if the package is unknown."
(bioconductor-packages-list type)) (bioconductor-packages-list type))
(cut assoc-ref <> "Version"))) (cut assoc-ref <> "Version")))
;; XXX taken from (guix scripts hash)
(define (vcs-file? file stat)
(case (stat:type stat)
((directory)
(member (basename file) '(".bzr" ".git" ".hg" ".svn" "CVS")))
((regular)
;; Git sub-modules have a '.git' file that is a regular text file.
(string=? (basename file) ".git"))
(else
#f)))
;; Little helper to download URLs only once. ;; Little helper to download URLs only once.
(define download (define download
(memoize (memoize
(lambda (url) (lambda* (url #:optional git)
(with-store store (download-to-store store url))))) (with-store store
(if git
(latest-repository-commit store url)
(download-to-store store url))))))
(define (fetch-description repository name) (define (fetch-description repository name)
"Return an alist of the contents of the DESCRIPTION file for the R package "Return an alist of the contents of the DESCRIPTION file for the R package
@ -211,7 +228,18 @@ from ~s: ~a (~s)~%"
(string-append dir "/DESCRIPTION") read-string)) (string-append dir "/DESCRIPTION") read-string))
(lambda (meta) (lambda (meta)
(if (boolean? type) meta (if (boolean? type) meta
(cons `(bioconductor-type . ,type) meta)))))))))))) (cons `(bioconductor-type . ,type) meta))))))))))
((git)
;; Download the git repository at "NAME"
(call-with-values
(lambda () (download name #t))
(lambda (dir commit)
(and=> (description->alist (with-input-from-file
(string-append dir "/DESCRIPTION") read-string))
(lambda (meta)
(cons* `(git . ,name)
`(git-commit . ,commit)
meta))))))))
(define (listify meta field) (define (listify meta field)
"Look up FIELD in the alist META. If FIELD contains a comma-separated "Look up FIELD in the alist META. If FIELD contains a comma-separated
@ -256,7 +284,7 @@ empty list when the FIELD cannot be found."
(define cran-guix-name (cut guix-name "r-" <>)) (define cran-guix-name (cut guix-name "r-" <>))
(define (needs-fortran? tarball) (define (tarball-needs-fortran? tarball)
"Check if the TARBALL contains Fortran source files." "Check if the TARBALL contains Fortran source files."
(define (check pattern) (define (check pattern)
(parameterize ((current-error-port (%make-void-port "rw+")) (parameterize ((current-error-port (%make-void-port "rw+"))
@ -266,69 +294,127 @@ empty list when the FIELD cannot be found."
(check "*.f95") (check "*.f95")
(check "*.f"))) (check "*.f")))
(define (directory-needs-fortran? dir)
"Check if the directory DIR contains Fortran source files."
(match (find-files dir "\\.f(90|95)?")
(() #f)
(_ #t)))
(define (needs-fortran? thing tarball?)
"Check if the THING contains Fortran source files."
(if tarball?
(tarball-needs-fortran? thing)
(directory-needs-fortran? thing)))
(define (files-match-pattern? directory regexp . file-patterns)
"Return #T if any of the files matching FILE-PATTERNS in the DIRECTORY match
the given REGEXP."
(let ((pattern (make-regexp regexp)))
(any (lambda (file)
(call-with-input-file file
(lambda (port)
(let loop ()
(let ((line (read-line port)))
(cond
((eof-object? line) #f)
((regexp-exec pattern line) #t)
(else (loop))))))))
(apply find-files directory file-patterns))))
(define (tarball-files-match-pattern? tarball regexp . file-patterns) (define (tarball-files-match-pattern? tarball regexp . file-patterns)
"Return #T if any of the files represented by FILE-PATTERNS in the TARBALL "Return #T if any of the files represented by FILE-PATTERNS in the TARBALL
match the given REGEXP." match the given REGEXP."
(call-with-temporary-directory (call-with-temporary-directory
(lambda (dir) (lambda (dir)
(let ((pattern (make-regexp regexp))) (parameterize ((current-error-port (%make-void-port "rw+")))
(parameterize ((current-error-port (%make-void-port "rw+"))) (apply system* "tar"
(apply system* "tar" "xf" tarball "-C" dir
"xf" tarball "-C" dir `("--wildcards" ,@file-patterns)))
`("--wildcards" ,@file-patterns))) (files-match-pattern? dir regexp))))
(any (lambda (file)
(call-with-input-file file
(lambda (port)
(let loop ()
(let ((line (read-line port)))
(cond
((eof-object? line) #f)
((regexp-exec pattern line) #t)
(else (loop))))))))
(find-files dir))))))
(define (needs-zlib? tarball) (define (directory-needs-zlib? dir)
"Return #T if any of the Makevars files in the src directory DIR contain a
zlib linker flag."
(files-match-pattern? dir "-lz" "(Makevars.*|configure.*)"))
(define (tarball-needs-zlib? tarball)
"Return #T if any of the Makevars files in the src directory of the TARBALL "Return #T if any of the Makevars files in the src directory of the TARBALL
contain a zlib linker flag." contain a zlib linker flag."
(tarball-files-match-pattern? (tarball-files-match-pattern?
tarball "-lz" tarball "-lz"
"*/src/Makevars*" "*/src/configure*" "*/configure*")) "*/src/Makevars*" "*/src/configure*" "*/configure*"))
(define (needs-pkg-config? tarball) (define (needs-zlib? thing tarball?)
"Check if the THING contains files indicating a dependency on zlib."
(if tarball?
(tarball-needs-zlib? thing)
(directory-needs-zlib? thing)))
(define (directory-needs-pkg-config? dir)
"Return #T if any of the Makevars files in the src directory DIR reference
the pkg-config tool."
(files-match-pattern? dir "pkg-config"
"(Makevars.*|configure.*)"))
(define (tarball-needs-pkg-config? tarball)
"Return #T if any of the Makevars files in the src directory of the TARBALL "Return #T if any of the Makevars files in the src directory of the TARBALL
reference the pkg-config tool." reference the pkg-config tool."
(tarball-files-match-pattern? (tarball-files-match-pattern?
tarball "pkg-config" tarball "pkg-config"
"*/src/Makevars*" "*/src/configure*" "*/configure*")) "*/src/Makevars*" "*/src/configure*" "*/configure*"))
(define (needs-pkg-config? thing tarball?)
"Check if the THING contains files indicating a dependency on pkg-config."
(if tarball?
(tarball-needs-pkg-config? thing)
(directory-needs-pkg-config? thing)))
;; XXX adapted from (guix scripts hash)
(define (file-hash file select? recursive?)
;; Compute the hash of FILE.
(if recursive?
(let-values (((port get-hash) (open-sha256-port)))
(write-file file port #:select? select?)
(force-output port)
(get-hash))
(call-with-input-file file port-sha256)))
(define (description->package repository meta) (define (description->package repository meta)
"Return the `package' s-expression for an R package published on REPOSITORY "Return the `package' s-expression for an R package published on REPOSITORY
from the alist META, which was derived from the R package's DESCRIPTION file." from the alist META, which was derived from the R package's DESCRIPTION file."
(let* ((base-url (case repository (let* ((base-url (case repository
((cran) %cran-url) ((cran) %cran-url)
((bioconductor) %bioconductor-url))) ((bioconductor) %bioconductor-url)
((git) #f)))
(uri-helper (case repository (uri-helper (case repository
((cran) cran-uri) ((cran) cran-uri)
((bioconductor) bioconductor-uri))) ((bioconductor) bioconductor-uri)
((git) #f)))
(name (assoc-ref meta "Package")) (name (assoc-ref meta "Package"))
(synopsis (assoc-ref meta "Title")) (synopsis (assoc-ref meta "Title"))
(version (assoc-ref meta "Version")) (version (assoc-ref meta "Version"))
(license (string->license (assoc-ref meta "License"))) (license (string->license (assoc-ref meta "License")))
;; Some packages have multiple home pages. Some have none. ;; Some packages have multiple home pages. Some have none.
(home-page (match (listify meta "URL") (home-page (case repository
((url rest ...) url) ((git) (assoc-ref meta 'git))
(_ (string-append base-url name)))) (else (match (listify meta "URL")
(source-url (match (apply uri-helper name version ((url rest ...) url)
(case repository (_ (string-append base-url name))))))
((bioconductor) (source-url (case repository
(list (assoc-ref meta 'bioconductor-type))) ((git) (assoc-ref meta 'git))
(else '()))) (else
((url rest ...) url) (match (apply uri-helper name version
((? string? url) url) (case repository
(_ #f))) ((bioconductor)
(tarball (download source-url)) (list (assoc-ref meta 'bioconductor-type)))
(else '())))
((url rest ...) url)
((? string? url) url)
(_ #f)))))
(git? (assoc-ref meta 'git))
(source (download source-url git?))
(sysdepends (append (sysdepends (append
(if (needs-zlib? tarball) '("zlib") '()) (if (needs-zlib? source (not git?)) '("zlib") '())
(filter (lambda (name) (filter (lambda (name)
(not (member name invalid-packages))) (not (member name invalid-packages)))
(map string-downcase (listify meta "SystemRequirements"))))) (map string-downcase (listify meta "SystemRequirements")))))
@ -339,41 +425,67 @@ from the alist META, which was derived from the R package's DESCRIPTION file."
(listify meta "Imports") (listify meta "Imports")
(listify meta "LinkingTo") (listify meta "LinkingTo")
(delete "R" (delete "R"
(listify meta "Depends")))))) (listify meta "Depends")))))
(package
`(package
(name ,(cran-guix-name name))
(version ,(case repository
((git)
`(git-version ,version revision commit))
(else version)))
(source (origin
(method ,(if git?
'git-fetch
'url-fetch))
(uri ,(case repository
((git)
`(git-reference
(url ,(assoc-ref meta 'git))
(commit commit)))
(else
`(,(procedure-name uri-helper) ,name version
,@(or (and=> (assoc-ref meta 'bioconductor-type)
(lambda (type)
(list (list 'quote type))))
'())))))
,@(if git?
'((file-name (git-file-name name version)))
'())
(sha256
(base32
,(bytevector->nix-base32-string
(case repository
((git)
(file-hash source (negate vcs-file?) #t))
(else (file-sha256 source))))))))
,@(if (not (and git?
(equal? (string-append "r-" name)
(cran-guix-name name))))
`((properties ,`(,'quasiquote ((,'upstream-name . ,name)))))
'())
(build-system r-build-system)
,@(maybe-inputs sysdepends)
,@(maybe-inputs (map cran-guix-name propagate) 'propagated-inputs)
,@(maybe-inputs
`(,@(if (needs-fortran? source (not git?))
'("gfortran") '())
,@(if (needs-pkg-config? source (not git?))
'("pkg-config") '()))
'native-inputs)
(home-page ,(if (string-null? home-page)
(string-append base-url name)
home-page))
(synopsis ,synopsis)
(description ,(beautify-description (or (assoc-ref meta "Description")
"")))
(license ,license))))
(values (values
`(package (case repository
(name ,(cran-guix-name name)) ((git)
(version ,version) `(let ((commit ,(assoc-ref meta 'git-commit))
(source (origin (revision "1"))
(method url-fetch) ,package))
(uri (,(procedure-name uri-helper) ,name version (else package))
,@(or (and=> (assoc-ref meta 'bioconductor-type)
(lambda (type)
(list (list 'quote type))))
'())))
(sha256
(base32
,(bytevector->nix-base32-string (file-sha256 tarball))))))
,@(if (not (equal? (string-append "r-" name)
(cran-guix-name name)))
`((properties ,`(,'quasiquote ((,'upstream-name . ,name)))))
'())
(build-system r-build-system)
,@(maybe-inputs sysdepends)
,@(maybe-inputs (map cran-guix-name propagate) 'propagated-inputs)
,@(maybe-inputs
`(,@(if (needs-fortran? tarball)
'("gfortran") '())
,@(if (needs-pkg-config? tarball)
'("pkg-config") '()))
'native-inputs)
(home-page ,(if (string-null? home-page)
(string-append base-url name)
home-page))
(synopsis ,synopsis)
(description ,(beautify-description (or (assoc-ref meta "Description")
"")))
(license ,license))
propagate))) propagate)))
(define cran->guix-package (define cran->guix-package

View File

@ -2,7 +2,7 @@
;;; Copyright © 2012, 2013, 2018, 2019 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2012, 2013, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2016 Jelle Licht <jlicht@fsfe.org> ;;; Copyright © 2016 Jelle Licht <jlicht@fsfe.org>
;;; Copyright © 2016 David Craven <david@craven.ch> ;;; Copyright © 2016 David Craven <david@craven.ch>
;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net> ;;; Copyright © 2017, 2019 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com> ;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
;;; Copyright © 2019 Robert Vollmert <rob@vllmrt.net> ;;; Copyright © 2019 Robert Vollmert <rob@vllmrt.net>
;;; ;;;
@ -251,6 +251,9 @@ package definition."
(define (package->definition guix-package) (define (package->definition guix-package)
(match guix-package (match guix-package
(('package ('name (? string? name)) _ ...) (('package ('name (? string? name)) _ ...)
`(define-public ,(string->symbol name)
,guix-package))
(('let anything ('package ('name (? string? name)) _ ...))
`(define-public ,(string->symbol name) `(define-public ,(string->symbol name)
,guix-package)))) ,guix-package))))

View File

@ -2,6 +2,7 @@
;;; Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2014 David Thompson <davet@gnu.org> ;;; Copyright © 2014 David Thompson <davet@gnu.org>
;;; Copyright © 2018 Kyle Meyer <kyle@kyleam.com> ;;; Copyright © 2018 Kyle Meyer <kyle@kyleam.com>
;;; Copyright © 2019 Ricardo Wurmus <rekado@elephly.net>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -113,7 +114,8 @@ Run IMPORTER with ARGS.\n"))
(pretty-print expr (newline-rewriting-port (pretty-print expr (newline-rewriting-port
(current-output-port)))))) (current-output-port))))))
(match (apply (resolve-importer importer) args) (match (apply (resolve-importer importer) args)
((and expr ('package _ ...)) ((and expr (or ('package _ ...)
('let _ ...)))
(print expr)) (print expr))
((? list? expressions) ((? list? expressions)
(for-each (lambda (expr) (for-each (lambda (expr)