aboutsummaryrefslogtreecommitdiff
path: root/guix/import/crate.scm
diff options
context:
space:
mode:
Diffstat (limited to 'guix/import/crate.scm')
-rw-r--r--guix/import/crate.scm233
1 files changed, 169 insertions, 64 deletions
diff --git a/guix/import/crate.scm b/guix/import/crate.scm
index 43823d006e..7a25b2243c 100644
--- a/guix/import/crate.scm
+++ b/guix/import/crate.scm
@@ -5,7 +5,8 @@
;;; Copyright © 2021 Nicolas Goaziou <mail@nicolasgoaziou.fr>
;;; Copyright © 2022 Hartmut Goebel <h.goebel@crazy-compilers.com>
;;; Copyright © 2023 Simon Tournier <zimon.toutoune@gmail.com>
-;;; Copyright © 2023 Efraim Flashner <efraim@flashner.co.il>
+;;; Copyright © 2023, 2024 Efraim Flashner <efraim@flashner.co.il>
+;;; Copyright © 2023, 2024 David Elsing <david.elsing@posteo.net>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -25,12 +26,15 @@
(define-module (guix import crate)
#:use-module (guix base32)
#:use-module (guix build-system cargo)
+ #:use-module (guix diagnostics)
#:use-module (gcrypt hash)
#:use-module (guix http-client)
+ #:use-module (guix i18n)
#:use-module (guix import json)
#:use-module (guix import utils)
#:use-module (guix memoization)
#:use-module (guix packages)
+ #:use-module (guix read-print)
#:use-module (guix upstream)
#:use-module (guix utils)
#:use-module (gnu packages)
@@ -40,6 +44,7 @@
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-2)
#:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-69)
#:use-module (srfi srfi-71)
#:export (crate->guix-package
guix-package->crate-name
@@ -99,7 +104,7 @@
;; Autoload Guile-Semver so we only have a soft dependency.
(module-autoload! (current-module)
- '(semver) '(string->semver semver->string semver<?))
+ '(semver) '(string->semver semver->string semver<? semver=? semver>?))
(module-autoload! (current-module)
'(semver ranges) '(string->semver-range semver-range-contains?))
@@ -164,16 +169,18 @@ record or #f if it was not found."
(list-matches "^(0+\\.){,2}[0-9]+" version))))
(define* (make-crate-sexp #:key name version cargo-inputs cargo-development-inputs
- home-page synopsis description license build?)
+ home-page synopsis description license build? yanked?)
"Return the `package' s-expression for a rust package with the given NAME,
VERSION, CARGO-INPUTS, CARGO-DEVELOPMENT-INPUTS, HOME-PAGE, SYNOPSIS, DESCRIPTION,
and LICENSE."
(define (format-inputs inputs)
(map
(match-lambda
- ((name version)
+ ((name version yanked)
(list (crate-name->package-name name)
- (version->semver-prefix version))))
+ (if yanked
+ (string-append version "-yanked")
+ (version->semver-prefix version)))))
inputs))
(let* ((port (http-fetch (crate-uri name version)))
@@ -183,6 +190,9 @@ and LICENSE."
(pkg `(package
(name ,guix-name)
(version ,version)
+ ,@(if yanked?
+ `(,(comment "; This version was yanked!\n" #t))
+ '())
(source (origin
(method url-fetch)
(uri (crate-uri ,name version))
@@ -190,6 +200,9 @@ and LICENSE."
(sha256
(base32
,(bytevector->nix-base32-string (port-sha256 port))))))
+ ,@(if yanked?
+ `((properties '((crate-version-yanked? . #t))))
+ '())
(build-system cargo-build-system)
,@(maybe-arguments (append (if build?
'()
@@ -206,7 +219,10 @@ and LICENSE."
((license) license)
(_ `(list ,@license)))))))
(close-port port)
- (package->definition pkg (version->semver-prefix version))))
+ (package->definition pkg
+ (if yanked?
+ (string-append version "-yanked")
+ (version->semver-prefix version)))))
(define (string->license string)
(filter-map (lambda (license)
@@ -217,13 +233,47 @@ and LICENSE."
'unknown-license!)))
(string-split string (string->char-set " /"))))
-(define* (crate->guix-package crate-name #:key version include-dev-deps?
- #:allow-other-keys)
+(define (min-element l less)
+ "Returns the smallest element of l according to less or #f if l is empty."
+
+ (let loop ((curr #f)
+ (remaining l))
+ (if (null-list? remaining)
+ curr
+ (let ((next (car remaining))
+ (remaining (cdr remaining)))
+ (if (and curr
+ (not (less next curr)))
+ (loop curr remaining)
+ (loop next remaining))))))
+
+(define (max-crate-version-of-semver semver-range range)
+ "Returns a <crate-version> of the highest version within the semver range."
+
+ (define (crate->semver crate)
+ (string->semver (crate-version-number crate)))
+
+ (min-element
+ (filter (lambda (crate)
+ (semver-range-contains? semver-range (crate->semver crate)))
+ range)
+ (lambda args
+ (apply semver>? (map crate->semver args)))))
+
+(define (nonyanked-crate-versions crate)
+ "Returns a list of <crate-version>s which are not yanked by upstream."
+ (filter (lambda (entry)
+ (not (crate-version-yanked? entry)))
+ (crate-versions crate)))
+
+(define* (crate->guix-package
+ crate-name
+ #:key version include-dev-deps? allow-yanked? #:allow-other-keys)
"Fetch the metadata for CRATE-NAME from crates.io, and return the
`package' s-expression corresponding to that package, or #f on failure.
When VERSION is specified, convert it into a semver range and attempt to fetch
the latest version matching this semver range; otherwise fetch the latest
-version of CRATE-NAME. If INCLUDE-DEV-DEPS is true then this will also
+version of CRATE-NAME. If INCLUDE-DEV-DEPS is true then this will also
look up the development dependencs for the given crate."
(define (semver-range-contains-string? range version)
@@ -242,63 +292,100 @@ look up the development dependencs for the given crate."
(or version
(crate-latest-version crate))))
- ;; find the highest existing package that fulfills the semver <range>
+ ;; Find the highest existing package that fulfills the semver <range>.
+ ;; Packages previously marked as yanked take lower priority.
(define (find-package-version name range)
(let* ((semver-range (string->semver-range range))
- (versions
- (sort
- (filter (lambda (version)
- (semver-range-contains? semver-range version))
+ (version
+ (min-element
+ (filter (match-lambda ((semver yanked)
+ (and
+ (or allow-yanked? (not yanked))
+ (semver-range-contains? semver-range semver))))
(map (lambda (pkg)
- (string->semver (package-version pkg)))
+ (let ((version (package-version pkg)))
+ (list
+ (string->semver version)
+ (assoc-ref (package-properties pkg)
+ 'crate-version-yanked?))))
(find-packages-by-name
(crate-name->package-name name))))
- semver<?)))
- (and (not (null-list? versions))
- (semver->string (last versions)))))
-
- ;; Find the highest version of a crate that fulfills the semver <range>
- ;; and hasn't been yanked.
+ (match-lambda* (((semver1 yanked1) (semver2 yanked2))
+ (or (and yanked1 (not yanked2))
+ (and (eq? yanked1 yanked2)
+ (semver<? semver1 semver2))))))))
+ (and (not (eq? #f version))
+ (match-let (((semver yanked) version))
+ (list (semver->string semver) yanked)))))
+
+ ;; Find the highest version of a crate that fulfills the semver <range>.
+ ;; If no matching non-yanked version has been found and allow-yanked? is #t,
+ ;; also consider yanked packages.
(define (find-crate-version crate range)
- (let* ((semver-range (string->semver-range range))
- (versions
- (sort
- (filter (lambda (entry)
- (and
- (not (crate-version-yanked? (second entry)))
- (semver-range-contains? semver-range (first entry))))
- (map (lambda (ver)
- (list (string->semver (crate-version-number ver))
- ver))
- (crate-versions crate)))
- (match-lambda* (((semver _) ...)
- (apply semver<? semver))))))
- (and (not (null-list? versions))
- (second (last versions)))))
-
- (define (dependency-name+version dep)
+ (let ((semver-range (string->semver-range range))
+ (versions (nonyanked-crate-versions crate)))
+ (or (and (not (null-list? versions))
+ (max-crate-version-of-semver semver-range versions))
+ (and allow-yanked?
+ (not (null-list? (crate-versions crate)))
+ (max-crate-version-of-semver semver-range
+ (crate-versions crate))))))
+
+ ;; If no non-yanked existing package version was found, check the upstream
+ ;; versions. If a non-yanked upsteam version exists, use it instead,
+ ;; otherwise use the existing package version, provided it exists.
+ (define (dependency-name+version+yanked dep)
(let* ((name (crate-dependency-id dep))
- (req (crate-dependency-requirement dep))
- (existing-version (find-package-version name req)))
- (if existing-version
- (list name existing-version)
+ (req (crate-dependency-requirement dep))
+ (existing-version (find-package-version name req)))
+ (if (and existing-version (not (second existing-version)))
+ (cons name existing-version)
(let* ((crate (lookup-crate* name))
(ver (find-crate-version crate req)))
- (list name
- (crate-version-number ver))))))
+ (if existing-version
+ (if (and ver (not (crate-version-yanked? ver)))
+ (if (semver=? (string->semver (first existing-version))
+ (string->semver (crate-version-number ver)))
+ (begin
+ (warning (G_ "~A: version ~a is no longer yanked~%")
+ name (first existing-version))
+ (cons name existing-version))
+ (list name
+ (crate-version-number ver)
+ (crate-version-yanked? ver)))
+ (begin
+ (warning (G_ "~A: using existing version ~a, which was yanked~%")
+ name (first existing-version))
+ (cons name existing-version)))
+ (begin
+ (unless ver
+ (leave (G_ "~A: no version found for requirement ~a~%") name req))
+ (if (crate-version-yanked? ver)
+ (warning (G_ "~A: imported version ~a was yanked~%")
+ name (crate-version-number ver)))
+ (list name
+ (crate-version-number ver)
+ (crate-version-yanked? ver))))))))
(define version*
(and crate
- (find-crate-version crate version-number)))
+ (or (find-crate-version crate version-number)
+ (leave (G_ "~A: version ~a not found~%") crate-name version-number))))
;; sort and map the dependencies to a list containing
;; pairs of (name version)
(define (sort-map-dependencies deps)
- (sort (map dependency-name+version
+ (sort (map dependency-name+version+yanked
deps)
- (match-lambda* (((name _) ...)
+ (match-lambda* (((name _ _) ...)
(apply string-ci<? name)))))
+ (define (remove-yanked-info deps)
+ (map
+ (match-lambda ((name version yanked)
+ (list name version)))
+ deps))
+
(if (and crate version*)
(let* ((dependencies (crate-version-dependencies version*))
(dep-crates dev-dep-crates (partition normal-dependency? dependencies))
@@ -308,6 +395,7 @@ look up the development dependencs for the given crate."
'())))
(values
(make-crate-sexp #:build? include-dev-deps?
+ #:yanked? (crate-version-yanked? version*)
#:name crate-name
#:version (crate-version-number version*)
#:cargo-inputs cargo-inputs
@@ -324,19 +412,27 @@ look up the development dependencs for the given crate."
#:description (crate-description crate)
#:license (and=> (crate-version-license version*)
string->license))
- (append cargo-inputs cargo-development-inputs)))
+ (append
+ (remove-yanked-info cargo-inputs)
+ (remove-yanked-info cargo-development-inputs))))
(values #f '())))
-(define* (crate-recursive-import crate-name #:key version)
- (recursive-import crate-name
- #:repo->guix-package (lambda* params
- ;; download development dependencies only for the top level package
- (let ((include-dev-deps? (equal? (car params) crate-name))
- (crate->guix-package* (memoize crate->guix-package)))
- (apply crate->guix-package*
- (append params `(#:include-dev-deps? ,include-dev-deps?)))))
- #:version version
- #:guix-name crate-name->package-name))
+(define* (crate-recursive-import
+ crate-name #:key version recursive-dev-dependencies? allow-yanked?)
+ (recursive-import
+ crate-name
+ #:repo->guix-package
+ (let ((crate->guix-package* (memoize crate->guix-package)))
+ (lambda* params
+ ;; download development dependencies only for the top level package
+ (let ((include-dev-deps?
+ (or (equal? (car params) crate-name)
+ recursive-dev-dependencies?)))
+ (apply crate->guix-package*
+ (append params `(#:include-dev-deps? ,include-dev-deps?
+ #:allow-yanked? ,allow-yanked?))))))
+ #:version version
+ #:guix-name crate-name->package-name))
(define (guix-package->crate-name package)
"Return the crate name of PACKAGE."
@@ -352,6 +448,7 @@ look up the development dependencs for the given crate."
(define (crate-name->package-name name)
(guix-name "rust-" name))
+
;;;
;;; Updater
@@ -365,12 +462,20 @@ look up the development dependencs for the given crate."
include a VERSION string to fetch a specific version."
(let* ((crate-name (guix-package->crate-name package))
(crate (lookup-crate crate-name))
- (version (or version (crate-latest-version crate)))
- (url (crate-uri crate-name version)))
- (upstream-source
- (package (package-name package))
- (version version)
- (urls (list url)))))
+ (version (or version
+ (let ((max-crate-version
+ (max-crate-version-of-semver
+ (string->semver-range
+ (string-append "^" (package-version package)))
+ (nonyanked-crate-versions crate))))
+ (and=> max-crate-version
+ crate-version-number)))))
+ (if version
+ (upstream-source
+ (package (package-name package))
+ (version version)
+ (urls (list (crate-uri crate-name version))))
+ #f)))
(define %crate-updater
(upstream-updater