diff mbox series

[bug#38769] import: Add importer for MELPA packages.

Message ID 874kum9xtv.fsf@zancanaro.id.au
State Accepted
Delegated to: Christopher Baines
Headers show
Series [bug#38769] import: Add importer for MELPA packages. | expand

Checks

Context Check Description
cbaines/comparison success View comparision
cbaines/git branch success View Git branch
cbaines/applying patch fail View Laminar job

Commit Message

Carlo Zancanaro March 18, 2020, 2:54 a.m. UTC
Hey Brett!

It's been a while, but I've finally found time to revisit this 
patch.

On Wed, Jan 08 2020, Brett Gilio wrote:
> ... we /should/ combine this with the ELPA importer in its 
> current tradition: `guix import elpa -a melpa`. That seems 
> preferable to me, as it would avoid the need to deprecate a 
> command flag in our UX.

I've done this.

Carlo

Comments

Carlo Zancanaro May 30, 2020, 2:26 p.m. UTC | #1
I just saw a message on guix-patches that reminded me that this was
still sitting around. Can anyone help me out getting this change into
Guix so Emacs packages are easier to import correctly?
Brett Gilio July 25, 2020, 1:49 a.m. UTC | #2
Carlo Zancanaro <carlo@zancanaro.id.au> writes:

> I just saw a message on guix-patches that reminded me that this was
> still sitting around. Can anyone help me out getting this change into
> Guix so Emacs packages are easier to import correctly?

Hey Carlo,

Sorry nobody got back to you on this! I am just recently coming off of a
haitus from contributing. I have a few things still on my backlog, but I
have marked this bug for review ASAP! If somebody else can get to it
faster than me, great! If not, I will surely look it over soon!

Brett Gilio
Christopher Baines Dec. 18, 2020, 10:32 a.m. UTC | #3
Carlo Zancanaro <carlo@zancanaro.id.au> writes:

> Hey Brett!
>
> It's been a while, but I've finally found time to revisit this
> patch.
>
> On Wed, Jan 08 2020, Brett Gilio wrote:
>> ... we /should/ combine this with the ELPA importer in its
>> current tradition: `guix import elpa -a melpa`. That seems
>> preferable to me, as it would avoid the need to deprecate a
>> command flag in our UX.
>
> I've done this.

I've had a go at trying this out, I tried importing ack from elpa and a
from melpa, and it seemed to work OK. The packages built at least, and
the outputs look reasonable.

Looking at the code, elpa-package->sexp is a little awkward, the code
would probably be clearer if the (or ...) bits in the package sexp were
moved out in to functions that deal with generating that part of the
package.

It seems to work though, so I'm happy to push this. Is this patch still
relevant?
Carlo Zancanaro Dec. 18, 2020, 11:16 a.m. UTC | #4
Hi Chris!

On Fri, Dec 18 2020, Christopher Baines wrote:
> Looking at the code, elpa-package->sexp is a little awkward, the 
> code would probably be clearer if the (or ...) bits in the 
> package sexp were moved out in to functions that deal with 
> generating that part of the package.

I agree with you. The "quasiquote, unquote, quasiquote, unquote" 
is a bit awkward, but I don't think it's unreasonable. I'm not 
that interested in revising the patch right now, but feel free to 
extract that logic before merging if you think it's necessary.

> It seems to work though, so I'm happy to push this. Is this 
> patch still relevant?

Yep, as far as I know this is still relevant.

Carlo
Christopher Baines Dec. 18, 2020, 12:40 p.m. UTC | #5
Carlo Zancanaro <carlo@zancanaro.id.au> writes:

> Hi Chris!
>
> On Fri, Dec 18 2020, Christopher Baines wrote:
>> Looking at the code, elpa-package->sexp is a little awkward, the
>> code would probably be clearer if the (or ...) bits in the
>> package sexp were moved out in to functions that deal with
>> generating that part of the package.
>
> I agree with you. The "quasiquote, unquote, quasiquote, unquote" is a
> bit awkward, but I don't think it's unreasonable. I'm not that
> interested in revising the patch right now, but feel free to extract
> that logic before merging if you think it's necessary.
>
>> It seems to work though, so I'm happy to push this. Is this patch
>> still relevant?
>
> Yep, as far as I know this is still relevant.

Cool, I've gone ahead and pushed this as
b129b43475442b1da43d8209914fee215f98aa29. Hopefully it'll be helpful.

Thanks,

Chris
diff mbox series

Patch

From eee82d9668410c3b71884082fa770417f6b53921 Mon Sep 17 00:00:00 2001
From: Carlo Zancanaro <carlo@zancanaro.id.au>
Date: Wed, 18 Mar 2020 13:38:50 +1100
Subject: [PATCH] import: elpa: Fetch MELPA packages with a stable
 git-reference.

* guix/import/elpa.scm (default-files-spec): New variable.
(download-git-repository, package-name->melpa-recipe, file-hash, vcs-file?,
git-repository->origin, melpa-recipe->origin, melpa-recipe->maybe-arguments):
New procedures.
(elpa-package->sexp): Add optional repo argument, and use it to determine
whether to attempt to construct a source using the MELPA recipe.
(elpa->guix-package): Pass repo to elpa-package->sexp.
---
 guix/import/elpa.scm | 189 +++++++++++++++++++++++++++++++++++++------
 1 file changed, 166 insertions(+), 23 deletions(-)

diff --git a/guix/import/elpa.scm b/guix/import/elpa.scm
index 2d4487dba0..2483b57385 100644
--- a/guix/import/elpa.scm
+++ b/guix/import/elpa.scm
@@ -21,6 +21,7 @@ 
 (define-module (guix import elpa)
   #:use-module (ice-9 match)
   #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 regex)
   #:use-module (web uri)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-9)
@@ -30,6 +31,8 @@ 
   #:use-module ((guix download) #:select (download-to-store))
   #:use-module (guix import utils)
   #:use-module (guix http-client)
+  #:use-module (guix git)
+  #:use-module ((guix serialization) #:select (write-file))
   #:use-module (guix store)
   #:use-module (guix ui)
   #:use-module (gcrypt hash)
@@ -195,10 +198,143 @@  include VERSION."
                             url)))
       (_ #f))))
 
-(define* (elpa-package->sexp pkg #:optional license)
+(define* (download-git-repository url ref)
+  "Fetch the given REF from the Git repository at URL."
+  (with-store store
+    (latest-repository-commit store url #:ref ref)))
+
+(define (package-name->melpa-recipe package-name)
+  "Fetch the MELPA recipe for PACKAGE-NAME, represented as an alist from
+keywords to values."
+  (define recipe-url
+    (string-append "https://raw.githubusercontent.com/melpa/melpa/master/recipes/"
+                   package-name))
+
+  (define (data->recipe data)
+    (match data
+      (() '())
+      ((key value . tail)
+       (cons (cons key value) (data->recipe tail)))))
+
+  (let* ((port (http-fetch/cached (string->uri recipe-url)
+                                  #:ttl (* 6 3600)))
+         (data (read port)))
+    (close-port port)
+    (data->recipe (cons ':name data))))
+
+;; XXX adapted from (guix scripts hash)
+(define (file-hash file select? recursive?)
+  ;; Compute the hash of FILE.
+  (if recursive?
+      (let-values (((port get-hash) (open-sha256-port)))
+        (write-file file port #:select? select?)
+        (force-output port)
+        (get-hash))
+      (call-with-input-file file port-sha256)))
+
+;; XXX taken from (guix scripts hash)
+(define (vcs-file? file stat)
+  (case (stat:type stat)
+    ((directory)
+     (member (basename file) '(".bzr" ".git" ".hg" ".svn" "CVS")))
+    ((regular)
+     ;; Git sub-modules have a '.git' file that is a regular text file.
+     (string=? (basename file) ".git"))
+    (else
+     #f)))
+
+(define (git-repository->origin recipe url)
+  "Fetch origin details from the Git repository at URL for the provided MELPA
+RECIPE."
+  (define ref
+    (cond
+     ((assoc-ref recipe #:branch)
+      => (lambda (branch) (cons 'branch branch)))
+     ((assoc-ref recipe #:commit)
+      => (lambda (commit) (cons 'commit commit)))
+     (else
+      '(branch . "master"))))
+
+  (let-values (((directory commit) (download-git-repository url ref)))
+    `(origin
+       (method git-fetch)
+       (uri (git-reference
+             (url ,url)
+             (commit ,commit)))
+       (sha256
+        (base32
+         ,(bytevector->nix-base32-string
+           (file-hash directory (negate vcs-file?) #t)))))))
+
+(define* (melpa-recipe->origin recipe)
+  "Fetch origin details from the MELPA recipe and associated repository for
+the package named PACKAGE-NAME."
+  (define (github-repo->url repo)
+    (string-append "https://github.com/" repo ".git"))
+  (define (gitlab-repo->url repo)
+    (string-append "https://gitlab.com/" repo ".git"))
+
+  (match (assq-ref recipe ':fetcher)
+    ('github (git-repository->origin recipe (github-repo->url (assq-ref recipe ':repo))))
+    ('gitlab (git-repository->origin recipe (gitlab-repo->url (assq-ref recipe ':repo))))
+    ('git    (git-repository->origin recipe (assq-ref recipe ':url)))
+    (#f #f)   ; if we're not using melpa then this stops us printing a warning
+    (_ (warning (G_ "Unsupported MELPA fetcher: ~a, falling back to unstable MELPA source.~%")
+                (assq-ref recipe ':fetcher))
+       #f)))
+
+(define default-files-spec
+  ;; This contains more than just the things contained in %default-include and
+  ;; %default-exclude, presumably because this includes source files (*.in,
+  ;; *.texi, etc.) which have already been processed for releases.
+  ;;
+  ;; Taken from:
+  ;; https://github.com/melpa/melpa/blob/e8dc709d0ab2b4a68c59315f42858bcb86095f11/package-build/package-build.el#L580-L585
+  '("*.el" "*.el.in" "dir"
+    "*.info" "*.texi" "*.texinfo"
+    "doc/dir" "doc/*.info" "doc/*.texi" "doc/*.texinfo"
+    (:exclude ".dir-locals.el" "test.el" "tests.el" "*-test.el" "*-tests.el")))
+
+(define* (melpa-recipe->maybe-arguments melpa-recipe)
+  "Extract arguments for the build system from MELPA-RECIPE."
+  (define (glob->regexp glob)
+    (string-append
+     "^"
+     (regexp-substitute/global #f "\\*\\*?" glob
+                               'pre
+                               (lambda (m)
+                                 (if (string= (match:substring m 0) "**")
+                                     ".*"
+                                     "[^/]+"))
+                               'post)
+     "$"))
+
+  (let ((files (assq-ref melpa-recipe ':files)))
+    (if files
+        (let* ((with-default (apply append (map (lambda (entry)
+                                                  (if (eq? ':defaults entry)
+                                                      default-files-spec
+                                                      (list entry)))
+                                                files)))
+               (inclusions (remove pair? with-default))
+               (exclusions (apply append (map (match-lambda
+                                                ((':exclude . values)
+                                                 values)
+                                                (_ '()))
+                                              with-default))))
+          `((arguments '(#:include ',(map glob->regexp inclusions)
+                         #:exclude ',(map glob->regexp exclusions)))))
+        '())))
+
+(define* (elpa-package->sexp pkg #:optional license repo)
   "Return the `package' S-expression for the Emacs package PKG, a record of
 type '<elpa-package>'."
 
+  (define melpa-recipe
+    (if (eq? repo 'melpa)
+        (package-name->melpa-recipe (elpa-package-name pkg))
+        #f))
+
   (define name (elpa-package-name pkg))
 
   (define version (elpa-package-version pkg))
@@ -223,27 +359,34 @@  type '<elpa-package>'."
        (list (list input-type
                    (list 'quasiquote inputs))))))
 
-  (let ((tarball (with-store store
-                   (download-to-store store source-url))))
-    (values
-     `(package
-        (name ,(elpa-name->package-name name))
-        (version ,version)
-        (source (origin
-                  (method url-fetch)
-                  (uri (string-append ,@(factorize-uri source-url version)))
-                  (sha256
-                   (base32
-                    ,(if tarball
-                         (bytevector->nix-base32-string (file-sha256 tarball))
-                         "failed to download package")))))
-        (build-system emacs-build-system)
-        ,@(maybe-inputs 'propagated-inputs dependencies)
-        (home-page ,(elpa-package-home-page pkg))
-        (synopsis ,(elpa-package-synopsis pkg))
-        (description ,(elpa-package-description pkg))
-        (license ,license))
-     dependencies-names)))
+  (define melpa-source
+    (melpa-recipe->origin melpa-recipe))
+
+  (values
+   `(package
+      (name ,(elpa-name->package-name name))
+      (version ,version)
+      (source ,(or melpa-source
+                   (let ((tarball (with-store store
+                                    (download-to-store store source-url))))
+                     `(origin
+                        (method url-fetch)
+                        (uri (string-append ,@(factorize-uri source-url version)))
+                        (sha256
+                         (base32
+                          ,(if tarball
+                               (bytevector->nix-base32-string (file-sha256 tarball))
+                               "failed to download package")))))))
+      (build-system emacs-build-system)
+      ,@(maybe-inputs 'propagated-inputs dependencies)
+      ,@(if melpa-source
+            (melpa-recipe->maybe-arguments melpa-recipe)
+            '())
+      (home-page ,(elpa-package-home-page pkg))
+      (synopsis ,(elpa-package-synopsis pkg))
+      (description ,(elpa-package-description pkg))
+      (license ,license))
+   dependencies-names))
 
 (define* (elpa->guix-package name #:optional (repo 'gnu))
   "Fetch the package NAME from REPO and produce a Guix package S-expression."
@@ -253,7 +396,7 @@  type '<elpa-package>'."
       ;; ELPA is known to contain only GPLv3+ code.  Other repos may contain
       ;; code under other license but there's no license metadata.
       (let ((license (and (memq repo '(gnu gnu/http)) 'license:gpl3+)))
-        (elpa-package->sexp package license)))))
+        (elpa-package->sexp package license repo)))))
 
 
 ;;;
-- 
2.25.1