Subject: bug#25235: Wrapped python programs get native-inputs in PYTHONPATH

From: [email-masked] (Ludovic Courtès)
Date: Thu, 30 Mar 2017 17:30:25 +0200

Hello,

Arun Isaac <[email-masked]> skribis:

> I'm working on fixing this bug. I have modified
> guix/build/python-build-system.scm for the same. In particular, I have
> added #:use-module (guix packages) because I need the functions
> `package-name' and `package-transitive-target-inputs'. But, when I try
> building any python-build-system package with something like
> "./pre-inst-env guix build scons", I get a ("no code for module" (guix
> packages)) error. What am I missing?
>
> Full backtrace follows:

[...]

> 2954: 4 [define-module* (guix build python-build-system) #:filename ...]
> 2929: 3 [resolve-imports ((# # gnu:) (#) (#) (#) ...)]
> 2870: 2 [resolve-interface (guix packages) #:select ...]
> In unknown file:
>    ?: 1 [scm-error misc-error #f "~A ~S" ("no code for module" (guix packages)) #f]

“Build-side” modules, which typically live in (guix build …), should not
depend on “host-side” modules such as (guix packages).  That’s because
if we did that, we’d effectively end up importing all of Guix on the
build side, but then we’d also have to serialize data structures such as
packages to pass them from one side to the other.  (I hope this makes
sense to you, but if it doesn’t maybe the intro of
<https://gnu.org/s/guix/manual/html_node/G_002dExpressions.html> can
shed some light.)

So in short, we cannot use ‘package-name’ and
‘package-transitive-target-inputs’ in this module.

(Time passes…)

I wasn’t sure how to fix this bug myself so I gave it a try and ended up
with the patch below, but I haven’t tested in detail.  (You’ll notice
(guix build-system python) is hard to work with because it doesn’t use
gexps yet.)

How does it look?

Thanks,
Ludo’.

diff --git a/guix/build-system/python.scm b/guix/build-system/python.scm
index 17173f121..a05fd5a79 100644
--- a/guix/build-system/python.scm
+++ b/guix/build-system/python.scm
@@ -153,7 +153,7 @@ pre-defined variants."
                 #:rest arguments)
   "Return a bag for NAME."
   (define private-keywords
-    '(#:source #:target #:python #:inputs #:native-inputs))
+    '(#:source #:target #:python #:inputs))
 
   (and (not target)                               ;XXX: no cross-compilation
        (bag
@@ -174,6 +174,9 @@ pre-defined variants."
 
 (define* (python-build store name inputs
                        #:key
+                       (native-inputs '())
+                       ;; TODO: Something like this:
+                       ;; (disallowed-references native-inputs)
                        (tests? #t)
                        (test-target "test")
                        (use-setuptools? #t)
@@ -189,17 +192,24 @@ pre-defined variants."
                                   (guix build utils))))
   "Build SOURCE using PYTHON, and with INPUTS.  This assumes that SOURCE
 provides a 'setup.py' file as its build system."
+  (define canonicalize-reference
+    (match-lambda
+      (((? derivation? source))
+       (derivation->output-path source))
+      (((? package? package))
+       (derivation->output-path
+        (package-derivation store package system)))
+      ((source)
+       source)
+      (source
+       source)))
+
   (define builder
     `(begin
        (use-modules ,@modules)
        (python-build #:name ,name
-                     #:source ,(match (assoc-ref inputs "source")
-                                 (((? derivation? source))
-                                  (derivation->output-path source))
-                                 ((source)
-                                  source)
-                                 (source
-                                  source))
+                     #:source ,(canonicalize-reference
+                                (assoc-ref inputs "source"))
                      #:configure-flags ,configure-flags
                      #:system ,system
                      #:test-target ,test-target
@@ -209,7 +219,17 @@ provides a 'setup.py' file as its build system."
                      #:outputs %outputs
                      #:search-paths ',(map search-path-specification->sexp
                                            search-paths)
-                     #:inputs %build-inputs)))
+                     #:inputs %build-inputs
+
+                     ;; We call them "native inputs" but there's no
+                     ;; cross-compilation here, so that really means
+                     ;; "build-time-only" inputs, things should not be
+                     ;; run-time dependencies.
+                     #:native-inputs ',(map (match-lambda
+                                              ((name . rest)
+                                               `(,name
+                                                 . ,(canonicalize-reference rest))))
+                                            native-inputs))))
 
   (define guile-for-build
     (match guile
@@ -222,6 +242,8 @@ provides a 'setup.py' file as its build system."
 
   (build-expression->derivation store name builder
                                 #:inputs inputs
+                                ;; TODO:
+                                ;; #:disallowed-references disallowed-references
                                 #:system system
                                 #:modules imported-modules
                                 #:outputs outputs
diff --git a/guix/build/python-build-system.scm b/guix/build/python-build-system.scm
index dd07986b9..1ca26104b 100644
--- a/guix/build/python-build-system.scm
+++ b/guix/build/python-build-system.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2013, 2015, 2016 Ludovic Courtès <[email-masked]>
+;;; Copyright © 2013, 2015, 2016, 2017 Ludovic Courtès <[email-masked]>
 ;;; Copyright © 2013 Andreas Enge <[email-masked]>
 ;;; Copyright © 2013 Nikita Karetnikov <[email-masked]>
 ;;; Copyright © 2015 Mark H Weaver <[email-masked]>
@@ -184,7 +184,7 @@ when running checks after installing the package."
                          configure-flags)))
     (call-setuppy "install" params use-setuptools?)))
 
-(define* (wrap #:key inputs outputs #:allow-other-keys)
+(define* (wrap #:key native-inputs inputs outputs #:allow-other-keys)
   (define (list-of-files dir)
     (map (cut string-append dir "/" <>)
          (or (scandir dir (lambda (f)
@@ -199,14 +199,27 @@ when running checks after installing the package."
                         (string-append dir "/sbin"))))
                 outputs))
 
+  (define build-time-inputs
+    ;; Built-time-only dependencies.
+    (match native-inputs
+      (((names . directories) ...)
+       directories)))
+
+  (define (build-time-dependency? item)
+    (any (cut string-prefix? <> item)
+         build-time-inputs))
+
+  ;; Wrap binaries such that PYTHONPATH is set appropriately, but remove
+  ;; build-time-only dependencies (aka. #:native-inputs) from the search path.
   (let* ((out  (assoc-ref outputs "out"))
          (python (assoc-ref inputs "python"))
+         (path (search-path-as-string->list
+                (or (getenv "PYTHONPATH") "")))
          (var `("PYTHONPATH" prefix
                 ,(cons (string-append out "/lib/python"
                                       (get-python-version python)
                                       "/site-packages")
-                       (search-path-as-string->list
-                        (or (getenv "PYTHONPATH") ""))))))
+                       (remove build-time-dependency? path)))))
     (for-each (lambda (dir)
                 (let ((files (list-of-files dir)))
                   (for-each (cut wrap-program <> var)