guix_mirror_bot pushed a commit to branch master
in repository guix.

commit 7ee79c9e82245b83ef76c84b67466ad93bea84aa
Author: Maxim Cournoyer <[email protected]>
AuthorDate: Tue Jun 9 09:34:25 2026 +0900

    import: npm-binary: Add a '--binary' option.
    
    This changes the default behavior of the 'npm-binary' importer to prefer 
using
    pristine sources from Git when available, along with attempting to build the
    package.  It adds a '--binary' option to the importer to preserve the 
previous
    behavior.
    
    * guix/import/git.scm (get-tags): Export.
    * guix/import/npm-binary.scm (<repository>): New JSON mapping.
    (<package-revision>): Add a repository field.
    (npm-package->package-sexp) [#:binary?]: New keyword.  Update doc.
    Conditionally splice #:phases argument.
    <normalize-git-url, sexpify-git-tag/maybe>: New nested procedures.
    (npm-package->package-sexp) [binary?]: Expand origin conditionally to 
BINARY?.
    (npm-binary->guix-package) [#:binary?]: New keyword.
    (npm-binary-recursive-import): Likewise.
    * guix/import/utils.scm (git->origin) [#:eager?]: New keyword.  Update doc.
    * guix/scripts/import/npm-binary.scm (show-help): Document new --binary 
option.
    (guix-import-npm-binary): Honor the new option.
    
    Change-Id: I8e8c1c333faa8f5c96184ada064b63bdf2b4e165
---
 doc/guix.texi                      |  15 +++--
 guix/import/git.scm                |   3 +-
 guix/import/npm-binary.scm         | 129 ++++++++++++++++++++++++++++++-------
 guix/import/utils.scm              |  18 ++++--
 guix/scripts/import/npm-binary.scm |  14 +++-
 5 files changed, 139 insertions(+), 40 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index d974a5cdf2..7950843c3c 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -15732,13 +15732,6 @@ The npm-binary importer also allows you to specify a 
version string:
 guix import npm-binary buffer-crc32@@1.0.0
 @end example
 
-@quotation Note
-Generated package expressions skip the build step of the
-@code{node-build-system}. As such, generated package expressions often
-refer to transpiled or generated files, instead of being built from
-source.
-@end quotation
-
 @quotation Note
 Currently, the generated package definition contains a phase to delete
 the development dependencies (@code{devDependencies}) from the
@@ -15756,6 +15749,14 @@ Additional options include:
 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 --binary
+@itemx -b
+Import a binary NPM package.  This means preferring the use of a
+@url{https://registry.npmjs.org/} source origin, which typically bundles
+pre-built artifacts, along removing the @code{build} phase to avoid
+rebuilding such artifacts from source.  This should only be used as a
+last resort, as it is preferable building everything from source, to
+preserve referential transparency and the chain of trust.
 @end table
 
 @item opam
diff --git a/guix/import/git.scm b/guix/import/git.scm
index 81ea9b7398..e6dab7f25f 100644
--- a/guix/import/git.scm
+++ b/guix/import/git.scm
@@ -34,7 +34,8 @@
   #:use-module (srfi srfi-34)
   #:use-module (srfi srfi-35)
   #:use-module (srfi srfi-71)
-  #:export (%generic-git-updater))
+  #:export (%generic-git-updater
+            get-tags))
 
 ;;; Commentary:
 ;;;
diff --git a/guix/import/npm-binary.scm b/guix/import/npm-binary.scm
index b924ebdf0a..e299c25960 100644
--- a/guix/import/npm-binary.scm
+++ b/guix/import/npm-binary.scm
@@ -26,6 +26,7 @@
   #:use-module (gnu packages)
   #:use-module (guix base32)
   #:use-module (guix http-client)
+  #:use-module ((guix import git) #:select (get-tags))
   #:use-module (guix import json)
   #:use-module (guix import utils)
   #:use-module (guix memoization)
@@ -83,6 +84,14 @@
   json->dist
   (tarball dist-tarball))
 
+(define (string-or-#f s)
+  (and (string? s) s))
+
+(define-json-mapping <repository> make-repository repository?
+  json->repository
+  (type repository-type "type" string-or-#f)
+  (url repository-url "url" string-or-#f))
+
 (define (empty-or-string s)
   (if (string? s) s ""))
 
@@ -120,7 +129,18 @@
                  license)))))
   (description package-revision-description             ;string
                "description" empty-or-string)
-  (dist package-revision-dist "dist" json->dist))       ;dist
+  (dist package-revision-dist "dist" json->dist)
+  (repository package-revision-repository "repository" ;<repository>
+              (lambda (value)
+                ;; Guard against historical values such as strings or anything
+                ;; else.
+                (match value
+                  ((? alist?)
+                   (json->repository value))
+                  ((? unspecified?) #f)
+                  ((? string? url)
+                   (make-repository "git" url))
+                  (_ #f)))))
 
 (define (versions->package-revisions versions)
   (match versions
@@ -203,8 +223,10 @@
          (name (npm-name->name npm-name)))
     (name+version->symbol name version)))
 
-(define (npm-package->package-sexp npm-package)
-  "Return the `package' s-expression for an NPM-PACKAGE."
+(define* (npm-package->package-sexp npm-package #:key binary?)
+  "Return the `package' s-expression for an NPM-PACKAGE.  Prefer the binary
+distribution URL as the source origin when BINARY? is #t, otherwise use the
+associated Git repository, if available."
   (define resolve-spec
     (match-lambda
       (($ <versioned-package> name version)
@@ -221,6 +243,20 @@
         (string-drop-right url 7)
         url))
 
+  (define (normalize-git-url url)
+    (let ((url (cond
+                ((string-prefix? "git+https://"; url)
+                 (string-replace-substring url "git+https://"; "https://";))
+                ((string-prefix? "git+ssh://" url)
+                 (string-replace-substring url "git+ssh://" "https://";))
+                ((string-prefix? "git://" url)
+                 (string-replace-substring url "git://" "https://";))
+                (else url))))
+      (if (and (github-hosted? url)
+               (string-suffix? ".git" url))
+          (string-drop-right url 4)
+          url)))
+
   (define (sexpify-url/maybe dist-url name version)
     ;; Return a S-exp for the package URL, which is computed using the package
     ;; version, if it matches the distribution tarball DIST-URL.
@@ -245,14 +281,51 @@
                           ,(string-append "/-/" name "-")  version ".tgz")
           dist-url)))
 
+  (define (sexpify-git-tag/maybe tag version)
+    ;; Try to find a string-append pattern to build TAG from VERSION.  For 
example,
+    ;;
+    ;; (sexpify-git-tag/maybe "3.0.0v3.0.0v3.0.0" "3.0.0")
+    ;;   => '(string-append version "v" version "v" version)
+    (match (string-split (string-replace-substring
+                          tag version ":")
+                         #\:)
+      (("" "")                          ;tag == version
+       'version)
+      ((prefix "")
+       `(string-append ,prefix version))
+      (("" suffix)
+       `(string-append version ,suffix))
+      ((component components* ..1)
+       ;; Intersperse 'version between each component.
+       (let ((interspersed (append
+                            (append-map
+                             (lambda (x)
+                               (list x 'version))
+                             (drop-right `(,component ,@components*) 1))
+                            (list (last components*)))))
+         `(string-append ,@(remove (lambda (x)
+                                     (and (string? x)
+                                          (string-null? x)))
+                                   interspersed))))
+      (_ tag)))
+
   (match npm-package
     (($ <package-revision>
         name version home-page dependencies dev-dependencies
-        peer-dependencies license description dist)
+        peer-dependencies license description dist repository)
      (let* ((version-string (semver->string
                              (package-revision-version npm-package)))
             (dist-url (dist-tarball dist))
             (url (sexpify-url/maybe dist-url name version-string))
+            (git-url? (and (not binary?) repository
+                           (and=> (repository-type repository)
+                                  (cut string=? "git" <>))))
+            (git-url (and git-url? (and=> (repository-url repository)
+                                          normalize-git-url)))
+            (git-tag (and git-url
+                          (and=> (false-if-exception (get-tags git-url))
+                                 (cut assoc-ref <> version-string))))
+            (use-git-url? (and git-url git-tag))
             (name (npm-name->name name))
             (home-page (if (string? home-page)
                            (sanitize-home-page-url home-page)
@@ -264,28 +337,39 @@
             (dev-names (append (map versioned-package-name dev-dependencies)
                                peer-names))
             (extra-phases
-             (match dev-names
-               (() '())
-               ((dev-names ...)
-                `((add-after 'patch-dependencies 'delete-dev-dependencies
-                    (lambda _
-                      (modify-json (delete-dev-dependencies)))))))))
+             (append
+              (if binary?
+                  '((delete 'build))
+                  '())
+              (match dev-names
+                (() '())
+                ((dev-names ...)
+                 `((add-after 'patch-dependencies 'delete-dev-dependencies
+                     (lambda _
+                       (modify-json (delete-dev-dependencies))))))))))
        (values
         `(package
            (name ,name)
            (version ,version-string)
-           (source (origin
-                     (method url-fetch)
-                     (uri ,url)
-                     (sha256 (base32 ,(hash-url dist-url)))))
+           (source ,(if use-git-url?
+                        (git->origin git-url sexpify-git-tag/maybe
+                                     #:ref (cons 'tag git-tag)
+                                     #:eager? #t
+                                     git-tag version-string)
+                        `(origin
+                           (method url-fetch)
+                           (uri ,url)
+                           (sha256 (base32 ,(hash-url dist-url))))))
            (build-system node-build-system)
            (arguments
             (list
              #:tests? #f
-             #:phases
-             #~(modify-phases %standard-phases
-                 (delete 'build)
-                 ,@extra-phases)))
+             ,@(match extra-phases
+                 (() '())
+                 ((phase ..1)
+                  `(#:phases
+                    #~(modify-phases %standard-phases
+                        ,@extra-phases))))))
            ,@(match dependencies
                (() '())
                ((dependencies ...)
@@ -309,15 +393,16 @@
 ;;;
 
 (define npm-binary->guix-package
-  (lambda* (name #:key (version *semver-range-any*) #:allow-other-keys)
+  (lambda* (name #:key (version *semver-range-any*) binary? #:allow-other-keys)
     (let* ((svr (match version
                   ((? string?) (string->semver-range version))
                   (_ version)))
            (pkg (resolve-package name svr)))
-      (npm-package->package-sexp pkg))))
+      (npm-package->package-sexp pkg #:binary? binary?))))
 
-(define* (npm-binary-recursive-import package-name #:key version)
+(define* (npm-binary-recursive-import package-name #:key version binary?)
   (recursive-import package-name
                     #:repo->guix-package (memoize npm-binary->guix-package)
                     #:version version
-                    #:guix-name npm-name->name))
+                    #:guix-name npm-name->name
+                    #:binary? binary?))
diff --git a/guix/import/utils.scm b/guix/import/utils.scm
index a9ff90f0ee..4e0e2876af 100644
--- a/guix/import/utils.scm
+++ b/guix/import/utils.scm
@@ -7,7 +7,7 @@
 ;;; Copyright © 2019 Robert Vollmert <[email protected]>
 ;;; Copyright © 2020 Helio Machado <[email protected]>
 ;;; Copyright © 2020 Martin Becze <[email protected]>
-;;; Copyright © 2021, 2024 Maxim Cournoyer <[email protected]>
+;;; Copyright © 2021, 2024, 2026 Maxim Cournoyer <[email protected]>
 ;;; Copyright © 2021 Sarah Morgensen <[email protected]>
 ;;; Copyright © 2021 Xinglu Chen <[email protected]>
 ;;; Copyright © 2022 Alice Brenon <[email protected]>
@@ -265,22 +265,26 @@ the commit hash, the downloaded directory and its content 
hash."
      (sha256
       (base32 ,hash))))
 
-(define* (git->origin url proc #:key ref #:rest rest)
+(define* (git->origin url proc #:key ref eager? #:rest rest)
   "Return a generated `origin' block of a package depending on the Git version
 control system, and the directory in the store where the package has been
 downloaded, in case further processing is necessary.
 
-Unless overwritten with REF, the ref (as defined by the (guix git) module)
-is calculated from the evaluation of PROC with trailing arguments.  PROC must
-be a procedure with a 'body property, used to generate the origin sexp."
-  (let* ((args (strip-keyword-arguments '(#:ref) rest))
+Unless overwritten with REF, the ref (as defined by the (guix git) module) is
+calculated from the evaluation of PROC with trailing arguments.  If EAGER? is
+#t, the same computation (PROC applied to the trailing arguments) is used as
+value of the `commit' field of the generated `git-reference' S-expression.
+Otherwise, PROC must be a procedure with a 'body property, which is used as
+the `commit' field S-expression."
+  (let* ((args (strip-keyword-arguments '(#:ref #:eager?) rest))
          (commit (apply proc args))
          (ref (or ref (and commit `(tag-or-commit . ,commit))))
          (_ directory hash
             (if (or ref commit)
                 (download-git-repository url ref)
                 (values #f #f #f))))
-    (values (git-origin url (peek-body proc) hash) directory)))
+    (values (git-origin url (if eager? commit (peek-body proc)) hash)
+            directory)))
 
 (define* (default-git-error home-page #:optional location)
   "Return a procedure to be passed to a `git-error' `catch' for HOME-PAGE at
diff --git a/guix/scripts/import/npm-binary.scm 
b/guix/scripts/import/npm-binary.scm
index b2771bc539..a2fd446065 100644
--- a/guix/scripts/import/npm-binary.scm
+++ b/guix/scripts/import/npm-binary.scm
@@ -50,6 +50,8 @@ Import and convert the npm package PACKAGE-NAME using the
   -r, --recursive        import packages recursively"))
   (display (G_ "
   -V, --version          display version information and exit"))
+  (display (G_ "
+  -b, --binary           prefer pre-built sources and skip building"))
   (newline)
   (show-bug-report-information))
 
@@ -65,6 +67,9 @@ Import and convert the npm package PACKAGE-NAME using the
          (option '(#\r "recursive") #f #f
                  (lambda (opt name arg result)
                    (alist-cons 'recursive #t result)))
+         (option '(#\b "binary") #f #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'binary #t result)))
          %standard-import-options))
 
 (define* (package-name->name+version* spec)
@@ -94,16 +99,19 @@ contain '@'."
                              (('argument . value)
                               value)
                              (_ #f))
-                           (reverse opts))))
+                           (reverse opts)))
+         (binary? (assoc-ref opts 'binary)))
     (match args
       ((spec)
        (define-values (package-name version)
          (package-name->name+version* spec))
        (match (if (assoc-ref opts 'recursive)
                   ;; Recursive import
-                  (npm-binary-recursive-import package-name #:version version)
+                  (npm-binary-recursive-import package-name #:version version
+                                               #:binary? binary?)
                   ;; Single import
-                  (npm-binary->guix-package package-name #:version version))
+                  (npm-binary->guix-package package-name #:version version
+                                            #:binary? binary?))
          ((or #f '())
           (leave (G_ "failed to download meta-data for package '~a@~a'~%")
                  package-name version))

Reply via email to