egg.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  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-name->guix-name name)
  80. "Return the package name for CHICKEN egg NAME."
  81. (string-append package-name-prefix name))
  82. (define (eggs-repository)
  83. "Update or fetch the latest version of the eggs repository and return the path
  84. to the repository."
  85. (let* ((url "git://code.call-cc.org/eggs-5-all")
  86. (directory commit _ (update-cached-checkout url)))
  87. directory))
  88. (define (egg-directory name)
  89. "Return the directory containing the source code for the egg NAME."
  90. (let ((eggs-directory (eggs-repository)))
  91. (string-append eggs-directory "/" name)))
  92. (define (find-latest-version name)
  93. "Get the latest version of the egg NAME."
  94. (let ((directory (scandir (egg-directory name))))
  95. (if directory
  96. (last directory)
  97. #f)))
  98. (define* (egg-metadata name #:key (version #f) (file #f))
  99. "Return the package metadata file for the egg NAME at version VERSION, or if
  100. FILE is specified, return the package metadata in FILE."
  101. (call-with-input-file (or file
  102. (string-append (egg-directory name) "/"
  103. (or version
  104. (find-latest-version name))
  105. "/" name ".egg"))
  106. read))
  107. (define (guix-name->egg-name name)
  108. "Return the CHICKEN egg name corresponding to the Guix package NAME."
  109. (if (string-prefix? package-name-prefix name)
  110. (string-drop name (string-length package-name-prefix))
  111. name))
  112. (define (guix-package->egg-name package)
  113. "Return the CHICKEN egg name of the Guix CHICKEN PACKAGE."
  114. (or (assq-ref (package-properties package) 'upstream-name)
  115. (guix-name->egg-name (package-name package))))
  116. (define (egg-package? package)
  117. "Check if PACKAGE is an CHICKEN egg package."
  118. (and (eq? (package-build-system package) chicken-build-system)
  119. (string-prefix? package-name-prefix (package-name package))))
  120. (define string->license
  121. ;; Doesn't seem to use a specific format.
  122. ;; <https://wiki.call-cc.org/eggs-licensing>
  123. (match-lambda
  124. ("GPL-2" 'license:gpl2)
  125. ("GPL-2+" 'license:gpl2+)
  126. ("GPL-3" 'license:gpl3)
  127. ("GPL-3+" 'license:gpl3+)
  128. ("GPL" 'license:gpl?)
  129. ("AGPL-3" 'license:agpl3)
  130. ("AGPL" 'license:agpl?)
  131. ("LGPL-2.0" 'license:lgpl2.0)
  132. ("LGPL-2.0+" 'license:lgpl2.0+)
  133. ("LGPL-2.1" 'license:lgpl2.1)
  134. ("LGPL-2.1+" 'license:lgpl2.1+)
  135. ("LGPL-3" 'license:lgpl3)
  136. ("LGPL-3" 'license:lgpl3+)
  137. ("LGPL" 'license:lgpl?)
  138. ("BSD-1-Clause" 'license:bsd-1)
  139. ("BSD-2-Clause" 'license:bsd-2)
  140. ("BSD-3-Clause" 'license:bsd-3)
  141. ("BSD" 'license:bsd?)
  142. ("MIT" 'license:expat)
  143. ("ISC" 'license:isc)
  144. ("Artistic-2" 'license:artistic2.0)
  145. ("Apache-2.0" 'license:asl2.0)
  146. ("Public Domain" 'license:public-domain)
  147. ((x) (string->license x))
  148. ((lst ...) `(list ,@(map string->license lst)))
  149. (_ #f)))
  150. ;;;
  151. ;;; Egg importer.
  152. ;;;
  153. (define* (egg->guix-package name version #:key (file #f) (source #f))
  154. "Import a CHICKEN egg called NAME from either the given .egg FILE, or from the
  155. latest NAME metadata downloaded from the official repository if FILE is #f.
  156. Return a <package> record or #f on failure. If VERSION is specified, import
  157. the particular version from the egg repository.
  158. SOURCE is a ``file-like'' object containing the source code corresponding to
  159. the egg. If SOURCE is not specified, the latest tarball for egg NAME will be
  160. downloaded.
  161. Specifying the SOURCE argument is mainly useful for developing a CHICKEN egg
  162. locally. Note that if FILE and SOURCE are specified, recursive import will
  163. not work."
  164. (define egg-content (if file
  165. (egg-metadata name #:file file)
  166. (egg-metadata name #:version version)))
  167. (if (not egg-content)
  168. (values #f '()) ; egg doesn't exist
  169. (let* ((version* (or (assoc-ref egg-content 'version)
  170. (find-latest-version name)))
  171. (version (if (list? version*) (first version*) version*))
  172. (source-url (if source #f `(egg-uri ,name version)))
  173. (tarball (if source
  174. #f
  175. (with-store store
  176. (download-to-store
  177. store (egg-uri name version))))))
  178. (define egg-home-page
  179. (string-append (%eggs-home-page) "/" name))
  180. (define egg-synopsis
  181. (match (assoc-ref egg-content 'synopsis)
  182. ((synopsis) synopsis)
  183. (_ #f)))
  184. (define egg-licenses
  185. (let ((licenses*
  186. (match (assoc-ref egg-content 'license)
  187. ((license)
  188. (map string->license (string-split license #\/)))
  189. (#f
  190. '()))))
  191. (match licenses*
  192. ((license) license)
  193. ((license1 license2 ...) `(list ,@licenses*)))))
  194. (define (maybe-symbol->string sym)
  195. (if (symbol? sym) (symbol->string sym) sym))
  196. (define (prettify-system-dependency name)
  197. ;; System dependencies sometimes have spaces and/or upper case
  198. ;; letters in them.
  199. ;;
  200. ;; There will probably still be some weird edge cases.
  201. (string-map (lambda (char)
  202. (case char
  203. ((#\space) #\-)
  204. (else char)))
  205. (maybe-symbol->string name)))
  206. (define* (egg-parse-dependency name #:key (system? #f))
  207. (define extract-name
  208. (match-lambda
  209. ((name version) name)
  210. (name name)))
  211. (define (prettify-name name)
  212. (if system?
  213. (prettify-system-dependency name)
  214. (maybe-symbol->string name)))
  215. (let ((name (prettify-name (extract-name name))))
  216. ;; Dependencies are sometimes specified as symbols and sometimes
  217. ;; as strings
  218. (string->symbol (string-append
  219. (if system? "" package-name-prefix)
  220. name))))
  221. (define egg-propagated-inputs
  222. (let ((dependencies (assoc-ref egg-content 'dependencies)))
  223. (if (list? dependencies)
  224. (map egg-parse-dependency
  225. dependencies)
  226. '())))
  227. ;; TODO: Or should these be propagated?
  228. (define egg-inputs
  229. (let ((dependencies (assoc-ref egg-content 'foreign-dependencies)))
  230. (if (list? dependencies)
  231. (map (lambda (name)
  232. (egg-parse-dependency name #:system? #t))
  233. dependencies)
  234. '())))
  235. (define egg-native-inputs
  236. (let* ((test-dependencies (or (assoc-ref egg-content
  237. 'test-dependencies)
  238. '()))
  239. (build-dependencies (or (assoc-ref egg-content
  240. 'build-dependencies)
  241. '()))
  242. (test+build-dependencies (append test-dependencies
  243. build-dependencies)))
  244. (match test+build-dependencies
  245. ((_ _ ...) (map egg-parse-dependency
  246. test+build-dependencies))
  247. (() '()))))
  248. ;; Copied from (guix import hackage).
  249. (define (maybe-inputs input-type inputs)
  250. (match inputs
  251. (()
  252. '())
  253. ((inputs ...)
  254. (list (list input-type
  255. `(list ,@inputs))))))
  256. (values
  257. `(package
  258. (name ,(egg-name->guix-name name))
  259. (version ,version)
  260. (source
  261. ,(if source
  262. source
  263. `(origin
  264. (method url-fetch)
  265. (uri ,source-url)
  266. (sha256
  267. (base32 ,(if tarball
  268. (bytevector->nix-base32-string
  269. (file-sha256 tarball))
  270. "failed to download tar archive"))))))
  271. (build-system chicken-build-system)
  272. (arguments ,(list 'quasiquote (list #:egg-name name)))
  273. ,@(maybe-inputs 'native-inputs egg-native-inputs)
  274. ,@(maybe-inputs 'inputs egg-inputs)
  275. ,@(maybe-inputs 'propagated-inputs egg-propagated-inputs)
  276. (home-page ,egg-home-page)
  277. (synopsis ,egg-synopsis)
  278. (description #f)
  279. (license ,egg-licenses))
  280. (filter (lambda (name)
  281. (not (member name '("srfi-4"))))
  282. (map (compose guix-name->egg-name symbol->string)
  283. (append egg-propagated-inputs
  284. egg-native-inputs)))))))
  285. (define egg->guix-package/m ;memoized variant
  286. (memoize egg->guix-package))
  287. (define* (egg-recursive-import package-name #:optional version)
  288. (recursive-import package-name
  289. #:version version
  290. #:repo->guix-package (lambda* (name #:key version repo)
  291. (egg->guix-package/m name version))
  292. #:guix-name egg-name->guix-name))
  293. ;;;
  294. ;;; Updater.
  295. ;;;
  296. (define (latest-release package)
  297. "Return an @code{<upstream-source>} for the latest release of PACKAGE."
  298. (let* ((egg-name (guix-package->egg-name package))
  299. (version (find-latest-version egg-name))
  300. (source-url (egg-uri egg-name version)))
  301. (upstream-source
  302. (package (package-name package))
  303. (version version)
  304. (urls (list source-url)))))
  305. (define %egg-updater
  306. (upstream-updater
  307. (name 'egg)
  308. (description "Updater for CHICKEN egg packages")
  309. (pred egg-package?)
  310. (latest latest-release)))
  311. ;;; egg.scm ends here