Subject: [bug#51838] [PATCH 03/11] guix: node-build-system: Support compiling add-ons with node-gyp.

From: Philip McGrath <[email-masked]>
Date: Sun, 14 Nov 2021 08:04:01 -0500

* gnu/packages/node.scm (node)[arguments]: Replace 'patch-npm-shebang
and 'patch-node-shebang with a new 'patch-nested-shebangs that also
handles node-gyp and other shebangs under "/lib/node_modules".
[inputs]: Add Python for node-gyp as "python-for-target".
(node-lts)[inputs]: Likewise.
(libnode)[arguments]: Adjust to delete 'patch-nested-shebangs rather
than 'patch-npm-shebang and 'patch-node-shebang.
* guix/build-system/node.scm (lower): Add optional #:python argument
and corresponding implicit input. Add the version of libuv used
as an input to the #:node package as a new implicit input.
* guix/build/node-build-system.scm (set-node-gyp-paths): New
function. Sets the "npm_config_nodedir" and "npm_config_python"
environment variables. Adds the "node-gyp-bin" directory to "PATH".
(configure-gyp): New function. Run `node-gyp configure` if we see
a `binding.gyp` file.
(%standard-phases): Add 'set-node-gyp-paths after 'set-paths.
Add 'configure-gyp after 'configure.

Co-authored-by: Pierre Langlois <[email-masked]>
---
 gnu/packages/node.scm            | 34 ++++++++++++++++----------------
 guix/build-system/node.scm       | 11 +++++++++--
 guix/build/node-build-system.scm | 23 +++++++++++++++++++++
 3 files changed, 49 insertions(+), 19 deletions(-)

diff --git a/gnu/packages/node.scm b/gnu/packages/node.scm
index 89a9bc7533..ad5179426a 100644
--- a/gnu/packages/node.scm
+++ b/gnu/packages/node.scm
@@ -237,21 +237,20 @@ (define-public node
                             python
                             (string-append python "3")))
                       "configure" flags))))
-         (add-after 'patch-shebangs 'patch-npm-shebang
-           (lambda* (#:key outputs #:allow-other-keys)
-             (let* ((bindir (string-append (assoc-ref outputs "out")
-                                           "/bin"))
-                    (npm    (string-append bindir "/npm"))
-                    (target (readlink npm)))
-               (with-directory-excursion bindir
-                 (patch-shebang target (list bindir))))))
-         (add-after 'install 'patch-node-shebang
-           (lambda* (#:key outputs #:allow-other-keys)
-             (let* ((bindir (string-append (assoc-ref outputs "out")
-                                           "/bin"))
-                    (npx    (readlink (string-append bindir "/npx"))))
-               (with-directory-excursion bindir
-                 (patch-shebang npx (list bindir)))))))))
+         (add-after 'patch-shebangs 'patch-nested-shebangs
+           (lambda* (#:key inputs outputs #:allow-other-keys)
+             (let* ((prefix (assoc-ref outputs "out"))
+                    (path (map (lambda (dir)
+                                 (string-append dir "/bin"))
+                               (list prefix
+                                     (assoc-ref inputs "python-for-target")))))
+               (for-each
+                (lambda (file)
+                  (patch-shebang file path))
+                (find-files (string-append prefix "/lib/node_modules")
+                            (lambda (file stat)
+                              (executable-file? file))
+                            #:stat lstat))))))))
     (native-inputs
      `(;; Runtime dependencies for binaries used as a bootstrap.
        ("c-ares" ,c-ares)
@@ -274,6 +273,7 @@ (define-public node
     (inputs
      `(("bash" ,bash-minimal)
        ("coreutils" ,coreutils)
+       ("python-for-target" ,python-wrapper) ;; for node-gyp (supports python3)
        ("c-ares" ,c-ares)
        ("http-parser" ,http-parser)
        ("icu4c" ,icu4c)
@@ -795,6 +795,7 @@ (define-public node-lts
     (inputs
      `(("bash" ,bash-minimal)
        ("coreutils" ,coreutils)
+       ("python-for-target" ,python-wrapper) ;; for node-gyp (supports python3)
        ("c-ares" ,c-ares)
        ("icu4c" ,icu4c-67)
        ("libuv" ,libuv-for-node)
@@ -813,5 +814,4 @@ (define-public libnode
         `(cons* "--shared" "--without-npm" ,flags))
        ((#:phases phases '%standard-phases)
         `(modify-phases ,phases
-           (delete 'patch-npm-shebang)
-           (delete 'patch-node-shebang)))))))
+           (delete 'patch-nested-shebangs)))))))
diff --git a/guix/build-system/node.scm b/guix/build-system/node.scm
index 98f63f87ef..fee4142a99 100644
--- a/guix/build-system/node.scm
+++ b/guix/build-system/node.scm
@@ -1,6 +1,8 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016 Jelle Licht <[email-masked]>
 ;;; Copyright © 2019 Timothy Sample <[email-masked]>
+;;; Copyright © 2021 Pierre Langlois <[email-masked]>
+;;; Copyright © 2021 Philip McGrath <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -24,6 +26,7 @@ (define-module (guix build-system node)
   #:use-module (guix search-paths)
   #:use-module (guix build-system)
   #:use-module (guix build-system gnu)
+  #:use-module (guix build-system python)
   #:use-module (ice-9 match)
   #:export (%node-build-system-modules
             node-build
@@ -44,11 +47,12 @@ (define (default-node)
 (define* (lower name
                 #:key source inputs native-inputs outputs system target
                 (node (default-node))
+                (python (default-python)) ;; for node-gyp
                 #:allow-other-keys
                 #:rest arguments)
   "Return a bag for NAME."
   (define private-keywords
-    '(#:source #:target #:node #:inputs #:native-inputs))
+    '(#:source #:target #:node #:python #:inputs #:native-inputs))
 
   (and (not target)                    ;XXX: no cross-compilation
        (bag
@@ -58,10 +62,13 @@ (define private-keywords
                               `(("source" ,source))
                               '())
                         ,@inputs
-
                         ;; Keep the standard inputs of 'gnu-build-system'.
                         ,@(standard-packages)))
          (build-inputs `(("node" ,node)
+                         ("python" ,python)
+                        ;; We don't always need libuv, but the libuv and
+                        ;; node versions need to match:
+                        ("libuv" ,@(assoc-ref (package-inputs node) "libuv"))
                          ,@native-inputs))
          (outputs outputs)
          (build node-build)
diff --git a/guix/build/node-build-system.scm b/guix/build/node-build-system.scm
index 70a367618e..6aeb0149dd 100644
--- a/guix/build/node-build-system.scm
+++ b/guix/build/node-build-system.scm
@@ -2,6 +2,8 @@
 ;;; Copyright © 2015 David Thompson <[email-masked]>
 ;;; Copyright © 2016, 2020 Jelle Licht <[email-masked]>
 ;;; Copyright © 2019, 2021 Timothy Sample <[email-masked]>
+;;; Copyright © 2021 Pierre Langlois <[email-masked]>
+;;; Copyright © 2021 Philip McGrath <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -46,6 +48,19 @@ (define (set-home . _)
               (format #t "set HOME to ~s~%" (getenv "HOME")))))))
   #t)
 
+(define* (set-node-gyp-paths #:key inputs #:allow-other-keys)
+  "Initialize environment variables needed for building native addons."
+  (setenv "npm_config_nodedir" (assoc-ref inputs "node"))
+  (setenv "npm_config_python" (assoc-ref inputs "python"))
+  (setenv "PATH"
+          (string-append (getenv "PATH")
+                         ":"
+                         ;; Put this at the end to make it easier to override,
+                         ;; just in case that should ever be necessary:
+                         (assoc-ref inputs "node")
+                         "/lib/node_modules/npm/bin/node-gyp-bin"))
+  #t)
+
 (define (module-name module)
   (let* ((package.json (string-append module "/package.json"))
          (package-meta (call-with-input-file package.json read-json)))
@@ -101,6 +116,12 @@ (define* (configure #:key outputs inputs #:allow-other-keys)
     (invoke npm "--offline" "--ignore-scripts" "install")
     #t))
 
+(define* (configure-gyp #:key inputs #:allow-other-keys)
+  "Run 'node-gyp configure' if we see a 'binding.gyp' file."
+  (if (file-exists? "binding.gyp")
+      (invoke (which "node-gyp") "configure")
+      #t))
+
 (define* (build #:key inputs #:allow-other-keys)
   (let ((package-meta (call-with-input-file "package.json" read-json)))
     (if (and=> (assoc-ref package-meta "scripts")
@@ -144,9 +165,11 @@ (define* (install #:key outputs inputs #:allow-other-keys)
 
 (define %standard-phases
   (modify-phases gnu:%standard-phases
+    (add-after 'set-paths 'set-node-gyp-paths set-node-gyp-paths)
     (add-after 'unpack 'set-home set-home)
     (add-before 'configure 'patch-dependencies patch-dependencies)
     (replace 'configure configure)
+    (add-after 'configure 'configure-gyp configure-gyp)
     (replace 'build build)
     (replace 'check check)
     (add-before 'install 'repack repack)
-- 
2.32.0