egg.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
  3. ;;; Copyright © 2021 Tobias Geerinckx-Rice <me@tobias.gr>
  4. ;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
  5. ;;;
  6. ;;; This file is part of GNU Guix.
  7. ;;;
  8. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  9. ;;; under the terms of the GNU General Public License as published by
  10. ;;; the Free Software Foundation; either version 3 of the License, or (at
  11. ;;; your option) any later version.
  12. ;;;
  13. ;;; GNU Guix is distributed in the hope that it will be useful, but
  14. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;;; GNU General Public License for more details.
  17. ;;;
  18. ;;; You should have received a copy of the GNU General Public License
  19. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  20. (define-module (guix import egg)
  21. #:use-module (ice-9 ftw)
  22. #:use-module (ice-9 match)
  23. #:use-module (srfi srfi-1)
  24. #:use-module (srfi srfi-71)
  25. #:use-module (gcrypt hash)
  26. #:use-module (guix git)
  27. #:use-module (guix i18n)
  28. #:use-module (guix base32)
  29. #:use-module (guix diagnostics)
  30. #:use-module (guix memoization)
  31. #:use-module (guix packages)
  32. #:use-module (guix upstream)
  33. #:use-module (guix build-system)
  34. #:use-module (guix build-system chicken)
  35. #:use-module (guix store)
  36. #:use-module ((guix download) #:select (download-to-store url-fetch))
  37. #:use-module (guix import utils)
  38. #:use-module ((guix licenses) #:prefix license:)
  39. #:export (egg->guix-package
  40. egg-recursive-import
  41. %egg-updater
  42. ;; For tests.
  43. guix-package->egg-name))
  44. ;;; Commentary:
  45. ;;;
  46. ;;; (guix import egg) provides package importer for CHICKEN eggs. See the
  47. ;;; official specification format for eggs
  48. ;;; <https://wiki.call-cc.org/man/5/Egg%20specification%20format>.
  49. ;;;
  50. ;;; The following happens under the hood:
  51. ;;;
  52. ;;; * <git://code.call-cc.org/eggs-5-all> is a Git repository that contains
  53. ;;; all versions of all CHICKEN eggs. We look clone this repository and, by
  54. ;;; default, retrieve the latest version number, and the PACKAGE.egg file,
  55. ;;; which contains a list of lists containing metadata about the egg.
  56. ;;;
  57. ;;; * All the eggs are stored as tarballs at
  58. ;;; <https://code.call-cc.org/egg-tarballs/5>, so we grab the tarball for
  59. ;;; the egg from there.
  60. ;;;
  61. ;;; * The rest of the package fields will be parsed from the PACKAGE.egg file.
  62. ;;;
  63. ;;; Todos:
  64. ;;;
  65. ;;; * Support for CHICKEN 4?
  66. ;;;
  67. ;;; * Some packages will specify a specific version of a depencency in the
  68. ;;; PACKAGE.egg file, how should we handle this?
  69. ;;;
  70. ;;; Code:
  71. ;;;
  72. ;;; Egg metadata fetcher and helper functions.
  73. ;;;
  74. (define package-name-prefix "chicken-")
  75. (define %eggs-url
  76. (make-parameter "https://code.call-cc.org/egg-tarballs/5"))
  77. (define %eggs-home-page
  78. (make-parameter "https://wiki.call-cc.org/egg"))
  79. (define (egg-source-url name version)
  80. "Return the URL to the source tarball for version VERSION of the CHICKEN egg
  81. NAME."
  82. `(egg-uri ,name version))
  83. (define (egg-name->guix-name name)
  84. "Return the package name for CHICKEN egg NAME."
  85. (string-append package-name-prefix name))
  86. (define (eggs-repository)
  87. "Update or fetch the latest version of the eggs repository and return the path
  88. to the repository."
  89. (let* ((url "git://code.call-cc.org/eggs-5-all")
  90. (directory commit _ (update-cached-checkout url)))
  91. directory))
  92. (define (egg-directory name)
  93. "Return the directory containing the source code for the egg NAME."
  94. (let ((eggs-directory (eggs-repository)))
  95. (string-append eggs-directory "/" name)))
  96. (define (find-latest-version name)
  97. "Get the latest version of the egg NAME."
  98. (let ((directory (scandir (egg-directory name))))
  99. (if directory
  100. (last directory)
  101. #f)))
  102. (define* (egg-metadata name #:key (version #f) (file #f))
  103. "Return the package metadata file for the egg NAME at version VERSION, or if
  104. FILE is specified, return the package metadata in FILE."
  105. (call-with-input-file (or file
  106. (string-append (egg-directory name) "/"
  107. (or version
  108. (find-latest-version name))
  109. "/" name ".egg"))
  110. read))
  111. (define (guix-name->egg-name name)
  112. "Return the CHICKEN egg name corresponding to the Guix package NAME."
  113. (if (string-prefix? package-name-prefix name)
  114. (string-drop name (string-length package-name-prefix))
  115. name))
  116. (define (guix-package->egg-name package)
  117. "Return the CHICKEN egg name of the Guix CHICKEN PACKAGE."
  118. (or (assq-ref (package-properties package) 'upstream-name)
  119. (guix-name->egg-name (package-name package))))
  120. (define (egg-package? package)
  121. "Check if PACKAGE is an CHICKEN egg package."
  122. (and (eq? (package-build-system package) chicken-build-system)
  123. (string-prefix? package-name-prefix (package-name package))))
  124. (define string->license
  125. ;; Doesn't seem to use a specific format.
  126. ;; <https://wiki.call-cc.org/eggs-licensing>
  127. (match-lambda
  128. ("GPL-2" 'license:gpl2)
  129. ("GPL-2+" 'license:gpl2+)
  130. ("GPL-3" 'license:gpl3)
  131. ("GPL-3+" 'license:gpl3+)
  132. ("GPL" 'license:gpl?)
  133. ("AGPL-3" 'license:agpl3)
  134. ("AGPL" 'license:agpl?)
  135. ("LGPL-2.0" 'license:lgpl2.0)
  136. ("LGPL-2.0+" 'license:lgpl2.0+)
  137. ("LGPL-2.1" 'license:lgpl2.1)
  138. ("LGPL-2.1+" 'license:lgpl2.1+)
  139. ("LGPL-3" 'license:lgpl3)
  140. ("LGPL-3" 'license:lgpl3+)
  141. ("LGPL" 'license:lgpl?)
  142. ("BSD-1-Clause" 'license:bsd-1)
  143. ("BSD-2-Clause" 'license:bsd-2)
  144. ("BSD-3-Clause" 'license:bsd-3)
  145. ("BSD" 'license:bsd?)
  146. ("MIT" 'license:expat)
  147. ("ISC" 'license:isc)
  148. ("Artistic-2" 'license:artistic2.0)
  149. ("Apache-2.0" 'license:asl2.0)
  150. ("Public Domain" 'license:public-domain)
  151. ((x) (string->license x))
  152. ((lst ...) `(list ,@(map string->license lst)))
  153. (_ #f)))
  154. ;;;
  155. ;;; Egg importer.
  156. ;;;
  157. (define* (egg->guix-package name version #:key (file #f) (source #f))
  158. "Import a CHICKEN egg called NAME from either the given .egg FILE, or from the
  159. latest NAME metadata downloaded from the official repository if FILE is #f.
  160. Return a <package> record or #f on failure. If VERSION is specified, import
  161. the particular version from the egg repository.
  162. SOURCE is a ``file-like'' object containing the source code corresponding to
  163. the egg. If SOURCE is not specified, the latest tarball for egg NAME will be
  164. downloaded.
  165. Specifying the SOURCE argument is mainly useful for developing a CHICKEN egg
  166. locally. Note that if FILE and SOURCE are specified, recursive import will
  167. not work."
  168. (define egg-content (if file
  169. (egg-metadata name #:file file)
  170. (egg-metadata name #:version version)))
  171. (if (not egg-content)
  172. (values #f '()) ; egg doesn't exist
  173. (let* ((version* (or (assoc-ref egg-content 'version)
  174. (find-latest-version name)))
  175. (version (if (list? version*) (first version*) version*))
  176. (source-url (if source #f (egg-source-url name version)))
  177. (tarball (if source
  178. #f
  179. (with-store store
  180. (download-to-store
  181. store (egg-uri name version))))))
  182. (define egg-home-page
  183. (string-append (%eggs-home-page) "/" name))
  184. (define egg-synopsis
  185. (match (assoc-ref egg-content 'synopsis)
  186. ((synopsis) synopsis)
  187. (_ #f)))
  188. (define egg-licenses
  189. (let ((licenses*
  190. (match (assoc-ref egg-content 'license)
  191. ((license)
  192. (map string->license (string-split license #\/)))
  193. (#f
  194. '()))))
  195. (match licenses*
  196. ((license) license)
  197. ((license1 license2 ...) `(list ,@licenses*)))))
  198. (define (maybe-symbol->string sym)
  199. (if (symbol? sym) (symbol->string sym) sym))
  200. (define (prettify-system-dependency name)
  201. ;; System dependencies sometimes have spaces and/or upper case
  202. ;; letters in them.
  203. ;;
  204. ;; There will probably still be some weird edge cases.
  205. (string-map (lambda (char)
  206. (case char
  207. ((#\space) #\-)
  208. (else char)))
  209. (maybe-symbol->string name)))
  210. (define* (egg-parse-dependency name #:key (system? #f))
  211. (define extract-name
  212. (match-lambda
  213. ((name version) name)
  214. (name name)))
  215. (define (prettify-name name)
  216. (if system?
  217. (prettify-system-dependency name)
  218. (maybe-symbol->string name)))
  219. (let ((name (prettify-name (extract-name name))))
  220. ;; Dependencies are sometimes specified as symbols and sometimes
  221. ;; as strings
  222. (string->symbol (string-append
  223. (if system? "" package-name-prefix)
  224. name))))
  225. (define egg-propagated-inputs
  226. (let ((dependencies (assoc-ref egg-content 'dependencies)))
  227. (if (list? dependencies)
  228. (map egg-parse-dependency
  229. dependencies)
  230. '())))
  231. ;; TODO: Or should these be propagated?
  232. (define egg-inputs
  233. (let ((dependencies (assoc-ref egg-content 'foreign-dependencies)))
  234. (if (list? dependencies)
  235. (map (lambda (name)
  236. (egg-parse-dependency name #:system? #t))
  237. dependencies)
  238. '())))
  239. (define egg-native-inputs
  240. (let* ((test-dependencies (or (assoc-ref egg-content
  241. 'test-dependencies)
  242. '()))
  243. (build-dependencies (or (assoc-ref egg-content
  244. 'build-dependencies)
  245. '()))
  246. (test+build-dependencies (append test-dependencies
  247. build-dependencies)))
  248. (match test+build-dependencies
  249. ((_ _ ...) (map egg-parse-dependency
  250. test+build-dependencies))
  251. (() '()))))
  252. ;; Copied from (guix import hackage).
  253. (define (maybe-inputs input-type inputs)
  254. (match inputs
  255. (()
  256. '())
  257. ((inputs ...)
  258. (list (list input-type
  259. `(list ,@inputs))))))
  260. (values
  261. `(package
  262. (name ,(egg-name->guix-name name))
  263. (version ,version)
  264. (source
  265. ,(if source
  266. source
  267. `(origin
  268. (method url-fetch)
  269. (uri ,source-url)
  270. (sha256
  271. (base32 ,(if tarball
  272. (bytevector->nix-base32-string
  273. (file-sha256 tarball))
  274. "failed to download tar archive"))))))
  275. (build-system chicken-build-system)
  276. (arguments ,(list 'quasiquote (list #:egg-name name)))
  277. ,@(maybe-inputs 'native-inputs egg-native-inputs)
  278. ,@(maybe-inputs 'inputs egg-inputs)
  279. ,@(maybe-inputs 'propagated-inputs egg-propagated-inputs)
  280. (home-page ,egg-home-page)
  281. (synopsis ,egg-synopsis)
  282. (description #f)
  283. (license ,egg-licenses))
  284. (filter (lambda (name)
  285. (not (member name '("srfi-4"))))
  286. (map (compose guix-name->egg-name symbol->string)
  287. (append egg-propagated-inputs
  288. egg-native-inputs)))))))
  289. (define egg->guix-package/m ;memoized variant
  290. (memoize egg->guix-package))
  291. (define* (egg-recursive-import package-name #:optional version)
  292. (recursive-import package-name
  293. #:version version
  294. #:repo->guix-package (lambda* (name #:key version repo)
  295. (egg->guix-package/m name version))
  296. #:guix-name egg-name->guix-name))
  297. ;;;
  298. ;;; Updater.
  299. ;;;
  300. (define (latest-release package)
  301. "Return an @code{<upstream-source>} for the latest release of PACKAGE."
  302. (let* ((egg-name (guix-package->egg-name package))
  303. (version (find-latest-version egg-name))
  304. (source-url (egg-source-url egg-name version)))
  305. (upstream-source
  306. (package (package-name package))
  307. (version version)
  308. (urls (list source-url)))))
  309. (define %egg-updater
  310. (upstream-updater
  311. (name 'egg)
  312. (description "Updater for CHICKEN egg packages")
  313. (pred egg-package?)
  314. (latest latest-release)))
  315. ;;; egg.scm ends here