cran.scm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ricardo Wurmus <rekado@elephly.net>
  3. ;;; Copyright © 2015, 2016, 2017, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
  4. ;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
  5. ;;; Copyright © 2020 Martin Becze <mjbecze@riseup.net>
  6. ;;;
  7. ;;; This file is part of GNU Guix.
  8. ;;;
  9. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  10. ;;; under the terms of the GNU General Public License as published by
  11. ;;; the Free Software Foundation; either version 3 of the License, or (at
  12. ;;; your option) any later version.
  13. ;;;
  14. ;;; GNU Guix is distributed in the hope that it will be useful, but
  15. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;;; GNU General Public License for more details.
  18. ;;;
  19. ;;; You should have received a copy of the GNU General Public License
  20. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  21. (define-module (guix import cran)
  22. #:use-module (ice-9 match)
  23. #:use-module (ice-9 regex)
  24. #:use-module (ice-9 popen)
  25. #:use-module ((ice-9 rdelim) #:select (read-string read-line))
  26. #:use-module (srfi srfi-1)
  27. #:use-module (srfi srfi-2)
  28. #:use-module (srfi srfi-11)
  29. #:use-module (srfi srfi-26)
  30. #:use-module (srfi srfi-34)
  31. #:use-module (srfi srfi-35)
  32. #:use-module (ice-9 receive)
  33. #:use-module (web uri)
  34. #:use-module (guix memoization)
  35. #:use-module (guix http-client)
  36. #:use-module (gcrypt hash)
  37. #:use-module (guix store)
  38. #:use-module ((guix serialization) #:select (write-file))
  39. #:use-module (guix base32)
  40. #:use-module ((guix download) #:select (download-to-store))
  41. #:use-module (guix import utils)
  42. #:use-module ((guix build utils)
  43. #:select (find-files
  44. delete-file-recursively
  45. with-directory-excursion))
  46. #:use-module (guix utils)
  47. #:use-module (guix git)
  48. #:use-module ((guix build-system r) #:select (cran-uri bioconductor-uri))
  49. #:use-module (guix ui)
  50. #:use-module (guix upstream)
  51. #:use-module (guix packages)
  52. #:use-module (gnu packages)
  53. #:export (%input-style
  54. cran->guix-package
  55. bioconductor->guix-package
  56. cran-recursive-import
  57. %cran-updater
  58. %bioconductor-updater
  59. %bioconductor-version
  60. cran-package?
  61. bioconductor-package?
  62. bioconductor-data-package?
  63. bioconductor-experiment-package?
  64. description->alist
  65. description->package))
  66. ;;; Commentary:
  67. ;;;
  68. ;;; Generate a package declaration template for the latest version of an R
  69. ;;; package on CRAN, using the DESCRIPTION file downloaded from
  70. ;;; cran.r-project.org.
  71. ;;;
  72. ;;; Code:
  73. (define %input-style
  74. (make-parameter 'variable)) ; or 'specification
  75. (define string->license
  76. (match-lambda
  77. ("AGPL-3" 'agpl3+)
  78. ("Artistic-2.0" 'artistic2.0)
  79. ("Apache License 2.0" 'asl2.0)
  80. ("BSD_2_clause" 'bsd-2)
  81. ("BSD_2_clause + file LICENSE" 'bsd-2)
  82. ("BSD_3_clause" 'bsd-3)
  83. ("BSD_3_clause + file LICENSE" 'bsd-3)
  84. ("GPL" '(list gpl2+ gpl3+))
  85. ("GPL (>= 2)" 'gpl2+)
  86. ("GPL (>= 3)" 'gpl3+)
  87. ("GPL-2" 'gpl2)
  88. ("GPL-3" 'gpl3)
  89. ("LGPL-2" 'lgpl2.0)
  90. ("LGPL-2.1" 'lgpl2.1)
  91. ("LGPL-3" 'lgpl3)
  92. ("LGPL (>= 2)" 'lgpl2.0+)
  93. ("LGPL (>= 2.1)" 'lgpl2.1+)
  94. ("LGPL (>= 3)" 'lgpl3+)
  95. ("MIT" 'expat)
  96. ("MIT + file LICENSE" 'expat)
  97. ((x) (string->license x))
  98. ((lst ...) `(list ,@(map string->license lst)))
  99. (_ #f)))
  100. (define (description->alist description)
  101. "Convert a DESCRIPTION string into an alist."
  102. (let ((lines (string-split description #\newline))
  103. (parse (lambda (line acc)
  104. (if (string-null? line) acc
  105. ;; Keys usually start with a capital letter and end with
  106. ;; ":". There are some exceptions, unfortunately (such
  107. ;; as "biocViews"). There are no blanks in a key.
  108. (if (string-match "^[A-Za-z][^ :]+:( |\n|$)" line)
  109. ;; New key/value pair
  110. (let* ((pos (string-index line #\:))
  111. (key (string-take line pos))
  112. (value (string-drop line (+ 1 pos))))
  113. (cons (cons key
  114. (string-trim-both value))
  115. acc))
  116. ;; This is a continuation of the previous pair
  117. (match-let ((((key . value) . rest) acc))
  118. (cons (cons key (string-join
  119. (list value
  120. (string-trim-both line))))
  121. rest)))))))
  122. (fold parse '() lines)))
  123. (define (format-inputs names)
  124. "Generate a sorted list of package inputs from a list of package NAMES."
  125. (map (lambda (name)
  126. (case (%input-style)
  127. ((specification)
  128. `(specification->package ,name))
  129. (else
  130. (string->symbol name))))
  131. (sort names string-ci<?)))
  132. (define* (maybe-inputs package-inputs #:optional (type 'inputs))
  133. "Given a list of PACKAGE-INPUTS, tries to generate the TYPE field of a
  134. package definition."
  135. (match package-inputs
  136. (()
  137. '())
  138. ((package-inputs ...)
  139. `((,type (list ,@(format-inputs package-inputs)))))))
  140. (define %cran-url "https://cran.r-project.org/web/packages/")
  141. (define %cran-canonical-url "https://cran.r-project.org/package=")
  142. (define %bioconductor-url "https://bioconductor.org/packages/")
  143. ;; The latest Bioconductor release is 3.13. Bioconductor packages should be
  144. ;; updated together.
  145. (define %bioconductor-version "3.13")
  146. (define* (bioconductor-packages-list-url #:optional type)
  147. (string-append "https://bioconductor.org/packages/"
  148. %bioconductor-version
  149. (match type
  150. ('annotation "/data/annotation")
  151. ('experiment "/data/experiment")
  152. (_ "/bioc"))
  153. "/src/contrib/PACKAGES"))
  154. (define* (bioconductor-packages-list #:optional type)
  155. "Return the latest version of package NAME for the current bioconductor
  156. release."
  157. (let ((url (string->uri (bioconductor-packages-list-url type))))
  158. (guard (c ((http-get-error? c)
  159. (format (current-error-port)
  160. "error: failed to retrieve list of packages from ~s: ~a (~s)~%"
  161. (uri->string (http-get-error-uri c))
  162. (http-get-error-code c)
  163. (http-get-error-reason c))
  164. #f))
  165. ;; Split the big list on empty lines, then turn each chunk into an
  166. ;; alist of attributes.
  167. (map (lambda (chunk)
  168. (description->alist (string-join chunk "\n")))
  169. (let* ((port (http-fetch/cached url))
  170. (lines (read-lines port)))
  171. (close-port port)
  172. (chunk-lines lines))))))
  173. (define* (latest-bioconductor-package-version name #:optional type)
  174. "Return the version string corresponding to the latest release of the
  175. bioconductor package NAME, or #F if the package is unknown."
  176. (and=> (find (lambda (meta)
  177. (string=? (assoc-ref meta "Package") name))
  178. (bioconductor-packages-list type))
  179. (cut assoc-ref <> "Version")))
  180. ;; XXX taken from (guix scripts hash)
  181. (define (vcs-file? file stat)
  182. (case (stat:type stat)
  183. ((directory)
  184. (member (basename file) '(".bzr" ".git" ".hg" ".svn" "CVS")))
  185. ((regular)
  186. ;; Git sub-modules have a '.git' file that is a regular text file.
  187. (string=? (basename file) ".git"))
  188. (else
  189. #f)))
  190. ;; Little helper to download URLs only once.
  191. (define download
  192. (memoize
  193. (lambda* (url #:key method)
  194. (with-store store
  195. (cond
  196. ((eq? method 'git)
  197. (latest-repository-commit store url))
  198. ((eq? method 'hg)
  199. (call-with-temporary-directory
  200. (lambda (dir)
  201. (unless (zero? (system* "hg" "clone" url dir))
  202. (leave (G_ "~A: hg download failed~%") url))
  203. (with-directory-excursion dir
  204. (let* ((port (open-pipe* OPEN_READ "hg" "id" "--id"))
  205. (changeset (string-trim-right (read-string port))))
  206. (close-pipe port)
  207. (for-each delete-file-recursively
  208. (find-files dir "^\\.hg$" #:directories? #t))
  209. (let ((store-directory
  210. (add-to-store store (basename url) #t "sha256" dir)))
  211. (values store-directory changeset)))))))
  212. (else (download-to-store store url)))))))
  213. (define (fetch-description repository name)
  214. "Return an alist of the contents of the DESCRIPTION file for the R package
  215. NAME in the given REPOSITORY, or #f in case of failure. NAME is
  216. case-sensitive."
  217. (case repository
  218. ((cran)
  219. (let ((url (string-append %cran-url name "/DESCRIPTION")))
  220. (guard (c ((http-get-error? c)
  221. (format (current-error-port)
  222. "error: failed to retrieve package information \
  223. from ~s: ~a (~s)~%"
  224. (uri->string (http-get-error-uri c))
  225. (http-get-error-code c)
  226. (http-get-error-reason c))
  227. #f))
  228. (let* ((port (http-fetch url))
  229. (result (description->alist (read-string port))))
  230. (close-port port)
  231. result))))
  232. ((bioconductor)
  233. ;; Currently, the bioconductor project does not offer a way to access a
  234. ;; package's DESCRIPTION file over HTTP, so we determine the version,
  235. ;; download the source tarball, and then extract the DESCRIPTION file.
  236. (and-let* ((type (or
  237. (and (latest-bioconductor-package-version name) #t)
  238. (and (latest-bioconductor-package-version name 'annotation) 'annotation)
  239. (and (latest-bioconductor-package-version name 'experiment) 'experiment)))
  240. (version (latest-bioconductor-package-version name type))
  241. (url (car (bioconductor-uri name version type)))
  242. (tarball (download url)))
  243. (call-with-temporary-directory
  244. (lambda (dir)
  245. (parameterize ((current-error-port (%make-void-port "rw+"))
  246. (current-output-port (%make-void-port "rw+")))
  247. (and (zero? (system* "tar" "--wildcards" "-x"
  248. "--strip-components=1"
  249. "-C" dir
  250. "-f" tarball "*/DESCRIPTION"))
  251. (and=> (description->alist (with-input-from-file
  252. (string-append dir "/DESCRIPTION") read-string))
  253. (lambda (meta)
  254. (if (boolean? type) meta
  255. (cons `(bioconductor-type . ,type) meta))))))))))
  256. ((git)
  257. (and (string-prefix? "http" name)
  258. ;; Download the git repository at "NAME"
  259. (call-with-values
  260. (lambda () (download name #:method 'git))
  261. (lambda (dir commit)
  262. (and=> (description->alist (with-input-from-file
  263. (string-append dir "/DESCRIPTION") read-string))
  264. (lambda (meta)
  265. (cons* `(git . ,name)
  266. `(git-commit . ,commit)
  267. meta)))))))
  268. ((hg)
  269. (and (string-prefix? "http" name)
  270. ;; Download the mercurial repository at "NAME"
  271. (call-with-values
  272. (lambda () (download name #:method 'hg))
  273. (lambda (dir changeset)
  274. (and=> (description->alist (with-input-from-file
  275. (string-append dir "/DESCRIPTION") read-string))
  276. (lambda (meta)
  277. (cons* `(hg . ,name)
  278. `(hg-changeset . ,changeset)
  279. meta)))))))))
  280. (define (listify meta field)
  281. "Look up FIELD in the alist META. If FIELD contains a comma-separated
  282. string, turn it into a list and strip off parenthetic expressions. Return the
  283. empty list when the FIELD cannot be found."
  284. (let ((value (assoc-ref meta field)))
  285. (if (not value)
  286. '()
  287. ;; Strip off parentheses
  288. (let ((items (string-split (regexp-substitute/global
  289. #f "( *\\([^\\)]+\\)) *"
  290. value 'pre 'post)
  291. #\,)))
  292. (remove (lambda (item)
  293. (or (string-null? item)
  294. ;; When there is whitespace inside of items it is
  295. ;; probably because this was not an actual list to
  296. ;; begin with.
  297. (string-any char-set:whitespace item)))
  298. (map string-trim-both items))))))
  299. ;; Trick Guile 3 so that it keeps the 'listify' binding accessible *and*
  300. ;; private even though this module is declarative.
  301. (set! listify listify)
  302. (define default-r-packages
  303. (list "base"
  304. "compiler"
  305. "datasets"
  306. "grDevices"
  307. "graphics"
  308. "grid"
  309. "methods"
  310. "parallel"
  311. "splines"
  312. "stats"
  313. "stats4"
  314. "tcltk"
  315. "tools"
  316. "translations"
  317. "utils"))
  318. ;; The field for system dependencies is often abused to specify non-package
  319. ;; dependencies (such as c++11). This list is used to ignore them.
  320. (define invalid-packages
  321. (list "c++11"
  322. "c++14"
  323. "linux"
  324. "getopt::long"
  325. "xquartz"))
  326. (define cran-guix-name (cut guix-name "r-" <>))
  327. (define (tarball-needs-fortran? tarball)
  328. "Check if the TARBALL contains Fortran source files."
  329. (define (check pattern)
  330. (parameterize ((current-error-port (%make-void-port "rw+"))
  331. (current-output-port (%make-void-port "rw+")))
  332. (zero? (system* "tar" "--wildcards" "--list" pattern "-f" tarball))))
  333. (or (check "*.f90")
  334. (check "*.f95")
  335. (check "*.f")))
  336. (define (directory-needs-fortran? dir)
  337. "Check if the directory DIR contains Fortran source files."
  338. (match (find-files dir "\\.f(90|95)$")
  339. (() #f)
  340. (_ #t)))
  341. (define (needs-fortran? thing tarball?)
  342. "Check if the THING contains Fortran source files."
  343. (if tarball?
  344. (tarball-needs-fortran? thing)
  345. (directory-needs-fortran? thing)))
  346. (define (files-match-pattern? directory regexp . file-patterns)
  347. "Return #T if any of the files matching FILE-PATTERNS in the DIRECTORY match
  348. the given REGEXP."
  349. (let ((pattern (make-regexp regexp)))
  350. (any (lambda (file)
  351. (call-with-input-file file
  352. (lambda (port)
  353. (let loop ()
  354. (let ((line (read-line port)))
  355. (cond
  356. ((eof-object? line) #f)
  357. ((regexp-exec pattern line) #t)
  358. (else (loop))))))))
  359. (apply find-files directory file-patterns))))
  360. (define (tarball-files-match-pattern? tarball regexp . file-patterns)
  361. "Return #T if any of the files represented by FILE-PATTERNS in the TARBALL
  362. match the given REGEXP."
  363. (call-with-temporary-directory
  364. (lambda (dir)
  365. (parameterize ((current-error-port (%make-void-port "rw+")))
  366. (apply system* "tar"
  367. "xf" tarball "-C" dir
  368. `("--wildcards" ,@file-patterns)))
  369. (files-match-pattern? dir regexp))))
  370. (define (directory-needs-zlib? dir)
  371. "Return #T if any of the Makevars files in the src directory DIR contain a
  372. zlib linker flag."
  373. (files-match-pattern? dir "-lz" "(Makevars.*|configure.*)"))
  374. (define (tarball-needs-zlib? tarball)
  375. "Return #T if any of the Makevars files in the src directory of the TARBALL
  376. contain a zlib linker flag."
  377. (tarball-files-match-pattern?
  378. tarball "-lz"
  379. "*/src/Makevars*" "*/src/configure*" "*/configure*"))
  380. (define (needs-zlib? thing tarball?)
  381. "Check if the THING contains files indicating a dependency on zlib."
  382. (if tarball?
  383. (tarball-needs-zlib? thing)
  384. (directory-needs-zlib? thing)))
  385. (define (directory-needs-pkg-config? dir)
  386. "Return #T if any of the Makevars files in the src directory DIR reference
  387. the pkg-config tool."
  388. (files-match-pattern? dir "pkg-config"
  389. "(Makevars.*|configure.*)"))
  390. (define (tarball-needs-pkg-config? tarball)
  391. "Return #T if any of the Makevars files in the src directory of the TARBALL
  392. reference the pkg-config tool."
  393. (tarball-files-match-pattern?
  394. tarball "pkg-config"
  395. "*/src/Makevars*" "*/src/configure*" "*/configure*"))
  396. (define (needs-pkg-config? thing tarball?)
  397. "Check if the THING contains files indicating a dependency on pkg-config."
  398. (if tarball?
  399. (tarball-needs-pkg-config? thing)
  400. (directory-needs-pkg-config? thing)))
  401. (define (needs-knitr? meta)
  402. (member "knitr" (listify meta "VignetteBuilder")))
  403. ;; XXX adapted from (guix scripts hash)
  404. (define (file-hash file select? recursive?)
  405. ;; Compute the hash of FILE.
  406. (if recursive?
  407. (let-values (((port get-hash) (open-sha256-port)))
  408. (write-file file port #:select? select?)
  409. (force-output port)
  410. (get-hash))
  411. (call-with-input-file file port-sha256)))
  412. (define (description->package repository meta)
  413. "Return the `package' s-expression for an R package published on REPOSITORY
  414. from the alist META, which was derived from the R package's DESCRIPTION file."
  415. (let* ((base-url (case repository
  416. ((cran) %cran-url)
  417. ((bioconductor) %bioconductor-url)
  418. ((git) #f)
  419. ((hg) #f)))
  420. (canonical-url-base (case repository
  421. ((cran) %cran-canonical-url)
  422. ((bioconductor) %bioconductor-url)
  423. ((git) #f)))
  424. (uri-helper (case repository
  425. ((cran) cran-uri)
  426. ((bioconductor) bioconductor-uri)
  427. ((git) #f)
  428. ((hg) #f)))
  429. (name (assoc-ref meta "Package"))
  430. (synopsis (assoc-ref meta "Title"))
  431. (version (assoc-ref meta "Version"))
  432. (license (string->license (assoc-ref meta "License")))
  433. ;; Some packages have multiple home pages. Some have none.
  434. (home-page (case repository
  435. ((git) (assoc-ref meta 'git))
  436. ((hg) (assoc-ref meta 'hg))
  437. (else (match (listify meta "URL")
  438. ((url rest ...) url)
  439. (_ (string-append canonical-url-base name))))))
  440. (source-url (case repository
  441. ((git) (assoc-ref meta 'git))
  442. ((hg) (assoc-ref meta 'hg))
  443. (else
  444. (match (apply uri-helper name version
  445. (case repository
  446. ((bioconductor)
  447. (list (assoc-ref meta 'bioconductor-type)))
  448. (else '())))
  449. ((url rest ...) url)
  450. ((? string? url) url)
  451. (_ #f)))))
  452. (git? (assoc-ref meta 'git))
  453. (hg? (assoc-ref meta 'hg))
  454. (source (download source-url #:method (cond
  455. (git? 'git)
  456. (hg? 'hg)
  457. (else #f))))
  458. (sysdepends (append
  459. (if (needs-zlib? source (not (or git? hg?))) '("zlib") '())
  460. (filter (lambda (name)
  461. (not (member name invalid-packages)))
  462. (map string-downcase (listify meta "SystemRequirements")))))
  463. (propagate (filter (lambda (name)
  464. (not (member name (append default-r-packages
  465. invalid-packages))))
  466. (lset-union equal?
  467. (listify meta "Imports")
  468. (listify meta "LinkingTo")
  469. (delete "R"
  470. (listify meta "Depends")))))
  471. (package
  472. `(package
  473. (name ,(cran-guix-name name))
  474. (version ,(case repository
  475. ((git)
  476. `(git-version ,version revision commit))
  477. ((hg)
  478. `(string-append ,version "-" revision "." changeset))
  479. (else version)))
  480. (source (origin
  481. (method ,(cond
  482. (git? 'git-fetch)
  483. (hg? 'hg-fetch)
  484. (else 'url-fetch)))
  485. (uri ,(case repository
  486. ((git)
  487. `(git-reference
  488. (url ,(assoc-ref meta 'git))
  489. (commit commit)))
  490. ((hg)
  491. `(hg-reference
  492. (url ,(assoc-ref meta 'hg))
  493. (changeset changeset)))
  494. (else
  495. `(,(procedure-name uri-helper) ,name version
  496. ,@(or (and=> (assoc-ref meta 'bioconductor-type)
  497. (lambda (type)
  498. (list (list 'quote type))))
  499. '())))))
  500. ,@(cond
  501. (git?
  502. '((file-name (git-file-name name version))))
  503. (hg?
  504. '((file-name (string-append name "-" version "-checkout"))))
  505. (else '()))
  506. (sha256
  507. (base32
  508. ,(bytevector->nix-base32-string
  509. (case repository
  510. ((git)
  511. (file-hash source (negate vcs-file?) #t))
  512. ((hg)
  513. (file-hash source (negate vcs-file?) #t))
  514. (else (file-sha256 source))))))))
  515. ,@(if (not (and git? hg?
  516. (equal? (string-append "r-" name)
  517. (cran-guix-name name))))
  518. `((properties ,`(,'quasiquote ((,'upstream-name . ,name)))))
  519. '())
  520. (build-system r-build-system)
  521. ,@(maybe-inputs sysdepends)
  522. ,@(maybe-inputs (map cran-guix-name propagate) 'propagated-inputs)
  523. ,@(maybe-inputs
  524. `(,@(if (needs-fortran? source (not (or git? hg?)))
  525. '("gfortran") '())
  526. ,@(if (needs-pkg-config? source (not (or git? hg?)))
  527. '("pkg-config") '())
  528. ,@(if (needs-knitr? meta)
  529. '("r-knitr") '()))
  530. 'native-inputs)
  531. (home-page ,(if (string-null? home-page)
  532. (string-append base-url name)
  533. home-page))
  534. (synopsis ,synopsis)
  535. (description ,(beautify-description (or (assoc-ref meta "Description")
  536. "")))
  537. (license ,license))))
  538. (values
  539. (case repository
  540. ((git)
  541. `(let ((commit ,(assoc-ref meta 'git-commit))
  542. (revision "1"))
  543. ,package))
  544. ((hg)
  545. `(let ((changeset ,(assoc-ref meta 'hg-changeset))
  546. (revision "1"))
  547. ,package))
  548. (else package))
  549. propagate)))
  550. (define cran->guix-package
  551. (memoize
  552. (lambda* (package-name #:key (repo 'cran) version)
  553. "Fetch the metadata for PACKAGE-NAME from REPO and return the `package'
  554. s-expression corresponding to that package, or #f on failure."
  555. (let ((description (fetch-description repo package-name)))
  556. (if description
  557. (description->package repo description)
  558. (case repo
  559. ((git)
  560. ;; Retry import from Bioconductor
  561. (cran->guix-package package-name #:repo 'bioconductor))
  562. ((hg)
  563. ;; Retry import from Bioconductor
  564. (cran->guix-package package-name #:repo 'bioconductor))
  565. ((bioconductor)
  566. ;; Retry import from CRAN
  567. (cran->guix-package package-name #:repo 'cran))
  568. (else
  569. (raise (condition
  570. (&message
  571. (message "couldn't find meta-data for R package")))))))))))
  572. (define* (cran-recursive-import package-name #:key (repo 'cran))
  573. (recursive-import package-name
  574. #:repo repo
  575. #:repo->guix-package cran->guix-package
  576. #:guix-name cran-guix-name))
  577. ;;;
  578. ;;; Updater.
  579. ;;;
  580. (define (package->upstream-name package)
  581. "Return the upstream name of the PACKAGE."
  582. (let* ((properties (package-properties package))
  583. (upstream-name (and=> properties
  584. (cut assoc-ref <> 'upstream-name))))
  585. (if upstream-name
  586. upstream-name
  587. (match (package-source package)
  588. ((? origin? origin)
  589. (match (origin-uri origin)
  590. ((or (? string? url) (url _ ...))
  591. (let ((end (string-rindex url #\_))
  592. (start (string-rindex url #\/)))
  593. ;; The URL ends on
  594. ;; (string-append "/" name "_" version ".tar.gz")
  595. (and start end (substring url (+ start 1) end))))
  596. (_ #f)))
  597. (_ #f)))))
  598. (define (latest-cran-release pkg)
  599. "Return an <upstream-source> for the latest release of the package PKG."
  600. (define upstream-name
  601. (package->upstream-name pkg))
  602. (define meta
  603. (fetch-description 'cran upstream-name))
  604. (and meta
  605. (let ((version (assoc-ref meta "Version")))
  606. ;; CRAN does not provide signatures.
  607. (upstream-source
  608. (package (package-name pkg))
  609. (version version)
  610. (urls (cran-uri upstream-name version))
  611. (input-changes
  612. (changed-inputs pkg
  613. (description->package 'cran meta)))))))
  614. (define (latest-bioconductor-release pkg)
  615. "Return an <upstream-source> for the latest release of the package PKG."
  616. (define upstream-name
  617. (package->upstream-name pkg))
  618. (define version
  619. (latest-bioconductor-package-version upstream-name))
  620. (and version
  621. ;; Bioconductor does not provide signatures.
  622. (upstream-source
  623. (package (package-name pkg))
  624. (version version)
  625. (urls (bioconductor-uri upstream-name version))
  626. (input-changes
  627. (changed-inputs
  628. pkg
  629. (cran->guix-package upstream-name #:repo 'bioconductor))))))
  630. (define (cran-package? package)
  631. "Return true if PACKAGE is an R package from CRAN."
  632. (and (string-prefix? "r-" (package-name package))
  633. ;; Check if the upstream name can be extracted from package uri.
  634. (package->upstream-name package)
  635. ;; Check if package uri(s) are prefixed by "mirror://cran".
  636. ((url-predicate (cut string-prefix? "mirror://cran" <>)) package)))
  637. (define (bioconductor-package? package)
  638. "Return true if PACKAGE is an R package from Bioconductor."
  639. (let ((predicate (lambda (uri)
  640. (and (string-prefix? "https://bioconductor.org" uri)
  641. ;; Data packages are neither listed in SVN nor on
  642. ;; the Github mirror, so we have to exclude them
  643. ;; from the set of bioconductor packages that can be
  644. ;; updated automatically.
  645. (not (string-contains uri "/data/annotation/"))
  646. ;; Experiment packages are in a separate repository.
  647. (not (string-contains uri "/data/experiment/"))))))
  648. (and (string-prefix? "r-" (package-name package))
  649. ((url-predicate predicate) package))))
  650. (define (bioconductor-data-package? package)
  651. "Return true if PACKAGE is an R data package from Bioconductor."
  652. (let ((predicate (lambda (uri)
  653. (and (string-prefix? "https://bioconductor.org" uri)
  654. (string-contains uri "/data/annotation/")))))
  655. (and (string-prefix? "r-" (package-name package))
  656. ((url-predicate predicate) package))))
  657. (define (bioconductor-experiment-package? package)
  658. "Return true if PACKAGE is an R experiment package from Bioconductor."
  659. (let ((predicate (lambda (uri)
  660. (and (string-prefix? "https://bioconductor.org" uri)
  661. (string-contains uri "/data/experiment/")))))
  662. (and (string-prefix? "r-" (package-name package))
  663. ((url-predicate predicate) package))))
  664. (define %cran-updater
  665. (upstream-updater
  666. (name 'cran)
  667. (description "Updater for CRAN packages")
  668. (pred cran-package?)
  669. (latest latest-cran-release)))
  670. (define %bioconductor-updater
  671. (upstream-updater
  672. (name 'bioconductor)
  673. (description "Updater for Bioconductor packages")
  674. (pred bioconductor-package?)
  675. (latest latest-bioconductor-release)))
  676. ;;; cran.scm ends here