julia-build-system.scm 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2019, 2020 Nicolò Balzarotti <nicolo@nixo.xyz>
  3. ;;; Copyright © 2021 Jean-Baptiste Volatier <jbv@pm.me>
  4. ;;; Copyright © 2021, 2022 Simon Tournier <zimon.toutoune@gmail.com>
  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/>.
  20. (define-module (guix build julia-build-system)
  21. #:use-module ((guix build gnu-build-system) #:prefix gnu:)
  22. #:use-module (guix build utils)
  23. #:use-module (rnrs io ports)
  24. #:use-module (ice-9 match)
  25. #:use-module (ice-9 regex)
  26. #:use-module (ice-9 rdelim)
  27. #:use-module (ice-9 popen)
  28. #:use-module (srfi srfi-1)
  29. #:export (%standard-phases
  30. julia-build))
  31. ;; Commentary:
  32. ;;
  33. ;; Builder-side code of the standard build procedure for Julia packages.
  34. ;;
  35. ;; Code:
  36. (define (invoke-julia code)
  37. (invoke "julia" "-e" code))
  38. ;; subpath where we store the package content
  39. (define %package-path "/share/julia/loadpath/")
  40. (define (project.toml->name file)
  41. "Look for Julia package name in the TOML file FILE (usually named
  42. Project.toml)."
  43. (call-with-input-file file
  44. (lambda (in)
  45. (let loop ((line (read-line in 'concat)))
  46. (if (eof-object? line)
  47. #f
  48. (let ((m (string-match "name\\s*=\\s*\"(.*)\"" line)))
  49. (if m (match:substring m 1)
  50. (loop (read-line in 'concat)))))))))
  51. (define (project.toml->uuid file)
  52. "Look for Julia package uuid in the TOML file FILE (usually named
  53. Project.toml)."
  54. (call-with-input-file file
  55. (lambda (in)
  56. (let loop ((line (read-line in 'concat)))
  57. (if (eof-object? line)
  58. #f
  59. (let ((m (string-match "uuid\\s*=\\s*\"(.*)\"" line)))
  60. (if m (match:substring m 1)
  61. (loop (read-line in 'concat)))))))))
  62. (define* (install #:key source inputs outputs julia-package-name
  63. #:allow-other-keys)
  64. (let* ((out (assoc-ref outputs "out"))
  65. (package-dir (string-append out %package-path
  66. (or
  67. julia-package-name
  68. (project.toml->name "Project.toml")))))
  69. (mkdir-p package-dir)
  70. (copy-recursively (getcwd) package-dir)))
  71. (define* (precompile #:key source inputs outputs julia-package-name
  72. #:allow-other-keys)
  73. (let* ((out (assoc-ref outputs "out"))
  74. (builddir (string-append out "/share/julia/"))
  75. (package (or julia-package-name (project.toml->name "Project.toml"))))
  76. (mkdir-p builddir)
  77. ;; With a patch, SOURCE_DATE_EPOCH is honored
  78. (setenv "SOURCE_DATE_EPOCH" "1")
  79. (setenv "JULIA_DEPOT_PATH" builddir)
  80. ;; Add new package dir to the load path.
  81. (setenv "JULIA_LOAD_PATH"
  82. (string-append builddir "loadpath/" ":"
  83. (or (getenv "JULIA_LOAD_PATH")
  84. "")))
  85. ;; Actual precompilation:
  86. (invoke-julia
  87. ;; When using Julia as a user, Julia writes precompile cache to the first
  88. ;; entry of the DEPOT_PATH list (by default, the home dir). We want to
  89. ;; write it to the store, so let's push the store path as the first
  90. ;; element of DEPOT_PATH. Once the cache file exists, this hack is not
  91. ;; needed anymore (like in the check phase). If the user install new
  92. ;; packages, those will be installed and precompiled in the home dir.
  93. (string-append "pushfirst!(DEPOT_PATH, pop!(DEPOT_PATH)); using "
  94. package))))
  95. (define* (check #:key tests? source inputs outputs julia-package-name
  96. parallel-tests? #:allow-other-keys)
  97. (when tests?
  98. (let* ((out (assoc-ref outputs "out"))
  99. (package (or julia-package-name (project.toml->name "Project.toml")))
  100. (builddir (string-append out "/share/julia/"))
  101. (job-count (if parallel-tests?
  102. (parallel-job-count)
  103. 1))
  104. ;; The --proc argument of Julia *adds* extra processors rather than
  105. ;; specify the exact count to use, so zero must be specified to
  106. ;; disable parallel processing...
  107. (additional-procs (max 0 (1- job-count))))
  108. ;; With a patch, SOURCE_DATE_EPOCH is honored
  109. (setenv "SOURCE_DATE_EPOCH" "1")
  110. (setenv "JULIA_DEPOT_PATH" builddir)
  111. (setenv "JULIA_LOAD_PATH"
  112. (string-append builddir "loadpath/" ":"
  113. (or (getenv "JULIA_LOAD_PATH")
  114. "")))
  115. (setenv "JULIA_CPU_THREADS" (number->string job-count))
  116. (setenv "HOME" "/tmp")
  117. (apply invoke "julia"
  118. `("--depwarn=yes"
  119. ,@(if parallel-tests?
  120. ;; XXX: ... but '--procs' doesn't accept 0 as a valid
  121. ;; value, so just omit the argument entirely.
  122. (list (string-append "--procs="
  123. (number->string additional-procs)))
  124. '())
  125. ,(string-append builddir "loadpath/"
  126. package "/test/runtests.jl"))))))
  127. (define* (link-depot #:key source inputs outputs
  128. julia-package-name julia-package-uuid #:allow-other-keys)
  129. (let* ((out (assoc-ref outputs "out"))
  130. (name+version (strip-store-file-name out))
  131. (version (last (string-split name+version #\-)))
  132. (package-name (or
  133. julia-package-name
  134. (project.toml->name "Project.toml")))
  135. (package-dir (string-append out %package-path package-name))
  136. (uuid (or julia-package-uuid (project.toml->uuid "Project.toml")))
  137. (pipe (open-pipe* OPEN_READ "julia" "-e"
  138. (format #f "using Pkg;
  139. println(Base.version_slug(Base.UUID(\"~a\"),
  140. Base.SHA1(Pkg.GitTools.tree_hash(\".\"))))" uuid)))
  141. (slug (string-trim-right (get-string-all pipe))))
  142. ;; Few packages do not have the regular Project.toml file, then when they
  143. ;; are propagated, dependencies do not find them and an raise error.
  144. (unless (file-exists? "Project.toml")
  145. (julia-create-package-toml (getcwd)
  146. julia-package-name julia-package-uuid
  147. version
  148. #:file "Project.toml"))
  149. ;; When installing a package, julia looks first at in the JULIA_DEPOT_PATH
  150. ;; for a path like packages/PACKAGE/XXXX
  151. ;; Where XXXX is a slug encoding the package UUID and SHA1 of the files
  152. ;; Here we create a link with the correct path to enable julia to find the
  153. ;; package
  154. (mkdir-p (string-append out "/share/julia/packages/" package-name))
  155. (symlink package-dir (string-append out "/share/julia/packages/"
  156. package-name "/" slug))))
  157. (define* (julia-create-package-toml location
  158. name uuid version
  159. #:optional
  160. (deps '())
  161. #:key
  162. (file "Project.toml"))
  163. "Some packages are not using the new Project.toml dependency specifications.
  164. Write this FILE manually, so that Julia can find its dependencies."
  165. (let ((f (open-file
  166. (string-append location "/" file)
  167. "w")))
  168. (display (string-append
  169. "
  170. name = \"" name "\"
  171. uuid = \"" uuid "\"
  172. version = \"" version "\"
  173. ") f)
  174. (when (not (null? deps))
  175. (display "[deps]\n" f)
  176. (for-each (lambda dep
  177. (display (string-append (car (car dep)) " = \"" (cdr (car dep)) "\"\n")
  178. f))
  179. deps))
  180. (close-port f)))
  181. (define %standard-phases
  182. (modify-phases gnu:%standard-phases
  183. (delete 'check) ; tests must be run after installation
  184. (replace 'install install)
  185. (add-after 'install 'precompile precompile)
  186. (add-after 'unpack 'link-depot link-depot)
  187. (add-after 'install 'check check)
  188. ;; TODO: In the future we could add a "system-image-generation" phase
  189. ;; where we use PackageCompiler.jl to speed up package loading times
  190. (delete 'configure)
  191. (delete 'bootstrap)
  192. (delete 'patch-usr-bin-file)
  193. (delete 'build)))
  194. (define* (julia-build #:key inputs julia-package-name julia-package-uuid
  195. (phases %standard-phases)
  196. #:allow-other-keys #:rest args)
  197. "Build the given Julia package, applying all of PHASES in order."
  198. (apply gnu:gnu-build
  199. #:inputs inputs #:phases phases
  200. #:julia-package-name julia-package-name
  201. #:julia-package-uuid julia-package-uuid
  202. args))