guile-build-system.scm 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018, 2019 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix build guile-build-system)
  19. #:use-module ((guix build gnu-build-system) #:prefix gnu:)
  20. #:use-module (guix build utils)
  21. #:use-module (srfi srfi-1)
  22. #:use-module (srfi srfi-26)
  23. #:use-module (ice-9 match)
  24. #:use-module (ice-9 popen)
  25. #:use-module (ice-9 rdelim)
  26. #:use-module (ice-9 regex)
  27. #:use-module (ice-9 format)
  28. #:use-module (guix build utils)
  29. #:export (target-guile-effective-version
  30. %standard-phases
  31. guile-build))
  32. (define* (target-guile-effective-version #:optional guile)
  33. "Return the effective version of GUILE or whichever 'guile' is in $PATH.
  34. Return #false if it cannot be determined."
  35. (let* ((pipe (open-pipe* OPEN_READ
  36. (if guile
  37. (string-append guile "/bin/guile")
  38. "guile")
  39. "-c" "(display (effective-version))"))
  40. (line (read-line pipe)))
  41. (and (zero? (close-pipe pipe))
  42. (string? line)
  43. line)))
  44. (define (file-sans-extension file) ;TODO: factorize
  45. "Return the substring of FILE without its extension, if any."
  46. (let ((dot (string-rindex file #\.)))
  47. (if dot
  48. (substring file 0 dot)
  49. file)))
  50. (define %scheme-file-regexp
  51. ;; Regexp to match Scheme files.
  52. "\\.(scm|sls)$")
  53. (define %documentation-file-regexp
  54. ;; Regexp to match README files and the likes.
  55. "^(README.*|.*\\.html|.*\\.org|.*\\.md)$")
  56. (define* (set-locale-path #:key inputs native-inputs
  57. #:allow-other-keys)
  58. "Set 'GUIX_LOCPATH'."
  59. (match (assoc-ref (or native-inputs inputs) "locales")
  60. (#f #t)
  61. (locales
  62. (setenv "GUIX_LOCPATH" (string-append locales "/lib/locale"))
  63. #t)))
  64. (define* (invoke-each commands
  65. #:key (max-processes (current-processor-count))
  66. report-progress)
  67. "Run each command in COMMANDS in a separate process, using up to
  68. MAX-PROCESSES processes in parallel. Call REPORT-PROGRESS at each step.
  69. Raise an error if one of the processes exit with non-zero."
  70. (define total
  71. (length commands))
  72. (define processes
  73. (make-hash-table))
  74. (define (wait-for-one-process)
  75. (match (waitpid WAIT_ANY)
  76. ((pid . status)
  77. (let ((command (hashv-ref processes pid)))
  78. (hashv-remove! processes command)
  79. (unless (zero? (status:exit-val status))
  80. (format (current-error-port)
  81. "process '~{~a ~}' failed with status ~a~%"
  82. command status)
  83. (exit 1))))))
  84. (define (fork-and-run-command command)
  85. (match (primitive-fork)
  86. (0
  87. (dynamic-wind
  88. (const #t)
  89. (lambda ()
  90. (apply execlp command))
  91. (lambda ()
  92. (primitive-exit 127))))
  93. (pid
  94. (hashv-set! processes pid command)
  95. #t)))
  96. (let loop ((commands commands)
  97. (running 0)
  98. (completed 0))
  99. (match commands
  100. (()
  101. (or (zero? running)
  102. (let ((running (- running 1))
  103. (completed (+ completed 1)))
  104. (wait-for-one-process)
  105. (report-progress total completed)
  106. (loop commands running completed))))
  107. ((command . rest)
  108. (if (< running max-processes)
  109. (let ((running (+ 1 running)))
  110. (fork-and-run-command command)
  111. (loop rest running completed))
  112. (let ((running (- running 1))
  113. (completed (+ completed 1)))
  114. (wait-for-one-process)
  115. (report-progress total completed)
  116. (loop commands running completed)))))))
  117. (define* (report-build-progress total completed
  118. #:optional (log-port (current-error-port)))
  119. "Report that COMPLETED out of TOTAL files have been completed."
  120. (format log-port "[~2d/~2d] Compiling...~%"
  121. completed total)
  122. (force-output log-port))
  123. (define* (build #:key outputs inputs native-inputs
  124. (source-directory ".")
  125. (compile-flags '())
  126. (scheme-file-regexp %scheme-file-regexp)
  127. (not-compiled-file-regexp #f)
  128. target
  129. #:allow-other-keys)
  130. "Build files in SOURCE-DIRECTORY that match SCHEME-FILE-REGEXP. Files
  131. matching NOT-COMPILED-FILE-REGEXP, if true, are not compiled but are
  132. installed; this is useful for files that are meant to be included."
  133. (let* ((out (assoc-ref outputs "out"))
  134. (guile (assoc-ref (or native-inputs inputs) "guile"))
  135. (effective (target-guile-effective-version guile))
  136. (module-dir (string-append out "/share/guile/site/"
  137. effective))
  138. (go-dir (string-append out "/lib/guile/"
  139. effective "/site-ccache/"))
  140. (guild (string-append guile "/bin/guild"))
  141. (flags (if target
  142. (cons (string-append "--target=" target)
  143. compile-flags)
  144. compile-flags)))
  145. (if target
  146. (format #t "Cross-compiling for '~a' with Guile ~a...~%"
  147. target effective)
  148. (format #t "Compiling with Guile ~a...~%" effective))
  149. (format #t "compile flags: ~s~%" flags)
  150. ;; Make installation directories.
  151. (mkdir-p module-dir)
  152. (mkdir-p go-dir)
  153. ;; Compile .scm files and install.
  154. (setenv "GUILE_AUTO_COMPILE" "0")
  155. (setenv "GUILE_LOAD_COMPILED_PATH"
  156. (string-append go-dir
  157. (match (getenv "GUILE_LOAD_COMPILED_PATH")
  158. (#f "")
  159. (path (string-append ":" path)))))
  160. (let ((source-files
  161. (with-directory-excursion source-directory
  162. (find-files "." scheme-file-regexp))))
  163. (invoke-each
  164. (filter-map (lambda (file)
  165. (and (or (not not-compiled-file-regexp)
  166. (not (string-match not-compiled-file-regexp
  167. file)))
  168. (cons* guild
  169. "guild" "compile"
  170. "-L" source-directory
  171. "-o" (string-append go-dir
  172. (file-sans-extension file)
  173. ".go")
  174. (string-append source-directory "/" file)
  175. flags)))
  176. source-files)
  177. #:max-processes (parallel-job-count)
  178. #:report-progress report-build-progress)
  179. (for-each
  180. (lambda (file)
  181. (install-file (string-append source-directory "/" file)
  182. (string-append module-dir
  183. "/" (dirname file))))
  184. source-files))
  185. #t))
  186. (define* (install-documentation #:key outputs
  187. (documentation-file-regexp
  188. %documentation-file-regexp)
  189. #:allow-other-keys)
  190. "Install files that mactch DOCUMENTATION-FILE-REGEXP."
  191. (let* ((out (assoc-ref outputs "out"))
  192. (doc (string-append out "/share/doc/"
  193. (strip-store-file-name out))))
  194. (for-each (cut install-file <> doc)
  195. (find-files "." documentation-file-regexp))
  196. #t))
  197. (define %standard-phases
  198. (modify-phases gnu:%standard-phases
  199. (delete 'bootstrap)
  200. (delete 'configure)
  201. (add-before 'install-locale 'set-locale-path
  202. set-locale-path)
  203. (replace 'build build)
  204. (add-after 'build 'install-documentation
  205. install-documentation)
  206. (delete 'check)
  207. (delete 'strip)
  208. (delete 'validate-runpath)
  209. (delete 'install)))
  210. (define* (guile-build #:key (phases %standard-phases)
  211. #:allow-other-keys #:rest args)
  212. "Build the given Guile package, applying all of PHASES in order."
  213. (apply gnu:gnu-build #:phases phases args))