substitute: Improve functional decomposition.
[guix.git] / guix / build / python-build-system.scm
blob26a7254db9b79e2df52185de5f726a2bb08eba92
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2013, 2015 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2013 Andreas Enge <andreas@enge.fr>
4 ;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
5 ;;;
6 ;;; This file is part of GNU Guix.
7 ;;;
8 ;;; GNU Guix is free software; you can redistribute it and/or modify it
9 ;;; under the terms of the GNU General Public License as published by
10 ;;; the Free Software Foundation; either version 3 of the License, or (at
11 ;;; your option) any later version.
12 ;;;
13 ;;; GNU Guix is distributed in the hope that it will be useful, but
14 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 ;;; GNU General Public License for more details.
17 ;;;
18 ;;; You should have received a copy of the GNU General Public License
19 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
21 (define-module (guix build python-build-system)
22   #:use-module ((guix build gnu-build-system) #:prefix gnu:)
23   #:use-module (guix build utils)
24   #:use-module (ice-9 match)
25   #:use-module (ice-9 ftw)
26   #:use-module (srfi srfi-1)
27   #:use-module (srfi srfi-26)
28   #:export (%standard-phases
29             python-build))
31 ;; Commentary:
33 ;; Builder-side code of the standard Python package build procedure.
35 ;; Code:
38 (define (call-setuppy command params)
39   (if (file-exists? "setup.py")
40       (begin
41          (format #t "running \"python setup.py\" with command ~s and parameters ~s~%"
42                 command params)
43          (zero? (apply system* "python" "setup.py" command params)))
44       (error "no setup.py found")))
46 (define* (build #:rest empty)
47   "Build a given Python package."
48   (call-setuppy "build" '()))
50 (define* (check #:key tests? test-target #:allow-other-keys)
51   "Run the test suite of a given Python package."
52   (if tests?
53     (call-setuppy test-target '())
54     #t))
56 (define (get-python-version python)
57   (string-take (string-take-right python 5) 3))
59 (define* (install #:key outputs inputs (configure-flags '())
60                   #:allow-other-keys)
61   "Install a given Python package."
62   (let* ((out (assoc-ref outputs "out"))
63          (params (append (list (string-append "--prefix=" out))
64                          configure-flags))
65          (python-version (get-python-version (assoc-ref inputs "python")))
66          (old-path (getenv "PYTHONPATH"))
67          (add-path (string-append out "/lib/python" python-version
68                                   "/site-packages/")))
69         ;; create the module installation directory and add it to PYTHONPATH
70         ;; to make setuptools happy
71         (mkdir-p add-path)
72         (setenv "PYTHONPATH"
73                 (string-append (if old-path
74                                    (string-append old-path ":")
75                                    "")
76                                add-path))
77         (call-setuppy "install" params)))
79 (define* (wrap #:key inputs outputs #:allow-other-keys)
80   (define (list-of-files dir)
81     (map (cut string-append dir "/" <>)
82          (or (scandir dir (lambda (f)
83                             (let ((s (stat (string-append dir "/" f))))
84                               (eq? 'regular (stat:type s)))))
85              '())))
87   (define bindirs
88     (append-map (match-lambda
89                  ((_ . dir)
90                   (list (string-append dir "/bin")
91                         (string-append dir "/sbin"))))
92                 outputs))
94   (let* ((out  (assoc-ref outputs "out"))
95          (python (assoc-ref inputs "python"))
96          (var `("PYTHONPATH" prefix
97                 ,(cons (string-append out "/lib/python"
98                                       (get-python-version python)
99                                       "/site-packages")
100                        (search-path-as-string->list
101                         (or (getenv "PYTHONPATH") ""))))))
102     (for-each (lambda (dir)
103                 (let ((files (list-of-files dir)))
104                   (for-each (cut wrap-program <> var)
105                             files)))
106               bindirs)))
108 (define* (rename-pth-file #:key name inputs outputs #:allow-other-keys)
109   "Rename easy-install.pth to NAME.pth to avoid conflicts between packages
110 installed with setuptools."
111   (let* ((out (assoc-ref outputs "out"))
112          (python (assoc-ref inputs "python"))
113          (site-packages (string-append out "/lib/python"
114                                        (get-python-version python)
115                                        "/site-packages"))
116          (easy-install-pth (string-append site-packages "/easy-install.pth"))
117          (new-pth (string-append site-packages "/" name ".pth")))
118     (when (file-exists? easy-install-pth)
119       (rename-file easy-install-pth new-pth))
120     #t))
122 (define %standard-phases
123   ;; 'configure' and 'build' phases are not needed.  Everything is done during
124   ;; 'install'.
125   (modify-phases gnu:%standard-phases
126     (delete 'configure)
127     (replace 'install install)
128     (replace 'check check)
129     (replace 'build build)
130     (add-after 'install 'wrap wrap)
131     (add-before 'strip 'rename-pth-file rename-pth-file)))
133 (define* (python-build #:key inputs (phases %standard-phases)
134                        #:allow-other-keys #:rest args)
135   "Build the given Python package, applying all of PHASES in order."
136   (apply gnu:gnu-build #:inputs inputs #:phases phases args))
138 ;;; python-build-system.scm ends here