egg.scm 13 KB

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