~ruther/guix-local

92d130e03594bd2b8e6688c83fc35a3f2c2954da — Hilton Chain 10 months ago efaa3e6
import: crate: Stop importing dependencies from crates.io.

* guix/scripts/import/crate.scm (show-help, %options, guix-import-crate)
[--recursive, --recursive-dev-dependencies, --mark-missing]: Remove options.
* doc/guix.texi (Invoking guix import)[crate]: Adjust accordingly.
Mention packaging workflow.
* guix/import/crate.scm (make-crate-sexp): Don't use "rust-" prefix and semver
suffix for package name.
[#:cargo-inputs, #:cargo-development-inputs, #:build?]: Remove arguments.
(crate->guix-package)[#:include-dev-deps?, #:mark-missing?]: Remove arguments.
(<crate-dependency>): Remove data type.
(make-crate-dependency, crate-dependency?, json->crate-dependency)
(crate-version-dependencies, package-names->package-inputs)
(maybe-cargo-inputs, maybe-cargo-development-inputs, maybe-arguments)
(version->semver-prefix, find-package-version, crate-recursive-import): Remove
procedures.
* tests/crate.scm (test-foo-crate, test-bar-crate): Adjust for modified tests.
(test-foo-dependencies, test-bar-dependencies, test-root-crate)
(test-root-dependencies, test-intermediate-a-crate)
(test-intermediate-a-dependencies, test-intermediate-b-crate)
(test-intermediate-b-dependencies, test-intermediate-c-crate)
(test-intermediate-c-dependencies, test-leaf-alice-crate)
(test-leaf-alice-dependencies, test-leaf-bob-crate)
(test-leaf-bob-dependencies, rust-leaf-bob-3, rust-leaf-bob-3.0.2-yanked):
Remove variables.
("crate->guix-package yanked", "crate->guix-package only yanked available"): New
tests.
("crate->guix-package"): Adjust accordingly.
("crate->guix-package-marks-missing-packages", "crate-recursive-import")
("crate-recursive-import-honors-existing-packages")
("crate-import-only-yanked-available"): Remove tests.

Change-Id: Ib1d24511ed0ea1a2373f53de12e06afa7950a7d7
4 files changed, 152 insertions(+), 1332 deletions(-)

M doc/guix.texi
M guix/import/crate.scm
M guix/scripts/import/crate.scm
M tests/crate.scm
M doc/guix.texi => doc/guix.texi +3 -13
@@ 14939,27 14939,17 @@0.1.0
Additional options include:

@table @code
@item --recursive
@itemx -r
Traverse the dependency graph of the given upstream package recursively
and generate package expressions for all those packages that are not yet
in Guix.
@item --recursive-dev-dependencies
If @option{--recursive-dev-dependencies} is specified, also the recursively
imported packages contain their development dependencies, which are recursively
imported as well.
@item --allow-yanked
If no non-yanked version of a crate is available, use the latest yanked
version instead instead of aborting.
@item --mark-missing
If a crate dependency is not (yet) packaged, make the corresponding
input in @code{#:cargo-inputs} or @code{#:cargo-development-inputs} into
a comment.
@item --lockfile=@var{file}
@itemx -f @var{file}
When @option{--lockfile} is specified, the importer will ignore other options
and won't output package expressions, instead importing source expressions
from @var{file}, a @file{Cargo.lock} file.

@xref{Packaging Rust Crates,,, guix-cookbook, GNU Guix Cookbook}, for packaging
workflow utilizing it.
@end table

@item elm

M guix/import/crate.scm => guix/import/crate.scm +27 -240
@@ 104,20 104,9 @@
  (yanked?       crate-version-yanked? "yanked")  ;boolean
  (links         crate-version-links))            ;alist

;; Crate dependency.  Each dependency (each edge in the graph) is annotated as
;; being a "normal" dependency or a development dependency.  There also
;; information about the minimum required version, such as "^0.0.41".
(define-json-mapping <crate-dependency> make-crate-dependency
  crate-dependency?
  json->crate-dependency
  (id            crate-dependency-id "crate_id")  ;string
  (kind          crate-dependency-kind "kind"     ;'normal | 'dev | 'build
                 string->symbol)
  (requirement   crate-dependency-requirement "req")) ;string

;; Autoload Guile-Semver so we only have a soft dependency.
(module-autoload! (current-module)
		  '(semver) '(string->semver semver->string semver<? semver=? semver>?))
		  '(semver) '(string->semver semver->string semver>?))
(module-autoload! (current-module)
		  '(semver ranges) '(string->semver-range semver-range-contains?))



@@ 138,91 127,17 @@ record or #f if it was not found."

(define lookup-crate* (memoize lookup-crate))

(define (crate-version-dependencies version)
  "Return the list of <crate-dependency> records of VERSION, a
<crate-version>."
  (let* ((path (assoc-ref (crate-version-links version) "dependencies"))
         (url  (string-append (%crate-base-url) path)))
    (match (assoc-ref (or (json-fetch url) '()) "dependencies")
      ((? vector? vector)
       (delete-duplicates (map json->crate-dependency (vector->list vector))))
      (_
       '()))))


;;;
;;; Converting crates to Guix packages.
;;;

(define* (package-names->package-inputs names #:optional (output #f))
  "Given a list of PACKAGE-NAMES or (PACKAGE-NAME VERSION) pairs, and an
optional OUTPUT, tries to generate a quoted list of inputs, as suitable to
use in an 'inputs' field of a package definition."
  (define (make-input input version)
    (cons* input (list 'unquote (string->symbol
                                 (if version
                                     (string-append input "-" version)
                                     input)))
           (or (and output (list output))
               '())))

  (map (match-lambda
         ((input version) (make-input input version))
         ((? blank? comment) comment)
         (input (make-input input #f)))
       names))

(define (maybe-cargo-inputs package-names)
  (match (package-names->package-inputs package-names)
    (()
     '())
    ((package-inputs ...)
     `(#:cargo-inputs ,package-inputs))))

(define (maybe-cargo-development-inputs package-names)
  (match (package-names->package-inputs package-names)
    (()
     '())
    ((package-inputs ...)
     `(#:cargo-development-inputs ,package-inputs))))

(define (maybe-arguments arguments)
  (match arguments
    (()
     '())
    ((args ...)
     `((arguments (,'quasiquote ,args))))))

(define (version->semver-prefix version)
  "Return the version up to and including the first non-zero part"
  (first
   (map match:substring
        (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? yanked?)
(define* (make-crate-sexp #:key name version
                          home-page synopsis description license 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 missing version yanked)
       (let ((input (list (crate-name->package-name name)
                          (if yanked
                              (string-append version "-yanked")
                              (version->semver-prefix version)))))
         (if missing
             (comment
               (string-append ";; " (string-join input "-") "\n")
               #f)
             input))))
     inputs))

VERSION, HOME-PAGE, SYNOPSIS, DESCRIPTION and LICENSE."
  (let* ((port (http-fetch (crate-uri name version)))
         (guix-name (crate-name->package-name name))
         (cargo-inputs (format-inputs cargo-inputs))
         (cargo-development-inputs (format-inputs cargo-development-inputs))
         (guix-name (downstream-package-name "" name))
         (description (beautify-description description))
         (pkg `(package
                   (name ,guix-name)


@@ 234,9 149,9 @@ and LICENSE."
                             (method url-fetch)
                             (uri (crate-uri ,name version))
                             (file-name
                               ,@(if yanked?
                                     `((string-append name "-" version "-yanked.tar.gz"))
                                     `((string-append name "-" version ".tar.gz"))))
                              ,@(if yanked?
                                    `((string-append name "-" version "-yanked.tar.gz"))
                                    `((string-append name "-" version ".tar.gz"))))
                             (sha256
                              (base32
                               ,(bytevector->nix-base32-string (port-sha256 port))))))


@@ 244,12 159,7 @@ and LICENSE."
                         `((properties '((crate-version-yanked? . #t))))
                         '())
                   (build-system cargo-build-system)
                   ,@(maybe-arguments (append (if build?
                                                 '()
                                                 '(#:skip-build? #t))
                                              (maybe-cargo-inputs cargo-inputs)
                                              (maybe-cargo-development-inputs
                                                cargo-development-inputs)))
                   (inputs (cargo-inputs ',(string->symbol guix-name)))
                   (home-page ,home-page)
                   (synopsis ,(beautify-synopsis synopsis))
                   (description ,(if (string-prefix? "This" description)


@@ 262,10 172,7 @@ and LICENSE."
                               ((license) license)
                               (_ `(list ,@license)))))))
         (close-port port)
         (package->definition pkg
                              (if yanked?
                                  (string-append version "-yanked")
                                  (version->semver-prefix version)))))
         (package->definition pkg)))

(define (string->license string)
  (filter-map (lambda (license)


@@ 310,50 217,13 @@ satisfies SEMVER-RANGE."
            (not (crate-version-yanked? entry)))
          (crate-versions crate)))

(define (find-package-version name range allow-yanked?)
  "Find the latest existing package that fulfills the SemVer RANGE.  If
ALLOW-YANKED? is #t, include packages marked as yanked at a lower
priority."
  (set! range (string->semver-range range))
  (let loop ((packages (find-packages-by-name
                         (crate-name->package-name name)))
             (semver #f)
             (yanked? #f))
    (match packages
      ((pkg packages ...)
       (let ((pkg-yanked? (assoc-ref (package-properties pkg)
                                    'crate-version-yanked?)))
         (if (or allow-yanked? (not pkg-yanked?))
             (let ((pkg-semver (string->semver (package-version pkg))))
               (if (and (or (not semver)
                            (and yanked? (not pkg-yanked?))
                            (and (eq? yanked? pkg-yanked?)
                                 (semver>? pkg-semver semver)))
                        (semver-range-contains? range pkg-semver))
                   (loop packages pkg-semver pkg-yanked?)
                   (loop packages semver yanked?)))
             (loop packages semver yanked?))))
      (() (and semver (list (semver->string semver) yanked?))))))

(define* (crate->guix-package
          crate-name
          #:key version include-dev-deps? allow-yanked? mark-missing?
          #:allow-other-keys)
          crate-name #:key version 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
look up the development dependencs for the given crate."

  (define (semver-range-contains-string? range version)
    (semver-range-contains? (string->semver-range range)
                            (string->semver version)))

  (define (normal-dependency? dependency)
    (or (eq? (crate-dependency-kind dependency) 'build)
        (eq? (crate-dependency-kind dependency) 'normal)))

version of CRATE-NAME."
  (define crate
    (lookup-crate* crate-name))



@@ 375,112 245,29 @@ look up the development dependencs for the given 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 upstream version exists, use it instead,
  ;; otherwise use the existing package version, provided it exists.
  (define (dependency-name+missing+version+yanked dep)
    (let* ((name (crate-dependency-id dep))
                 (req (crate-dependency-requirement dep))
                 (existing-version
                  (find-package-version name req allow-yanked?)))
      (if (and existing-version (not (second existing-version)))
          (cons* name #f existing-version)
          (let* ((crate (lookup-crate* name))
                 (ver (find-crate-version crate req)))
            (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 #f existing-version))
                        (list name
                              #f
                              (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 #f 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
                        mark-missing?
                        (crate-version-number ver)
                        (crate-version-yanked? ver))))))))

  (define version*
    (and crate
         (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+missing+version+yanked
               deps)
          (match-lambda* (((name _ _ _) ...)
                          (apply string-ci<? name)))))

  (define (remove-missing+yanked-info deps)
    (map
     (match-lambda ((name missing 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))
             (cargo-inputs (sort-map-dependencies dep-crates))
             (cargo-development-inputs (if include-dev-deps?
                                           (sort-map-dependencies dev-dep-crates)
                                           '())))
        (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
                          #:cargo-development-inputs cargo-development-inputs
                          #:home-page
                          (let ((home-page (crate-home-page crate)))
                            (if (string? home-page)
                                home-page
                                (let ((repository (crate-repository crate)))
                                  (if (string? repository)
                                      repository
                                      ""))))
                          #:synopsis (crate-description crate)
                          #:description (crate-description crate)
                          #:license (and=> (crate-version-license version*)
                                           string->license))
         (append
          (remove-missing+yanked-info cargo-inputs)
          (remove-missing+yanked-info cargo-development-inputs))))
      (make-crate-sexp #:yanked? (crate-version-yanked? version*)
                       #:name crate-name
                       #:version (crate-version-number version*)
                       #:home-page
                       (let ((home-page (crate-home-page crate)))
                         (if (string? home-page)
                             home-page
                             (let ((repository (crate-repository crate)))
                               (if (string? repository)
                                   repository
                                   ""))))
                       #:synopsis (crate-description crate)
                       #:description (crate-description crate)
                       #:license (and=> (crate-version-license version*)
                                        string->license))
      (values #f '())))

(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."
  (and-let* ((origin (package-source package))

M guix/scripts/import/crate.scm => guix/scripts/import/crate.scm +29 -56
@@ 50,17 50,8 @@
  (display (G_ "Usage: guix import crate PACKAGE-NAME
Import and convert the crates.io package for PACKAGE-NAME.\n"))
  (display (G_ "
  -r, --recursive        import packages recursively"))
  (display (G_ "
      --recursive-dev-dependencies
                         include dev-dependencies recursively"))
  (newline)
  (display (G_ "
      --allow-yanked     allow importing yanked crates if no alternative
                         satisfying the version requirement is found"))
  (display (G_ "
      --mark-missing     comment out the desired dependency if no
                         sufficient package exists for it"))
  (newline)
  (display (G_ "
  -f, --lockfile=FILE    import dependencies from FILE, a 'Cargo.lock' file"))


@@ 81,18 72,9 @@ Import and convert the crates.io package for PACKAGE-NAME.\n"))
         (option '(#\V "version") #f #f
                 (lambda args
                   (show-version-and-exit "guix import crate")))
         (option '(#\r "recursive") #f #f
                 (lambda (opt name arg result)
                   (alist-cons 'recursive #t result)))
         (option '("recursive-dev-dependencies") #f #f
                 (lambda (opt name arg result)
                   (alist-cons 'recursive-dev-dependencies #t result)))
         (option '("allow-yanked") #f #f
                 (lambda (opt name arg result)
                   (alist-cons 'allow-yanked #t result)))
         (option '("mark-missing") #f #f
                 (lambda (opt name arg result)
                   (alist-cons 'mark-missing #t result)))
         (option '(#\f "lockfile") #f #t
                 (lambda (opt name arg result)
                   (if (file-exists? arg)


@@ 124,44 106,35 @@ Import and convert the crates.io package for PACKAGE-NAME.\n"))
       (define-values (name version)
         (package-name->name+version spec))

       (match (cond
               (lockfile
                (let ((source-expressions
                       _
                       (cargo-lock->expressions lockfile name)))
                  (when file-to-insert
                    (let* ((source-expressions
                            cargo-inputs-entry
                            (cargo-lock->expressions lockfile name))
                           (term (first cargo-inputs-entry))
                           (cargo-inputs
                            `(define-cargo-inputs lookup-cargo-inputs
                               ,@(sort
                                  (cons cargo-inputs-entry
                                        (extract-cargo-inputs
                                         file-to-insert #:exclude term))
                                  (lambda (a b)
                                    (string< (symbol->string (first a))
                                             (symbol->string (first b)))))))
                           (_
                            (and=> (find-cargo-inputs-location file-to-insert)
                                   delete-expression))
                           (port (open-file file-to-insert "a")))
                      (pretty-print-with-comments port cargo-inputs)
                      (newline port)
                      (close-port port)))
                  source-expressions))
               ((assoc-ref opts 'recursive)
                (crate-recursive-import
                 name #:version version
                 #:recursive-dev-dependencies?
                 (assoc-ref opts 'recursive-dev-dependencies)
                 #:allow-yanked? (assoc-ref opts 'allow-yanked)))
               (else
                (crate->guix-package
                 name #:version version #:include-dev-deps? #t
                 #:allow-yanked? (assoc-ref opts 'allow-yanked)
                 #:mark-missing? (assoc-ref opts 'mark-missing))))
       (match (if lockfile
                  (let ((source-expressions
                         _
                         (cargo-lock->expressions lockfile name)))
                    (when file-to-insert
                      (let* ((source-expressions
                              cargo-inputs-entry
                              (cargo-lock->expressions lockfile name))
                             (term (first cargo-inputs-entry))
                             (cargo-inputs
                              `(define-cargo-inputs lookup-cargo-inputs
                                 ,@(sort
                                    (cons cargo-inputs-entry
                                          (extract-cargo-inputs
                                           file-to-insert #:exclude term))
                                    (lambda (a b)
                                      (string< (symbol->string (first a))
                                               (symbol->string (first b)))))))
                             (_
                              (and=> (find-cargo-inputs-location file-to-insert)
                                     delete-expression))
                             (port (open-file file-to-insert "a")))
                        (pretty-print-with-comments port cargo-inputs)
                        (newline port)
                        (close-port port)))
                    source-expressions)
                  (crate->guix-package
                   name #:version version
                   #:allow-yanked? (assoc-ref opts 'allow-yanked)))
         ((or #f '())
          (leave (G_ "failed to download meta-data for package '~a'~%")
                 (if version

M tests/crate.scm => tests/crate.scm +93 -1023
@@ 37,51 37,10 @@
  #:use-module (srfi srfi-11)
  #:use-module (srfi srfi-64))


;; crate versions and dependencies used here
;; foo-0.8.1
;; foo-1.0.0
;; foo-1.0.3
;; 	leaf-alice 0.7.5
;; bar-1.0.0
;;      leaf-bob   3.0.1
;;      leaf-bob   3.0.2 (dev-dependency)
;;      leaf-bob   4.0.0 (dev-dependency)
;;
;; root-1.0.0
;; root-1.0.4
;; 	intermediate-a 1.0.42
;; 	intermediate-b ^1.0.0
;; 	leaf-alice     ^0.7
;; 	leaf-bob       ^3
;; 	leaf-bob       3 (build-dependency)
;; 	intermediate-c 1 (dev-dependency)
;;
;; intermediate-a-1.0.40
;; intermediate-a-1.0.42
;; intermediate-a-1.1.0-alpha.1
;; 	intermediate-a	1.2.3
;; 	leaf-alice	0.7.5
;; 	leaf-bob	^3
;;
;; intermediate-b-1.2.3
;; 	leaf-bob	3.0.1
;;
;; intermediate-c-1.0.1
;;      leaf-alice      0.7.5 (dev-dependency)
;;
;; leaf-alice-0.7.3
;; leaf-alice-0.7.5
;;
;; leaf-bob-3.0.1
;; leaf-bob-3.0.2 (yanked)
;; leaf-bob-4.0.0 (yanked)


(define test-foo-crate
  "{
  \"crate\": {
    \"max_version\": \"1.0.3\",
    \"max_version\": \"1.0.0\",
    \"name\": \"foo\",
    \"description\": \"summary\",
    \"homepage\": \"http://example.com\",


@@ 111,23 70,12 @@
        \"links\": {
          \"dependencies\": \"/api/v1/crates/foo/1.0.3/dependencies\"
        },
        \"yanked\": false
        \"yanked\": true
      }
    ]
  }
}")

(define test-foo-dependencies
  "{
  \"dependencies\": [
     {
       \"crate_id\": \"leaf-alice\",
       \"kind\": \"normal\",
       \"req\": \"0.7.5\"
     }
  ]
}")

(define test-bar-crate
  "{
  \"crate\": {


@@ 145,338 93,18 @@
        \"links\": {
          \"dependencies\": \"/api/v1/crates/bar/1.0.0/dependencies\"
        },
        \"yanked\": false
      }
    ]
  }
}")

(define test-bar-dependencies
  "{
  \"dependencies\": [
     {
       \"crate_id\": \"leaf-bob\",
       \"kind\": \"normal\",
       \"req\": \"3.0.1\"
     },
     {
       \"crate_id\": \"leaf-bob\",
       \"kind\": \"dev\",
       \"req\": \"^3.0.2\"
     },
     {
       \"crate_id\": \"leaf-bob\",
       \"kind\": \"dev\",
       \"req\": \"^4.0.0\"
     }
  ]
}")

(define test-root-crate
  "{
  \"crate\": {
    \"max_version\": \"1.0.4\",
    \"name\": \"root\",
    \"description\": \"summary\",
    \"homepage\": \"http://example.com\",
    \"repository\": \"http://example.com\",
    \"keywords\": [\"dummy\", \"test\"],
    \"categories\": [\"test\"],
    \"actual_versions\": [
      { \"id\": 234240,
        \"num\": \"1.0.0\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/root/1.0.0/dependencies\"
        },
        \"yanked\": false
      },
      { \"id\": 234242,
        \"num\": \"1.0.4\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/root/1.0.4/dependencies\"
        },
        \"yanked\": false
      }
    ]
  }
}")

(define test-root-dependencies
  "{
  \"dependencies\": [
     {
       \"crate_id\": \"intermediate-a\",
       \"kind\": \"normal\",
       \"req\": \"1.0.42\"
     },
     {
       \"crate_id\": \"intermediate-b\",
       \"kind\": \"normal\",
       \"req\": \"^1.0.0\"
     },
     {
       \"crate_id\": \"leaf-alice\",
       \"kind\": \"normal\",
       \"req\": \"^0.7\"
     },
     {
       \"crate_id\": \"leaf-bob\",
       \"kind\": \"normal\",
       \"req\": \"^3\"
     },
     {
       \"crate_id\": \"leaf-bob\",
       \"kind\": \"build\",
       \"req\": \"3\"
     },
     {
       \"crate_id\": \"intermediate-c\",
       \"kind\": \"dev\",
       \"req\": \"1\"
     }
  ]
}")

(define test-intermediate-a-crate
  "{
  \"crate\": {
    \"max_version\": \"1.1.0-alpha.1\",
    \"name\": \"intermediate-a\",
    \"description\": \"summary\",
    \"homepage\": \"http://example.com\",
    \"repository\": \"http://example.com\",
    \"keywords\": [\"dummy\", \"test\"],
    \"categories\": [\"test\"],
    \"actual_versions\": [
      { \"id\": 234251,
        \"num\": \"1.0.40\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/intermediate-a/1.0.40/dependencies\"
        },
        \"yanked\": false
      },
      { \"id\": 234250,
        \"num\": \"1.0.42\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/intermediate-a/1.0.42/dependencies\"
        },
        \"yanked\": false
      },
      { \"id\": 234252,
        \"num\": \"1.1.0-alpha.1\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/intermediate-a/1.1.0-alpha.1/dependencies\"
        },
        \"yanked\": false
      }
    ]
  }
}")

(define test-intermediate-a-dependencies
  "{
  \"dependencies\": [
     {
       \"crate_id\": \"intermediate-b\",
       \"kind\": \"normal\",
       \"req\": \"1.2.3\"
     },
     {
       \"crate_id\": \"leaf-alice\",
       \"kind\": \"normal\",
       \"req\": \"0.7.5\"
     },
     {
       \"crate_id\": \"leaf-bob\",
       \"kind\": \"normal\",
       \"req\": \"^3\"
     }
  ]
}")

(define test-intermediate-b-crate
  "{
  \"crate\": {
    \"max_version\": \"1.2.3\",
    \"name\": \"intermediate-b\",
    \"description\": \"summary\",
    \"homepage\": \"http://example.com\",
    \"repository\": \"http://example.com\",
    \"keywords\": [\"dummy\", \"test\"],
    \"categories\": [\"test\"],
    \"actual_versions\": [
      { \"id\": 234260,
        \"num\": \"1.2.3\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/intermediate-b/1.2.3/dependencies\"
        },
        \"yanked\": false
      }
    ]
  }
}")

(define test-intermediate-b-dependencies
  "{
  \"dependencies\": [
     {
       \"crate_id\": \"leaf-bob\",
       \"kind\": \"normal\",
       \"req\": \"3.0.1\"
     }
  ]
}")

(define test-intermediate-c-crate
  "{
  \"crate\": {
    \"max_version\": \"1.0.1\",
    \"name\": \"intermediate-c\",
    \"description\": \"summary\",
    \"homepage\": \"http://example.com\",
    \"repository\": \"http://example.com\",
    \"keywords\": [\"dummy\", \"test\"],
    \"categories\": [\"test\"],
    \"actual_versions\": [
      { \"id\": 234290,
        \"num\": \"1.0.1\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/intermediate-c/1.0.1/dependencies\"
        },
        \"yanked\": false
      }
    ]
  }
}")

(define test-intermediate-c-dependencies
  "{
  \"dependencies\": [
     {
       \"crate_id\": \"leaf-alice\",
       \"kind\": \"dev\",
       \"req\": \"0.7.5\"
     }
  ]
}")

(define test-leaf-alice-crate
  "{
  \"crate\": {
    \"max_version\": \"0.7.5\",
    \"name\": \"leaf-alice\",
    \"description\": \"summary\",
    \"homepage\": \"http://example.com\",
    \"repository\": \"http://example.com\",
    \"keywords\": [\"dummy\", \"test\"],
    \"categories\": [\"test\"],
    \"actual_versions\": [
      { \"id\": 234270,
        \"num\": \"0.7.3\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/leaf-alice/0.7.3/dependencies\"
        },
        \"yanked\": false
      },
      { \"id\": 234272,
        \"num\": \"0.7.5\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/leaf-alice/0.7.5/dependencies\"
        },
        \"yanked\": false
      }
    ]
  }
}")

(define test-leaf-alice-dependencies
  "{
  \"dependencies\": []
}")

(define test-leaf-bob-crate
  "{
  \"crate\": {
    \"max_version\": \"3.0.1\",
    \"name\": \"leaf-bob\",
    \"description\": \"summary\",
    \"homepage\": \"http://example.com\",
    \"repository\": \"http://example.com\",
    \"keywords\": [\"dummy\", \"test\"],
    \"categories\": [\"test\"]
    \"actual_versions\": [
      { \"id\": 234280,
        \"num\": \"3.0.1\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/leaf-bob/3.0.1/dependencies\"
        },
        \"yanked\": false
      },
      { \"id\": 234281,
        \"num\": \"3.0.2\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/leaf-bob/3.0.2/dependencies\"
        },
        \"yanked\": true
      },
      { \"id\": 234282,
        \"num\": \"4.0.0\",
        \"license\": \"MIT OR Apache-2.0\",
        \"links\": {
          \"dependencies\": \"/api/v1/crates/leaf-bob/4.0.0/dependencies\"
        },
        \"yanked\": true
      }
    ]
  }
}")

(define test-leaf-bob-dependencies
  "{
  \"dependencies\": []
}")


(define test-source-hash
  "")

(define have-guile-semver?
  (false-if-exception (resolve-interface '(semver))))

(define rust-leaf-bob-3
  (package
    (name "rust-leaf-bob")
    (version "3.0.1")
    (source #f)
    (build-system #f)
    (home-page #f)
    (synopsis #f)
    (description #f)
    (license #f)))

(define rust-leaf-bob-3.0.2-yanked
  (package
    (name "rust-leaf-bob")
    (version "3.0.2")
    (source #f)
    (properties '((crate-version-yanked? . #t)))
    (build-system #f)
    (home-page #f)
    (synopsis #f)
    (description #f)
    (license #f)))

(define temp-file
  (string-append "t-crate-" (number->string (getpid))))



@@ 491,7 119,7 @@
    (source (dummy-origin
             (uri (crate-uri "rustc-serialize" "1.0")))))))

(unless have-guile-semver? (test-skip 1))
(unless have-guile-semver? (test-skip 3))
(test-assert "crate->guix-package"
  ;; Replace network resources with sample data.
  (mock ((guix http-client) http-fetch


@@ 499,463 127,110 @@
           (match url
             ("https://crates.io/api/v1/crates/foo"
              (open-input-string test-foo-crate))
             ("https://crates.io/api/v1/crates/foo/1.0.3/download"
              (set! test-source-hash
                    (bytevector->nix-base32-string
                     (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
              (open-input-string "empty file\n"))
             ("https://crates.io/api/v1/crates/foo/1.0.3/dependencies"
              (open-input-string test-foo-dependencies))
             ("https://crates.io/api/v1/crates/leaf-alice"
              (open-input-string test-leaf-alice-crate))
             ("https://crates.io/api/v1/crates/leaf-alice/0.7.5/download"
             ("https://crates.io/api/v1/crates/foo/1.0.0/download"
              (set! test-source-hash
                    (bytevector->nix-base32-string
                     (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
              (open-input-string "empty file\n"))
             ("https://crates.io/api/v1/crates/leaf-alice/0.7.5/dependencies"
              (open-input-string test-leaf-alice-dependencies))
             (_ (error "Unexpected URL: " url)))))

        (match (crate->guix-package "foo")
          ((define-public 'rust-foo-1
             (package (name "rust-foo")
                      (version "1.0.3")
                      (source
                       (origin
                         (method url-fetch)
                         (uri (crate-uri "foo" 'version))
                         (file-name (string-append name "-" version ".tar.gz"))
                         (sha256
                          (base32
                           (?  string? hash)))))
                      (build-system 'cargo-build-system)
                      (arguments
                       ('quasiquote
                        (#:skip-build? #t
                         #:cargo-inputs
                         (("rust-leaf-alice" ('unquote 'rust-leaf-alice-0.7))))))
                      (home-page "http://example.com")
                      (synopsis "summary")
                      (description "This package provides summary.")
                      (license (list license:expat license:asl2.0))))
          (`(define-public foo
              (package (name "foo")
                       (version "1.0.0")
                       (source
                        (origin
                          (method url-fetch)
                          (uri (crate-uri "foo" version))
                          (file-name (string-append name "-" version ".tar.gz"))
                          (sha256
                           (base32
                            ,(?  string? hash)))))
                       (build-system cargo-build-system)
                       (inputs (cargo-inputs 'foo))
                       (home-page "http://example.com")
                       (synopsis "summary")
                       (description "This package provides summary.")
                       (license (list license:expat license:asl2.0))))

           (string=? test-source-hash hash))
          (x
           (pk 'fail x #f)))))

(unless have-guile-semver? (test-skip 1))
(test-assert "crate->guix-package-marks-missing-packages"
(test-assert "crate->guix-package yanked"
  (mock
   ((gnu packages) find-packages-by-name
    (lambda* (name #:optional version)
      (match name
        ("rust-leaf-bob"
         (list rust-leaf-bob-3.0.2-yanked))
        (_ '()))))
   (mock
    ((guix http-client) http-fetch
     (lambda (url . rest)
       (match url
         ("https://crates.io/api/v1/crates/intermediate-b"
          (open-input-string test-intermediate-b-crate))
         ("https://crates.io/api/v1/crates/intermediate-b/1.2.3/download"
          (set! test-source-hash
                (bytevector->nix-base32-string
                 (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
          (open-input-string "empty file\n"))
         ("https://crates.io/api/v1/crates/intermediate-b/1.2.3/dependencies"
          (open-input-string test-intermediate-b-dependencies))
         ("https://crates.io/api/v1/crates/leaf-bob"
          (open-input-string test-leaf-bob-crate))
         ("https://crates.io/api/v1/crates/leaf-bob/3.0.1/download"
          (set! test-source-hash
                (bytevector->nix-base32-string
                 (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
          (open-input-string "empty file\n"))
         (_ (error "Unexpected URL: " url)))))
    (match (crate->guix-package "intermediate-b" #:mark-missing? #t)
      ((define-public 'rust-intermediate-b-1
         (package
           (name "rust-intermediate-b")
           (version "1.2.3")
           (source
            (origin
              (method url-fetch)
              (uri (crate-uri "intermediate-b" version))
              (file-name
               (string-append name "-" version ".tar.gz"))
              (sha256
               (base32
                (?  string? hash)))))
           (build-system cargo-build-system)
           (arguments
            ('quasiquote
             (#:skip-build? #t
              #:cargo-inputs
              (($ <comment> ";; rust-leaf-bob-3\n" #f)))))
           (home-page "http://example.com")
           (synopsis "summary")
           (description "This package provides summary.")
           (license (list license:expat license:asl2.0))))
       #t)
      (x
       (pk 'fail
           (pretty-print-with-comments (current-output-port) x)
           #f))))))
   ((guix http-client) http-fetch
    (lambda (url . rest)
      (match url
        ("https://crates.io/api/v1/crates/foo"
         (open-input-string test-foo-crate))
        ("https://crates.io/api/v1/crates/foo/1.0.3/download"
         (set! test-source-hash
               (bytevector->nix-base32-string
                (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
         (open-input-string "empty file\n"))
        (_ (error "Unexpected URL: " url)))))

(unless have-guile-semver? (test-skip 1))
(test-assert "crate-recursive-import"
  ;; Replace network resources with sample data.
  (mock ((guix http-client) http-fetch
         (lambda (url . rest)
           (match url
             ("https://crates.io/api/v1/crates/root"
              (open-input-string test-root-crate))
             ("https://crates.io/api/v1/crates/root/1.0.4/download"
              (set! test-source-hash
                    (bytevector->nix-base32-string
                     (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
              (open-input-string "empty file\n"))
             ("https://crates.io/api/v1/crates/root/1.0.4/dependencies"
              (open-input-string test-root-dependencies))
             ("https://crates.io/api/v1/crates/intermediate-a"
              (open-input-string test-intermediate-a-crate))
             ("https://crates.io/api/v1/crates/intermediate-a/1.0.42/download"
              (set! test-source-hash
                    (bytevector->nix-base32-string
                     (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
              (open-input-string "empty file\n"))
             ("https://crates.io/api/v1/crates/intermediate-a/1.0.42/dependencies"
              (open-input-string test-intermediate-a-dependencies))
             ("https://crates.io/api/v1/crates/intermediate-b"
              (open-input-string test-intermediate-b-crate))
             ("https://crates.io/api/v1/crates/intermediate-b/1.2.3/download"
              (set! test-source-hash
                    (bytevector->nix-base32-string
                     (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
              (open-input-string "empty file\n"))
             ("https://crates.io/api/v1/crates/intermediate-b/1.2.3/dependencies"
              (open-input-string test-intermediate-b-dependencies))
             ("https://crates.io/api/v1/crates/intermediate-c"
              (open-input-string test-intermediate-c-crate))
             ("https://crates.io/api/v1/crates/intermediate-c/1.0.1/download"
              (set! test-source-hash
                    (bytevector->nix-base32-string
                     (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
              (open-input-string "empty file\n"))
             ("https://crates.io/api/v1/crates/intermediate-c/1.0.1/dependencies"
              (open-input-string test-intermediate-c-dependencies))
             ("https://crates.io/api/v1/crates/leaf-alice"
              (open-input-string test-leaf-alice-crate))
             ("https://crates.io/api/v1/crates/leaf-alice/0.7.5/download"
              (set! test-source-hash
                    (bytevector->nix-base32-string
                     (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
              (open-input-string "empty file\n"))
             ("https://crates.io/api/v1/crates/leaf-alice/0.7.5/dependencies"
              (open-input-string test-leaf-alice-dependencies))
             ("https://crates.io/api/v1/crates/leaf-bob"
              (open-input-string test-leaf-bob-crate))
             ("https://crates.io/api/v1/crates/leaf-bob/3.0.1/download"
              (set! test-source-hash
                    (bytevector->nix-base32-string
                     (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
              (open-input-string "empty file\n"))
             ("https://crates.io/api/v1/crates/leaf-bob/3.0.1/dependencies"
              (open-input-string test-leaf-bob-dependencies))
             (_ (error "Unexpected URL: " url)))))
        (match (crate-recursive-import "root")
          ;; rust-intermediate-b has no dependency on the rust-leaf-alice
          ;; package, so this is a valid ordering
          (((define-public 'rust-intermediate-c-1
              (package
                (name "rust-intermediate-c")
                (version "1.0.1")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "intermediate-c" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments
                 ('quasiquote (#:skip-build? #t)))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-leaf-alice-0.7
              (package
                (name "rust-leaf-alice")
                (version "0.7.5")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "leaf-alice" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments ('quasiquote (#:skip-build? #t)))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-leaf-bob-3
              (package
                (name "rust-leaf-bob")
                (version "3.0.1")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "leaf-bob" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments ('quasiquote (#:skip-build? #t)))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-intermediate-b-1
              (package
                (name "rust-intermediate-b")
                (version "1.2.3")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "intermediate-b" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments
                 ('quasiquote (#:skip-build? #t
                               #:cargo-inputs
                               (("rust-leaf-bob"
                                 ('unquote rust-leaf-bob-3))))))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-intermediate-a-1
              (package
                (name "rust-intermediate-a")
                (version "1.0.42")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "intermediate-a" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments
                 ('quasiquote (#:skip-build? #t
                               #:cargo-inputs
                               (("rust-intermediate-b"
                                 ('unquote rust-intermediate-b-1))
                                ("rust-leaf-alice"
                                 ('unquote 'rust-leaf-alice-0.7))
                                ("rust-leaf-bob"
                                 ('unquote rust-leaf-bob-3))))))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-root-1
              (package
                (name "rust-root")
                (version "1.0.4")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "root" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments
                 ('quasiquote (#:cargo-inputs
                               (("rust-intermediate-a"
                                 ('unquote rust-intermediate-a-1))
                                ("rust-intermediate-b"
                                 ('unquote rust-intermediate-b-1))
                                ("rust-leaf-alice"
                                 ('unquote 'rust-leaf-alice-0.7))
                                ("rust-leaf-bob"
                                 ('unquote rust-leaf-bob-3)))
                               #:cargo-development-inputs
                               (("rust-intermediate-c"
                                 ('unquote rust-intermediate-c-1))))))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0)))))
           #t)
          (x
           (pk 'fail x #f)))
        (match (crate-recursive-import "root"
                                       #:recursive-dev-dependencies? #t)
          ;; rust-intermediate-b has no dependency on the rust-leaf-alice
          ;; package, so this is a valid ordering
          (((define-public 'rust-intermediate-c-1
              (package
                (name "rust-intermediate-c")
                (version "1.0.1")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "intermediate-c" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments
                 ('quasiquote (#:cargo-development-inputs
                               (("rust-leaf-alice"
                                 ('unquote rust-leaf-alice-0.7))))))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-leaf-alice-0.7
              (package
                (name "rust-leaf-alice")
                (version "0.7.5")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "leaf-alice" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-leaf-bob-3
              (package
                (name "rust-leaf-bob")
                (version "3.0.1")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "leaf-bob" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-intermediate-b-1
              (package
                (name "rust-intermediate-b")
                (version "1.2.3")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "intermediate-b" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments
                 ('quasiquote (#:cargo-inputs
                               (("rust-leaf-bob"
                                 ('unquote rust-leaf-bob-3))))))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-intermediate-a-1
              (package
                (name "rust-intermediate-a")
                (version "1.0.42")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "intermediate-a" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments
                 ('quasiquote (#:cargo-inputs
                               (("rust-intermediate-b"
                                 ('unquote rust-intermediate-b-1))
                                ("rust-leaf-alice"
                                 ('unquote 'rust-leaf-alice-0.7))
                                ("rust-leaf-bob"
                                 ('unquote rust-leaf-bob-3))))))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-root-1
              (package
                (name "rust-root")
                (version "1.0.4")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "root" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments
                 ('quasiquote (#:cargo-inputs
                               (("rust-intermediate-a"
                                 ('unquote rust-intermediate-a-1))
                                ("rust-intermediate-b"
                                 ('unquote rust-intermediate-b-1))
                                ("rust-leaf-alice"
                                 ('unquote 'rust-leaf-alice-0.7))
                                ("rust-leaf-bob"
                                 ('unquote rust-leaf-bob-3))
                                ("rust-leaf-bob"
                                 ('unquote rust-leaf-bob-3)))
                               #:cargo-development-inputs
                               (("rust-intermediate-c"
                                 ('unquote rust-intermediate-c-1))))))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0)))))
           #t)
          (x
           (pk 'fail x #f)))))
   (match (crate->guix-package "foo" #:version "1.0.3" #:allow-yanked? #t)
     (`(define-public foo
         (package (name "foo")
                  (version "1.0.3")
                  ,(? comment?)
                  (source
                   (origin
                     (method url-fetch)
                     (uri (crate-uri "foo" version))
                     (file-name (string-append name "-" version "-yanked.tar.gz"))
                     (sha256
                      (base32
                       ,(?  string? hash)))))
                  (properties '((crate-version-yanked? . #t)))
                  (build-system cargo-build-system)
                  (inputs (cargo-inputs 'foo))
                  (home-page "http://example.com")
                  (synopsis "summary")
                  (description "This package provides summary.")
                  (license (list license:expat license:asl2.0))))
      (string=? test-source-hash hash))
     (x
      (pk 'fail x #f)))))

(test-assert "crate->guix-package only yanked available"
  (mock
   ((guix http-client) http-fetch
    (lambda (url . rest)
      (match url
        ("https://crates.io/api/v1/crates/bar"
         (open-input-string test-foo-crate))
        ("https://crates.io/api/v1/crates/bar/1.0.0/download"
         (set! test-source-hash
               (bytevector->nix-base32-string
                (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
         (open-input-string "empty file\n"))
        (_ (error "Unexpected URL: " url)))))

   (match (crate->guix-package "bar")
     (`(define-public bar
         (package (name "bar")
                  (version "1.0.0")
                  (source
                   (origin
                     (method url-fetch)
                     (uri (crate-uri "bar" version))
                     (file-name
                      (string-append name "-" version ".tar.gz"))
                     (sha256
                      (base32
                       ,(?  string? hash)))))
                  (build-system cargo-build-system)
                  (inputs (cargo-inputs 'bar))
                  (home-page "http://example.com")
                  (synopsis "summary")
                  (description "This package provides summary.")
                  (license (list license:expat license:asl2.0))))
      (string=? test-source-hash hash))
     (x
      (pk 'fail x #f)))))

(test-equal "licenses: MIT OR Apache-2.0"
  '(license:expat license:asl2.0)


@@ 977,211 252,6 @@
  '(license:expat license:asl2.0)
  (string->license "MIT/Apache-2.0"))



(unless have-guile-semver? (test-skip 1))
(test-assert "crate-recursive-import-honors-existing-packages"
  (mock
   ((gnu packages) find-packages-by-name
    (lambda* (name #:optional version)
      (match name
        ("rust-leaf-bob"
         (list rust-leaf-bob-3 rust-leaf-bob-3.0.2-yanked))
        (_ '()))))
   (mock
    ((guix http-client) http-fetch
     (lambda (url . rest)
       (match url
         ("https://crates.io/api/v1/crates/bar"
          (open-input-string test-bar-crate))
         ("https://crates.io/api/v1/crates/bar/1.0.0/download"
          (set! test-source-hash
                (bytevector->nix-base32-string
                 (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
          (open-input-string "empty file\n"))
         ("https://crates.io/api/v1/crates/bar/1.0.0/dependencies"
          (open-input-string test-bar-dependencies))
         ("https://crates.io/api/v1/crates/leaf-bob"
          (open-input-string test-leaf-bob-crate))
         ("https://crates.io/api/v1/crates/leaf-bob/3.0.2/download"
          (set! test-source-hash
                (bytevector->nix-base32-string
                 (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
          (open-input-string "empty file\n"))
         ("https://crates.io/api/v1/crates/leaf-bob/3.0.2/dependencies"
          (open-input-string test-leaf-bob-dependencies))
         ("https://crates.io/api/v1/crates/leaf-bob/4.0.0/download"
          (set! test-source-hash
                (bytevector->nix-base32-string
                 (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
          (open-input-string "empty file\n"))
         ("https://crates.io/api/v1/crates/leaf-bob/4.0.0/dependencies"
          (open-input-string test-leaf-bob-dependencies))
         (_ (error "Unexpected URL: " url)))))
    (match (crate-recursive-import "bar"
                                   #:allow-yanked? #t)
      (((define-public 'rust-bar-1
          (package
            (name "rust-bar")
            (version "1.0.0")
            (source
             (origin
               (method url-fetch)
               (uri (crate-uri "bar" version))
               (file-name
                (string-append name "-" version ".tar.gz"))
               (sha256
                (base32
                 (?  string? hash)))))
            (build-system cargo-build-system)
            (arguments
             ('quasiquote (#:cargo-inputs
                           (("rust-leaf-bob"
                             ('unquote 'rust-leaf-bob-3)))
                           #:cargo-development-inputs
                           (("rust-leaf-bob"
                             ('unquote 'rust-leaf-bob-3.0.2-yanked))
                            ("rust-leaf-bob"
                             ('unquote 'rust-leaf-bob-4.0.0-yanked))))))
            (home-page "http://example.com")
            (synopsis "summary")
            (description "This package provides summary.")
            (license (list license:expat license:asl2.0)))))
       #t)
      (x
       (pk 'fail x #f))))))

(unless have-guile-semver? (test-skip 1))
(test-assert "crate-import-only-yanked-available"
  (mock
   ((guix http-client) http-fetch
    (lambda (url . rest)
      (match url
        ("https://crates.io/api/v1/crates/bar"
         (open-input-string test-bar-crate))
        ("https://crates.io/api/v1/crates/bar/1.0.0/download"
         (set! test-source-hash
               (bytevector->nix-base32-string
                (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
         (open-input-string "empty file\n"))
        ("https://crates.io/api/v1/crates/bar/1.0.0/dependencies"
         (open-input-string test-bar-dependencies))
        ("https://crates.io/api/v1/crates/leaf-bob"
         (open-input-string test-leaf-bob-crate))
        ("https://crates.io/api/v1/crates/leaf-bob/3.0.1/download"
         (set! test-source-hash
               (bytevector->nix-base32-string
                (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
         (open-input-string "empty file\n"))
        ("https://crates.io/api/v1/crates/leaf-bob/3.0.1/dependencies"
         (open-input-string test-leaf-bob-dependencies))
        ("https://crates.io/api/v1/crates/leaf-bob/3.0.2/download"
         (set! test-source-hash
               (bytevector->nix-base32-string
                (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
         (open-input-string "empty file\n"))
        ("https://crates.io/api/v1/crates/leaf-bob/3.0.2/dependencies"
         (open-input-string test-leaf-bob-dependencies))
        ("https://crates.io/api/v1/crates/leaf-bob/4.0.0/download"
         (set! test-source-hash
               (bytevector->nix-base32-string
                (gcrypt-sha256 (string->bytevector "empty file\n" "utf-8"))))
         (open-input-string "empty file\n"))
        ("https://crates.io/api/v1/crates/leaf-bob/4.0.0/dependencies"
         (open-input-string test-leaf-bob-dependencies))
        (_ (error "Unexpected URL: " url)))))
        (match (crate-recursive-import "bar"
                                       #:recursive-dev-dependencies? #t
                                       #:allow-yanked? #t)
          (((define-public 'rust-leaf-bob-4.0.0-yanked
              (package
                (name "rust-leaf-bob")
                (version "4.0.0")
                ($ <comment> "; This version was yanked!\n" #t)
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "leaf-bob" version))
                   (file-name
                    (string-append name "-" version "-yanked.tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (properties ('quote (('crate-version-yanked? . #t))))
                (build-system cargo-build-system)
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-leaf-bob-3.0.2-yanked
              (package
                (name "rust-leaf-bob")
                (version "3.0.2")
                ($ <comment> "; This version was yanked!\n" #t)
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "leaf-bob" version))
                   (file-name
                    (string-append name "-" version "-yanked.tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (properties ('quote (('crate-version-yanked? . #t))))
                (build-system cargo-build-system)
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-leaf-bob-3
              (package
                (name "rust-leaf-bob")
                (version "3.0.1")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "leaf-bob" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0))))
            (define-public 'rust-bar-1
              (package
                (name "rust-bar")
                (version "1.0.0")
                (source
                 (origin
                   (method url-fetch)
                   (uri (crate-uri "bar" version))
                   (file-name
                    (string-append name "-" version ".tar.gz"))
                   (sha256
                    (base32
                     (?  string? hash)))))
                (build-system cargo-build-system)
                (arguments
                 ('quasiquote (#:cargo-inputs
                               (("rust-leaf-bob"
                                 ('unquote 'rust-leaf-bob-3)))
                               #:cargo-development-inputs
                               (("rust-leaf-bob"
                                 ('unquote 'rust-leaf-bob-3.0.2-yanked))
                                ("rust-leaf-bob"
                                 ('unquote 'rust-leaf-bob-4.0.0-yanked))))))
                (home-page "http://example.com")
                (synopsis "summary")
                (description "This package provides summary.")
                (license (list license:expat license:asl2.0)))))
            #t)
          (x
           (pk 'fail (pretty-print-with-comments (current-output-port) x) #f)))))


(test-assert "crate-lockfile-import"
  (begin