julia-build-system.scm 9.5 KB

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