gnu: linux-libre 5.4: Update to 5.4.118.
[jackhill/guix/guix.git] / guix / utils.scm
CommitLineData
233e7676 1;;; GNU Guix --- Functional package management for GNU
579506e2 2;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
3f00ff8b 3;;; Copyright © 2013, 2014, 2015 Mark H Weaver <mhw@netris.org>
516e3b6f 4;;; Copyright © 2014 Eric Bavier <bavier@member.fsf.org>
29a7c98a 5;;; Copyright © 2014 Ian Denhardt <ian@zenhack.net>
1b846da8 6;;; Copyright © 2016 Mathieu Lirzin <mthl@gnu.org>
bbd00d20 7;;; Copyright © 2015 David Thompson <davet@gnu.org>
e45b573c 8;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
fb9a57a8 9;;; Copyright © 2018, 2020 Marius Bakke <marius@gnu.org>
46886728 10;;; Copyright © 2020 Efraim Flashner <efraim@flashner.co.il>
9505b54a 11;;; Copyright © 2021 Simon Tournier <zimon.toutoune@gmail.com>
fc7cf0c1 12;;; Copyright © 2021 Chris Marusich <cmmarusich@gmail.com>
e3deeebb 13;;;
233e7676 14;;; This file is part of GNU Guix.
e3deeebb 15;;;
233e7676 16;;; GNU Guix is free software; you can redistribute it and/or modify it
e3deeebb
LC
17;;; under the terms of the GNU General Public License as published by
18;;; the Free Software Foundation; either version 3 of the License, or (at
19;;; your option) any later version.
20;;;
233e7676 21;;; GNU Guix is distributed in the hope that it will be useful, but
e3deeebb
LC
22;;; WITHOUT ANY WARRANTY; without even the implied warranty of
23;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24;;; GNU General Public License for more details.
25;;;
26;;; You should have received a copy of the GNU General Public License
233e7676 27;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
e3deeebb
LC
28
29(define-module (guix utils)
00e219d1 30 #:use-module (guix config)
38b3122a 31 #:use-module (srfi srfi-1)
72d86963 32 #:use-module (srfi srfi-9)
01ac19dc 33 #:use-module (srfi srfi-11)
38b3122a 34 #:use-module (srfi srfi-26)
de4c3f26 35 #:use-module (srfi srfi-39)
27f7cbc9 36 #:use-module (ice-9 ftw)
6a7c4636 37 #:use-module (rnrs io ports) ;need 'port-position' etc.
c8be6f0d 38 #:use-module ((rnrs bytevectors) #:select (bytevector-u8-set!))
f9704f17 39 #:use-module (guix memoization)
27f7cbc9 40 #:use-module ((guix build utils) #:select (dump-port mkdir-p delete-file-recursively))
1752a17a 41 #:use-module ((guix build syscalls) #:select (mkdtemp! fdatasync))
9505b54a 42 #:use-module ((guix combinators) #:select (fold2))
a5e2fc73 43 #:use-module (guix diagnostics) ;<location>, &error-location, etc.
38b3122a 44 #:use-module (ice-9 format)
98090557 45 #:use-module (ice-9 regex)
72d86963 46 #:use-module (ice-9 match)
8ef3401f 47 #:use-module (ice-9 format)
31044751 48 #:use-module ((ice-9 iconv) #:prefix iconv:)
a04aef24 49 #:autoload (zlib) (make-zlib-input-port make-zlib-output-port)
2cd5c038 50 #:use-module (system foreign)
6a79eed9 51 #:re-export (<location> ;for backwards compatibility
a5e2fc73
LC
52 location
53 location?
54 location-file
55 location-line
56 location-column
57 source-properties->location
58 location->source-properties
59
60 &error-location
61 error-location?
f9a8dd05
LC
62 error-location
63
64 &fix-hint
65 fix-hint?
66 condition-fix-hint)
4c0c4db0 67 #:export (strip-keyword-arguments
0af2c24e
LC
68 default-keyword-arguments
69 substitute-keyword-arguments
6071122b 70 ensure-keyword-arguments
ff352cfb 71
07c8a98c
LC
72 current-source-directory
73
8eb80484 74 nix-system->gnu-triplet
98090557 75 gnu-triplet->nix-system
9b48fb88 76 %current-system
9c1edabd 77 %current-target-system
1b846da8 78 package-name->name+version
cba36e64 79 target-mingw?
e45b573c 80 target-arm32?
74f17180
MO
81 target-aarch64?
82 target-arm?
fc7cf0c1 83 target-powerpc?
259341cf 84 target-64bit?
fb9a57a8 85 cc-for-target
b720cf90 86 cxx-for-target
3d395e3d 87 pkg-config-for-target
fb9a57a8 88
0d1e6ce4
MW
89 version-compare
90 version>?
cde1e967 91 version>=?
29a7c98a 92 version-prefix
46886728 93 version-major+minor+point
29a7c98a 94 version-major+minor
47dc9a0d 95 version-major
579506e2 96 version-unique-prefix
7db3ff4a 97 guile-version>?
437f62f0 98 version-prefix?
56b943de 99 string-replace-substring
0fdd3bea 100 file-extension
ac10e0e1 101 file-sans-extension
da1027a7 102 tarball-sans-extension
089b1678 103 compressed-file?
3bb168b0 104 switch-symlinks
861693f3 105 call-with-temporary-output-file
db6e5e2b 106 call-with-temporary-directory
04d4c8a4 107 with-atomic-file-output
f0e492f0 108
d67a8819
LC
109 with-environment-variables
110 arguments-from-environment-variable
111
f0e492f0 112 config-directory
739ab68b 113 cache-directory
f0e492f0 114
d50cb56d 115 readlink*
50a3d594 116 edit-expression
7a8024a3
LC
117
118 filtered-port
80dea563 119 decompressed-port
01ac19dc
LC
120 call-with-decompressed-port
121 compressed-output-port
c8be6f0d 122 call-with-compressed-output-port
9505b54a 123 canonical-newline-port
124
125 string-distance
126 string-closest))
e3deeebb 127
38b3122a 128\f
d67a8819
LC
129;;;
130;;; Environment variables.
131;;;
132
133(define (call-with-environment-variables variables thunk)
134 "Call THUNK with the environment VARIABLES set."
135 (let ((environment (environ)))
136 (dynamic-wind
137 (lambda ()
138 (for-each (match-lambda
139 ((variable value)
140 (setenv variable value)))
141 variables))
142 thunk
143 (lambda ()
144 (environ environment)))))
145
146(define-syntax-rule (with-environment-variables variables exp ...)
147 "Evaluate EXP with the given environment VARIABLES set."
148 (call-with-environment-variables variables
149 (lambda () exp ...)))
150
151(define (arguments-from-environment-variable variable)
152 "Retrieve value of environment variable denoted by string VARIABLE in the
153form of a list of strings (`char-set:graphic' tokens) suitable for consumption
154by `args-fold', if VARIABLE is defined, otherwise return an empty list."
155 (let ((env (getenv variable)))
156 (if env
157 (string-tokenize env char-set:graphic)
158 '())))
159
160\f
e0fbbc88
LC
161;;;
162;;; Filtering & pipes.
163;;;
164
165(define (filtered-port command input)
166 "Return an input port where data drained from INPUT is filtered through
167COMMAND (a list). In addition, return a list of PIDs that the caller must
101d9f3f
LC
168wait. When INPUT is a file port, it must be unbuffered; otherwise, any
169buffered data is lost."
e0fbbc88 170 (let loop ((input input)
443eb4e9 171 (pids '()))
e0fbbc88
LC
172 (if (file-port? input)
173 (match (pipe)
174 ((in . out)
175 (match (primitive-fork)
176 (0
443eb4e9
LC
177 (dynamic-wind
178 (const #f)
179 (lambda ()
180 (close-port in)
181 (close-port (current-input-port))
182 (dup2 (fileno input) 0)
183 (close-port (current-output-port))
184 (dup2 (fileno out) 1)
185 (catch 'system-error
186 (lambda ()
187 (apply execl (car command) command))
188 (lambda args
189 (format (current-error-port)
190 "filtered-port: failed to execute '~{~a ~}': ~a~%"
191 command (strerror (system-error-errno args))))))
192 (lambda ()
193 (primitive-_exit 1))))
e0fbbc88
LC
194 (child
195 (close-port out)
196 (values in (cons child pids))))))
197
198 ;; INPUT is not a file port, so fork just for the sake of tunneling it
199 ;; through a file port.
200 (match (pipe)
201 ((in . out)
202 (match (primitive-fork)
203 (0
204 (dynamic-wind
205 (const #t)
206 (lambda ()
207 (close-port in)
208 (dump-port input out))
209 (lambda ()
5efa0e4d 210 (close-port input)
e0fbbc88 211 (false-if-exception (close out))
443eb4e9 212 (primitive-_exit 0))))
e0fbbc88 213 (child
5efa0e4d 214 (close-port input)
e0fbbc88
LC
215 (close-port out)
216 (loop in (cons child pids)))))))))
217
4e48923e
LC
218(define (lzip-port proc port . args)
219 "Return the lzip port produced by calling PROC (a symbol) on PORT and ARGS.
220Raise an error if lzlib support is missing."
4c0c65ac 221 (let ((make-port (module-ref (resolve-interface '(lzlib)) proc)))
db0cecdf
LC
222 (make-port port)))
223
224(define (zstd-port proc port . args)
225 "Return the zstd port produced by calling PROC (a symbol) on PORT and ARGS.
226Raise an error if zstd support is missing."
227 (let ((make-port (module-ref (resolve-interface '(zstd)) proc)))
228 (make-port port)))
4e48923e 229
7a8024a3
LC
230(define (decompressed-port compression input)
231 "Return an input port where INPUT is decompressed according to COMPRESSION,
232a symbol such as 'xz."
233 (match compression
234 ((or #f 'none) (values input '()))
235 ('bzip2 (filtered-port `(,%bzip2 "-dc") input))
e9be2c54 236 ('xz (filtered-port `(,%xz "-dc") input))
a04aef24
LC
237 ('gzip (values (make-zlib-input-port input #:format 'gzip)
238 '()))
4e48923e
LC
239 ('lzip (values (lzip-port 'make-lzip-input-port input)
240 '()))
db0cecdf
LC
241 ('zstd (values (zstd-port 'make-zstd-input-port input)
242 '()))
4e48923e 243 (_ (error "unsupported compression scheme" compression))))
7a8024a3 244
01ac19dc
LC
245(define (call-with-decompressed-port compression port proc)
246 "Call PROC with a wrapper around PORT, a file port, that decompresses data
30ce8012 247read from PORT according to COMPRESSION, a symbol such as 'xz."
01ac19dc
LC
248 (let-values (((decompressed pids)
249 (decompressed-port compression port)))
250 (dynamic-wind
251 (const #f)
252 (lambda ()
01ac19dc
LC
253 (proc decompressed))
254 (lambda ()
255 (close-port decompressed)
256 (unless (every (compose zero? cdr waitpid) pids)
257 (error "decompressed-port failure" pids))))))
258
80dea563
LC
259(define (filtered-output-port command output)
260 "Return an output port. Data written to that port is filtered through
261COMMAND and written to OUTPUT, an output file port. In addition, return a
262list of PIDs to wait for. OUTPUT must be unbuffered; otherwise, any buffered
263data is lost."
264 (match (pipe)
265 ((in . out)
266 (match (primitive-fork)
267 (0
268 (dynamic-wind
269 (const #f)
270 (lambda ()
271 (close-port out)
272 (close-port (current-input-port))
273 (dup2 (fileno in) 0)
274 (close-port (current-output-port))
275 (dup2 (fileno output) 1)
276 (catch 'system-error
277 (lambda ()
278 (apply execl (car command) command))
279 (lambda args
280 (format (current-error-port)
281 "filtered-output-port: failed to execute '~{~a ~}': ~a~%"
282 command (strerror (system-error-errno args))))))
283 (lambda ()
284 (primitive-_exit 1))))
285 (child
286 (close-port in)
287 (values out (list child)))))))
288
62d2b571
LC
289(define* (compressed-output-port compression output
290 #:key (options '()))
80dea563
LC
291 "Return an output port whose input is compressed according to COMPRESSION,
292a symbol such as 'xz, and then written to OUTPUT. In addition return a list
62d2b571
LC
293of PIDs to wait for. OPTIONS is a list of strings passed to the compression
294program--e.g., '(\"--fast\")."
80dea563
LC
295 (match compression
296 ((or #f 'none) (values output '()))
62d2b571 297 ('bzip2 (filtered-output-port `(,%bzip2 "-c" ,@options) output))
e9be2c54 298 ('xz (filtered-output-port `(,%xz "-c" ,@options) output))
a04aef24
LC
299 ('gzip (values (make-zlib-output-port output #:format 'gzip)
300 '()))
4e48923e
LC
301 ('lzip (values (lzip-port 'make-lzip-output-port output)
302 '()))
db0cecdf
LC
303 ('zstd (values (zstd-port 'make-zstd-output-port output)
304 '()))
4e48923e 305 (_ (error "unsupported compression scheme" compression))))
80dea563 306
62d2b571
LC
307(define* (call-with-compressed-output-port compression port proc
308 #:key (options '()))
01ac19dc 309 "Call PROC with a wrapper around PORT, a file port, that compresses data
62d2b571
LC
310that goes to PORT according to COMPRESSION, a symbol such as 'xz. OPTIONS is
311a list of command-line arguments passed to the compression program."
01ac19dc 312 (let-values (((compressed pids)
62d2b571
LC
313 (compressed-output-port compression port
314 #:options options)))
01ac19dc
LC
315 (dynamic-wind
316 (const #f)
317 (lambda ()
01ac19dc
LC
318 (proc compressed))
319 (lambda ()
320 (close-port compressed)
321 (unless (every (compose zero? cdr waitpid) pids)
322 (error "compressed-output-port failure" pids))))))
323
50a3d594
SB
324(define* (edit-expression source-properties proc #:key (encoding "UTF-8"))
325 "Edit the expression specified by SOURCE-PROPERTIES using PROC, which should
326be a procedure that takes the original expression in string and returns a new
327one. ENCODING will be used to interpret all port I/O, it default to UTF-8.
328This procedure returns #t on success."
329 (with-fluids ((%default-port-encoding encoding))
330 (let* ((file (assq-ref source-properties 'filename))
331 (line (assq-ref source-properties 'line))
332 (column (assq-ref source-properties 'column))
333 (in (open-input-file file))
334 ;; The start byte position of the expression.
335 (start (begin (while (not (and (= line (port-line in))
336 (= column (port-column in))))
337 (when (eof-object? (read-char in))
338 (error (format #f "~a: end of file~%" in))))
339 (ftell in)))
340 ;; The end byte position of the expression.
341 (end (begin (read in) (ftell in))))
342 (seek in 0 SEEK_SET) ; read from the beginning of the file.
343 (let* ((pre-bv (get-bytevector-n in start))
344 ;; The expression in string form.
31044751 345 (str (iconv:bytevector->string
50a3d594
SB
346 (get-bytevector-n in (- end start))
347 (port-encoding in)))
348 (post-bv (get-bytevector-all in))
349 (str* (proc str)))
350 ;; Verify the edited expression is still a scheme expression.
351 (call-with-input-string str* read)
352 ;; Update the file with edited expression.
353 (with-atomic-file-output file
354 (lambda (out)
355 (put-bytevector out pre-bv)
356 (display str* out)
357 ;; post-bv maybe the end-of-file object.
358 (when (not (eof-object? post-bv))
359 (put-bytevector out post-bv))
360 #t))))))
361
e0fbbc88 362\f
de4c3f26 363;;;
958dd3ce 364;;; Keyword arguments.
de4c3f26
LC
365;;;
366
5e110382
LC
367(define (strip-keyword-arguments keywords args)
368 "Remove all of the keyword arguments listed in KEYWORDS from ARGS."
369 (let loop ((args args)
370 (result '()))
371 (match args
372 (()
373 (reverse result))
374 (((? keyword? kw) arg . rest)
375 (loop rest
376 (if (memq kw keywords)
377 result
378 (cons* arg kw result))))
379 ((head . tail)
380 (loop tail (cons head result))))))
381
0af2c24e
LC
382(define (default-keyword-arguments args defaults)
383 "Return ARGS augmented with any keyword/value from DEFAULTS for
384keywords not already present in ARGS."
385 (let loop ((defaults defaults)
386 (args args))
387 (match defaults
388 ((kw value rest ...)
389 (loop rest
347df601 390 (if (memq kw args)
0af2c24e
LC
391 args
392 (cons* kw value args))))
393 (()
394 args))))
395
b8b129eb
EB
396(define-syntax collect-default-args
397 (syntax-rules ()
398 ((_)
399 '())
400 ((_ (_ _) rest ...)
401 (collect-default-args rest ...))
402 ((_ (kw _ dflt) rest ...)
403 (cons* kw dflt (collect-default-args rest ...)))))
404
0af2c24e
LC
405(define-syntax substitute-keyword-arguments
406 (syntax-rules ()
407 "Return a new list of arguments where the value for keyword arg KW is
b8b129eb
EB
408replaced by EXP. EXP is evaluated in a context where VAR is bound to the
409previous value of the keyword argument, or DFLT if given."
410 ((_ original-args ((kw var dflt ...) exp) ...)
411 (let loop ((args (default-keyword-arguments
412 original-args
413 (collect-default-args (kw var dflt ...) ...)))
0af2c24e
LC
414 (before '()))
415 (match args
416 ((kw var rest (... ...))
417 (loop rest (cons* exp kw before)))
418 ...
419 ((x rest (... ...))
420 (loop rest (cons x before)))
421 (()
422 (reverse before)))))))
423
6071122b
LC
424(define (delkw kw lst)
425 "Remove KW and its associated value from LST, a keyword/value list such
426as '(#:foo 1 #:bar 2)."
427 (let loop ((lst lst)
428 (result '()))
429 (match lst
430 (()
431 (reverse result))
432 ((kw? value rest ...)
433 (if (eq? kw? kw)
434 (append (reverse result) rest)
435 (loop rest (cons* value kw? result)))))))
436
437(define (ensure-keyword-arguments args kw/values)
438 "Force the keywords arguments KW/VALUES in the keyword argument list ARGS.
439For instance:
440
441 (ensure-keyword-arguments '(#:foo 2) '(#:foo 2))
442 => (#:foo 2)
443
444 (ensure-keyword-arguments '(#:foo 2) '(#:bar 3))
445 => (#:foo 2 #:bar 3)
446
447 (ensure-keyword-arguments '(#:foo 2) '(#:bar 3 #:foo 42))
448 => (#:foo 42 #:bar 3)
449"
450 (let loop ((args args)
451 (kw/values kw/values)
452 (result '()))
453 (match args
454 (()
455 (append (reverse result) kw/values))
456 ((kw value rest ...)
457 (match (memq kw kw/values)
458 ((_ value . _)
459 (loop rest (delkw kw kw/values) (cons* value kw result)))
460 (#f
461 (loop rest kw/values (cons* value kw result))))))))
462
958dd3ce
LC
463\f
464;;;
465;;; System strings.
466;;;
467
8eb80484
MW
468(define* (nix-system->gnu-triplet
469 #:optional (system (%current-system)) (vendor "unknown"))
470 "Return a guess of the GNU triplet corresponding to Nix system
471identifier SYSTEM."
3f00ff8b
MW
472 (match system
473 ("armhf-linux"
474 (string-append "arm-" vendor "-linux-gnueabihf"))
475 (_
476 (let* ((dash (string-index system #\-))
477 (arch (substring system 0 dash))
478 (os (substring system (+ 1 dash))))
479 (string-append arch
480 "-" vendor "-"
481 (if (string=? os "linux")
482 "linux-gnu"
483 os))))))
8eb80484 484
98090557
LC
485(define (gnu-triplet->nix-system triplet)
486 "Return the Nix system type corresponding to TRIPLET, a GNU triplet as
487returned by `config.guess'."
488 (let ((triplet (cond ((string-match "^i[345]86-(.*)$" triplet)
489 =>
490 (lambda (m)
491 (string-append "i686-" (match:substring m 1))))
492 (else triplet))))
3f00ff8b
MW
493 (cond ((string-match "^arm[^-]*-([^-]+-)?linux-gnueabihf" triplet)
494 "armhf-linux")
495 ((string-match "^([^-]+)-([^-]+-)?linux-gnu.*" triplet)
98090557
LC
496 =>
497 (lambda (m)
498 ;; Nix omits `-gnu' for GNU/Linux.
499 (string-append (match:substring m 1) "-linux")))
500 ((string-match "^([^-]+)-([^-]+-)?([[:alpha:]]+)([0-9]+\\.?)*$" triplet)
501 =>
502 (lambda (m)
503 ;; Nix strip the version number from names such as `gnu0.3',
504 ;; `darwin10.2.0', etc., and always strips the vendor part.
505 (string-append (match:substring m 1) "-"
506 (match:substring m 3))))
507 (else triplet))))
508
509(define %current-system
510 ;; System type as expected by Nix, usually ARCHITECTURE-KERNEL.
d8eea3d2
LC
511 ;; By default, this is equal to (gnu-triplet->nix-system %host-type).
512 (make-parameter %system))
ff352cfb 513
9c1edabd
LC
514(define %current-target-system
515 ;; Either #f or a GNU triplet representing the target system we are
516 ;; cross-building to.
517 (make-parameter #f))
518
aa042770
LC
519(define* (package-name->name+version spec
520 #:optional (delimiter #\@))
1b846da8
ML
521 "Given SPEC, a package name like \"foo@0.9.1b\", return two values: \"foo\"
522and \"0.9.1b\". When the version part is unavailable, SPEC and #f are
aa042770
LC
523returned. Both parts must not contain any '@'. Optionally, DELIMITER can be
524a character other than '@'."
525 (match (string-rindex spec delimiter)
1b846da8
ML
526 (#f (values spec #f))
527 (idx (values (substring spec 0 idx)
528 (substring spec (1+ idx))))))
529
cba36e64
JN
530(define* (target-mingw? #:optional (target (%current-target-system)))
531 (and target
532 (string-suffix? "-mingw32" target)))
533
300a54bb
LC
534(define* (target-arm32? #:optional (target (or (%current-target-system)
535 (%current-system))))
536 (string-prefix? "arm" target))
e45b573c 537
300a54bb
LC
538(define* (target-aarch64? #:optional (target (or (%current-target-system)
539 (%current-system))))
540 (string-prefix? "aarch64" target))
74f17180 541
300a54bb
LC
542(define* (target-arm? #:optional (target (or (%current-target-system)
543 (%current-system))))
544 (or (target-arm32? target) (target-aarch64? target)))
74f17180 545
fc7cf0c1
CM
546(define* (target-powerpc? #:optional (target (or (%current-target-system)
547 (%current-system))))
548 (string-prefix? "powerpc" target))
549
300a54bb
LC
550(define* (target-64bit? #:optional (system (or (%current-target-system)
551 (%current-system))))
93f21e1a 552 (any (cut string-prefix? <> system) '("x86_64" "aarch64" "mips64" "powerpc64")))
259341cf 553
fb9a57a8
MB
554(define* (cc-for-target #:optional (target (%current-target-system)))
555 (if target
556 (string-append target "-gcc")
557 "gcc"))
558
b720cf90
DM
559(define* (cxx-for-target #:optional (target (%current-target-system)))
560 (if target
561 (string-append target "-g++")
562 "g++"))
563
3d395e3d
EF
564(define* (pkg-config-for-target #:optional (target (%current-target-system)))
565 (if target
566 (string-append target "-pkg-config")
567 "pkg-config"))
568
0d1e6ce4
MW
569(define version-compare
570 (let ((strverscmp
571 (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
572 (error "could not find `strverscmp' (from GNU libc)"))))
573 (pointer->procedure int sym (list '* '*)))))
574 (lambda (a b)
575 "Return '> when A denotes a newer version than B,
576'< when A denotes a older version than B,
577or '= when they denote equal versions."
578 (let ((result (strverscmp (string->pointer a) (string->pointer b))))
579 (cond ((positive? result) '>)
580 ((negative? result) '<)
581 (else '=))))))
582
29a7c98a
ID
583(define (version-prefix version-string num-parts)
584 "Truncate version-string to the first num-parts components of the version.
585For example, (version-prefix \"2.1.47.4.23\" 3) returns \"2.1.47\""
586 (string-join (take (string-split version-string #\.) num-parts) "."))
587
46886728
EF
588(define (version-major+minor+point version-string)
589 "Return \"major>.<minor>.<point>\", where major, minor and point are the
590major, minor and point version numbers from the version-string. For example,
591(version-major+minor+point \"6.4.5.2\") returns \"6.4.5\" or
592(version-major+minor+point \"1.19.2-2581-324ca14c3003\") returns \"1.19.2\"."
593 (let* ((3-dot (version-prefix version-string 3))
594 (index (string-index 3-dot #\-)))
595 (or (false-if-exception (substring 3-dot 0 index))
596 3-dot)))
29a7c98a
ID
597
598(define (version-major+minor version-string)
599 "Return \"<major>.<minor>\", where major and minor are the major and
600minor version numbers from version-string."
601 (version-prefix version-string 2))
602
47dc9a0d 603(define (version-major version-string)
604 "Return the major version number as string from the version-string."
605 (version-prefix version-string 1))
606
579506e2
LC
607(define (version-unique-prefix version versions)
608 "Return the shortest version prefix to unambiguously identify VERSION among
609VERSIONS. For example:
610
611 (version-unique-prefix \"2.0\" '(\"3.0\" \"2.0\"))
612 => \"2\"
613
614 (version-unique-prefix \"2.2\" '(\"3.0.5\" \"2.0.9\" \"2.2.7\"))
615 => \"2.2\"
616
617 (version-unique-prefix \"27.1\" '(\"27.1\"))
618 => \"\"
619"
620 (define not-dot
621 (char-set-complement (char-set #\.)))
622
623 (define other-versions
624 (delete version versions))
625
626 (let loop ((prefix '())
627 (components (string-tokenize version not-dot)))
628 (define prefix-str
629 (string-join prefix "."))
630
631 (if (any (cut string-prefix? prefix-str <>) other-versions)
632 (match components
633 ((head . tail)
634 (loop `(,@prefix ,head) tail))
635 (()
636 version))
637 prefix-str)))
638
0d1e6ce4 639(define (version>? a b)
cde1e967 640 "Return #t when A denotes a version strictly newer than B."
0d1e6ce4
MW
641 (eq? '> (version-compare a b)))
642
cde1e967
LC
643(define (version>=? a b)
644 "Return #t when A denotes a version newer or equal to B."
645 (case (version-compare a b)
646 ((> =) #t)
647 (else #f)))
648
7db3ff4a
LC
649(define (guile-version>? str)
650 "Return #t if the running Guile version is greater than STR."
651 ;; Note: Using (version>? (version) "2.0.5") or similar doesn't work,
652 ;; because the result of (version) can have a prefix, like "2.0.5-deb1".
653 (version>? (string-append (major-version) "."
654 (minor-version) "."
655 (micro-version))
656 str))
657
437f62f0
LC
658(define version-prefix?
659 (let ((not-dot (char-set-complement (char-set #\.))))
660 (lambda (v1 v2)
661 "Return true if V1 is a version prefix of V2:
662
663 (version-prefix? \"4.1\" \"4.16.2\") => #f
664 (version-prefix? \"4.1\" \"4.1.2\") => #t
665"
666 (define (list-prefix? lst1 lst2)
667 (match lst1
668 (() #t)
669 ((head1 tail1 ...)
670 (match lst2
671 (() #f)
672 ((head2 tail2 ...)
673 (and (equal? head1 head2)
674 (list-prefix? tail1 tail2)))))))
675
676 (list-prefix? (string-tokenize v1 not-dot)
677 (string-tokenize v2 not-dot)))))
678
d67a8819
LC
679\f
680;;;
681;;; Files.
682;;;
683
0fdd3bea
LC
684(define (file-extension file)
685 "Return the extension of FILE or #f if there is none."
686 (let ((dot (string-rindex file #\.)))
687 (and dot (substring file (+ 1 dot) (string-length file)))))
688
ac10e0e1
LC
689(define (file-sans-extension file)
690 "Return the substring of FILE without its extension, if any."
691 (let ((dot (string-rindex file #\.)))
692 (if dot
693 (substring file 0 dot)
694 file)))
695
da1027a7
HG
696(define (tarball-sans-extension tarball)
697 "Return TARBALL without its .tar.* or .zip extension."
698 (let ((end (or (string-contains tarball ".tar")
6f32e27e 699 (string-contains tarball ".tgz")
da1027a7
HG
700 (string-contains tarball ".zip"))))
701 (substring tarball 0 end)))
702
089b1678
LC
703(define (compressed-file? file)
704 "Return true if FILE denotes a compressed file."
705 (->bool (member (file-extension file)
f4111243 706 '("gz" "bz2" "xz" "lz" "lzma" "tgz" "tbz2" "zip"))))
089b1678 707
3bb168b0
LC
708(define (switch-symlinks link target)
709 "Atomically switch LINK, a symbolic link, to point to TARGET. Works
710both when LINK already exists and when it does not."
711 (let ((pivot (string-append link ".new")))
712 (symlink target pivot)
713 (rename-file pivot link)))
714
56b943de
LC
715(define* (string-replace-substring str substr replacement
716 #:optional
717 (start 0)
718 (end (string-length str)))
719 "Replace all occurrences of SUBSTR in the START--END range of STR by
720REPLACEMENT."
721 (match (string-length substr)
722 (0
723 (error "string-replace-substring: empty substring"))
724 (substr-length
725 (let loop ((start start)
726 (pieces (list (substring str 0 start))))
727 (match (string-contains str substr start end)
728 (#f
729 (string-concatenate-reverse
730 (cons (substring str start) pieces)))
731 (index
732 (loop (+ index substr-length)
733 (cons* replacement
734 (substring str start index)
735 pieces))))))))
736
861693f3
LC
737(define (call-with-temporary-output-file proc)
738 "Call PROC with a name of a temporary file and open output port to that
739file; close the file and delete it when leaving the dynamic extent of this
740call."
57db49cc
LC
741 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
742 (template (string-append directory "/guix-file.XXXXXX"))
743 (out (mkstemp! template)))
861693f3
LC
744 (dynamic-wind
745 (lambda ()
746 #t)
747 (lambda ()
748 (proc template out))
749 (lambda ()
750 (false-if-exception (close out))
751 (false-if-exception (delete-file template))))))
04d4c8a4 752
db6e5e2b
DT
753(define (call-with-temporary-directory proc)
754 "Call PROC with a name of a temporary directory; close the directory and
755delete it when leaving the dynamic extent of this call."
756 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
757 (template (string-append directory "/guix-directory.XXXXXX"))
758 (tmp-dir (mkdtemp! template)))
759 (dynamic-wind
760 (const #t)
761 (lambda ()
762 (proc tmp-dir))
763 (lambda ()
27f7cbc9 764 (false-if-exception (delete-file-recursively tmp-dir))))))
db6e5e2b 765
04d4c8a4
LC
766(define (with-atomic-file-output file proc)
767 "Call PROC with an output port for the file that is going to replace FILE.
768Upon success, FILE is atomically replaced by what has been written to the
769output port, and PROC's result is returned."
770 (let* ((template (string-append file ".XXXXXX"))
771 (out (mkstemp! template)))
772 (with-throw-handler #t
773 (lambda ()
774 (let ((result (proc out)))
1752a17a
LC
775 (fdatasync out)
776 (close-port out)
04d4c8a4
LC
777 (rename-file template file)
778 result))
779 (lambda (key . args)
c25637df
LC
780 (false-if-exception (delete-file template))
781 (close-port out)))))
861693f3 782
f0e492f0
LC
783(define* (xdg-directory variable suffix #:key (ensure? #t))
784 "Return the name of the XDG directory that matches VARIABLE and SUFFIX,
785after making sure that it exists if ENSURE? is true. VARIABLE is an
786environment variable name like \"XDG_CONFIG_HOME\"; SUFFIX is a suffix like
787\"/.config\". Honor the XDG specs,
788<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>."
789 (let ((dir (and=> (or (getenv variable)
790 (and=> (or (getenv "HOME")
791 (passwd:dir (getpwuid (getuid))))
792 (cut string-append <> suffix)))
793 (cut string-append <> "/guix"))))
794 (when ensure?
795 (mkdir-p dir))
796 dir))
797
798(define config-directory
799 (cut xdg-directory "XDG_CONFIG_HOME" "/.config" <...>))
800
801(define cache-directory
802 (cut xdg-directory "XDG_CACHE_HOME" "/.cache" <...>))
739ab68b 803
d50cb56d
LC
804(define (readlink* file)
805 "Call 'readlink' until the result is not a symlink."
806 (define %max-symlink-depth 50)
807
808 (let loop ((file file)
809 (depth 0))
810 (define (absolute target)
811 (if (absolute-file-name? target)
812 target
813 (string-append (dirname file) "/" target)))
814
815 (if (>= depth %max-symlink-depth)
816 file
817 (call-with-values
818 (lambda ()
819 (catch 'system-error
820 (lambda ()
821 (values #t (readlink file)))
822 (lambda args
823 (let ((errno (system-error-errno args)))
824 (if (or (= errno EINVAL))
825 (values #f file)
826 (apply throw args))))))
827 (lambda (success? target)
828 (if success?
829 (loop (absolute target) (+ depth 1))
830 file))))))
c8be6f0d
FB
831
832(define (canonical-newline-port port)
833 "Return an input port that wraps PORT such that all newlines consist
3149c002 834 of a single linefeed."
c8be6f0d
FB
835 (define (get-position)
836 (if (port-has-port-position? port) (port-position port) #f))
837 (define (set-position! position)
838 (if (port-has-set-port-position!? port)
839 (set-port-position! position port)
840 #f))
841 (define (close) (close-port port))
842 (define (read! bv start n)
843 (let loop ((count 0)
844 (byte (get-u8 port)))
845 (cond ((eof-object? byte) count)
3149c002
RV
846 ;; XXX: consume all CRs even if not followed by LF.
847 ((eqv? byte (char->integer #\return)) (loop count (get-u8 port)))
c8be6f0d
FB
848 ((= count (- n 1))
849 (bytevector-u8-set! bv (+ start count) byte)
850 n)
c8be6f0d
FB
851 (else
852 (bytevector-u8-set! bv (+ start count) byte)
853 (loop (+ count 1) (get-u8 port))))))
854 (make-custom-binary-input-port "canonical-newline-port"
855 read!
856 get-position
857 set-position!
858 close))
ff352cfb
LC
859\f
860;;;
861;;; Source location.
862;;;
863
87b711d2
LC
864(define absolute-dirname
865 ;; Memoize to avoid repeated 'stat' storms from 'search-path'.
866 (mlambda (file)
867 "Return the absolute name of the directory containing FILE, or #f upon
cbbbb7be 868failure."
87b711d2
LC
869 (match (search-path %load-path file)
870 (#f #f)
871 ((? string? file)
872 ;; If there are relative names in %LOAD-PATH, FILE can be relative and
873 ;; needs to be canonicalized.
874 (if (string-prefix? "/" file)
875 (dirname file)
876 (canonicalize-path (dirname file)))))))
cbbbb7be 877
5dbae738
LC
878(define-syntax current-source-directory
879 (lambda (s)
d4dd37fc
LC
880 "Return the absolute name of the current directory, or #f if it could not
881be determined."
5dbae738
LC
882 (syntax-case s ()
883 ((_)
82781d87 884 (match (assq 'filename (or (syntax-source s) '()))
5dbae738 885 (('filename . (? string? file-name))
d4dd37fc 886 ;; If %FILE-PORT-NAME-CANONICALIZATION is 'relative, then FILE-NAME
cbbbb7be
LC
887 ;; can be relative. In that case, we try to find out at run time
888 ;; the absolute file name by looking at %LOAD-PATH; doing this at
889 ;; run time rather than expansion time is necessary to allow files
890 ;; to be moved on the file system.
b997d432
AS
891 (if (string-prefix? "/" file-name)
892 (dirname file-name)
893 #`(absolute-dirname #,file-name)))
894 ((or ('filename . #f) #f)
895 ;; raising an error would upset Geiser users
5dbae738 896 #f))))))
07c8a98c 897
9505b54a 898\f
899;;;
900;;; String comparison.
901;;;
902
903(define (string-distance s1 s2)
904 "Compute the Levenshtein distance between two strings."
905 ;; Naive implemenation
906 (define loop
907 (mlambda (as bt)
908 (match as
909 (() (length bt))
910 ((a s ...)
911 (match bt
912 (() (length as))
913 ((b t ...)
914 (if (char=? a b)
915 (loop s t)
916 (1+ (min
917 (loop as t)
918 (loop s bt)
919 (loop s t))))))))))
920
921 (let ((c1 (string->list s1))
922 (c2 (string->list s2)))
923 (loop c1 c2)))
924
925(define* (string-closest trial tests #:key (threshold 3))
926 "Return the string from TESTS that is the closest from the TRIAL,
927according to 'string-distance'. If the TESTS are too far from TRIAL,
928according to THRESHOLD, then #f is returned."
929 (identity ;discard second return value
930 (fold2 (lambda (test closest minimal)
931 (let ((dist (string-distance trial test)))
932 (if (and (< dist minimal) (< dist threshold))
933 (values test dist)
934 (values closest minimal))))
935 #f +inf.0
936 tests)))
937
79864851
SB
938;;; Local Variables:
939;;; eval: (put 'call-with-progress-reporter 'scheme-indent-function 1)
940;;; End: