tree-sitter-build-system.scm 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2022 Pierre Langlois <pierre.langlois@gmx.com>
  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 tree-sitter-build-system)
  19. #:use-module ((guix build node-build-system) #:prefix node:)
  20. #:use-module (guix build json)
  21. #:use-module (guix build utils)
  22. #:use-module (ice-9 match)
  23. #:use-module (ice-9 regex)
  24. #:use-module (srfi srfi-1)
  25. #:export (%standard-phases
  26. tree-sitter-build))
  27. ;; Commentary:
  28. ;;
  29. ;; Build procedures for tree-sitter grammar packages. This is the
  30. ;; builder-side code, which builds on top of the node build-system.
  31. ;;
  32. ;; Tree-sitter grammars are written in JavaScript and compiled to a native
  33. ;; shared object. The `tree-sitter generate' command invokes `node' in order
  34. ;; to evaluate the grammar.js into a grammar.json file, which is then
  35. ;; translated into C code. We then compile the C code ourselves. Packages
  36. ;; also sometimes add extra manually written C/C++ code.
  37. ;;
  38. ;; In order to support grammars depending on each other, such as C and C++,
  39. ;; JavaScript and TypeScript, this build-system installs the source of the
  40. ;; node module in a dedicated "js" output.
  41. ;;
  42. ;; Code:
  43. (define* (patch-dependencies #:key inputs #:allow-other-keys)
  44. "Rewrite dependencies in 'package.json'. We remove all runtime dependencies
  45. and replace development dependencies with tree-sitter grammar node modules."
  46. (define (rewrite package.json)
  47. (map (match-lambda
  48. (("dependencies" @ . _)
  49. '("dependencies" @))
  50. (("devDependencies" @ . _)
  51. `("devDependencies" @
  52. ,@(filter-map (match-lambda
  53. ((key . directory)
  54. (let ((node-module
  55. (string-append directory
  56. "/lib/node_modules/"
  57. key)))
  58. (and (directory-exists? node-module)
  59. `(,key . ,node-module)))))
  60. (alist-delete "node" inputs))))
  61. (other other))
  62. package.json))
  63. (node:with-atomic-json-file-replacement "package.json"
  64. (match-lambda
  65. (('@ . package.json)
  66. (cons '@ (rewrite package.json))))))
  67. ;; FIXME: The node build-system's configure phase does not support
  68. ;; cross-compiling so we re-define it.
  69. (define* (configure #:key native-inputs inputs #:allow-other-keys)
  70. (invoke (search-input-file (or native-inputs inputs) "/bin/npm")
  71. "--offline" "--ignore-scripts" "install"))
  72. (define* (build #:key grammar-directories #:allow-other-keys)
  73. (for-each (lambda (dir)
  74. (with-directory-excursion dir
  75. ;; Avoid generating binding code for other languages, we do
  76. ;; not support this use-case yet and it relies on running
  77. ;; `node-gyp' to build native addons.
  78. (invoke "tree-sitter" "generate" "--no-bindings")))
  79. grammar-directories))
  80. (define* (check #:key grammar-directories tests? #:allow-other-keys)
  81. (when tests?
  82. (for-each (lambda (dir)
  83. (with-directory-excursion dir
  84. (invoke "tree-sitter" "test")))
  85. grammar-directories)))
  86. (define* (install #:key target grammar-directories outputs #:allow-other-keys)
  87. (let ((lib (string-append (assoc-ref outputs "out")
  88. "/lib/tree-sitter")))
  89. (mkdir-p lib)
  90. (define (compile-language dir)
  91. (with-directory-excursion dir
  92. (let ((lang (assoc-ref (call-with-input-file "src/grammar.json"
  93. read-json)
  94. "name"))
  95. (source-file (lambda (path)
  96. (if (file-exists? path)
  97. path
  98. #f))))
  99. (apply invoke
  100. `(,(if target
  101. (string-append target "-g++")
  102. "g++")
  103. "-shared"
  104. "-fPIC"
  105. "-fno-exceptions"
  106. "-O2"
  107. "-g"
  108. "-o" ,(string-append lib "/libtree-sitter-" lang ".so")
  109. ;; An additional `scanner.{c,cc}' file is sometimes
  110. ;; provided.
  111. ,@(cond
  112. ((source-file "src/scanner.c")
  113. => (lambda (file) (list "-xc" "-std=c99" file)))
  114. ((source-file "src/scanner.cc")
  115. => (lambda (file) (list file)))
  116. (else '()))
  117. "-xc" "src/parser.c")))))
  118. (for-each compile-language grammar-directories)))
  119. (define* (install-js #:key native-inputs inputs outputs #:allow-other-keys)
  120. (invoke (search-input-file (or native-inputs inputs) "/bin/npm")
  121. "--prefix" (assoc-ref outputs "js")
  122. "--global"
  123. "--offline"
  124. "--loglevel" "info"
  125. "--production"
  126. ;; Skip scripts to prevent building bindings via GYP.
  127. "--ignore-scripts"
  128. "install" "../package.tgz"))
  129. (define %standard-phases
  130. (modify-phases node:%standard-phases
  131. (replace 'patch-dependencies patch-dependencies)
  132. (replace 'configure configure)
  133. (replace 'build build)
  134. (replace 'check check)
  135. (replace 'install install)
  136. (add-after 'install 'install-js install-js)))
  137. (define* (tree-sitter-build #:key inputs (phases %standard-phases)
  138. #:allow-other-keys #:rest args)
  139. (apply node:node-build #:inputs inputs #:phases phases args))
  140. ;;; tree-sitter-build-system.scm ends here