194 lines
7.5 KiB
Scheme
194 lines
7.5 KiB
Scheme
|
;;; GNU Guix --- Functional package management for GNU
|
||
|
;;; Copyright © 2018 Julien Lepiller <julien@lepiller.eu>
|
||
|
;;;
|
||
|
;;; 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 opam)
|
||
|
#:use-module (ice-9 match)
|
||
|
#:use-module (ice-9 vlist)
|
||
|
#:use-module ((ice-9 rdelim) #:select (read-line))
|
||
|
#:use-module (srfi srfi-1)
|
||
|
#:use-module (web uri)
|
||
|
#:use-module (guix http-client)
|
||
|
#:use-module (guix utils)
|
||
|
#:use-module (guix import utils)
|
||
|
#:use-module ((guix licenses) #:prefix license:)
|
||
|
#:export (opam->guix-package))
|
||
|
|
||
|
(define (opam-urls)
|
||
|
"Fetch the urls.txt file from the opam repository and returns the list of
|
||
|
URLs it contains."
|
||
|
(let ((port (http-fetch/cached (string->uri "https://opam.ocaml.org/urls.txt"))))
|
||
|
(let loop ((result '()))
|
||
|
(let ((line (read-line port)))
|
||
|
(if (eof-object? line)
|
||
|
(begin
|
||
|
(close port)
|
||
|
result)
|
||
|
(loop (cons line result)))))))
|
||
|
|
||
|
(define (vhash-ref hashtable key default)
|
||
|
(match (vhash-assoc key hashtable)
|
||
|
(#f default)
|
||
|
((_ . x) x)))
|
||
|
|
||
|
(define (hashtable-update hashtable line)
|
||
|
"Parse @var{line} to get the name and version of the package and adds them
|
||
|
to the hashtable."
|
||
|
(let* ((line (string-split line #\ )))
|
||
|
(match line
|
||
|
((url foo ...)
|
||
|
(if (equal? url "repo")
|
||
|
hashtable
|
||
|
(match (string-split url #\/)
|
||
|
((type name1 versionstr foo ...)
|
||
|
(if (equal? type "packages")
|
||
|
(match (string-split versionstr #\.)
|
||
|
((name2 versions ...)
|
||
|
(let ((version (string-join versions ".")))
|
||
|
(if (equal? name1 name2)
|
||
|
(let ((curr (vhash-ref hashtable name1 '())))
|
||
|
(vhash-cons name1 (cons version curr) hashtable))
|
||
|
hashtable)))
|
||
|
(_ hashtable))
|
||
|
hashtable))
|
||
|
(_ hashtable))))
|
||
|
(_ hashtable))))
|
||
|
|
||
|
(define (urls->hashtable urls)
|
||
|
"Transform urls.txt in a hashtable whose keys are package names and values
|
||
|
the list of available versions."
|
||
|
(let ((hashtable vlist-null))
|
||
|
(let loop ((urls urls) (hashtable hashtable))
|
||
|
(match urls
|
||
|
(() hashtable)
|
||
|
((url rest ...) (loop rest (hashtable-update hashtable url)))))))
|
||
|
|
||
|
(define (latest-version versions)
|
||
|
"Find the most recent version from a list of versions."
|
||
|
(match versions
|
||
|
((first rest ...)
|
||
|
(let loop ((versions rest) (m first))
|
||
|
(match versions
|
||
|
(() m)
|
||
|
((first rest ...)
|
||
|
(loop rest (if (version>? m first) m first))))))))
|
||
|
|
||
|
(define (fetch-package-url uri)
|
||
|
"Fetch and parse the url file. Return the URL the package can be downloaded
|
||
|
from."
|
||
|
(let ((port (http-fetch uri)))
|
||
|
(let loop ((result #f))
|
||
|
(let ((line (read-line port)))
|
||
|
(if (eof-object? line)
|
||
|
(begin
|
||
|
(close port)
|
||
|
result)
|
||
|
(let* ((line (string-split line #\ )))
|
||
|
(match line
|
||
|
((key value rest ...)
|
||
|
(if (member key '("archive:" "http:"))
|
||
|
(loop (string-trim-both value #\"))
|
||
|
(loop result))))))))))
|
||
|
|
||
|
(define (fetch-package-metadata uri)
|
||
|
"Fetch and parse the opam file. Return an association list containing the
|
||
|
homepage, the license and the list of inputs."
|
||
|
(let ((port (http-fetch uri)))
|
||
|
(let loop ((result '()) (dependencies? #f))
|
||
|
(let ((line (read-line port)))
|
||
|
(if (eof-object? line)
|
||
|
(begin
|
||
|
(close port)
|
||
|
result)
|
||
|
(let* ((line (string-split line #\ )))
|
||
|
(match line
|
||
|
((key value ...)
|
||
|
(let ((dependencies?
|
||
|
(if dependencies?
|
||
|
(not (equal? key "]"))
|
||
|
(equal? key "depends:")))
|
||
|
(val (string-trim-both (string-join value "") #\")))
|
||
|
(cond
|
||
|
((equal? key "homepage:")
|
||
|
(loop (cons `("homepage" . ,val) result) dependencies?))
|
||
|
((equal? key "license:")
|
||
|
(loop (cons `("license" . ,val) result) dependencies?))
|
||
|
((and dependencies? (not (equal? val "[")))
|
||
|
(match (string-split val #\{)
|
||
|
((val rest ...)
|
||
|
(let ((curr (assoc-ref result "inputs"))
|
||
|
(new (string-trim-both
|
||
|
val (list->char-set '(#\] #\[ #\")))))
|
||
|
(loop (cons `("inputs" . ,(cons new (if curr curr '()))) result)
|
||
|
(if (string-contains val "]") #f dependencies?))))))
|
||
|
(else (loop result dependencies?))))))))))))
|
||
|
|
||
|
(define (string->license str)
|
||
|
(cond
|
||
|
((equal? str "MIT") '(license:expat))
|
||
|
((equal? str "GPL2") '(license:gpl2))
|
||
|
((equal? str "LGPLv2") '(license:lgpl2))
|
||
|
(else `())))
|
||
|
|
||
|
(define (ocaml-name->guix-name name)
|
||
|
(cond
|
||
|
((equal? name "ocamlfind") "ocaml-findlib")
|
||
|
((string-prefix? "ocaml" name) name)
|
||
|
((string-prefix? "conf-" name) (substring name 5))
|
||
|
(else (string-append "ocaml-" name))))
|
||
|
|
||
|
(define (dependencies->inputs dependencies)
|
||
|
"Transform the list of dependencies in a list of inputs."
|
||
|
(if (not dependencies)
|
||
|
'()
|
||
|
(map (lambda (input)
|
||
|
(list input (list 'unquote (string->symbol input))))
|
||
|
(map ocaml-name->guix-name dependencies))))
|
||
|
|
||
|
(define (opam->guix-package name)
|
||
|
(let* ((hashtable (urls->hashtable (opam-urls)))
|
||
|
(versions (vhash-ref hashtable name #f)))
|
||
|
(unless (eq? versions #f)
|
||
|
(let* ((version (latest-version versions))
|
||
|
(package-url (string-append "https://opam.ocaml.org/packages/" name
|
||
|
"/" name "." version "/"))
|
||
|
(url-url (string-append package-url "url"))
|
||
|
(opam-url (string-append package-url "opam"))
|
||
|
(source-url (fetch-package-url url-url))
|
||
|
(metadata (fetch-package-metadata opam-url))
|
||
|
(dependencies (assoc-ref metadata "inputs"))
|
||
|
(inputs (dependencies->inputs dependencies)))
|
||
|
(call-with-temporary-output-file
|
||
|
(lambda (temp port)
|
||
|
(and (url-fetch source-url temp)
|
||
|
`(package
|
||
|
(name ,(ocaml-name->guix-name name))
|
||
|
(version ,version)
|
||
|
(source
|
||
|
(origin
|
||
|
(method url-fetch)
|
||
|
(uri ,source-url)
|
||
|
(sha256 (base32 ,(guix-hash-url temp)))))
|
||
|
(build-system ocaml-build-system)
|
||
|
,@(if (null? inputs)
|
||
|
'()
|
||
|
`((inputs ,(list 'quasiquote inputs))))
|
||
|
(home-page ,(assoc-ref metadata "homepage"))
|
||
|
(synopsis "")
|
||
|
(description "")
|
||
|
(license ,@(string->license (assoc-ref metadata "license")))))))))))
|