unofficial mirror of guix-patches@gnu.org 
 help / color / mirror / code / Atom feed
* [bug#48697] [PATCH] import: Add CHICKEN egg importer.
@ 2021-05-27 12:48 Xinglu Chen
  2021-05-27 19:21 ` raingloom
                   ` (2 more replies)
  0 siblings, 3 replies; 12+ messages in thread
From: Xinglu Chen @ 2021-05-27 12:48 UTC (permalink / raw)
  To: 48697; +Cc: raingloom

* guix/import/egg.scm: New file.
* guix/scripts/import/egg.scm: New file.
* tests/egg.scm: New file.
* Makefile.am (MODULES, SCM_TESTS): Register them.
* guix/scripts/import.scm (importers): Add egg importer.
* doc/guix.texi (Invoking guix import, Invoking guix refresh): Document it.
---
[ Cc’ing raingloom since you wrote the CHICKEN build system ]

This patch adds recursive importer for CHICKEN eggs, the generated
packages aren’t entirely complete, though.  It gets information from The
PACKAGE.egg, which is just a Scheme file that contains a list of lists
that specify the metadata for an egg.  However, it doesn’t specify a
description, so I have just set the ‘description’ field to #f for now.
The licensing policy for eggs is also a bit vague[1], there is no strict
naming format for licenses, and a lot of eggs just specify ‘BSD’ rather
than ‘BSD-N-Clause’.

The PACKAGE.egg file can also specify system dependencies, but there is
no consistent format for this, sometimes strings are used, other times
symbols are used, and sometimes the version of the package is also
included.  The user will have to double check the names and make sure
they are correct.  I am also unsure about whether the system
dependencies should be ‘propagated-inputs’ or just ‘inputs’.

[1]: https://wiki.call-cc.org/eggs-licensing

Try it out!

--8<---------------cut here---------------start------------->8---
~/src/guix $ ./pre-inst-env guix import egg -r medea

Starting download of /tmp/guix-file.nFumLb
From https://code.call-cc.org/egg-tarballs/5/medea/medea-4.tar.gz...
 medea-4.tar.gz  7KiB                 162KiB/s 00:00 [##################] 100.0%

Starting download of /tmp/guix-file.8QWL6a
From https://code.call-cc.org/egg-tarballs/5/comparse/comparse-3.tar.gz...
 …-3.tar.gz  7KiB                     203KiB/s 00:00 [##################] 100.0%

Starting download of /tmp/guix-file.hZPs19
From https://code.call-cc.org/egg-tarballs/5/srfi-13/srfi-13-0.3.1.tar.gz...
 …3.1.tar.gz  28KiB                   328KiB/s 00:00 [##################] 100.0%

Starting download of /tmp/guix-file.VMAPo7
From https://code.call-cc.org/egg-tarballs/5/lazy-seq/lazy-seq-2.tar.gz...
 …-2.tar.gz  3KiB                     158KiB/s 00:00 [##################] 100.0%

Starting download of /tmp/guix-file.6LM9K7
From https://code.call-cc.org/egg-tarballs/5/trie/trie-2.tar.gz...
 trie-2.tar.gz  1KiB                  1.5MiB/s 00:00 [##################] 100.0%

Starting download of /tmp/guix-file.8Yn8L7
From https://code.call-cc.org/egg-tarballs/5/matchable/matchable-1.1.tar.gz...
 …1.1.tar.gz  14KiB                   370KiB/s 00:00 [##################] 100.0%
(define-public chicken-srfi-13
  (package
    (name "chicken-srfi-13")
    (version "0.3.1")
    (source
      (origin
        (method url-fetch)
        (uri "https://code.call-cc.org/egg-tarballs/5/srfi-13/srfi-13-0.3.1.tar.gz")
        (sha256
          (base32
            "1430grdp9d13rjp7asn5cs7cbcllbv89g49gx95zqlp07y9df6dm"))))
    (build-system chicken-build-system)
    (arguments `(#:egg-name "srfi-13"))
    (native-inputs `(("chicken-test" ,chicken-test)))
    (propagated-inputs
      `(("chicken-srfi-14" ,chicken-srfi-14)))
    (home-page
      "https://api.call-cc.org/5/doc/srfi-13")
    (synopsis "SRFI-13 string library")
    (description #f)
    (license license:bsd?)))

[...]

(define-public chicken-medea
  (package
    (name "chicken-medea")
    (version "4")
    (source
      (origin
        (method url-fetch)
        (uri "https://code.call-cc.org/egg-tarballs/5/medea/medea-4.tar.gz")
        (sha256
          (base32
            "1ad7x1sziynjjgxiv70ghbabc1mwa15mdx38wazfzn3nl7ma1hm6"))))
    (build-system chicken-build-system)
    (arguments `(#:egg-name "medea"))
    (native-inputs `(("chicken-test" ,chicken-test)))
    (propagated-inputs
      `(("chicken-comparse" ,chicken-comparse)
        ("chicken-srfi-1" ,chicken-srfi-1)
        ("chicken-srfi-13" ,chicken-srfi-13)
        ("chicken-srfi-14" ,chicken-srfi-14)
        ("chicken-srfi-69" ,chicken-srfi-69)))
    (home-page "https://api.call-cc.org/5/doc/medea")
    (synopsis
      "A JSON parser (and emitter) built with comparse")
    (description #f)
    (license license:bsd?)))
--8<---------------cut here---------------end--------------->8---

 Makefile.am                 |   3 +
 doc/guix.texi               |  24 +++
 guix/import/egg.scm         | 363 ++++++++++++++++++++++++++++++++++++
 guix/scripts/import.scm     |   4 +-
 guix/scripts/import/egg.scm | 107 +++++++++++
 tests/egg.scm               |  90 +++++++++
 6 files changed, 589 insertions(+), 2 deletions(-)
 create mode 100644 guix/import/egg.scm
 create mode 100644 guix/scripts/import/egg.scm
 create mode 100644 tests/egg.scm

diff --git a/Makefile.am b/Makefile.am
index 8db7d6a320..3e72c3ebd0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -248,6 +248,7 @@ MODULES =					\
   guix/import/cpan.scm				\
   guix/import/cran.scm				\
   guix/import/crate.scm				\
+  guix/import/egg.scm   			\
   guix/import/elpa.scm   			\
   guix/import/gem.scm				\
   guix/import/github.scm   			\
@@ -293,6 +294,7 @@ MODULES =					\
   guix/scripts/challenge.scm			\
   guix/scripts/import/crate.scm			\
   guix/scripts/import/cran.scm			\
+  guix/scripts/import/egg.scm   		\
   guix/scripts/import/elpa.scm  		\
   guix/scripts/import/gem.scm			\
   guix/scripts/import/gnu.scm			\
@@ -449,6 +451,7 @@ SCM_TESTS =					\
   tests/debug-link.scm				\
   tests/derivations.scm			\
   tests/discovery.scm				\
+  tests/egg.scm				\
   tests/elpa.scm				\
   tests/file-systems.scm			\
   tests/gem.scm				\
diff --git a/doc/guix.texi b/doc/guix.texi
index e8b0485f78..6a38ff707f 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -11594,6 +11594,28 @@ using this mode, the symbol of the package is made by appending the
 version to its name, so that multiple versions of the same package can
 coexist.
 @end table
+
+@item egg
+@cindex egg
+Import metadata for @uref{https://wiki.call-cc.org/eggs, CHICKEN eggs}.
+The information is taken from @file{PACKAGE.egg} files found in the
+@uref{git://code.call-cc.org/eggs-5-latest, eggs-5-latest} Git
+repository.  However, it does not provide all the information that we
+need, there is no ``description'' field, and the licenses used are not
+always precise (BSD is often used instead of BSD-N).
+
+@example
+guix import egg sourcehut
+@end example
+
+Additional options include:
+@table @code
+@item --recursive
+@itemx -r
+Traverse the dependency graph of the given upstream package recursively
+and generate package expressions for all those packages that are not yet
+in Guix.
+@end table
 @end table
 
 The structure of the @command{guix import} code is modular.  It would be
@@ -11748,6 +11770,8 @@ the updater for KDE packages;
 the updater for X.org packages;
 @item kernel.org
 the updater for packages hosted on kernel.org;
+@item egg
+the updater for @uref{https://wiki.call-cc.org/eggs/, Egg} packages;
 @item elpa
 the updater for @uref{https://elpa.gnu.org/, ELPA} packages;
 @item cran
diff --git a/guix/import/egg.scm b/guix/import/egg.scm
new file mode 100644
index 0000000000..eb342dac32
--- /dev/null
+++ b/guix/import/egg.scm
@@ -0,0 +1,363 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;;
+;;; 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 (ice-9 string-fun)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
+  #:use-module (web uri)
+  #:use-module (sxml simple)
+  #:use-module ((sxml xpath) #:hide (filter))
+  #:use-module (gcrypt hash)
+  #:use-module (guix git)
+  #:use-module (guix i18n)
+  #:use-module (guix http-client)
+  #:use-module (guix base32)
+  #:use-module (guix memoization)
+  #:use-module (guix packages)
+  #:use-module (guix upstream)
+  #:use-module (guix build-system)
+  #: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
+
+            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-latest> is a Git repository that contains
+;;;   the latest version of all CHICKEN eggs.  We look clone this repository
+;;;   and 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 depencency in the
+;;;   PACKAGE.egg file, how should we handle this?
+;;;
+;;; Code:
+
+\f
+;;;
+;;; 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://api.call-cc.org/5/doc"))
+
+(define (egg-source-url name version)
+  "Return the URL to the source tarball for version VERSION of the CHICKEN egg
+NAME."
+  (string-append (%eggs-url) "/" name "/" name "-" version ".tar.gz"))
+
+(define (egg-name->guix-name name)
+  "Return the package name for CHICKEN egg NAME."
+  (string-append package-name-prefix name))
+
+(define (get-eggs-repository)
+  "Update or fetch the latest version of the eggs repository and return the path
+to the repository."
+  (let*-values (((url) "git://code.call-cc.org/eggs-5-latest")
+                ((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 (get-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)
+        (begin
+          (format #t (G_ "Package not found in eggs repository: ~a~%") name)
+          #f))))
+
+(define (get-metadata port)
+  "Parse the egg metadata from PORT."
+  (let ((content (read port)))
+    (close-port port)
+    content))
+
+(define (egg-metadata name)
+  "Return the package metadata file for the egg NAME."
+  (let ((version (find-latest-version name)))
+    (if version
+        (get-metadata (open-file
+                       (string-append (egg-directory name) "/"
+                                      version "/" name ".egg")
+                       "r"))
+        #f)))
+
+(define (guix-name->egg-name name)
+  "Return the CHICKEN egg name corresponding to the Guix package NAME."
+  (if (string-prefix? package-name-prefix name)
+      (substring name (string-length package-name-prefix))
+      name))
+
+(define (guix-package->egg-name package)
+  "Return the CHICKEN egg name of the Guix CHICKEN PACKAGE."
+  (let ((upstream-name (assoc-ref
+                        (package-properties package)
+                        'upstream-name))
+        (name (package-name package)))
+    (if upstream-name
+        upstream-name
+        (guix-name->egg-name name))))
+
+(define (egg-package? package)
+  "Check if PACKAGE is an CHICKEN egg package."
+  (and (eq? (build-system-name (package-build-system package)) 'chicken)
+       (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-3" 'license:gpl3)
+   ("GPL" 'license:gpl?)
+   ("AGPL-3" 'license:agpl3)
+   ("AGPL" 'license:agpl?)
+   ("LGPL-2.1" 'license:lgpl2.1)
+   ("LGPL-3" 'license:lgpl3)
+   ("LGPL" 'license:lgpl?)
+   ("BSD-2" 'license:bsd-2)
+   ("BSD-3" '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)))
+
+\f
+;;;
+;;; Egg importer.
+;;;
+
+(define* (egg->guix-package name #:key (port #f) (source #f))
+  "Import CHICKEN egg NAME from and return a @code{<package>} record type for
+the egg, or @code{#f} on failure.  PORT is the port for the NAME.egg to read
+from.  SOURCE is the a ``file-like'' object containing the source code
+corresonding to the egg.  If SOURCE is not specified, the tarball for the egg
+will be downloaded.
+
+Specifying the SOURCE argument is mainly useful for developing a CHICKEN egg
+locally.  Note that if PORT and SOURCE are specified,
+recursive import will not work."
+  (define egg-content (if port
+                          (get-metadata port)
+                          (egg-metadata name)))
+  (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-source-url name version)))
+             (tarball (if source
+                          #f
+                          (with-store store
+                            (download-to-store store source-url)))))
+
+        (define (safe-append lst1 lst2)
+          (match (list lst1 lst2)
+            ((#f #f) #f)
+            ((lst1 #f) lst1)
+            ((#f lst2) lst2)
+            (_ (append lst1 lst2))))
+
+        (define egg-home-page
+          (string-append (%eggs-home-page) "/" name))
+
+        (define egg-synopsis
+          (let ((synopsis (assoc-ref egg-content 'synopsis)))
+            (if (list? synopsis)
+                (first synopsis)
+                #f)))
+
+        (define egg-license
+          (let ((license (assoc-ref egg-content 'license)))
+            (if (list? license)
+                ;; Multiple licenses are separated by `/'.
+                (string->license (string-split (first license) #\/))
+                #f)))
+
+        (define (prettify-system-dependency name)
+          (let ((name* (if (symbol? name)
+                           (symbol->string name)
+                           name)))
+            ;; System dependecies sometimes have spaces and/or upper case
+            ;; letters in them.
+            ;;
+            ;; There will probably still be some weird edge cases.
+            (string-replace-substring (string-downcase name*) " " "")))
+
+        (define (maybe-string->symbol str)
+          (if (string? str) (string->symbol string) str))
+
+        (define (maybe-symbol->string sym)
+          (if (symbol? sym) (symbol->string sym) sym))
+
+        (define* (egg-parse-dependency name #:key (system? #f))
+          (let* ((name* (if (list? name)
+                            (first name) ; (name version)
+                            name))
+                 (name (if system?
+                           (prettify-system-dependency name*)
+                           (maybe-symbol->string name*))))
+            ;; Dependencies are sometimes specified as symbols and sometimes
+            ;; as strings
+            (list (string-append (if system? "" package-name-prefix)
+                                 name)
+                  (list 'unquote
+                        (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 (assoc-ref egg-content
+                                               'test-dependencies))
+                 (build-dependencies (assoc-ref egg-content
+                                                'build-dependencies))
+                 (test+build-dependencies (safe-append
+                                           test-dependencies
+                                           build-dependencies)))
+            (if (list? 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 'quasiquote 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-license))
+         (filter (lambda (name)
+                   (not (member name '("srfi-4"))))
+                 (map (compose guix-name->egg-name first)
+                      (append egg-propagated-inputs
+                              egg-native-inputs)))))))
+
+(define egg->guix-package/m                   ;memoized variant
+  (memoize egg->guix-package))
+
+(define (egg-recursive-import package-name)
+  (recursive-import package-name
+                    #:repo->guix-package (lambda* (name #:key version repo)
+                                           (egg->guix-package/m name))
+                    #:guix-name egg-name->guix-name))
+
+\f
+;;;
+;;; 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-source-url 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
diff --git a/guix/scripts/import.scm b/guix/scripts/import.scm
index bbd9a3b190..f53d1ac1f4 100644
--- a/guix/scripts/import.scm
+++ b/guix/scripts/import.scm
@@ -76,8 +76,8 @@ rather than \\n."
 ;;; Entry point.
 ;;;
 
-(define importers '("gnu" "pypi" "cpan" "hackage" "stackage" "elpa" "gem"
-                    "go" "cran" "crate" "texlive" "json" "opam"))
+(define importers '("gnu" "pypi" "cpan" "hackage" "stackage" "egg" "elpa"
+                    "gem" "go" "cran" "crate" "texlive" "json" "opam"))
 
 (define (resolve-importer name)
   (let ((module (resolve-interface
diff --git a/guix/scripts/import/egg.scm b/guix/scripts/import/egg.scm
new file mode 100644
index 0000000000..7dbd6fcd5a
--- /dev/null
+++ b/guix/scripts/import/egg.scm
@@ -0,0 +1,107 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;;
+;;; 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 scripts import egg)
+  #:use-module (guix ui)
+  #:use-module (guix utils)
+  #:use-module (guix scripts)
+  #:use-module (guix import egg)
+  #:use-module (guix scripts import)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-37)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 format)
+  #:export (guix-import-egg))
+
+\f
+;;;
+;;; Command-line options.
+;;;
+
+(define %default-options
+  '())
+
+(define (show-help)
+  (display (G_ "Usage: guix import egg PACKAGE-NAME
+Import and convert the egg package for PACKAGE-NAME.\n"))
+  (display (G_ "
+  -h, --help             display this help and exit"))
+  (display (G_ "
+  -r, --recursive        import packages recursively"))
+  (display (G_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  ;; Specification of the command-line options.
+  (cons* (option '(#\h "help") #f #f
+                 (lambda args
+                   (show-help)
+                   (exit 0)))
+         (option '(#\V "version") #f #f
+                 (lambda args
+                   (show-version-and-exit "guix import egg")))
+         (option '(#\r "recursive") #f #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'recursive #t result)))
+         %standard-import-options))
+
+\f
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-import-egg . args)
+  (define (parse-options)
+    ;; Return the alist of option values.
+    (args-fold* args %options
+                (lambda (opt name arg result)
+                  (leave (G_ "~A: unrecognized option~%") name))
+                (lambda (arg result)
+                  (alist-cons 'argument arg result))
+                %default-options))
+
+  (let* ((opts (parse-options))
+         (repo (and=> (assoc-ref opts 'repo) string->symbol))
+         (args (filter-map (match-lambda
+                            (('argument . value)
+                             value)
+                            (_ #f))
+                           (reverse opts))))
+    (match args
+      ((package-name)
+       (if (assoc-ref opts 'recursive)
+           ;; Recursive import
+           (map (match-lambda
+                  ((and ('package ('name name) . rest) pkg)
+                   `(define-public ,(string->symbol name)
+                      ,pkg))
+                  (_ #f))
+                (egg-recursive-import package-name))
+           ;; Single import
+           (let ((sexp (egg->guix-package package-name)))
+             (unless sexp
+               (leave (G_ "failed to download meta-data for package '~a'~%")
+                      package-name))
+             sexp)))
+      (()
+       (leave (G_ "too few arguments~%")))
+      ((many ...)
+       (leave (G_ "too many arguments~%"))))))
diff --git a/tests/egg.scm b/tests/egg.scm
new file mode 100644
index 0000000000..b853f777c4
--- /dev/null
+++ b/tests/egg.scm
@@ -0,0 +1,90 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;;
+;;; 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 (test-eggs)
+  #:use-module (guix import egg)
+  #:use-module (guix gexp)
+  #:use-module (guix base32)
+  #:use-module (gcrypt hash)
+  #:use-module (guix tests)
+  #:use-module ((guix build syscalls) #:select (mkdtemp!))
+  #:use-module ((guix build utils) #:select (delete-file-recursively mkdir-p which))
+  #:use-module ((guix utils) #:select (call-with-temporary-output-file))
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-64)
+  #:use-module (web uri)
+  #:use-module (ice-9 match))
+
+(define test-egg-1
+  "((synopsis \"Example egg\")
+    (license \"GPL-3/MIT\")
+    (version \"1.0.0\")
+    (test-dependencies test srfi-1)
+    (foreign-dependencies libgit2)
+    (build-dependencies begin-syntax)
+    (dependencies datatype)
+    (author \"John Doe\"))")
+
+(test-begin "egg")
+
+(test-equal "guix-package->egg-name"
+  "bar"
+  (guix-package->egg-name
+   (dummy-package "dummy"
+                  (name "chicken-bar"))))
+
+;; Copied from tests/hackage.scm
+(define-syntax-rule (define-package-matcher name pattern)
+  (define* (name obj)
+    (match obj
+      (pattern #t)
+      (x       (pk 'fail x #f)))))
+
+(define (eval-test-with-egg-file egg-test matcher)
+  (define port (open-input-string egg-test))
+  (matcher (egg->guix-package "foo"
+                              #:port port
+                              #:source (plain-file "foo-egg" "content"))))
+
+(define-package-matcher match-chicken-foo
+  ('package
+    ('name "chicken-foo")
+    ('version "1.0.0")
+    ('source (? file-like? source))
+    ('build-system 'chicken-build-system)
+    ('arguments ('quasiquote ('#:egg-name "foo")))
+    ('native-inputs
+     ('quasiquote
+      (("chicken-test" ('unquote chicken-test))
+       ("chicken-srfi-1" ('unquote chicken-srfi-1))
+       ("chicken-begin-syntax" ('unquote chicken-begin-syntax)))))
+    ('inputs
+     ('quasiquote
+      (("libgit2" ('unquote libgit2)))))
+    ('propagated-inputs
+     ('quasiquote
+      (("chicken-datatype" ('unquote chicken-datatype)))))
+    ('home-page "https://api.call-cc.org/5/doc/foo")
+    ('synopsis "Example egg")
+    ('description #f)
+    ('license (list 'license:gpl3 'license:expat))))
+
+(test-assert "egg->guix-package local file"
+  (eval-test-with-egg-file test-egg-1 match-chicken-foo))
+
+(test-end "egg")

base-commit: 23ab7067f32495a8a02710c08b7cfe0e21be7f3f
-- 
2.31.1






^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [bug#48697] [PATCH] import: Add CHICKEN egg importer.
  2021-05-27 12:48 [bug#48697] [PATCH] import: Add CHICKEN egg importer Xinglu Chen
@ 2021-05-27 19:21 ` raingloom
  2021-05-29 16:44 ` Ludovic Courtès
  2021-05-31 18:29 ` [bug#48697] [PATCH v3] " Xinglu Chen
  2 siblings, 0 replies; 12+ messages in thread
From: raingloom @ 2021-05-27 19:21 UTC (permalink / raw)
  To: Xinglu Chen; +Cc: 48697

On Thu, 27 May 2021 14:48:30 +0200
Xinglu Chen <public@yoctocell.xyz> wrote:

> * guix/import/egg.scm: New file.
> * guix/scripts/import/egg.scm: New file.
> * tests/egg.scm: New file.
> * Makefile.am (MODULES, SCM_TESTS): Register them.
> * guix/scripts/import.scm (importers): Add egg importer.
> * doc/guix.texi (Invoking guix import, Invoking guix refresh):
> Document it. ---
> [ Cc’ing raingloom since you wrote the CHICKEN build system ]
> 
> This patch adds recursive importer for CHICKEN eggs, the generated
> packages aren’t entirely complete, though.  It gets information from
> The PACKAGE.egg, which is just a Scheme file that contains a list of
> lists that specify the metadata for an egg.  However, it doesn’t
> specify a description, so I have just set the ‘description’ field to
> #f for now. The licensing policy for eggs is also a bit vague[1],
> there is no strict naming format for licenses, and a lot of eggs just
> specify ‘BSD’ rather than ‘BSD-N-Clause’.

Aaaa, thank you so much for this! I was actually considering writing
one, but now I don't have to. UwU
But seriously, thanks! Gonna give it a spin soon.




^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#48697] [PATCH] import: Add CHICKEN egg importer.
  2021-05-27 12:48 [bug#48697] [PATCH] import: Add CHICKEN egg importer Xinglu Chen
  2021-05-27 19:21 ` raingloom
@ 2021-05-29 16:44 ` Ludovic Courtès
  2021-05-29 19:51   ` Xinglu Chen
  2021-05-31 18:29 ` [bug#48697] [PATCH v3] " Xinglu Chen
  2 siblings, 1 reply; 12+ messages in thread
From: Ludovic Courtès @ 2021-05-29 16:44 UTC (permalink / raw)
  To: Xinglu Chen; +Cc: 48697, raingloom

Hello!

Xinglu Chen <public@yoctocell.xyz> skribis:

> * guix/import/egg.scm: New file.
> * guix/scripts/import/egg.scm: New file.
> * tests/egg.scm: New file.
> * Makefile.am (MODULES, SCM_TESTS): Register them.
> * guix/scripts/import.scm (importers): Add egg importer.
> * doc/guix.texi (Invoking guix import, Invoking guix refresh): Document it.

Woohoo, very nice!

> This patch adds recursive importer for CHICKEN eggs, the generated
> packages aren’t entirely complete, though.  It gets information from The
> PACKAGE.egg, which is just a Scheme file that contains a list of lists
> that specify the metadata for an egg.  However, it doesn’t specify a
> description, so I have just set the ‘description’ field to #f for now.
> The licensing policy for eggs is also a bit vague[1], there is no strict
> naming format for licenses, and a lot of eggs just specify ‘BSD’ rather
> than ‘BSD-N-Clause’.

On IRC, Mario Goulart of CHICKEN fame mentioned that they are working on
it, linking to this discussion:

  https://lists.nongnu.org/archive/html/chicken-hackers/2020-10/msg00003.html

So hopefully it’ll improve soon.  In the meantime, what this patch does
(leaving a question mark when licensing is vague) LGTM.

> The PACKAGE.egg file can also specify system dependencies, but there is
> no consistent format for this, sometimes strings are used, other times
> symbols are used, and sometimes the version of the package is also
> included.  The user will have to double check the names and make sure
> they are correct.  I am also unsure about whether the system
> dependencies should be ‘propagated-inputs’ or just ‘inputs’.

Probably just ‘inputs’, no?  Why would they need to be propagated?  For
Guile (and Python, etc.) they have to be propagated because you need
.scm and .go files to be in the search path; but with CHICKEN, I believe
you end up with .so files, so there’s probably no need for propagation?
Not sure actually.

> +  #:use-module (ice-9 string-fun)

Uh, first time I see this one (!).  Maybe add #:select to clarify why
it’s used for.

> +(define %eggs-home-page
> +  (make-parameter "https://api.call-cc.org/5/doc"))

On IRC, Mario suggested that a better value may be
"https://wiki.call-cc.org/egg/".

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

I’d call it ‘eggs-repository’ (without ‘get-’).

Also, I recommend using srfi-71 instead of srfi-11 in new code.

> +(define (find-latest-version name)
> +  "Get the latest version of the egg NAME."
> +  (let ((directory (scandir (egg-directory name))))
> +    (if directory
> +        (last directory)
> +        (begin
> +          (format #t (G_ "Package not found in eggs repository: ~a~%") name)
> +          #f))))

This should be rendered with ‘warning’ from (guix diagnostics).

Or maybe it should be raised as a ‘formatted-message’ exception?

> +(define (get-metadata port)
> +  "Parse the egg metadata from PORT."
> +  (let ((content (read port)))
> +    (close-port port)
> +    content))
> +
> +(define (egg-metadata name)
> +  "Return the package metadata file for the egg NAME."
> +  (let ((version (find-latest-version name)))
> +    (if version
> +        (get-metadata (open-file
> +                       (string-append (egg-directory name) "/"
> +                                      version "/" name ".egg")
> +                       "r"))
> +        #f)))

Rather:

  (call-with-input-file (string-append …)
    read)

… and you can remove ‘get-metadata’.

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

Use ‘string-drop’ instead of ‘substring’; I find it slightly clearer.

> +(define (guix-package->egg-name package)
> +  "Return the CHICKEN egg name of the Guix CHICKEN PACKAGE."
> +  (let ((upstream-name (assoc-ref
> +                        (package-properties package)
> +                        'upstream-name))
> +        (name (package-name package)))
> +    (if upstream-name
> +        upstream-name
> +        (guix-name->egg-name name))))

This can be simplified a bit:

  (define (guix-package->egg-name 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? (build-system-name (package-build-system package)) 'chicken)
> +       (string-prefix? package-name-prefix (package-name package))))

I suggest not relying on build system names; they’re just a debugging
aid.  Thus, replace the first ‘eq?’ with:

  (eq? (package-build-system package) chicken-build-system)

> +        (define (safe-append lst1 lst2)
> +          (match (list lst1 lst2)
> +            ((#f #f) #f)
> +            ((lst1 #f) lst1)
> +            ((#f lst2) lst2)
> +            (_ (append lst1 lst2))))

This looks like a weird interface.  I’d simply ensure you always
manipulate lists: empty lists or non-empty lists.

> +        (define egg-home-page
> +          (string-append (%eggs-home-page) "/" name))
> +
> +        (define egg-synopsis
> +          (let ((synopsis (assoc-ref egg-content 'synopsis)))
> +            (if (list? synopsis)
> +                (first synopsis)
> +                #f)))

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

> +        (define egg-license
> +          (let ((license (assoc-ref egg-content 'license)))
> +            (if (list? license)
> +                ;; Multiple licenses are separated by `/'.
> +                (string->license (string-split (first license) #\/))
> +                #f)))

Just make it ‘egg-licenses’ (plural) since the ‘license’ field of
<package> can be a list, and then:

  (match (assoc-ref egg-content 'license)
    ((license)
     (map string->license (string-split license #\/)))
    (#f
     '()))

> +        (define (prettify-system-dependency name)
> +          (let ((name* (if (symbol? name)
> +                           (symbol->string name)
> +                           name)))
> +            ;; System dependecies sometimes have spaces and/or upper case

Typo (“dependencies”).

> +            ;; letters in them.
> +            ;;
> +            ;; There will probably still be some weird edge cases.
> +            (string-replace-substring (string-downcase name*) " " "")))

How about:

  (string-map (lambda (chr)
                (case chr
                  ((#\space) #\-)
                  (else chr)))
              (string-downcase name))

?

> +        (define* (egg-parse-dependency name #:key (system? #f))
> +          (let* ((name* (if (list? name)
> +                            (first name) ; (name version)
> +                            name))
> +                 (name (if system?
> +                           (prettify-system-dependency name*)
> +                           (maybe-symbol->string name*))))

Use ‘match’.

> +        (define egg-native-inputs
> +          (let* ((test-dependencies (assoc-ref egg-content
> +                                               'test-dependencies))
> +                 (build-dependencies (assoc-ref egg-content
> +                                                'build-dependencies))
> +                 (test+build-dependencies (safe-append
> +                                           test-dependencies
> +                                           build-dependencies)))
> +            (if (list? test+build-dependencies)
> +                (map egg-parse-dependency
> +                     test+build-dependencies)
> +                '())))

Use ‘match’.  Arrange so ‘test-dependencies’ and ‘build-dependencies’
are always lists so you don’t need ‘safe-append’.

Last, could you add files that contain translatable strings to
‘po/guix/POTFILES.in’?

Otherwise LGTM.  Could you send an updated patch?

Thank you!

Ludo’.




^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#48697] [PATCH] import: Add CHICKEN egg importer.
  2021-05-29 16:44 ` Ludovic Courtès
@ 2021-05-29 19:51   ` Xinglu Chen
  2021-05-31 16:15     ` Ludovic Courtès
  0 siblings, 1 reply; 12+ messages in thread
From: Xinglu Chen @ 2021-05-29 19:51 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 48697, raingloom

[-- Attachment #1: Type: text/plain, Size: 6790 bytes --]

On Sat, May 29 2021, Ludovic Courtès wrote:

> Hello!
>
> Xinglu Chen <public@yoctocell.xyz> skribis:
>
>> * guix/import/egg.scm: New file.
>> * guix/scripts/import/egg.scm: New file.
>> * tests/egg.scm: New file.
>> * Makefile.am (MODULES, SCM_TESTS): Register them.
>> * guix/scripts/import.scm (importers): Add egg importer.
>> * doc/guix.texi (Invoking guix import, Invoking guix refresh): Document it.
>
> Woohoo, very nice!
>
>> This patch adds recursive importer for CHICKEN eggs, the generated
>> packages aren’t entirely complete, though.  It gets information from The
>> PACKAGE.egg, which is just a Scheme file that contains a list of lists
>> that specify the metadata for an egg.  However, it doesn’t specify a
>> description, so I have just set the ‘description’ field to #f for now.
>> The licensing policy for eggs is also a bit vague[1], there is no strict
>> naming format for licenses, and a lot of eggs just specify ‘BSD’ rather
>> than ‘BSD-N-Clause’.
>
> On IRC, Mario Goulart of CHICKEN fame mentioned that they are working on
> it, linking to this discussion:
>
>   https://lists.nongnu.org/archive/html/chicken-hackers/2020-10/msg00003.html

That’s great to see!

>> The PACKAGE.egg file can also specify system dependencies, but there is
>> no consistent format for this, sometimes strings are used, other times
>> symbols are used, and sometimes the version of the package is also
>> included.  The user will have to double check the names and make sure
>> they are correct.  I am also unsure about whether the system
>> dependencies should be ‘propagated-inputs’ or just ‘inputs’.
>
> Probably just ‘inputs’, no?  Why would they need to be propagated?  For
> Guile (and Python, etc.) they have to be propagated because you need
> .scm and .go files to be in the search path; but with CHICKEN, I believe
> you end up with .so files, so there’s probably no need for propagation?
> Not sure actually.

Ah, that makes sense, I am actually not that familiar with CHICKEN. :)

>> +  #:use-module (ice-9 string-fun)
>
> Uh, first time I see this one (!).  Maybe add #:select to clarify why
> it’s used for.

It’s needed for the ‘string-replace-substring’ procedure.  Here is the
relevant part of the Guile manual (in case you were curios)

     The following additional functions are available in the module
  ‘(ice-9 string-fun)’.  They can be used with:
  
       (use-modules (ice-9 string-fun))
  
   -- Scheme Procedure: string-replace-substring str substring replacement
       Return a new string where every instance of SUBSTRING in string STR
       has been replaced by REPLACEMENT.  For example:
  
            (string-replace-substring "a ring of strings" "ring" "rut")
            ⇒ "a rut of struts"

>> +(define %eggs-home-page
>> +  (make-parameter "https://api.call-cc.org/5/doc"))
>
> On IRC, Mario suggested that a better value may be
> "https://wiki.call-cc.org/egg/".

Indeed, that looks like a better page.

>> +(define (get-eggs-repository)
>> +  "Update or fetch the latest version of the eggs repository and return the path
>> +to the repository."
>> +  (let*-values (((url) "git://code.call-cc.org/eggs-5-latest")
>> +                ((directory commit _)
>> +                 (update-cached-checkout url)))
>> +    directory))
>
> I’d call it ‘eggs-repository’ (without ‘get-’).
>
> Also, I recommend using srfi-71 instead of srfi-11 in new code.

Oh, I didn’t know about that, definitely looks nicer.

>> +(define (find-latest-version name)
>> +  "Get the latest version of the egg NAME."
>> +  (let ((directory (scandir (egg-directory name))))
>> +    (if directory
>> +        (last directory)
>> +        (begin
>> +          (format #t (G_ "Package not found in eggs repository: ~a~%") name)
>> +          #f))))
>
> This should be rendered with ‘warning’ from (guix diagnostics).
>
> Or maybe it should be raised as a ‘formatted-message’ exception?

Not sure if it should be an exception or not, if you run ‘guix import egg’
on a package that doesn’t exist, it will already throw an error

--8<---------------cut here---------------start------------->8---
$ ./pre-inst-env guix import egg lasdkfj
Package not found in eggs repository: lasdkfj
guix import: error: failed to download meta-data for package 'lasdkfj'
--8<---------------cut here---------------end--------------->8---

>> +(define (get-metadata port)
>> +  "Parse the egg metadata from PORT."
>> +  (let ((content (read port)))
>> +    (close-port port)
>> +    content))
>> +
>> +(define (egg-metadata name)
>> +  "Return the package metadata file for the egg NAME."
>> +  (let ((version (find-latest-version name)))
>> +    (if version
>> +        (get-metadata (open-file
>> +                       (string-append (egg-directory name) "/"
>> +                                      version "/" name ".egg")
>> +                       "r"))
>> +        #f)))
>
> Rather:
>
>   (call-with-input-file (string-append …)
>     read)
>
> … and you can remove ‘get-metadata’.

Good idea.

>> +            ;; letters in them.
>> +            ;;
>> +            ;; There will probably still be some weird edge cases.
>> +            (string-replace-substring (string-downcase name*) " " "")))
>
> How about:
>
>   (string-map (lambda (chr)
>                 (case chr
>                   ((#\space) #\-)
>                   (else chr)))
>               (string-downcase name))
>
> ?

That would also work, and then we don’t have to import (ice-9
string-fun). :)

>> +        (define egg-native-inputs
>> +          (let* ((test-dependencies (assoc-ref egg-content
>> +                                               'test-dependencies))
>> +                 (build-dependencies (assoc-ref egg-content
>> +                                                'build-dependencies))
>> +                 (test+build-dependencies (safe-append
>> +                                           test-dependencies
>> +                                           build-dependencies)))
>> +            (if (list? test+build-dependencies)
>> +                (map egg-parse-dependency
>> +                     test+build-dependencies)
>> +                '())))
>
> Use ‘match’.  Arrange so ‘test-dependencies’ and ‘build-dependencies’
> are always lists so you don’t need ‘safe-append’.
>
> Last, could you add files that contain translatable strings to
> ‘po/guix/POTFILES.in’?

Sure!

> Otherwise LGTM.  Could you send an updated patch?
>
> Thank you!
>
> Ludo’.

Thanks for the review!  v2 will be coming. :)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 861 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#48697] [PATCH] import: Add CHICKEN egg importer.
  2021-05-29 19:51   ` Xinglu Chen
@ 2021-05-31 16:15     ` Ludovic Courtès
  2021-05-31 18:00       ` Xinglu Chen
  0 siblings, 1 reply; 12+ messages in thread
From: Ludovic Courtès @ 2021-05-31 16:15 UTC (permalink / raw)
  To: Xinglu Chen; +Cc: 48697, raingloom

Heya,

Xinglu Chen <public@yoctocell.xyz> skribis:

> On Sat, May 29 2021, Ludovic Courtès wrote:

[...]

>>> +(define (find-latest-version name)
>>> +  "Get the latest version of the egg NAME."
>>> +  (let ((directory (scandir (egg-directory name))))
>>> +    (if directory
>>> +        (last directory)
>>> +        (begin
>>> +          (format #t (G_ "Package not found in eggs repository: ~a~%") name)
>>> +          #f))))
>>
>> This should be rendered with ‘warning’ from (guix diagnostics).
>>
>> Or maybe it should be raised as a ‘formatted-message’ exception?
>
> Not sure if it should be an exception or not, if you run ‘guix import egg’
> on a package that doesn’t exist, it will already throw an error
>
> $ ./pre-inst-env guix import egg lasdkfj
> Package not found in eggs repository: lasdkfj
> guix import: error: failed to download meta-data for package 'lasdkfj'

True!  So perhaps we can omit the extra message altogether since it
doesn’t convey any more info.

Thanks,
Ludo’.




^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#48697] [PATCH] import: Add CHICKEN egg importer.
  2021-05-31 16:15     ` Ludovic Courtès
@ 2021-05-31 18:00       ` Xinglu Chen
  0 siblings, 0 replies; 12+ messages in thread
From: Xinglu Chen @ 2021-05-31 18:00 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 48697, raingloom

[-- Attachment #1: Type: text/plain, Size: 1143 bytes --]

On Mon, May 31 2021, Ludovic Courtès wrote:

> Heya,
>
> Xinglu Chen <public@yoctocell.xyz> skribis:
>
>> On Sat, May 29 2021, Ludovic Courtès wrote:
>
> [...]
>
>>>> +(define (find-latest-version name)
>>>> +  "Get the latest version of the egg NAME."
>>>> +  (let ((directory (scandir (egg-directory name))))
>>>> +    (if directory
>>>> +        (last directory)
>>>> +        (begin
>>>> +          (format #t (G_ "Package not found in eggs repository: ~a~%") name)
>>>> +          #f))))
>>>
>>> This should be rendered with ‘warning’ from (guix diagnostics).
>>>
>>> Or maybe it should be raised as a ‘formatted-message’ exception?
>>
>> Not sure if it should be an exception or not, if you run ‘guix import egg’
>> on a package that doesn’t exist, it will already throw an error
>>
>> $ ./pre-inst-env guix import egg lasdkfj
>> Package not found in eggs repository: lasdkfj
>> guix import: error: failed to download meta-data for package 'lasdkfj'
>
> True!  So perhaps we can omit the extra message altogether since it
> doesn’t convey any more info.

That would work as well. :)


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 861 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#48697] [PATCH v3] import: Add CHICKEN egg importer.
  2021-05-27 12:48 [bug#48697] [PATCH] import: Add CHICKEN egg importer Xinglu Chen
  2021-05-27 19:21 ` raingloom
  2021-05-29 16:44 ` Ludovic Courtès
@ 2021-05-31 18:29 ` Xinglu Chen
  2021-06-01 21:10   ` Ludovic Courtès
  2021-06-02 15:18   ` [bug#48697] [PATCH v4] " Xinglu Chen
  2 siblings, 2 replies; 12+ messages in thread
From: Xinglu Chen @ 2021-05-31 18:29 UTC (permalink / raw)
  To: 48697; +Cc: Ludovic Courtès, raingloom

* guix/import/egg.scm: New file.
* guix/scripts/import/egg.scm: New file.
* tests/egg.scm: New file.
* Makefile.am (MODULES, SCM_TESTS): Register them.
* po/guix/POTFILES.in: Likewise.
* guix/scripts/import.scm (importers): Add egg importer.
* doc/guix.texi (Invoking guix import, Invoking guix refresh): Document it.
---
Changes since v2:
* Remove message telling the user that the egg doesn’t exist, ‘guix
  import’ will already throw an error if this happens.

* Fixed the failing test, I just had to change (license (list 'a 'b)) to
  (license '(a b)), duh.
  
 Makefile.am                 |   3 +
 doc/guix.texi               |  24 +++
 guix/import/egg.scm         | 341 ++++++++++++++++++++++++++++++++++++
 guix/scripts/import.scm     |   4 +-
 guix/scripts/import/egg.scm | 107 +++++++++++
 po/guix/POTFILES.in         |   2 +
 tests/egg.scm               |  94 ++++++++++
 7 files changed, 573 insertions(+), 2 deletions(-)
 create mode 100644 guix/import/egg.scm
 create mode 100644 guix/scripts/import/egg.scm
 create mode 100644 tests/egg.scm

diff --git a/Makefile.am b/Makefile.am
index 8db7d6a320..3e72c3ebd0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -248,6 +248,7 @@ MODULES =					\
   guix/import/cpan.scm				\
   guix/import/cran.scm				\
   guix/import/crate.scm				\
+  guix/import/egg.scm   			\
   guix/import/elpa.scm   			\
   guix/import/gem.scm				\
   guix/import/github.scm   			\
@@ -293,6 +294,7 @@ MODULES =					\
   guix/scripts/challenge.scm			\
   guix/scripts/import/crate.scm			\
   guix/scripts/import/cran.scm			\
+  guix/scripts/import/egg.scm   		\
   guix/scripts/import/elpa.scm  		\
   guix/scripts/import/gem.scm			\
   guix/scripts/import/gnu.scm			\
@@ -449,6 +451,7 @@ SCM_TESTS =					\
   tests/debug-link.scm				\
   tests/derivations.scm			\
   tests/discovery.scm				\
+  tests/egg.scm				\
   tests/elpa.scm				\
   tests/file-systems.scm			\
   tests/gem.scm				\
diff --git a/doc/guix.texi b/doc/guix.texi
index 0126099d68..0340fd4968 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -11601,6 +11601,28 @@ using this mode, the symbol of the package is made by appending the
 version to its name, so that multiple versions of the same package can
 coexist.
 @end table
+
+@item egg
+@cindex egg
+Import metadata for @uref{https://wiki.call-cc.org/eggs, CHICKEN eggs}.
+The information is taken from @file{PACKAGE.egg} files found in the
+@uref{git://code.call-cc.org/eggs-5-latest, eggs-5-latest} Git
+repository.  However, it does not provide all the information that we
+need, there is no ``description'' field, and the licenses used are not
+always precise (BSD is often used instead of BSD-N).
+
+@example
+guix import egg sourcehut
+@end example
+
+Additional options include:
+@table @code
+@item --recursive
+@itemx -r
+Traverse the dependency graph of the given upstream package recursively
+and generate package expressions for all those packages that are not yet
+in Guix.
+@end table
 @end table
 
 The structure of the @command{guix import} code is modular.  It would be
@@ -11755,6 +11777,8 @@ the updater for KDE packages;
 the updater for X.org packages;
 @item kernel.org
 the updater for packages hosted on kernel.org;
+@item egg
+the updater for @uref{https://wiki.call-cc.org/eggs/, Egg} packages;
 @item elpa
 the updater for @uref{https://elpa.gnu.org/, ELPA} packages;
 @item cran
diff --git a/guix/import/egg.scm b/guix/import/egg.scm
new file mode 100644
index 0000000000..4505dfbc4d
--- /dev/null
+++ b/guix/import/egg.scm
@@ -0,0 +1,341 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;;
+;;; 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-latest> is a Git repository that contains
+;;;   the latest version of all CHICKEN eggs.  We look clone this repository
+;;;   and 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 depencency in the
+;;;   PACKAGE.egg file, how should we handle this?
+;;;
+;;; Code:
+
+\f
+;;;
+;;; 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-source-url name version)
+  "Return the URL to the source tarball for version VERSION of the CHICKEN egg
+NAME."
+  (string-append (%eggs-url) "/" name "/" name "-" version ".tar.gz"))
+
+(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-latest")
+         (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 #:optional file)
+  "Return the package metadata file for the egg NAME, or if FILE is specified,
+return the package metadata in FILE."
+  (call-with-input-file (or file
+                            (string-append (egg-directory name) "/"
+                                           (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-3" 'license:gpl3)
+   ("GPL" 'license:gpl?)
+   ("AGPL-3" 'license:agpl3)
+   ("AGPL" 'license:agpl?)
+   ("LGPL-2.1" 'license:lgpl2.1)
+   ("LGPL-3" 'license:lgpl3)
+   ("LGPL" 'license:lgpl?)
+   ("BSD-2" 'license:bsd-2)
+   ("BSD-3" '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)))
+
+\f
+;;;
+;;; Egg importer.
+;;;
+
+(define* (egg->guix-package name #:key (file #f) (source #f))
+  "Import CHICKEN egg NAME from and return a <package> record type for the
+egg, or #f on failure.  FILE is the filepath to the NAME.egg file.  SOURCE is
+the a ``file-like'' object containing the source code corresonding to the egg.
+If SOURCE is not specified, the tarball for the egg 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)
+                          (egg-metadata name)))
+  (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-source-url name version)))
+             (tarball (if source
+                          #f
+                          (with-store store
+                            (download-to-store store source-url)))))
+
+        (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
+          (match (assoc-ref egg-content 'license)
+            ((license)
+             (map string->license (string-split license #\/)))
+            (#f
+             '())))
+
+        (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
+            (list (string-append (if system? "" package-name-prefix)
+                                 name)
+                  (list 'unquote
+                        (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 'quasiquote 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 first)
+                      (append egg-propagated-inputs
+                              egg-native-inputs)))))))
+
+(define egg->guix-package/m                   ;memoized variant
+  (memoize egg->guix-package))
+
+(define (egg-recursive-import package-name)
+  (recursive-import package-name
+                    #:repo->guix-package (lambda* (name #:key version repo)
+                                           (egg->guix-package/m name))
+                    #:guix-name egg-name->guix-name))
+
+\f
+;;;
+;;; 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-source-url 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
diff --git a/guix/scripts/import.scm b/guix/scripts/import.scm
index bbd9a3b190..f53d1ac1f4 100644
--- a/guix/scripts/import.scm
+++ b/guix/scripts/import.scm
@@ -76,8 +76,8 @@ rather than \\n."
 ;;; Entry point.
 ;;;
 
-(define importers '("gnu" "pypi" "cpan" "hackage" "stackage" "elpa" "gem"
-                    "go" "cran" "crate" "texlive" "json" "opam"))
+(define importers '("gnu" "pypi" "cpan" "hackage" "stackage" "egg" "elpa"
+                    "gem" "go" "cran" "crate" "texlive" "json" "opam"))
 
 (define (resolve-importer name)
   (let ((module (resolve-interface
diff --git a/guix/scripts/import/egg.scm b/guix/scripts/import/egg.scm
new file mode 100644
index 0000000000..7dbd6fcd5a
--- /dev/null
+++ b/guix/scripts/import/egg.scm
@@ -0,0 +1,107 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;;
+;;; 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 scripts import egg)
+  #:use-module (guix ui)
+  #:use-module (guix utils)
+  #:use-module (guix scripts)
+  #:use-module (guix import egg)
+  #:use-module (guix scripts import)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-37)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 format)
+  #:export (guix-import-egg))
+
+\f
+;;;
+;;; Command-line options.
+;;;
+
+(define %default-options
+  '())
+
+(define (show-help)
+  (display (G_ "Usage: guix import egg PACKAGE-NAME
+Import and convert the egg package for PACKAGE-NAME.\n"))
+  (display (G_ "
+  -h, --help             display this help and exit"))
+  (display (G_ "
+  -r, --recursive        import packages recursively"))
+  (display (G_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  ;; Specification of the command-line options.
+  (cons* (option '(#\h "help") #f #f
+                 (lambda args
+                   (show-help)
+                   (exit 0)))
+         (option '(#\V "version") #f #f
+                 (lambda args
+                   (show-version-and-exit "guix import egg")))
+         (option '(#\r "recursive") #f #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'recursive #t result)))
+         %standard-import-options))
+
+\f
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-import-egg . args)
+  (define (parse-options)
+    ;; Return the alist of option values.
+    (args-fold* args %options
+                (lambda (opt name arg result)
+                  (leave (G_ "~A: unrecognized option~%") name))
+                (lambda (arg result)
+                  (alist-cons 'argument arg result))
+                %default-options))
+
+  (let* ((opts (parse-options))
+         (repo (and=> (assoc-ref opts 'repo) string->symbol))
+         (args (filter-map (match-lambda
+                            (('argument . value)
+                             value)
+                            (_ #f))
+                           (reverse opts))))
+    (match args
+      ((package-name)
+       (if (assoc-ref opts 'recursive)
+           ;; Recursive import
+           (map (match-lambda
+                  ((and ('package ('name name) . rest) pkg)
+                   `(define-public ,(string->symbol name)
+                      ,pkg))
+                  (_ #f))
+                (egg-recursive-import package-name))
+           ;; Single import
+           (let ((sexp (egg->guix-package package-name)))
+             (unless sexp
+               (leave (G_ "failed to download meta-data for package '~a'~%")
+                      package-name))
+             sexp)))
+      (()
+       (leave (G_ "too few arguments~%")))
+      ((many ...)
+       (leave (G_ "too many arguments~%"))))))
diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in
index 74cc5ebf9a..8fd944ff22 100644
--- a/po/guix/POTFILES.in
+++ b/po/guix/POTFILES.in
@@ -7,6 +7,7 @@ gnu/system.scm
 gnu/services/shepherd.scm
 gnu/system/mapped-devices.scm
 gnu/system/shadow.scm
+guix/import/egg.scm
 guix/import/opam.scm
 gnu/installer.scm
 gnu/installer/connman.scm
@@ -99,6 +100,7 @@ guix/scripts/environment.scm
 guix/scripts/time-machine.scm
 guix/scripts/import/cpan.scm
 guix/scripts/import/crate.scm
+guix/scripts/import/egg.scm
 guix/scripts/import/gem.scm
 guix/scripts/import/gnu.scm
 guix/scripts/import/go.scm
diff --git a/tests/egg.scm b/tests/egg.scm
new file mode 100644
index 0000000000..d89368da61
--- /dev/null
+++ b/tests/egg.scm
@@ -0,0 +1,94 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;;
+;;; 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 (test-eggs)
+  #:use-module (guix import egg)
+  #:use-module (guix gexp)
+  #:use-module (guix base32)
+  #:use-module (gcrypt hash)
+  #:use-module (guix tests)
+  #:use-module ((guix build syscalls) #:select (mkdtemp!))
+  #:use-module ((guix build utils) #:select (delete-file-recursively mkdir-p which))
+  #:use-module ((guix utils) #:select (call-with-temporary-output-file))
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-64)
+  #:use-module (web uri)
+  #:use-module (ice-9 match))
+
+(define test-egg-1
+  '((synopsis "Example egg")
+    (license "GPL-3/MIT")
+    (version "1.0.0")
+    (test-dependencies test srfi-1)
+    (foreign-dependencies libgit2)
+    (build-dependencies begin-syntax)
+    (dependencies datatype)
+    (author "John Doe")))
+
+(define test-egg-1-file "/tmp/guix-egg-1")
+
+(test-begin "egg")
+
+(test-equal "guix-package->egg-name"
+  "bar"
+  (guix-package->egg-name
+   (dummy-package "dummy"
+                  (name "chicken-bar"))))
+
+;; Copied from tests/hackage.scm
+(define-syntax-rule (define-package-matcher name pattern)
+  (define* (name obj)
+    (match obj
+      (pattern #t)
+      (x       (pk 'fail x #f)))))
+
+(define (eval-test-with-egg-file egg-test egg-file matcher)
+  (call-with-output-file egg-file
+    (lambda (port)
+      (write egg-test port)))
+  (matcher (egg->guix-package "foo"
+                              #:file egg-file
+                              #:source (plain-file "foo-egg" "content"))))
+
+(define-package-matcher match-chicken-foo
+  ('package
+    ('name "chicken-foo")
+    ('version "1.0.0")
+    ('source (? file-like? source))
+    ('build-system 'chicken-build-system)
+    ('arguments ('quasiquote ('#:egg-name "foo")))
+    ('native-inputs
+     ('quasiquote
+      (("chicken-test" ('unquote chicken-test))
+       ("chicken-srfi-1" ('unquote chicken-srfi-1))
+       ("chicken-begin-syntax" ('unquote chicken-begin-syntax)))))
+    ('inputs
+     ('quasiquote
+      (("libgit2" ('unquote libgit2)))))
+    ('propagated-inputs
+     ('quasiquote
+      (("chicken-datatype" ('unquote chicken-datatype)))))
+    ('home-page "https://wiki.call-cc.org/egg/foo")
+    ('synopsis "Example egg")
+    ('description #f)
+    ('license '(license:gpl3 license:expat))))
+
+(test-assert "egg->guix-package local file"
+  (eval-test-with-egg-file test-egg-1 test-egg-1-file match-chicken-foo))
+
+(test-end "egg")

base-commit: f463f376e91ccc1fe4ab68d5e822b5d71a1234f5
-- 
2.31.1






^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [bug#48697] [PATCH v3] import: Add CHICKEN egg importer.
  2021-05-31 18:29 ` [bug#48697] [PATCH v3] " Xinglu Chen
@ 2021-06-01 21:10   ` Ludovic Courtès
  2021-06-01 22:05     ` Xinglu Chen
  2021-06-02 15:18   ` [bug#48697] [PATCH v4] " Xinglu Chen
  1 sibling, 1 reply; 12+ messages in thread
From: Ludovic Courtès @ 2021-06-01 21:10 UTC (permalink / raw)
  To: Xinglu Chen; +Cc: 48697, raingloom

Hi!

Xinglu Chen <public@yoctocell.xyz> skribis:

> * guix/import/egg.scm: New file.
> * guix/scripts/import/egg.scm: New file.
> * tests/egg.scm: New file.
> * Makefile.am (MODULES, SCM_TESTS): Register them.
> * po/guix/POTFILES.in: Likewise.
> * guix/scripts/import.scm (importers): Add egg importer.
> * doc/guix.texi (Invoking guix import, Invoking guix refresh): Document it.
> ---
> Changes since v2:
> * Remove message telling the user that the egg doesn’t exist, ‘guix
>   import’ will already throw an error if this happens.
>
> * Fixed the failing test, I just had to change (license (list 'a 'b)) to
>   (license '(a b)), duh.

This LGTM, except this last bit, as shown here:

> +(define-package-matcher match-chicken-foo
> +  ('package
> +    ('name "chicken-foo")
> +    ('version "1.0.0")
> +    ('source (? file-like? source))
> +    ('build-system 'chicken-build-system)
> +    ('arguments ('quasiquote ('#:egg-name "foo")))
> +    ('native-inputs
> +     ('quasiquote
> +      (("chicken-test" ('unquote chicken-test))
> +       ("chicken-srfi-1" ('unquote chicken-srfi-1))
> +       ("chicken-begin-syntax" ('unquote chicken-begin-syntax)))))
> +    ('inputs
> +     ('quasiquote
> +      (("libgit2" ('unquote libgit2)))))
> +    ('propagated-inputs
> +     ('quasiquote
> +      (("chicken-datatype" ('unquote chicken-datatype)))))
> +    ('home-page "https://wiki.call-cc.org/egg/foo")
> +    ('synopsis "Example egg")
> +    ('description #f)
> +    ('license '(license:gpl3 license:expat))))

The generated ‘license’ team is incorrect.  For multiple licenses, the
generated code should look like:

  (package
    ;; …
    (license (list license:gpl3 license:expat)))

For a single license:

  (package
    ;; …
    (license license:gpl3+))

Compare with:

--8<---------------cut here---------------start------------->8---
$ ./pre-inst-env guix import egg sourcehut | tail -1

Starting download of /tmp/guix-file.N300R0
From https://code.call-cc.org/egg-tarballs/5/sourcehut/sourcehut-0.3.3.tar.gz...
 ….3.3.tar.gz  20KiB                  678KiB/s 00:00 [##################] 100.0%
  (license (license:bsd?)))
--8<---------------cut here---------------end--------------->8---

That’s the last remaining issue in my view.  Could you send a v3?

BTW, if you want, you can also send a snippet for ‘etc/news.scm’ to
announce ‘guix import egg’ to fellow users!  You can take inspiration
from the ‘guix import go’ news entry; I’ll fix up the commit ID when
committing.

Thanks!

Ludo’.




^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#48697] [PATCH v3] import: Add CHICKEN egg importer.
  2021-06-01 21:10   ` Ludovic Courtès
@ 2021-06-01 22:05     ` Xinglu Chen
  0 siblings, 0 replies; 12+ messages in thread
From: Xinglu Chen @ 2021-06-01 22:05 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 48697, raingloom

[-- Attachment #1: Type: text/plain, Size: 2780 bytes --]

On Tue, Jun 01 2021, Ludovic Courtès wrote:

> Hi!
>
> Xinglu Chen <public@yoctocell.xyz> skribis:
>
>> * guix/import/egg.scm: New file.
>> * guix/scripts/import/egg.scm: New file.
>> * tests/egg.scm: New file.
>> * Makefile.am (MODULES, SCM_TESTS): Register them.
>> * po/guix/POTFILES.in: Likewise.
>> * guix/scripts/import.scm (importers): Add egg importer.
>> * doc/guix.texi (Invoking guix import, Invoking guix refresh): Document it.
>> ---
>> Changes since v2:
>> * Remove message telling the user that the egg doesn’t exist, ‘guix
>>   import’ will already throw an error if this happens.
>>
>> * Fixed the failing test, I just had to change (license (list 'a 'b)) to
>>   (license '(a b)), duh.
>
> This LGTM, except this last bit, as shown here:
>
>> +(define-package-matcher match-chicken-foo
>> +  ('package
>> +    ('name "chicken-foo")
>> +    ('version "1.0.0")
>> +    ('source (? file-like? source))
>> +    ('build-system 'chicken-build-system)
>> +    ('arguments ('quasiquote ('#:egg-name "foo")))
>> +    ('native-inputs
>> +     ('quasiquote
>> +      (("chicken-test" ('unquote chicken-test))
>> +       ("chicken-srfi-1" ('unquote chicken-srfi-1))
>> +       ("chicken-begin-syntax" ('unquote chicken-begin-syntax)))))
>> +    ('inputs
>> +     ('quasiquote
>> +      (("libgit2" ('unquote libgit2)))))
>> +    ('propagated-inputs
>> +     ('quasiquote
>> +      (("chicken-datatype" ('unquote chicken-datatype)))))
>> +    ('home-page "https://wiki.call-cc.org/egg/foo")
>> +    ('synopsis "Example egg")
>> +    ('description #f)
>> +    ('license '(license:gpl3 license:expat))))
>
> The generated ‘license’ team is incorrect.  For multiple licenses, the
> generated code should look like:
>
>   (package
>     ;; …
>     (license (list license:gpl3 license:expat)))
>
> For a single license:
>
>   (package
>     ;; …
>     (license license:gpl3+))
>
> Compare with:
>
> --8<---------------cut here---------------start------------->8---
> $ ./pre-inst-env guix import egg sourcehut | tail -1
>
> Starting download of /tmp/guix-file.N300R0
> From https://code.call-cc.org/egg-tarballs/5/sourcehut/sourcehut-0.3.3.tar.gz...
>  ….3.3.tar.gz  20KiB                  678KiB/s 00:00 [##################] 100.0%
>   (license (license:bsd?)))
> --8<---------------cut here---------------end--------------->8---

Ah, good catch!

> That’s the last remaining issue in my view.  Could you send a v3?
>
> BTW, if you want, you can also send a snippet for ‘etc/news.scm’ to
> announce ‘guix import egg’ to fellow users!  You can take inspiration
> from the ‘guix import go’ news entry; I’ll fix up the commit ID when
> committing.

Sure, sound good!


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 861 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#48697] [PATCH v4] import: Add CHICKEN egg importer.
  2021-05-31 18:29 ` [bug#48697] [PATCH v3] " Xinglu Chen
  2021-06-01 21:10   ` Ludovic Courtès
@ 2021-06-02 15:18   ` Xinglu Chen
  2021-06-03 11:09     ` bug#48697: " Ludovic Courtès
  1 sibling, 1 reply; 12+ messages in thread
From: Xinglu Chen @ 2021-06-02 15:18 UTC (permalink / raw)
  To: 48697; +Cc: Ludovic Courtès

* guix/import/egg.scm: New file.
* guix/scripts/import/egg.scm: New file.
* tests/egg.scm: New file.
* Makefile.am (MODULES, SCM_TESTS): Register them.
* po/guix/POTFILES.in: Likewise.
* guix/scripts/import.scm (importers): Add egg importer.
* doc/guix.texi (Invoking guix import, Invoking guix refresh): Document it.
* etc/news.scm: Add entry.
---
Changes since v3:

* Fix generated license field, as pointed out by Ludovic.  I also added
  a new test where the test egg only contains a single license, rather
  than multiple ones.

* I took a look at the egg index[1] and added some more cases to the
 ‘string->license’ procedure.

* Add news entry for the importer, both English and Chinese.  My Chinese
  is not that good so the Chinese translation might not be the best, but
  still better than nothing I guess...

Also, don’t forget to update the commit id in the news entry :)

[1]: https://wiki.call-cc.org/chicken-projects/egg-index-5.html

 Makefile.am                 |   3 +
 doc/guix.texi               |  24 +++
 etc/news.scm                |  24 +++
 guix/import/egg.scm         | 352 ++++++++++++++++++++++++++++++++++++
 guix/scripts/import.scm     |   4 +-
 guix/scripts/import/egg.scm | 107 +++++++++++
 po/guix/POTFILES.in         |   2 +
 tests/egg.scm               | 132 ++++++++++++++
 8 files changed, 646 insertions(+), 2 deletions(-)
 create mode 100644 guix/import/egg.scm
 create mode 100644 guix/scripts/import/egg.scm
 create mode 100644 tests/egg.scm

diff --git a/Makefile.am b/Makefile.am
index 8db7d6a320..3e72c3ebd0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -248,6 +248,7 @@ MODULES =					\
   guix/import/cpan.scm				\
   guix/import/cran.scm				\
   guix/import/crate.scm				\
+  guix/import/egg.scm   			\
   guix/import/elpa.scm   			\
   guix/import/gem.scm				\
   guix/import/github.scm   			\
@@ -293,6 +294,7 @@ MODULES =					\
   guix/scripts/challenge.scm			\
   guix/scripts/import/crate.scm			\
   guix/scripts/import/cran.scm			\
+  guix/scripts/import/egg.scm   		\
   guix/scripts/import/elpa.scm  		\
   guix/scripts/import/gem.scm			\
   guix/scripts/import/gnu.scm			\
@@ -449,6 +451,7 @@ SCM_TESTS =					\
   tests/debug-link.scm				\
   tests/derivations.scm			\
   tests/discovery.scm				\
+  tests/egg.scm				\
   tests/elpa.scm				\
   tests/file-systems.scm			\
   tests/gem.scm				\
diff --git a/doc/guix.texi b/doc/guix.texi
index 0126099d68..0340fd4968 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -11601,6 +11601,28 @@ using this mode, the symbol of the package is made by appending the
 version to its name, so that multiple versions of the same package can
 coexist.
 @end table
+
+@item egg
+@cindex egg
+Import metadata for @uref{https://wiki.call-cc.org/eggs, CHICKEN eggs}.
+The information is taken from @file{PACKAGE.egg} files found in the
+@uref{git://code.call-cc.org/eggs-5-latest, eggs-5-latest} Git
+repository.  However, it does not provide all the information that we
+need, there is no ``description'' field, and the licenses used are not
+always precise (BSD is often used instead of BSD-N).
+
+@example
+guix import egg sourcehut
+@end example
+
+Additional options include:
+@table @code
+@item --recursive
+@itemx -r
+Traverse the dependency graph of the given upstream package recursively
+and generate package expressions for all those packages that are not yet
+in Guix.
+@end table
 @end table
 
 The structure of the @command{guix import} code is modular.  It would be
@@ -11755,6 +11777,8 @@ the updater for KDE packages;
 the updater for X.org packages;
 @item kernel.org
 the updater for packages hosted on kernel.org;
+@item egg
+the updater for @uref{https://wiki.call-cc.org/eggs/, Egg} packages;
 @item elpa
 the updater for @uref{https://elpa.gnu.org/, ELPA} packages;
 @item cran
diff --git a/etc/news.scm b/etc/news.scm
index 65d83061df..6eb483f7ff 100644
--- a/etc/news.scm
+++ b/etc/news.scm
@@ -14,6 +14,7 @@
 ;; Copyright © 2021 Zhu Zihao <all_but_last@163.com>
 ;; Copyright © 2021 Chris Marusich <cmmarusich@gmail.com>
 ;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
 ;;
 ;; Copying and distribution of this file, with or without modification, are
 ;; permitted in any medium without royalty provided the copyright notice and
@@ -22,6 +23,29 @@
 (channel-news
  (version 0)
 
+ (entry (commit "0000000000000000000000000000000000000000")
+        (title
+         (en "New @command{guix import egg} command")
+         (zh "新的 @command{guix import egg} 命令"))
+        (body
+         (en "The new @command{guix import egg} command allows packagers to
+generate a package definition or a template thereof given the name of a
+CHICKEN egg package, like so:
+
+@example
+guix import egg sourcehut
+@end example
+
+Run @command{info \"(guix) Invoking guix import\"} for more info.")
+         (zh "新的 @command{guix import egg} 命令能让贡献者从一个CHICKEN egg生
+成一个包装或包装样板。
+
+@example
+guix import egg sourcehut
+@end example
+
+想了解更多可以运行 @command{info \"(guix) Invoking guix import\"}。")))
+ 
  (entry (commit "2161820ebbbab62a5ce76c9101ebaec54dc61586")
         (title
          (en "Risk of local privilege escalation during user account creation")
diff --git a/guix/import/egg.scm b/guix/import/egg.scm
new file mode 100644
index 0000000000..26f8364732
--- /dev/null
+++ b/guix/import/egg.scm
@@ -0,0 +1,352 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;;
+;;; 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-latest> is a Git repository that contains
+;;;   the latest version of all CHICKEN eggs.  We look clone this repository
+;;;   and 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 depencency in the
+;;;   PACKAGE.egg file, how should we handle this?
+;;;
+;;; Code:
+
+\f
+;;;
+;;; 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-source-url name version)
+  "Return the URL to the source tarball for version VERSION of the CHICKEN egg
+NAME."
+  (string-append (%eggs-url) "/" name "/" name "-" version ".tar.gz"))
+
+(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-latest")
+         (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 #:optional file)
+  "Return the package metadata file for the egg NAME, or if FILE is specified,
+return the package metadata in FILE."
+  (call-with-input-file (or file
+                            (string-append (egg-directory name) "/"
+                                           (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)))
+
+\f
+;;;
+;;; Egg importer.
+;;;
+
+(define* (egg->guix-package name #:key (file #f) (source #f))
+  "Import CHICKEN egg NAME from and return a <package> record type for the
+egg, or #f on failure.  FILE is the filepath to the NAME.egg file.  SOURCE is
+the a ``file-like'' object containing the source code corresonding to the egg.
+If SOURCE is not specified, the tarball for the egg 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)
+                          (egg-metadata name)))
+  (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-source-url name version)))
+             (tarball (if source
+                          #f
+                          (with-store store
+                            (download-to-store store source-url)))))
+
+        (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
+            (list (string-append (if system? "" package-name-prefix)
+                                 name)
+                  (list 'unquote
+                        (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 'quasiquote 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 first)
+                      (append egg-propagated-inputs
+                              egg-native-inputs)))))))
+
+(define egg->guix-package/m                   ;memoized variant
+  (memoize egg->guix-package))
+
+(define (egg-recursive-import package-name)
+  (recursive-import package-name
+                    #:repo->guix-package (lambda* (name #:key version repo)
+                                           (egg->guix-package/m name))
+                    #:guix-name egg-name->guix-name))
+
+\f
+;;;
+;;; 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-source-url 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
diff --git a/guix/scripts/import.scm b/guix/scripts/import.scm
index bbd9a3b190..f53d1ac1f4 100644
--- a/guix/scripts/import.scm
+++ b/guix/scripts/import.scm
@@ -76,8 +76,8 @@ rather than \\n."
 ;;; Entry point.
 ;;;
 
-(define importers '("gnu" "pypi" "cpan" "hackage" "stackage" "elpa" "gem"
-                    "go" "cran" "crate" "texlive" "json" "opam"))
+(define importers '("gnu" "pypi" "cpan" "hackage" "stackage" "egg" "elpa"
+                    "gem" "go" "cran" "crate" "texlive" "json" "opam"))
 
 (define (resolve-importer name)
   (let ((module (resolve-interface
diff --git a/guix/scripts/import/egg.scm b/guix/scripts/import/egg.scm
new file mode 100644
index 0000000000..7dbd6fcd5a
--- /dev/null
+++ b/guix/scripts/import/egg.scm
@@ -0,0 +1,107 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;;
+;;; 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 scripts import egg)
+  #:use-module (guix ui)
+  #:use-module (guix utils)
+  #:use-module (guix scripts)
+  #:use-module (guix import egg)
+  #:use-module (guix scripts import)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-37)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 format)
+  #:export (guix-import-egg))
+
+\f
+;;;
+;;; Command-line options.
+;;;
+
+(define %default-options
+  '())
+
+(define (show-help)
+  (display (G_ "Usage: guix import egg PACKAGE-NAME
+Import and convert the egg package for PACKAGE-NAME.\n"))
+  (display (G_ "
+  -h, --help             display this help and exit"))
+  (display (G_ "
+  -r, --recursive        import packages recursively"))
+  (display (G_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  ;; Specification of the command-line options.
+  (cons* (option '(#\h "help") #f #f
+                 (lambda args
+                   (show-help)
+                   (exit 0)))
+         (option '(#\V "version") #f #f
+                 (lambda args
+                   (show-version-and-exit "guix import egg")))
+         (option '(#\r "recursive") #f #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'recursive #t result)))
+         %standard-import-options))
+
+\f
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-import-egg . args)
+  (define (parse-options)
+    ;; Return the alist of option values.
+    (args-fold* args %options
+                (lambda (opt name arg result)
+                  (leave (G_ "~A: unrecognized option~%") name))
+                (lambda (arg result)
+                  (alist-cons 'argument arg result))
+                %default-options))
+
+  (let* ((opts (parse-options))
+         (repo (and=> (assoc-ref opts 'repo) string->symbol))
+         (args (filter-map (match-lambda
+                            (('argument . value)
+                             value)
+                            (_ #f))
+                           (reverse opts))))
+    (match args
+      ((package-name)
+       (if (assoc-ref opts 'recursive)
+           ;; Recursive import
+           (map (match-lambda
+                  ((and ('package ('name name) . rest) pkg)
+                   `(define-public ,(string->symbol name)
+                      ,pkg))
+                  (_ #f))
+                (egg-recursive-import package-name))
+           ;; Single import
+           (let ((sexp (egg->guix-package package-name)))
+             (unless sexp
+               (leave (G_ "failed to download meta-data for package '~a'~%")
+                      package-name))
+             sexp)))
+      (()
+       (leave (G_ "too few arguments~%")))
+      ((many ...)
+       (leave (G_ "too many arguments~%"))))))
diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in
index 74cc5ebf9a..8fd944ff22 100644
--- a/po/guix/POTFILES.in
+++ b/po/guix/POTFILES.in
@@ -7,6 +7,7 @@ gnu/system.scm
 gnu/services/shepherd.scm
 gnu/system/mapped-devices.scm
 gnu/system/shadow.scm
+guix/import/egg.scm
 guix/import/opam.scm
 gnu/installer.scm
 gnu/installer/connman.scm
@@ -99,6 +100,7 @@ guix/scripts/environment.scm
 guix/scripts/time-machine.scm
 guix/scripts/import/cpan.scm
 guix/scripts/import/crate.scm
+guix/scripts/import/egg.scm
 guix/scripts/import/gem.scm
 guix/scripts/import/gnu.scm
 guix/scripts/import/go.scm
diff --git a/tests/egg.scm b/tests/egg.scm
new file mode 100644
index 0000000000..0884d8d429
--- /dev/null
+++ b/tests/egg.scm
@@ -0,0 +1,132 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;;
+;;; 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 (test-eggs)
+  #:use-module (guix import egg)
+  #:use-module (guix gexp)
+  #:use-module (guix base32)
+  #:use-module (gcrypt hash)
+  #:use-module (guix tests)
+  #:use-module ((guix build syscalls) #:select (mkdtemp!))
+  #:use-module ((guix build utils) #:select (delete-file-recursively mkdir-p which))
+  #:use-module ((guix utils) #:select (call-with-temporary-output-file))
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-64)
+  #:use-module (web uri)
+  #:use-module (ice-9 match))
+
+(define test-egg-1
+  '((synopsis "Example egg")
+    (license "GPL-3/MIT")
+    (version "1.0.0")
+    (test-dependencies test srfi-1)
+    (foreign-dependencies libgit2)
+    (build-dependencies begin-syntax)
+    (dependencies datatype)
+    (author "John Doe")))
+
+(define test-egg-2
+  '((synopsis "Example egg")
+    (license "GPL-3+")
+    (version "0.3")
+    (test-dependencies test)
+    (foreign-dependencies libgit2)
+    (build-dependencies begin-syntax)
+    (dependencies datatype)
+    (author "Alice Bobson")))
+
+(define test-egg-1-file "/tmp/guix-egg-1")
+(define test-egg-2-file "/tmp/guix-egg-2")
+
+(test-begin "egg")
+
+(test-equal "guix-package->egg-name"
+  "bar"
+  (guix-package->egg-name
+   (dummy-package "dummy"
+                  (name "chicken-bar"))))
+
+;; Copied from tests/hackage.scm
+(define-syntax-rule (define-package-matcher name pattern)
+  (define* (name obj)
+    (match obj
+      (pattern #t)
+      (x       (pk 'fail x #f)))))
+
+(define (eval-test-with-egg-file egg-name egg-test egg-file matcher)
+  (call-with-output-file egg-file
+    (lambda (port)
+      (write egg-test port)))
+  (matcher (egg->guix-package egg-name
+                              #:file egg-file
+                              #:source (plain-file
+                                        (string-append egg-name "-egg")
+                                        "content"))))
+
+(define-package-matcher match-chicken-foo
+  ('package
+    ('name "chicken-foo")
+    ('version "1.0.0")
+    ('source (? file-like? source))
+    ('build-system 'chicken-build-system)
+    ('arguments ('quasiquote ('#:egg-name "foo")))
+    ('native-inputs
+     ('quasiquote
+      (("chicken-test" ('unquote chicken-test))
+       ("chicken-srfi-1" ('unquote chicken-srfi-1))
+       ("chicken-begin-syntax" ('unquote chicken-begin-syntax)))))
+    ('inputs
+     ('quasiquote
+      (("libgit2" ('unquote libgit2)))))
+    ('propagated-inputs
+     ('quasiquote
+      (("chicken-datatype" ('unquote chicken-datatype)))))
+    ('home-page "https://wiki.call-cc.org/egg/foo")
+    ('synopsis "Example egg")
+    ('description #f)
+    ('license '(list license:gpl3 license:expat))))
+
+(define-package-matcher match-chicken-bar
+  ('package
+    ('name "chicken-bar")
+    ('version "0.3")
+    ('source (? file-like? source))
+    ('build-system 'chicken-build-system)
+    ('arguments ('quasiquote ('#:egg-name "bar")))
+    ('native-inputs
+     ('quasiquote
+      (("chicken-test" ('unquote chicken-test))
+       ("chicken-begin-syntax" ('unquote chicken-begin-syntax)))))
+    ('inputs
+     ('quasiquote
+      (("libgit2" ('unquote libgit2)))))
+    ('propagated-inputs
+     ('quasiquote
+      (("chicken-datatype" ('unquote chicken-datatype)))))
+    ('home-page "https://wiki.call-cc.org/egg/bar")
+    ('synopsis "Example egg")
+    ('description #f)
+    ('license 'license:gpl3+)))
+
+(test-assert "egg->guix-package local file, multiple licenses"
+  (eval-test-with-egg-file "foo" test-egg-1 test-egg-1-file match-chicken-foo))
+
+(test-assert "egg->guix-package local file, single license"
+  (eval-test-with-egg-file "bar" test-egg-2 test-egg-2-file match-chicken-bar))
+
+(test-end "egg")

base-commit: f463f376e91ccc1fe4ab68d5e822b5d71a1234f5
-- 
2.31.1






^ permalink raw reply related	[flat|nested] 12+ messages in thread

* bug#48697: [PATCH v4] import: Add CHICKEN egg importer.
  2021-06-02 15:18   ` [bug#48697] [PATCH v4] " Xinglu Chen
@ 2021-06-03 11:09     ` Ludovic Courtès
  2021-06-03 13:58       ` [bug#48697] " Xinglu Chen
  0 siblings, 1 reply; 12+ messages in thread
From: Ludovic Courtès @ 2021-06-03 11:09 UTC (permalink / raw)
  To: Xinglu Chen; +Cc: 48697-done

Heya,

Xinglu Chen <public@yoctocell.xyz> skribis:

> * guix/import/egg.scm: New file.
> * guix/scripts/import/egg.scm: New file.
> * tests/egg.scm: New file.
> * Makefile.am (MODULES, SCM_TESTS): Register them.
> * po/guix/POTFILES.in: Likewise.
> * guix/scripts/import.scm (importers): Add egg importer.
> * doc/guix.texi (Invoking guix import, Invoking guix refresh): Document it.
> * etc/news.scm: Add entry.

Pushed, thanks!

Note that the ‘etc/news.scm’ bit has to happen in a followup commit
since it refers to the commit that introduces the change; I made this
change.

Can’t wait to see eggs!  :-)

Ludo’.




^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#48697] [PATCH v4] import: Add CHICKEN egg importer.
  2021-06-03 11:09     ` bug#48697: " Ludovic Courtès
@ 2021-06-03 13:58       ` Xinglu Chen
  0 siblings, 0 replies; 12+ messages in thread
From: Xinglu Chen @ 2021-06-03 13:58 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 48697-done

[-- Attachment #1: Type: text/plain, Size: 767 bytes --]

On Thu, Jun 03 2021, Ludovic Courtès wrote:

> Heya,
>
> Xinglu Chen <public@yoctocell.xyz> skribis:
>
>> * guix/import/egg.scm: New file.
>> * guix/scripts/import/egg.scm: New file.
>> * tests/egg.scm: New file.
>> * Makefile.am (MODULES, SCM_TESTS): Register them.
>> * po/guix/POTFILES.in: Likewise.
>> * guix/scripts/import.scm (importers): Add egg importer.
>> * doc/guix.texi (Invoking guix import, Invoking guix refresh): Document it.
>> * etc/news.scm: Add entry.
>
> Pushed, thanks!
>
> Note that the ‘etc/news.scm’ bit has to happen in a followup commit
> since it refers to the commit that introduces the change; I made this
> change.

OK, I will keep this in mind next time.

> Can’t wait to see eggs!  :-)

Me too!  :)


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 861 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2021-06-03 14:00 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-27 12:48 [bug#48697] [PATCH] import: Add CHICKEN egg importer Xinglu Chen
2021-05-27 19:21 ` raingloom
2021-05-29 16:44 ` Ludovic Courtès
2021-05-29 19:51   ` Xinglu Chen
2021-05-31 16:15     ` Ludovic Courtès
2021-05-31 18:00       ` Xinglu Chen
2021-05-31 18:29 ` [bug#48697] [PATCH v3] " Xinglu Chen
2021-06-01 21:10   ` Ludovic Courtès
2021-06-01 22:05     ` Xinglu Chen
2021-06-02 15:18   ` [bug#48697] [PATCH v4] " Xinglu Chen
2021-06-03 11:09     ` bug#48697: " Ludovic Courtès
2021-06-03 13:58       ` [bug#48697] " Xinglu Chen

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).