egg.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix import egg)
  19. #:use-module (ice-9 ftw)
  20. #:use-module (ice-9 match)
  21. #:use-module (srfi srfi-1)
  22. #:use-module (srfi srfi-71)
  23. #:use-module (gcrypt hash)
  24. #:use-module (guix git)
  25. #:use-module (guix i18n)
  26. #:use-module (guix base32)
  27. #:use-module (guix diagnostics)
  28. #:use-module (guix memoization)
  29. #:use-module (guix packages)
  30. #:use-module (guix upstream)
  31. #:use-module (guix build-system)
  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. #:use-module ((guix licenses) #:prefix license:)
  37. #:export (egg->guix-package
  38. egg-recursive-import
  39. %egg-updater
  40. ;; For tests.
  41. guix-package->egg-name))
  42. ;;; Commentary:
  43. ;;;
  44. ;;; (guix import egg) provides package importer for CHICKEN eggs. See the
  45. ;;; official specification format for eggs
  46. ;;; <https://wiki.call-cc.org/man/5/Egg%20specification%20format>.
  47. ;;;
  48. ;;; The following happens under the hood:
  49. ;;;
  50. ;;; * <git://code.call-cc.org/eggs-5-latest> is a Git repository that contains
  51. ;;; the latest version of all CHICKEN eggs. We look clone this repository
  52. ;;; and retrieve the latest version number, and the PACKAGE.egg file, which
  53. ;;; contains a list of lists containing metadata about the egg.
  54. ;;;
  55. ;;; * All the eggs are stored as tarballs at
  56. ;;; <https://code.call-cc.org/egg-tarballs/5>, so we grab the tarball for
  57. ;;; the egg from there.
  58. ;;;
  59. ;;; * The rest of the package fields will be parsed from the PACKAGE.egg file.
  60. ;;;
  61. ;;; Todos:
  62. ;;;
  63. ;;; * Support for CHICKEN 4?
  64. ;;;
  65. ;;; * Some packages will specify a specific version of a depencency in the
  66. ;;; PACKAGE.egg file, how should we handle this?
  67. ;;;
  68. ;;; Code:
  69. ;;;
  70. ;;; Egg metadata fetcher and helper functions.
  71. ;;;
  72. (define package-name-prefix "chicken-")
  73. (define %eggs-url
  74. (make-parameter "https://code.call-cc.org/egg-tarballs/5"))
  75. (define %eggs-home-page
  76. (make-parameter "https://wiki.call-cc.org/egg"))
  77. (define (egg-source-url name version)
  78. "Return the URL to the source tarball for version VERSION of the CHICKEN egg
  79. NAME."
  80. (string-append (%eggs-url) "/" name "/" name "-" version ".tar.gz"))
  81. (define (egg-name->guix-name name)
  82. "Return the package name for CHICKEN egg NAME."
  83. (string-append package-name-prefix name))
  84. (define (eggs-repository)
  85. "Update or fetch the latest version of the eggs repository and return the path
  86. to the repository."
  87. (let* ((url "git://code.call-cc.org/eggs-5-latest")
  88. (directory commit _ (update-cached-checkout url)))
  89. directory))
  90. (define (egg-directory name)
  91. "Return the directory containing the source code for the egg NAME."
  92. (let ((eggs-directory (eggs-repository)))
  93. (string-append eggs-directory "/" name)))
  94. (define (find-latest-version name)
  95. "Get the latest version of the egg NAME."
  96. (let ((directory (scandir (egg-directory name))))
  97. (if directory
  98. (last directory)
  99. #f)))
  100. (define* (egg-metadata name #:optional file)
  101. "Return the package metadata file for the egg NAME, or if FILE is specified,
  102. return the package metadata in FILE."
  103. (call-with-input-file (or file
  104. (string-append (egg-directory name) "/"
  105. (find-latest-version name)
  106. "/" name ".egg"))
  107. read))
  108. (define (guix-name->egg-name name)
  109. "Return the CHICKEN egg name corresponding to the Guix package NAME."
  110. (if (string-prefix? package-name-prefix name)
  111. (string-drop name (string-length package-name-prefix))
  112. name))
  113. (define (guix-package->egg-name package)
  114. "Return the CHICKEN egg name of the Guix CHICKEN PACKAGE."
  115. (or (assq-ref (package-properties package) 'upstream-name)
  116. (guix-name->egg-name (package-name package))))
  117. (define (egg-package? package)
  118. "Check if PACKAGE is an CHICKEN egg package."
  119. (and (eq? (package-build-system package) chicken-build-system)
  120. (string-prefix? package-name-prefix (package-name package))))
  121. (define string->license
  122. ;; Doesn't seem to use a specific format.
  123. ;; <https://wiki.call-cc.org/eggs-licensing>
  124. (match-lambda
  125. ("GPL-2" 'license:gpl2)
  126. ("GPL-2+" 'license:gpl2+)
  127. ("GPL-3" 'license:gpl3)
  128. ("GPL-3+" 'license:gpl3+)
  129. ("GPL" 'license:gpl?)
  130. ("AGPL-3" 'license:agpl3)
  131. ("AGPL" 'license:agpl?)
  132. ("LGPL-2.0" 'license:lgpl2.0)
  133. ("LGPL-2.0+" 'license:lgpl2.0+)
  134. ("LGPL-2.1" 'license:lgpl2.1)
  135. ("LGPL-2.1+" 'license:lgpl2.1+)
  136. ("LGPL-3" 'license:lgpl3)
  137. ("LGPL-3" 'license:lgpl3+)
  138. ("LGPL" 'license:lgpl?)
  139. ("BSD-1-Clause" 'license:bsd-1)
  140. ("BSD-2-Clause" 'license:bsd-2)
  141. ("BSD-3-Clause" 'license:bsd-3)
  142. ("BSD" 'license:bsd?)
  143. ("MIT" 'license:expat)
  144. ("ISC" 'license:isc)
  145. ("Artistic-2" 'license:artistic2.0)
  146. ("Apache-2.0" 'license:asl2.0)
  147. ("Public Domain" 'license:public-domain)
  148. ((x) (string->license x))
  149. ((lst ...) `(list ,@(map string->license lst)))
  150. (_ #f)))
  151. ;;;
  152. ;;; Egg importer.
  153. ;;;
  154. (define* (egg->guix-package name #:key (file #f) (source #f))
  155. "Import CHICKEN egg NAME from and return a <package> record type for the
  156. egg, or #f on failure. FILE is the filepath to the NAME.egg file. SOURCE is
  157. the a ``file-like'' object containing the source code corresonding to the egg.
  158. If SOURCE is not specified, the tarball for the egg will be 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)
  164. (egg-metadata name)))
  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-source-url name version)))
  171. (tarball (if source
  172. #f
  173. (with-store store
  174. (download-to-store store source-url)))))
  175. (define egg-home-page
  176. (string-append (%eggs-home-page) "/" name))
  177. (define egg-synopsis
  178. (match (assoc-ref egg-content 'synopsis)
  179. ((synopsis) synopsis)
  180. (_ #f)))
  181. (define egg-licenses
  182. (let ((licenses*
  183. (match (assoc-ref egg-content 'license)
  184. ((license)
  185. (map string->license (string-split license #\/)))
  186. (#f
  187. '()))))
  188. (match licenses*
  189. ((license) license)
  190. ((license1 license2 ...) `(list ,@licenses*)))))
  191. (define (maybe-symbol->string sym)
  192. (if (symbol? sym) (symbol->string sym) sym))
  193. (define (prettify-system-dependency name)
  194. ;; System dependencies sometimes have spaces and/or upper case
  195. ;; letters in them.
  196. ;;
  197. ;; There will probably still be some weird edge cases.
  198. (string-map (lambda (char)
  199. (case char
  200. ((#\space) #\-)
  201. (else char)))
  202. (maybe-symbol->string name)))
  203. (define* (egg-parse-dependency name #:key (system? #f))
  204. (define extract-name
  205. (match-lambda
  206. ((name version) name)
  207. (name name)))
  208. (define (prettify-name name)
  209. (if system?
  210. (prettify-system-dependency name)
  211. (maybe-symbol->string name)))
  212. (let ((name (prettify-name (extract-name name))))
  213. ;; Dependencies are sometimes specified as symbols and sometimes
  214. ;; as strings
  215. (list (string-append (if system? "" package-name-prefix)
  216. name)
  217. (list 'unquote
  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 'quasiquote 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 first)
  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)
  288. (recursive-import package-name
  289. #:repo->guix-package (lambda* (name #:key version repo)
  290. (egg->guix-package/m name))
  291. #:guix-name egg-name->guix-name))
  292. ;;;
  293. ;;; Updater.
  294. ;;;
  295. (define (latest-release package)
  296. "Return an @code{<upstream-source>} for the latest release of PACKAGE."
  297. (let* ((egg-name (guix-package->egg-name package))
  298. (version (find-latest-version egg-name))
  299. (source-url (egg-source-url 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. (latest latest-release)))
  310. ;;; egg.scm ends here