1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2015 David Thompson <davet@gnu.org>
3 ;;; Copyright © 2016 Ben Woodcroft <donttrustben@gmail.com>
4 ;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
5 ;;; Copyright © 2020, 2021 Ludovic Courtès <ludo@gnu.org>
6 ;;; Copyright © 2020 Martin Becze <mjbecze@riseup.net>
7 ;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
8 ;;; Copyright © 2022 Taiju HIGASHI <higashi@taiju.info>
10 ;;; This file is part of GNU Guix.
12 ;;; GNU Guix is free software; you can redistribute it and/or modify it
13 ;;; under the terms of the GNU General Public License as published by
14 ;;; the Free Software Foundation; either version 3 of the License, or (at
15 ;;; your option) any later version.
17 ;;; GNU Guix is distributed in the hope that it will be useful, but
18 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;;; GNU General Public License for more details.
22 ;;; You should have received a copy of the GNU General Public License
23 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
25 (define-module (guix import gem)
26 #:use-module (ice-9 match)
27 #:use-module (srfi srfi-1)
29 #:use-module ((guix download) #:prefix download:)
30 #:use-module (guix import utils)
31 #:use-module (guix import json)
32 #:use-module (guix packages)
33 #:use-module (guix upstream)
34 #:use-module ((guix licenses) #:prefix license:)
35 #:use-module (guix base16)
36 #:use-module (guix base32)
37 #:use-module ((guix build-system ruby) #:select (rubygems-uri))
38 #:export (gem->guix-package
40 gem-recursive-import))
42 ;; Gems as defined by the API at <https://rubygems.org/api/v1/gems>.
43 (define-json-mapping <gem> make-gem gem?
45 (name gem-name) ;string
46 (platform gem-platform) ;string
47 (version gem-version) ;string
48 (authors gem-authors) ;string
49 (licenses gem-licenses "licenses" ;list of strings
51 ;; This is sometimes #nil (the JSON 'null' value). Arrange
52 ;; to always return a list.
53 (cond ((not licenses) '())
54 ((unspecified? licenses) '())
55 ((vector? licenses) (vector->list licenses))
58 (sha256 gem-sha256 "sha" ;bytevector
59 base16-string->bytevector)
60 (home-page gem-home-page "homepage_uri") ;string
61 (dependencies gem-dependencies "dependencies" ;<gem-dependencies>
62 json->gem-dependencies))
64 (define-json-mapping <gem-dependencies> make-gem-dependencies
66 json->gem-dependencies
67 (development gem-dependencies-development ;list of <gem-dependency>
69 json->gem-dependency-list)
70 (runtime gem-dependencies-runtime ;list of <gem-dependency>
72 json->gem-dependency-list))
74 (define (json->gem-dependency-list vector)
75 (if (and vector (not (unspecified? vector)))
76 (map json->gem-dependency (vector->list vector))
79 (define-json-mapping <gem-dependency> make-gem-dependency gem-dependency?
81 (name gem-dependency-name) ;string
82 (requirements gem-dependency-requirements)) ;string
85 (define* (rubygems-fetch name #:optional version)
86 "Return a <gem> record for the package NAME and VERSION, or #f on failure. If VERSION is #f or missing, return the latest version gem."
89 (string-append "https://rubygems.org/api/v2/rubygems/" name "/versions/" version ".json")
90 (string-append "https://rubygems.org/api/v1/gems/" name ".json")))
93 (define (ruby-package-name name)
94 "Given the NAME of a package on RubyGems, return a Guix-compliant name for
96 (if (string-prefix? "ruby-" name)
98 (string-append "ruby-" (snake-case name))))
100 (define (make-gem-sexp name version hash home-page synopsis description
101 dependencies licenses)
102 "Return the `package' s-expression for a Ruby package with the given NAME,
103 VERSION, HASH, HOME-PAGE, DESCRIPTION, DEPENDENCIES, and LICENSES."
105 (name ,(ruby-package-name name))
109 (uri (rubygems-uri ,name version))
112 ,(bytevector->nix-base32-string hash)))))
113 (build-system ruby-build-system)
114 ,@(if (null? dependencies)
117 (list ,@(map string->symbol dependencies)))))
119 (description ,description)
120 (home-page ,home-page)
121 (license ,(match licenses
123 ((license) (license->symbol license))
124 (_ `(list ,@(map license->symbol licenses)))))))
126 (define* (gem->guix-package package-name #:key (repo 'rubygems) version)
127 "Fetch the metadata for PACKAGE-NAME from rubygems.org, and return the
128 `package' s-expression corresponding to that package, or #f on failure.
129 Optionally include a VERSION string to fetch a specific version gem."
130 (let ((gem (if version
131 (rubygems-fetch package-name version)
132 (rubygems-fetch package-name))))
134 (let* ((dependencies-names (map gem-dependency-name
135 (gem-dependencies-runtime
136 (gem-dependencies gem))))
137 (dependencies (map (lambda (dep)
138 (if (string=? dep "bundler")
139 "bundler" ; special case, no prefix
140 (ruby-package-name dep)))
142 (licenses (map string->license (gem-licenses gem))))
143 (values (make-gem-sexp (gem-name gem) (gem-version gem)
144 (gem-sha256 gem) (gem-home-page gem)
146 (beautify-description (gem-info gem))
152 (define (guix-package->gem-name package)
153 "Given a PACKAGE built from rubygems.org, return the name of the
154 package on RubyGems."
155 (let ((source-url (and=> (package-source package) origin-uri)))
156 ;; The URL has the form:
157 ;; 'https://rubygems.org/downloads/' +
158 ;; package name + '-' + version + '.gem'
159 ;; e.g. "https://rubygems.org/downloads/hashery-2.1.1.gem"
160 (substring source-url 31 (string-rindex source-url #\-))))
162 (define (string->license str)
163 "Convert the string STR into a license object."
165 ("GNU LGPL" license:lgpl2.0)
167 ((or "BSD" "BSD License") license:bsd-3)
168 ((or "MIT" "MIT license" "Expat license") license:expat)
169 ("Public domain" license:public-domain)
170 ((or "Apache License, Version 2.0" "Apache 2.0") license:asl2.0)
174 (url-prefix-predicate "https://rubygems.org/downloads/"))
176 (define (latest-release package)
177 "Return an <upstream-source> for the latest release of PACKAGE."
178 (let* ((gem-name (guix-package->gem-name package))
179 (gem (rubygems-fetch gem-name))
180 (version (gem-version gem))
181 (url (rubygems-uri gem-name version)))
183 (package (package-name package))
190 (description "Updater for RubyGem packages")
192 (latest latest-release)))
194 (define* (gem-recursive-import package-name #:optional version)
195 (recursive-import package-name
197 #:repo->guix-package gem->guix-package
198 #:guix-name ruby-package-name