cran.scm 31 KB

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