gnu: emacs-org: Update to 9.4.
[jackhill/guix/guix.git] / guix / import / opam.scm
CommitLineData
282f9179 1;;; GNU Guix --- Functional package management for GNU
b24443bf
JL
2;;; Copyright © 2018 Julien Lepiller <julien@lepiller.eu>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (guix import opam)
cce654fa 20 #:use-module (ice-9 ftw)
b24443bf 21 #:use-module (ice-9 match)
cce654fa
JL
22 #:use-module (ice-9 peg)
23 #:use-module (ice-9 receive)
b24443bf 24 #:use-module ((ice-9 rdelim) #:select (read-line))
cce654fa
JL
25 #:use-module (ice-9 textual-ports)
26 #:use-module (ice-9 vlist)
b24443bf 27 #:use-module (srfi srfi-1)
cce654fa 28 #:use-module (srfi srfi-2)
b24443bf 29 #:use-module (web uri)
755e6d4a
JL
30 #:use-module (guix build-system)
31 #:use-module (guix build-system ocaml)
b24443bf 32 #:use-module (guix http-client)
cce654fa
JL
33 #:use-module (guix git)
34 #:use-module (guix ui)
755e6d4a
JL
35 #:use-module (guix packages)
36 #:use-module (guix upstream)
b24443bf
JL
37 #:use-module (guix utils)
38 #:use-module (guix import utils)
39 #:use-module ((guix licenses) #:prefix license:)
6090b0be 40 #:export (opam->guix-package
755e6d4a 41 opam-recursive-import
282f9179
LC
42 %opam-updater
43
44 ;; The following patterns are exported for testing purposes.
45 string-pat
46 multiline-string
47 list-pat
48 dict
49 condition))
b24443bf 50
cce654fa 51;; Define a PEG parser for the opam format
f31ce9ec
JL
52(define-peg-pattern comment none (and "#" (* STRCHR) "\n"))
53(define-peg-pattern SP none (or " " "\n" comment))
cce654fa
JL
54(define-peg-pattern SP2 body (or " " "\n"))
55(define-peg-pattern QUOTE none "\"")
56(define-peg-pattern QUOTE2 body "\"")
57(define-peg-pattern COLON none ":")
58;; A string character is any character that is not a quote, or a quote preceded by a backslash.
59(define-peg-pattern STRCHR body
c60d98d4 60 (or " " "!" "\n" (and (ignore "\\") "\"")
cce654fa
JL
61 (and (ignore "\\") "\\") (range #\# #\頋)))
62(define-peg-pattern operator all (or "=" "!" "<" ">"))
63
64(define-peg-pattern records body (* (and (or record weird-record) (* SP))))
65(define-peg-pattern record all (and key COLON (* SP) value))
66(define-peg-pattern weird-record all (and key (* SP) dict))
67(define-peg-pattern key body (+ (or (range #\a #\z) "-")))
68(define-peg-pattern value body (and (or conditional-value ground-value operator) (* SP)))
3e159dd0
JL
69(define-peg-pattern choice-pat all (and (ignore "(") (* SP) choice (* SP) (ignore ")")))
70(define-peg-pattern choice body
71 (or (and (or conditional-value ground-value) (* SP) (ignore "|") (* SP) choice)
72 conditional-value
73 ground-value))
74(define-peg-pattern ground-value body (and (or multiline-string string-pat choice-pat list-pat var) (* SP)))
cce654fa
JL
75(define-peg-pattern conditional-value all (and ground-value (* SP) condition))
76(define-peg-pattern string-pat all (and QUOTE (* STRCHR) QUOTE))
77(define-peg-pattern list-pat all (and (ignore "[") (* SP) (* (and value (* SP))) (ignore "]")))
78(define-peg-pattern var all (+ (or (range #\a #\z) "-")))
79(define-peg-pattern multiline-string all
80 (and QUOTE QUOTE QUOTE (* SP)
81 (* (or SP2 STRCHR (and QUOTE2 (not-followed-by QUOTE))
82 (and QUOTE2 QUOTE2 (not-followed-by QUOTE))))
83 QUOTE QUOTE QUOTE))
84(define-peg-pattern dict all (and (ignore "{") (* SP) records (* SP) (ignore "}")))
85
86(define-peg-pattern condition body (and (ignore "{") condition-form (ignore "}")))
87
88(define-peg-pattern condition-form body
89 (and
90 (* SP)
91 (or condition-and condition-or condition-form2)
92 (* SP)))
93(define-peg-pattern condition-form2 body
94 (and (* SP) (or condition-greater-or-equal condition-greater
95 condition-lower-or-equal condition-lower
3e159dd0
JL
96 condition-neq condition-eq condition-not
97 condition-content) (* SP)))
cce654fa
JL
98
99;(define-peg-pattern condition-operator all (and (ignore operator) (* SP) condition-string))
100(define-peg-pattern condition-greater-or-equal all (and (ignore (and ">" "=")) (* SP) condition-string))
101(define-peg-pattern condition-greater all (and (ignore ">") (* SP) condition-string))
102(define-peg-pattern condition-lower-or-equal all (and (ignore (and "<" "=")) (* SP) condition-string))
103(define-peg-pattern condition-lower all (and (ignore "<") (* SP) condition-string))
104(define-peg-pattern condition-and all (and condition-form2 (* SP) (? (ignore "&")) (* SP) condition-form))
105(define-peg-pattern condition-or all (and condition-form2 (* SP) (ignore "|") (* SP) condition-form))
0f4432c6
JL
106(define-peg-pattern condition-eq all (and (? condition-content) (* SP) (ignore "=") (* SP) condition-content))
107(define-peg-pattern condition-neq all (and (? condition-content) (* SP) (ignore (and "!" "=")) (* SP) condition-content))
3e159dd0
JL
108(define-peg-pattern condition-not all (and (ignore (and "!")) (* SP) condition-content))
109(define-peg-pattern condition-content body (or condition-paren condition-string condition-var))
cce654fa 110(define-peg-pattern condition-content2 body (and condition-content (* SP) (not-followed-by (or "&" "=" "!"))))
3e159dd0 111(define-peg-pattern condition-paren body (and "(" condition-form ")"))
cce654fa 112(define-peg-pattern condition-string all (and QUOTE (* STRCHR) QUOTE))
3e159dd0 113(define-peg-pattern condition-var all (+ (or (range #\a #\z) "-" ":")))
cce654fa
JL
114
115(define (get-opam-repository)
116 "Update or fetch the latest version of the opam repository and return the
117path to the repository."
8d1d5657 118 (receive (location commit _)
cce654fa
JL
119 (update-cached-checkout "https://github.com/ocaml/opam-repository")
120 location))
b24443bf
JL
121
122(define (latest-version versions)
123 "Find the most recent version from a list of versions."
cce654fa
JL
124 (fold (lambda (a b) (if (version>? a b) a b)) (car versions) versions))
125
126(define (find-latest-version package repository)
127 "Get the latest version of a package as described in the given repository."
128 (let* ((dir (string-append repository "/packages/" package))
129 (versions (scandir dir (lambda (name) (not (string-prefix? "." name))))))
130 (if versions
131 (let ((versions (map
132 (lambda (dir)
133 (string-join (cdr (string-split dir #\.)) "."))
134 versions)))
c3a191fa
JL
135 ;; Workaround for janestreet re-versionning
136 (let ((v-versions (filter (lambda (version) (string-prefix? "v" version)) versions)))
137 (if (null? v-versions)
138 (latest-version versions)
139 (string-append "v" (latest-version (map (lambda (version) (substring version 1)) v-versions))))))
cce654fa
JL
140 (begin
141 (format #t (G_ "Package not found in opam repository: ~a~%") package)
142 #f))))
143
144(define (get-metadata opam-file)
145 (with-input-from-file opam-file
146 (lambda _
147 (peg:tree (match-pattern records (get-string-all (current-input-port)))))))
b24443bf 148
beee37ec
JL
149(define (substitute-char str what with)
150 (string-join (string-split str what) with))
151
b24443bf 152(define (ocaml-name->guix-name name)
beee37ec
JL
153 (substitute-char
154 (cond
155 ((equal? name "ocamlfind") "ocaml-findlib")
156 ((string-prefix? "ocaml" name) name)
157 ((string-prefix? "conf-" name) (substring name 5))
158 (else (string-append "ocaml-" name)))
159 #\_ "-"))
b24443bf 160
cce654fa 161(define (metadata-ref file lookup)
cce654fa
JL
162 (fold (lambda (record acc)
163 (match record
164 ((record key val)
165 (if (equal? key lookup)
166 (match val
167 (('list-pat . stuff) stuff)
168 (('string-pat stuff) stuff)
169 (('multiline-string stuff) stuff)
170 (('dict records ...) records))
171 acc))))
172 #f file))
173
174(define (native? condition)
175 (match condition
176 (('condition-var var)
177 (match var
178 ("with-test" #t)
179 ("test" #t)
180 ("build" #t)
181 (_ #f)))
182 ((or ('condition-or cond-left cond-right) ('condition-and cond-left cond-right))
183 (or (native? cond-left)
184 (native? cond-right)))
185 (_ #f)))
186
187(define (dependency->input dependency)
188 (match dependency
189 (('string-pat str) str)
3e159dd0
JL
190 ;; Arbitrary select the first dependency
191 (('choice-pat choice ...) (dependency->input (car choice)))
cce654fa
JL
192 (('conditional-value val condition)
193 (if (native? condition) "" (dependency->input val)))))
194
195(define (dependency->native-input dependency)
196 (match dependency
197 (('string-pat str) "")
3e159dd0
JL
198 ;; Arbitrary select the first dependency
199 (('choice-pat choice ...) (dependency->input (car choice)))
cce654fa
JL
200 (('conditional-value val condition)
201 (if (native? condition) (dependency->input val) ""))))
202
6090b0be
JL
203(define (dependency->name dependency)
204 (match dependency
205 (('string-pat str) str)
3e159dd0
JL
206 ;; Arbitrary select the first dependency
207 (('choice-pat choice ...) (dependency->input (car choice)))
6090b0be
JL
208 (('conditional-value val condition)
209 (dependency->name val))))
210
211(define (dependency-list->names lst)
212 (filter
213 (lambda (name)
214 (not (or
215 (string-prefix? "conf-" name)
216 (equal? name "ocaml")
217 (equal? name "findlib"))))
218 (map dependency->name lst)))
219
cce654fa
JL
220(define (ocaml-names->guix-names names)
221 (map ocaml-name->guix-name
222 (remove (lambda (name)
223 (or (equal? "" name))
224 (equal? "ocaml" name))
225 names)))
226
227(define (depends->inputs depends)
228 (filter (lambda (name)
229 (and (not (equal? "" name))
230 (not (equal? "ocaml" name))
231 (not (equal? "ocamlfind" name))))
232 (map dependency->input depends)))
233
234(define (depends->native-inputs depends)
235 (filter (lambda (name) (not (equal? "" name)))
236 (map dependency->native-input depends)))
237
238(define (dependency-list->inputs lst)
239 (map
240 (lambda (dependency)
241 (list dependency (list 'unquote (string->symbol dependency))))
242 (ocaml-names->guix-names lst)))
b24443bf 243
282f9179
LC
244(define* (opam-fetch name #:optional (repository (get-opam-repository)))
245 (and-let* ((repository repository)
cce654fa 246 (version (find-latest-version name repository))
755e6d4a
JL
247 (file (string-append repository "/packages/" name "/" name "." version "/opam")))
248 `(("metadata" ,@(get-metadata file))
d3366a8e
JL
249 ("version" . ,(if (string-prefix? "v" version)
250 (substring version 1)
251 version)))))
755e6d4a 252
06f0453a 253(define* (opam->guix-package name #:key (repository (get-opam-repository)))
282f9179
LC
254 "Import OPAM package NAME from REPOSITORY (a directory name) or, if
255REPOSITORY is #f, from the official OPAM repository. Return a 'package' sexp
256or #f on failure."
257 (and-let* ((opam-file (opam-fetch name repository))
755e6d4a
JL
258 (version (assoc-ref opam-file "version"))
259 (opam-content (assoc-ref opam-file "metadata"))
6090b0be 260 (url-dict (metadata-ref opam-content "url"))
cce654fa
JL
261 (source-url (metadata-ref url-dict "src"))
262 (requirements (metadata-ref opam-content "depends"))
ba3ff730 263 (dependencies (dependency-list->names requirements))
7b1c7ecd 264 (native-dependencies (depends->native-inputs requirements))
cce654fa 265 (inputs (dependency-list->inputs (depends->inputs requirements)))
7b1c7ecd
JL
266 (native-inputs (dependency-list->inputs
267 ;; Do not add dune nor jbuilder since they are
268 ;; implicit inputs of the dune-build-system.
269 (filter
270 (lambda (name)
271 (not (member name '("dune" "jbuilder"))))
272 native-dependencies))))
273 ;; If one of these are required at build time, it means we
274 ;; can use the much nicer dune-build-system.
ba3ff730
JL
275 (let ((use-dune? (or (member "dune" (append dependencies native-dependencies))
276 (member "jbuilder" (append dependencies native-dependencies)))))
7b1c7ecd
JL
277 (call-with-temporary-output-file
278 (lambda (temp port)
279 (and (url-fetch source-url temp)
280 (values
281 `(package
282 (name ,(ocaml-name->guix-name name))
283 (version ,(if (string-prefix? "v" version)
284 (substring version 1)
285 version))
286 (source
287 (origin
288 (method url-fetch)
289 (uri ,source-url)
290 (sha256 (base32 ,(guix-hash-url temp)))))
291 (build-system ,(if use-dune?
292 'dune-build-system
293 'ocaml-build-system))
294 ,@(if (null? inputs)
295 '()
b2f810fe 296 `((propagated-inputs ,(list 'quasiquote inputs))))
7b1c7ecd
JL
297 ,@(if (null? native-inputs)
298 '()
299 `((native-inputs ,(list 'quasiquote native-inputs))))
300 ,@(if (equal? name (guix-name->opam-name (ocaml-name->guix-name name)))
301 '()
302 `((properties
303 ,(list 'quasiquote `((upstream-name . ,name))))))
304 (home-page ,(metadata-ref opam-content "homepage"))
305 (synopsis ,(metadata-ref opam-content "synopsis"))
306 (description ,(metadata-ref opam-content "description"))
307 (license #f))
ba3ff730
JL
308 (filter
309 (lambda (name)
310 (not (member name '("dune" "jbuilder"))))
311 dependencies))))))))
6090b0be
JL
312
313(define (opam-recursive-import package-name)
314 (recursive-import package-name #f
315 #:repo->guix-package (lambda (name repo)
316 (opam->guix-package name))
317 #:guix-name ocaml-name->guix-name))
755e6d4a 318
beee37ec
JL
319(define (guix-name->opam-name name)
320 (if (string-prefix? "ocaml-" name)
321 (substring name 6)
322 name))
323
755e6d4a
JL
324(define (guix-package->opam-name package)
325 "Given an OCaml PACKAGE built from OPAM, return the name of the
326package in OPAM."
327 (let ((upstream-name (assoc-ref
328 (package-properties package)
329 'upstream-name))
330 (name (package-name package)))
beee37ec
JL
331 (if upstream-name
332 upstream-name
333 (guix-name->opam-name name))))
755e6d4a
JL
334
335(define (opam-package? package)
336 "Return true if PACKAGE is an OCaml package from OPAM"
337 (and
a506a4c3 338 (member (build-system-name (package-build-system package)) '(dune ocaml))
755e6d4a
JL
339 (not (string-prefix? "ocaml4" (package-name package)))))
340
341(define (latest-release package)
342 "Return an <upstream-source> for the latest release of PACKAGE."
343 (and-let* ((opam-name (guix-package->opam-name package))
344 (opam-file (opam-fetch opam-name))
345 (version (assoc-ref opam-file "version"))
346 (opam-content (assoc-ref opam-file "metadata"))
347 (url-dict (metadata-ref opam-content "url"))
348 (source-url (metadata-ref url-dict "src")))
349 (upstream-source
350 (package (package-name package))
351 (version version)
352 (urls (list source-url)))))
353
354(define %opam-updater
355 (upstream-updater
356 (name 'opam)
357 (description "Updater for OPAM packages")
358 (pred opam-package?)
359 (latest latest-release)))