Subject: [bug#77093] [PATCH rust-team 17/18] import: crate: Add ‘--lockfile’ option.

From: Hilton Chain <[email-masked]>
Date: Tue, 18 Mar 2025 15:24:30 +0800

* guix/scripts/import/crate.scm (%options): Add ‘--lockfile’ option.
* guix/scripts/import/crate.scm (show-help): Add it.
(guix-import-crate): Use it.
* doc/guix.texi (Invoking guix import): Document it.
* tests/crate.scm (temp-file): New variable.
("crate-lockfile-import"): New test.

Change-Id: I291478e04adf9f2df0bf216425a5e8aeba0bedd9
---
 doc/guix.texi                 | 15 ++++++
 guix/scripts/import/crate.scm | 58 +++++++++++++++++++----
 tests/crate.scm               | 88 +++++++++++++++++++++++++++++++++++
 3 files changed, 151 insertions(+), 10 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index c20e1d7f9c..029c8cf59c 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -14703,6 +14703,21 @@ Invoking guix import
 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, importer will ignore other
+options and won't output package definitions, instead generating source
+definition for all dependencies in @var{file}, a @file{Cargo.lock} file.
+For example:
+
+@example
+guix import crate --lockfile=/path/to/Cargo.lock my-package
+@end example
+
+generates sources from @file{/path/to/Cargo.lock}, and a list
+@code{my-package-cargo-inputs} referencing these sources.  The generated
+list is intended for package's base @code{inputs}, replacing
+@code{#:cargo-inputs} and @code{#:cargo-development-inputs}.
 @end table
 
 @item elm
diff --git a/guix/scripts/import/crate.scm b/guix/scripts/import/crate.scm
index 723cbb3665..3b536e135a 100644
--- a/guix/scripts/import/crate.scm
+++ b/guix/scripts/import/crate.scm
@@ -25,11 +25,13 @@
 (define-module (guix scripts import crate)
   #:use-module (guix ui)
   #:use-module (guix utils)
+  #:use-module (guix read-print)
   #:use-module (guix scripts)
   #:use-module (guix import crate)
   #:use-module (guix scripts import)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-37)
   #:use-module (ice-9 match)
   #:use-module (ice-9 format)
@@ -60,6 +62,9 @@ (define (show-help)
                          sufficient package exists for it"))
   (newline)
   (display (G_ "
+  -f, --lockfile=FILE    import dependencies from FILE, a 'Cargo.lock' file"))
+  (newline)
+  (display (G_ "
   -h, --help             display this help and exit"))
   (display (G_ "
   -V, --version          display version information and exit"))
@@ -87,6 +92,9 @@ (define %options
          (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)
+                   (alist-cons 'lockfile arg result)))
          %standard-import-options))
 
 
@@ -101,6 +109,8 @@ (define (guix-import-crate . args)
                         #:build-options? #f))
 
   (let* ((opts (parse-options))
+         (lockfile (assoc-ref opts 'lockfile))
+         (file-to-insert (assoc-ref opts 'file-to-insert))
          (args (filter-map (match-lambda
                              (('argument . value)
                               value)
@@ -111,16 +121,44 @@ (define (guix-import-crate . args)
        (define-values (name version)
          (package-name->name+version spec))
 
-       (match (if (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))
-                  (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 (cond
+               ((and=> lockfile
+                       (lambda (file)
+                         (or (file-exists? file)
+                             (leave (G_ "file '~a' does not exist~%") file))))
+                (let-values (((crate-definitions input-list)
+                              (cargo-lock->definitions lockfile name)))
+                  (if file-to-insert
+                      ;; Adjusted from ‘--insert’ option of guix-import.
+                      (let ((term (second input-list)))
+                        (begin
+                          ;; Remove existing input list definition.
+                          (and=> (find-definition-location file-to-insert term)
+                                 delete-expression)
+                          ;; Insert input list alphabetically.
+                          (or (and=> (find-definition-insertion-location
+                                      file-to-insert term)
+                                     (cut insert-expression <> input-list))
+                              (let ((port (open-file file-to-insert "a")))
+                                (newline port)
+                                (pretty-print-with-comments port input-list)
+                                (newline port)
+                                (newline port)
+                                (close-port port))))
+                        crate-definitions)
+                      `(,@crate-definitions
+                        ,input-list))))
+               ((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))))
          ((or #f '())
           (leave (G_ "failed to download meta-data for package '~a'~%")
                  (if version
diff --git a/tests/crate.scm b/tests/crate.scm
index d0dc779cd2..d46f753f9c 100644
--- a/tests/crate.scm
+++ b/tests/crate.scm
@@ -34,6 +34,7 @@ (define-module (test-crate)
   #:use-module (gnu packages)
   #:use-module (ice-9 iconv)
   #:use-module (ice-9 match)
+  #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-64))

@@ -476,6 +477,9 @@ (define rust-leaf-bob-3.0.2-yanked
     (description #f)
     (license #f)))
 
+(define temp-file
+  (string-append "t-utils-" (number->string (getpid))))
+
 
 (test-begin "crate")
 
@@ -1178,4 +1182,88 @@ (define rust-leaf-bob-3.0.2-yanked
           (x
            (pk 'fail (pretty-print-with-comments (current-output-port) x) #f)))))
 
+
+(test-assert "crate-lockfile-import"
+  (begin
+    (call-with-output-file temp-file
+      (lambda (port)
+        (display "\
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = \"adler2\"
+version = \"2.0.0\"
+source = \"registry+https://github.com/rust-lang/crates.io-index\"
+checksum = \"512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627\"
+
+[[package]]
+name = \"aho-corasick\"
+version = \"1.1.3\"
+source = \"registry+https://github.com/rust-lang/crates.io-index\"
+checksum = \"8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916\"
+dependencies = [
+ \"memchr\",
+]
+
+[[package]]
+name = \"smithay\"
+version = \"0.4.0\"
+source = \"git+https://github.com/Smithay/smithay.git?rev=\
+0cd3345c59f7cb139521f267956a1a4e33248393#\
+0cd3345c59f7cb139521f267956a1a4e33248393\"
+dependencies = [
+ \"appendlist\",
+]
+
+[[package]]
+name = \"test\"
+version = \"25.2.0\"\n" port)))
+    (mock
+     ((guix scripts download) guix-download
+      (lambda _
+        (format #t "~a~%~a~%"
+                "/gnu/store/in056fyrz6nvy3jpxrxglgj30g0lwniv-smithay-0cd3345"
+                "191h87bpzg0l1ihfb4hmx00b86pfb5mwwc6s8i49al0vigc14l37")))
+     (let-values
+         (((crates-definitions input-list)
+           (cargo-lock->definitions temp-file "test")))
+       (and
+        (match crates-definitions
+          (((define 'rust-adler2-2.0.0
+              (crate-source
+               "adler2" "2.0.0"
+               "09r6drylvgy8vv8k20lnbvwq8gp09h7smfn6h1rxsy15pgh629si"))
+            (define 'rust-aho-corasick-1.1.3
+              (crate-source
+               "aho-corasick" "1.1.3"
+               "05mrpkvdgp5d20y2p989f187ry9diliijgwrs254fs9s1m1x6q4f"))
+            (define 'rust-smithay-0.4.0.0cd3345
+              ($ <comment>
+                 ";; TODO: Define standalone package if this is a workspace.\n"
+                 #f)
+              (origin
+                (method git-fetch)
+                (uri (git-reference
+                      (url "https://github.com/Smithay/smithay.git")
+                      (commit "0cd3345c59f7cb139521f267956a1a4e33248393")))
+                (file-name (git-file-name "rust-smithay" "0.4.0.0cd3345"))
+                (sha256
+                 (base32
+                  "191h87bpzg0l1ihfb4hmx00b86pfb5mwwc6s8i49al0vigc14l37")))))
+           #t)
+          (x
+           (pk 'fail (pretty-print-with-comments (current-output-port) x) #f)))
+        (match input-list
+          ((define-public 'test-cargo-inputs
+             (list rust-adler2-2.0.0
+                   rust-aho-corasick-1.1.3
+                   rust-smithay-0.4.0.0cd3345))
+           #t)
+          (x
+           (pk 'fail x #f))))))))
+
 (test-end "crate")
+
+(false-if-exception (delete-file temp-file))
-- 
2.48.1