summaryrefslogtreecommitdiff
path: root/guix/import/egg.scm
blob: 0d6d72c465f6814a4a414ad1d847947f32f91d0d (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
;;; Copyright © 2021 Tobias Geerinckx-Rice <me@tobias.gr>
;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (guix import egg)
  #:use-module (ice-9 ftw)
  #:use-module (ice-9 match)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-71)
  #:use-module (gcrypt hash)
  #:use-module (guix git)
  #:use-module (guix i18n)
  #:use-module (guix base32)
  #:use-module (guix diagnostics)
  #:use-module (guix memoization)
  #:use-module (guix packages)
  #:use-module (guix upstream)
  #:use-module (guix build-system)
  #:use-module (guix build-system chicken)
  #:use-module (guix store)
  #:use-module ((guix download) #:select (download-to-store url-fetch))
  #:use-module (guix import utils)
  #:use-module ((guix licenses) #:prefix license:)
  #:export (egg->guix-package
            egg-recursive-import
            %egg-updater

            ;; For tests.
            guix-package->egg-name))

;;; Commentary:
;;;
;;; (guix import egg) provides package importer for CHICKEN eggs.  See the
;;; official specification format for eggs
;;; <https://wiki.call-cc.org/man/5/Egg%20specification%20format>.
;;;
;;; The following happens under the hood:
;;;
;;; * <git://code.call-cc.org/eggs-5-all> is a Git repository that contains
;;;   all versions of all CHICKEN eggs.  We look clone this repository and, by
;;;   default, retrieve the latest version number, and the PACKAGE.egg file,
;;;   which contains a list of lists containing metadata about the egg.
;;;
;;; * All the eggs are stored as tarballs at
;;;   <https://code.call-cc.org/egg-tarballs/5>, so we grab the tarball for
;;;   the egg from there.
;;;
;;; * The rest of the package fields will be parsed from the PACKAGE.egg file.
;;;
;;; Todos:
;;;
;;; * Support for CHICKEN 4?
;;;
;;; * Some packages will specify a specific version of a dependency in the
;;;   PACKAGE.egg file, how should we handle this?
;;;
;;; Code:


;;;
;;; Egg metadata fetcher and helper functions.
;;;

(define package-name-prefix "chicken-")

(define %eggs-url
  (make-parameter "https://code.call-cc.org/egg-tarballs/5"))

(define %eggs-home-page
  (make-parameter "https://wiki.call-cc.org/egg"))

(define (egg-name->guix-name name)
  "Return the package name for CHICKEN egg NAME."
  (string-append package-name-prefix name))

(define (eggs-repository)
  "Update or fetch the latest version of the eggs repository and return the path
to the repository."
  (let* ((url "git://code.call-cc.org/eggs-5-all")
         (directory commit _ (update-cached-checkout url)))
    directory))

(define (egg-directory name)
  "Return the directory containing the source code for the egg NAME."
  (let ((eggs-directory (eggs-repository)))
    (string-append eggs-directory "/" name)))

(define (find-latest-version name)
  "Get the latest version of the egg NAME."
  (let ((directory (scandir (egg-directory name))))
    (if directory
        (last directory)
        #f)))

(define* (egg-metadata name #:key (version #f) (file #f))
  "Return the package metadata file for the egg NAME at version VERSION, or if
FILE is specified, return the package metadata in FILE."
  (call-with-input-file (or file
                            (string-append (egg-directory name) "/"
                                           (or version
                                               (find-latest-version name))
                                           "/" name ".egg"))
    read))

(define (guix-name->egg-name name)
  "Return the CHICKEN egg name corresponding to the Guix package NAME."
  (if (string-prefix? package-name-prefix name)
      (string-drop name (string-length package-name-prefix))
      name))

(define (guix-package->egg-name package)
  "Return the CHICKEN egg name of the Guix CHICKEN PACKAGE."
  (or (assq-ref (package-properties package) 'upstream-name)
      (guix-name->egg-name (package-name package))))

(define (egg-package? package)
  "Check if PACKAGE is an CHICKEN egg package."
  (and (eq? (package-build-system package) chicken-build-system)
       (string-prefix? package-name-prefix (package-name package))))

(define string->license
  ;; Doesn't seem to use a specific format.
  ;; <https://wiki.call-cc.org/eggs-licensing>
  (match-lambda
   ("GPL-2" 'license:gpl2)
   ("GPL-2+" 'license:gpl2+)
   ("GPL-3" 'license:gpl3)
   ("GPL-3+" 'license:gpl3+)
   ("GPL" 'license:gpl?)
   ("AGPL-3" 'license:agpl3)
   ("AGPL" 'license:agpl?)
   ("LGPL-2.0" 'license:lgpl2.0)
   ("LGPL-2.0+" 'license:lgpl2.0+)
   ("LGPL-2.1" 'license:lgpl2.1)
   ("LGPL-2.1+" 'license:lgpl2.1+)
   ("LGPL-3" 'license:lgpl3)
   ("LGPL-3" 'license:lgpl3+)
   ("LGPL" 'license:lgpl?)
   ("BSD-1-Clause" 'license:bsd-1)
   ("BSD-2-Clause" 'license:bsd-2)
   ("BSD-3-Clause" 'license:bsd-3)
   ("BSD" 'license:bsd?)
   ("MIT" 'license:expat)
   ("ISC" 'license:isc)
   ("Artistic-2" 'license:artistic2.0)
   ("Apache-2.0" 'license:asl2.0)
   ("Public Domain" 'license:public-domain)
   ((x) (string->license x))
   ((lst ...) `(list ,@(map string->license lst)))
   (_ #f)))


;;;
;;; Egg importer.
;;;

(define* (egg->guix-package name version #:key (file #f) (source #f))
  "Import a CHICKEN egg called NAME from either the given .egg FILE, or from the
latest NAME metadata downloaded from the official repository if FILE is #f.
Return a <package> record or #f on failure.  If VERSION is specified, import
the particular version from the egg repository.

SOURCE is a ``file-like'' object containing the source code corresponding to
the egg.  If SOURCE is not specified, the latest tarball for egg NAME will be
downloaded.

Specifying the SOURCE argument is mainly useful for developing a CHICKEN egg
locally.  Note that if FILE and SOURCE are specified, recursive import will
not work."
  (define egg-content (if file
                          (egg-metadata name #:file file)
                          (egg-metadata name #:version version)))
  (if (not egg-content)
      (values #f '())                    ; egg doesn't exist
      (let* ((version* (or (assoc-ref egg-content 'version)
                           (find-latest-version name)))
             (version (if (list? version*) (first version*) version*))
             (source-url (if source #f `(egg-uri ,name version)))
             (tarball (if source
                          #f
                          (with-store store
                            (download-to-store
                             store (egg-uri name version))))))

        (define egg-home-page
          (string-append (%eggs-home-page) "/" name))

        (define egg-synopsis
          (match (assoc-ref egg-content 'synopsis)
            ((synopsis) synopsis)
            (_ #f)))

        (define egg-licenses
          (let ((licenses*
                 (match (assoc-ref egg-content 'license)
                   ((license)
                    (map string->license (string-split license #\/)))
                   (#f
                    '()))))
            (match licenses*
              ((license) license)
              ((license1 license2 ...) `(list ,@licenses*)))))

        (define (maybe-symbol->string sym)
          (if (symbol? sym) (symbol->string sym) sym))

        (define (prettify-system-dependency name)
          ;; System dependencies sometimes have spaces and/or upper case
          ;; letters in them.
          ;;
          ;; There will probably still be some weird edge cases.
          (string-map (lambda (char)
                        (case char
                          ((#\space) #\-)
                          (else char)))
                      (maybe-symbol->string name)))

        (define* (egg-parse-dependency name #:key (system? #f))
          (define extract-name
            (match-lambda
              ((name version) name)
              (name name)))

          (define (prettify-name name)
            (if system?
                (prettify-system-dependency name)
                (maybe-symbol->string name)))
          
          (let ((name (prettify-name (extract-name name))))
            ;; Dependencies are sometimes specified as symbols and sometimes
            ;; as strings
            (string->symbol (string-append
                             (if system? "" package-name-prefix)
                             name))))

        (define egg-propagated-inputs
          (let ((dependencies (assoc-ref egg-content 'dependencies)))
            (if (list? dependencies)
                (map egg-parse-dependency
                     dependencies)
                '())))

        ;; TODO: Or should these be propagated?
        (define egg-inputs
          (let ((dependencies (assoc-ref egg-content 'foreign-dependencies)))
            (if (list? dependencies)
                (map (lambda (name)
                       (egg-parse-dependency name #:system? #t))
                     dependencies)
                '())))

        (define egg-native-inputs
          (let* ((test-dependencies (or (assoc-ref egg-content
                                                   'test-dependencies)
                                        '()))
                 (build-dependencies (or (assoc-ref egg-content
                                                    'build-dependencies)
                                         '()))
                 (test+build-dependencies (append test-dependencies
                                                  build-dependencies)))
            (match test+build-dependencies
              ((_ _ ...) (map egg-parse-dependency
                              test+build-dependencies))
              (() '()))))

        ;; Copied from (guix import hackage).
        (define (maybe-inputs input-type inputs)
          (match inputs
            (()
             '())
            ((inputs ...)
             (list (list input-type
                         `(list ,@inputs))))))

        (values
         `(package
            (name ,(egg-name->guix-name name))
            (version ,version)
            (source
             ,(if source
                  source
                  `(origin
                     (method url-fetch)
                     (uri ,source-url)
                     (sha256
                      (base32 ,(if tarball
                                   (bytevector->nix-base32-string
                                    (file-sha256 tarball))
                                   "failed to download tar archive"))))))
            (build-system chicken-build-system)
            (arguments ,(list 'quasiquote (list #:egg-name name)))
            ,@(maybe-inputs 'native-inputs egg-native-inputs)
            ,@(maybe-inputs 'inputs egg-inputs)
            ,@(maybe-inputs 'propagated-inputs egg-propagated-inputs)
            (home-page ,egg-home-page)
            (synopsis ,egg-synopsis)
            (description #f)
            (license ,egg-licenses))
         (filter (lambda (name)
                   (not (member name '("srfi-4"))))
                 (map (compose guix-name->egg-name symbol->string)
                      (append egg-propagated-inputs
                              egg-native-inputs)))))))

(define egg->guix-package/m                   ;memoized variant
  (memoize egg->guix-package))

(define* (egg-recursive-import package-name #:optional version)
  (recursive-import package-name
                    #:version version
                    #:repo->guix-package (lambda* (name #:key version repo)
                                           (egg->guix-package/m name version))
                    #:guix-name egg-name->guix-name))


;;;
;;; Updater.
;;;

(define (latest-release package)
  "Return an @code{<upstream-source>} for the latest release of PACKAGE."
  (let* ((egg-name (guix-package->egg-name package))
         (version (find-latest-version egg-name))
         (source-url (egg-uri egg-name version)))
    (upstream-source
     (package (package-name package))
     (version version)
     (urls (list source-url)))))

(define %egg-updater
  (upstream-updater
   (name 'egg)
   (description "Updater for CHICKEN egg packages")
   (pred egg-package?)
   (latest latest-release)))

;;; egg.scm ends here