Subject: [bug#49946] [WIP PATCH v3 00/26] gnu: Add tree-sitter for emacs (and neovim as well?).

From: Pierre Langlois <[email-masked]>
Date: Wed, 09 Mar 2022 13:46:29 +0000

Hi Luis,

Luis Henrique Gomes Higino <[email-masked]> writes:

> Hi Guix, Hi Pierre,
>
> I apologize for my delay, the last few weeks were quite busy :-/.

No worries! :-).

>
> Pierre Langlois <[email-masked]> writes:
>
>> I'm sharing it early as a WIP first, as I just noticed the other day
>> that Luis also contributed a patches for tree-sitter for neovim[0]. The
>> series I have here is quite a lot bigger though so I'm happy to rebase
>> it on top of yours, Luis :-). The package I had for tree-sitter looks
>> basically the same, I would then move it to a new file with all
>> tree-sitter related packages, if you're happy with that.
>
> That's OK by me!
>
>> So I'm wondering, are these tree-sitter grammar packages also useful for
>> the neovim package for tree-sitter?
>
> I'm not exactly sure about this. Currently, most Neovim users install their
> grammars through the installer included in
> https://github.com/nvim-treesitter/nvim-treesitter. That ensures 
> the user gets the version that is ABI compatible with nvim-treesitter. That
> makes me uncertain if we could use the same grammar packages for Neovim and
> Emacs, at least for now.

I see, I suppose for the moment we're not packaging nvim-treesitter so
we don't have to concern ourselves with how language grammars are
delivered to users.

In the future it might be cool to package it, out of curiosity I took a
look at it and it:

  - [0]: Clones the repo with `git'.
  - [1]: Run `npm install' and `tree-sitter generate'.
  - [2]: Compiles the generated grammar with gcc.

[0]: https://github.com/nvim-treesitter/nvim-treesitter/blob/90485c890503f973271db1ae02ddba6d6fd46397/lua/nvim-treesitter/install.lua#L378
[1]: https://github.com/nvim-treesitter/nvim-treesitter/blob/90485c890503f973271db1ae02ddba6d6fd46397/lua/nvim-treesitter/install.lua#L228
[2]: https://github.com/nvim-treesitter/nvim-treesitter/blob/3aac7f9db9ee1973152426c097216e0071dd2293/lua/nvim-treesitter/shell_command_selectors.lua#L96

The only concern I can think of is that when it runs `npm install', it's
likely that it downloads a `tree-sitter' binary, rather than use
Guix's tree-sitter-cli package.  In my series I had to patch
package.json files for each grammar to remove `tree-sitter-cli' as a
dependency:

https://github.com/tree-sitter/tree-sitter-c/blob/e348e8ec5efd3aac020020e4af53d2ff18f393a9/package.json#L20

Otherwise it would pull in the official tree-sitter-cli node package,
which then downloads a `tree-sitter' binary :-/

https://github.com/tree-sitter/tree-sitter/blob/master/cli/npm/package.json

Anyways, I don't think that's something we should concern ourselves with
for now.  If we ever want to package nvim-treesitter, I think we might
have to patch it so it uses Guix-provided grammars instead of building
them locally.  For emacs that wasn't so difficult to do, but the real
issue is making sure the grammar is compabible, and luckily the emacs
package had tests we could run for that.  We would also need to run
tests if we're to package nvim-treesitter.

>
>> I do wonder if I'm going about it the right way for Guix though, for
>> instance I'm wondering if I should rewrite the grammar packages using a
>> new custom build system, do people think it would be worth it? That
>> could be done as a follow-up of course.
>
> I'm not a very experienced packager myself, but from what I saw in your patch,
> it seems rather unnecessary, as the grammar packages are very simple.

That was also my impression :-).  Since then I did play with building
one to see what it would look like, also as an escuse to go and learn
how they work.  I'm still on the fence about it though, I've attached
what it could look like, if anybody else is also unsure.  I think I'd
prefer to work on it as a potential follow-up after an initial set of
patches get merged.

Thanks,
Pierre

>From a38e3e66788129bbd441f9d28e450dba8a7438d1 Mon Sep 17 00:00:00 2001
From: Pierre Langlois <[email-masked]>
Date: Wed, 23 Feb 2022 20:38:51 +0000
Subject: [PATCH] wip build-system for tree-sitter grammars.

---
 Makefile.am                             |   2 +
 gnu/packages/tree-sitter.scm            | 312 ++++++++----------------
 guix/build-system/tree-sitter.scm       | 109 +++++++++
 guix/build/tree-sitter-build-system.scm | 124 ++++++++++
 4 files changed, 342 insertions(+), 205 deletions(-)
 create mode 100644 guix/build-system/tree-sitter.scm
 create mode 100644 guix/build/tree-sitter-build-system.scm

diff --git a/Makefile.am b/Makefile.am
index 8850c4562c..c5da931041 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -167,6 +167,7 @@ MODULES =					\
   guix/build-system/ruby.scm			\
   guix/build-system/scons.scm			\
   guix/build-system/texlive.scm			\
+  guix/build-system/tree-sitter.scm		\
   guix/build-system/trivial.scm			\
   guix/ftp-client.scm				\
   guix/http-client.scm				\
@@ -220,6 +221,7 @@ MODULES =					\
   guix/build/ruby-build-system.scm		\
   guix/build/scons-build-system.scm		\
   guix/build/texlive-build-system.scm		\
+  guix/build/tree-sitter-build-system.scm	\
   guix/build/waf-build-system.scm		\
   guix/build/haskell-build-system.scm		\
   guix/build/julia-build-system.scm		\
diff --git a/gnu/packages/tree-sitter.scm b/gnu/packages/tree-sitter.scm
index 0186518b23..725ab16910 100644
--- a/gnu/packages/tree-sitter.scm
+++ b/gnu/packages/tree-sitter.scm
@@ -23,6 +23,7 @@ (define-module (gnu packages tree-sitter)
   #:use-module (guix build-system emacs)
   #:use-module (guix build-system gnu)
   #:use-module (guix build-system node)
+  #:use-module (guix build-system tree-sitter)
   #:use-module (guix build-system trivial)
   #:use-module (guix download)
   #:use-module (guix git-download)
@@ -224,70 +225,7 @@ (define-public tree-sitter-c
                 "0454jziys33i4kbwnvi9xcck0fzya792ghy32ahgk1hhv96xga9w"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
-    (build-system node-build-system)
-    (native-inputs
-     (list tree-sitter tree-sitter-cli))
-    (outputs '("out" "js"))
-    (arguments
-     (list
-      #:phases
-      (with-imported-modules '((guix build json))
-        #~(modify-phases %standard-phases
-            (add-after 'patch-dependencies 'delete-dependencies
-              (lambda _
-                (delete-dependencies '("tree-sitter-cli"
-                                       "nan"
-                                       "node-gyp"))))
-            (replace 'build
-              (lambda _
-                (invoke "tree-sitter" "generate" "--no-bindings")))
-            (replace 'check
-              (lambda _
-                (invoke "tree-sitter" "test")))
-            (replace 'install
-              (lambda _
-                (use-modules (guix build json)
-                             (ice-9 regex))
-                (let* ((name (assoc-ref
-                              (call-with-input-file "package.json" read-json)
-                              "name"))
-                       (lang (cond
-                              ((string-match "^(@.*/)?tree-sitter-(.*)$" name)
-                               => (lambda (m)
-                                    (match:substring m 2)))
-                              (else #f)))
-                       (lib (string-append #$output "/lib/tree-sitter")))
-                  (mkdir-p lib)
-                  (define (source-file path)
-                    (if (file-exists? path)
-                        path
-                        #f))
-                  (apply invoke
-                         `(,#$(cxx-for-target)
-                           "-shared"
-                           "-fPIC"
-                           "-fno-exceptions"
-                           "-O2"
-                           "-g"
-                           "-o" ,(string-append lib "/" lang ".so")
-                           ,@(cond
-                              ((source-file "src/scanner.c")
-                               => (lambda (file) (list "-xc" "-std=c99" file)))
-                              ((source-file "src/scanner.cc")
-                               => (lambda (file) (list file)))
-                              (else '()))
-                           "-xc" "src/parser.c")))))
-            (add-after 'install 'install-js-module
-              (lambda* (#:key inputs #:allow-other-keys)
-                (invoke (search-input-file inputs "/bin/npm")
-                        "--prefix" #$output:js
-                        "--global"
-                        "--offline"
-                        "--loglevel" "info"
-                        "--production"
-                        ;; Skip scripts to prevent building bindings via GYP.
-                        "--ignore-scripts"
-                        "install" "../package.tgz")))))))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-c")
     (synopsis "Tree-sitter C grammar")
     (description
@@ -296,7 +234,6 @@ (define (source-file path)
 
 (define-public tree-sitter-cpp
   (package
-    (inherit tree-sitter-c)
     (name "tree-sitter-cpp")
     (version "0.20.0")
     (source (origin
@@ -311,7 +248,8 @@ (define-public tree-sitter-cpp
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
     (inputs
-     (list `(,tree-sitter-c "js")))
+     (list tree-sitter-c))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-cpp")
     (synopsis "Tree-sitter C++ grammar")
     (description
@@ -319,7 +257,7 @@ (define-public tree-sitter-cpp
     (license license:expat)))
 
 (define-public tree-sitter-c-sharp
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-c-sharp")
     (version "0.19.1")
     (source (origin
@@ -333,6 +271,7 @@ (define-public tree-sitter-c-sharp
                 "054fmpf47cwh59gbg00sc0nl237ba4rnxi73miz39yqzcs87055r"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-c-sharp")
     (synopsis "Tree-sitter C# grammar")
     (description
@@ -340,7 +279,7 @@ (define-public tree-sitter-c-sharp
     (license license:expat)))
 
 (define-public tree-sitter-bash
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-bash")
     (version "0.19.0")
     (source (origin
@@ -354,17 +293,7 @@ (define-public tree-sitter-bash
                 "18c030bb65r50i6z37iy7jb9z9i8i36y7b08dbc9bchdifqsijs5"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
-    (arguments
-     (substitute-keyword-arguments (package-arguments tree-sitter-c)
-      ((#:phases phases)
-       `(modify-phases ,phases
-          (replace 'delete-dependencies
-           (lambda _
-             (delete-dependencies '("tree-sitter-cli"
-                                    "nan"
-                                    "node-gyp"
-                                    "prebuild"
-                                    "prebuild-install"))))))))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-bash")
     (synopsis "Tree-sitter Bash grammar")
     (description
@@ -372,7 +301,7 @@ (define-public tree-sitter-bash
     (license license:expat)))
 
 (define-public tree-sitter-css
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-css")
     (version "0.19.0")
     (source (origin
@@ -386,6 +315,7 @@ (define-public tree-sitter-css
                 "014jrlgi7zfza9g38hsr4vlbi8964i5p7iglaih6qmzaiml7bja2"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-css")
     (synopsis "Tree-sitter CSS grammar")
     (description
@@ -395,7 +325,7 @@ (define-public tree-sitter-css
 (define-public tree-sitter-elixir
   (let ((commit "de20391afe5cb03ef1e8a8e43167e7b58cc52869")
         (revision "1"))
-    (package (inherit tree-sitter-c)
+    (package
       (name "tree-sitter-elixir")
       (version (git-version "0.19.0" revision commit))
       (source (origin
@@ -409,16 +339,7 @@ (define-public tree-sitter-elixir
                   "0zrkrwhw3g1vazsxcwrfd1fk4wvs9hdwmwp6073mfh370bz4140h"))
                 (modules '((guix build utils)))
                 (snippet tree-sitter-delete-generated-files)))
-      (arguments
-       (substitute-keyword-arguments (package-arguments tree-sitter-c)
-         ((#:phases phases)
-          `(modify-phases ,phases
-             (replace 'delete-dependencies
-               (lambda _
-                 (delete-dependencies '("tree-sitter-cli"
-                                        "node-gyp"
-                                        "nan"
-                                        "prettier"))))))))
+      (build-system tree-sitter-build-system)
       (home-page "https://elixir-lang.org/tree-sitter-elixir/")
       (synopsis "Tree-sitter Elixir grammar")
       (description
@@ -428,7 +349,7 @@ (define-public tree-sitter-elixir
                      license:expat)))))
 
 (define-public tree-sitter-elm
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-elm")
     (version "5.5.1")
     (source (origin
@@ -442,18 +363,7 @@ (define-public tree-sitter-elm
                 "10hbi4vyj4hjixqswdcbvzl60prldczz29mlp02if61wvwiwvqrw"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
-    (arguments
-     (substitute-keyword-arguments (package-arguments tree-sitter-c)
-      ((#:phases phases)
-       `(modify-phases ,phases
-          (replace 'delete-dependencies
-           (lambda _
-             (delete-dependencies '("tree-sitter-cli"
-                                    "nan"
-                                    "node-gyp"
-                                    "@asgerf/dts-tree-sitter"
-                                    "prebuild"
-                                    "prebuild-install"))))))))
+    (build-system tree-sitter-build-system)
     (home-page "https://elm-tooling.github.io/tree-sitter-elm/")
     (synopsis "Tree-sitter Elm grammar")
     (description
@@ -461,7 +371,7 @@ (define-public tree-sitter-elm
     (license license:expat)))
 
 (define-public tree-sitter-go
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-go")
     (version "0.19.1")
     (source (origin
@@ -475,6 +385,7 @@ (define-public tree-sitter-go
                 "0nxs47vd2fc2fr0qlxq496y852rwg39flhg334s7dlyq7d3lcx4x"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-go")
     (synopsis "Tree-sitter Go grammar")
     (description
@@ -482,7 +393,7 @@ (define-public tree-sitter-go
     (license license:expat)))
 
 (define-public tree-sitter-html
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-html")
     (version "0.19.0")
     (source (origin
@@ -496,6 +407,7 @@ (define-public tree-sitter-html
                 "1hg7vbcy7bir6b8x11v0a4x0glvqnsqc3i2ixiarbxmycbgl3axy"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-html")
     (synopsis "Tree-sitter HTML grammar")
     (description
@@ -503,7 +415,7 @@ (define-public tree-sitter-html
     (license license:expat)))
 
 (define-public tree-sitter-java
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-java")
     (version "0.19.1")
     (source (origin
@@ -517,6 +429,7 @@ (define-public tree-sitter-java
                 "07zw9ygb45hnvlx9qlz7rlz8hc3byjy03d24v72i5iyhpiiwlhvl"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-java")
     (synopsis "Tree-sitter Java grammar")
     (description
@@ -524,7 +437,7 @@ (define-public tree-sitter-java
     (license license:expat)))
 
 (define-public tree-sitter-javascript
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-javascript")
     (version "0.20.0")
     (source (origin
@@ -538,6 +451,7 @@ (define-public tree-sitter-javascript
                 "175yrk382n2di0c2xn4gpv8y4n83x1lg4hqn04vabf0yqynlkq67"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-javascript")
     (synopsis "Tree-sitter Javascript grammar")
     (description
@@ -546,7 +460,7 @@ (define-public tree-sitter-javascript
     (license license:expat)))
 
 (define-public tree-sitter-json
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-json")
     (version "0.19.0")
     (source (origin
@@ -560,6 +474,7 @@ (define-public tree-sitter-json
                 "06pjh31bv9ja9hlnykk257a6zh8bsxg2fqa54al7qk1r4n9ksnff"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-json")
     (synopsis "Tree-sitter JSON grammar")
     (description
@@ -567,7 +482,7 @@ (define-public tree-sitter-json
     (license license:expat)))
 
 (define-public tree-sitter-julia
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-julia")
     (version "0.19.0")
     (source (origin
@@ -581,6 +496,7 @@ (define-public tree-sitter-julia
                 "1pbnmvhy2gq4vg1b0sjzmjm4s2gsgdjh7h01yj8qrrqbcl29c463"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-julia")
     (synopsis "Tree-sitter Julia grammar")
     (description
@@ -590,7 +506,7 @@ (define-public tree-sitter-julia
 (define-public tree-sitter-ocaml
   (let ((commit "0348562f385bc2bd67ecf181425e1afd6d454192")
         (revision "1"))
-    (package (inherit tree-sitter-c)
+    (package
       (name "tree-sitter-ocaml")
       (version (git-version "0.19.0" revision commit))
       (source (origin
@@ -617,43 +533,44 @@ (define-public tree-sitter-ocaml
                      '("ocaml" "interface"))
                     #t))))
       (arguments
-       (substitute-keyword-arguments (package-arguments tree-sitter-c)
-        ((#:phases phases)
-         #~(modify-phases #$phases
-             (replace 'build
-               (lambda _
-                 (for-each (lambda (dir)
-                             (with-directory-excursion dir
-                               (invoke "tree-sitter" "generate" "--no-bindings")))
-                           '("ocaml" "interface"))))
-             (replace 'check
-               (lambda _
-                 (for-each (lambda (dir)
-                             (with-directory-excursion dir
-                               (invoke "tree-sitter" "test")))
-                           '("ocaml" "interface"))))
-             (replace 'install
-               (lambda _
-                 (let ((lib (string-append #$output "/lib/tree-sitter/")))
-                   (mkdir-p lib)
-                   (invoke #$(cxx-for-target)
-                           "-shared"
-                           "-fPIC"
-                           "-fno-exceptions"
-                           "-O2"
-                           "-g"
-                           "-o" (string-append lib "/ocaml.so")
-                           "ocaml/src/scanner.cc"
-                           "-xc" "ocaml/src/parser.c")
-                   (invoke #$(cxx-for-target)
-                           "-shared"
-                           "-fPIC"
-                           "-fno-exceptions"
-                           "-O2"
-                           "-g"
-                           "-o" (string-append lib "/ocaml-interface.so")
-                           "interface/src/scanner.cc"
-                           "-xc" "interface/src/parser.c"))))))))
+       (list
+        #:phases
+        #~(modify-phases %standard-phases
+            (replace 'build
+              (lambda _
+                (for-each (lambda (dir)
+                            (with-directory-excursion dir
+                              (invoke "tree-sitter" "generate" "--no-bindings")))
+                          '("ocaml" "interface"))))
+            (replace 'check
+              (lambda _
+                (for-each (lambda (dir)
+                            (with-directory-excursion dir
+                              (invoke "tree-sitter" "test")))
+                          '("ocaml" "interface"))))
+            (replace 'install
+              (lambda _
+                (let ((lib (string-append #$output "/lib/tree-sitter/")))
+                  (mkdir-p lib)
+                  (invoke #$(cxx-for-target)
+                          "-shared"
+                          "-fPIC"
+                          "-fno-exceptions"
+                          "-O2"
+                          "-g"
+                          "-o" (string-append lib "/ocaml.so")
+                          "ocaml/src/scanner.cc"
+                          "-xc" "ocaml/src/parser.c")
+                  (invoke #$(cxx-for-target)
+                          "-shared"
+                          "-fPIC"
+                          "-fno-exceptions"
+                          "-O2"
+                          "-g"
+                          "-o" (string-append lib "/ocaml-interface.so")
+                          "interface/src/scanner.cc"
+                          "-xc" "interface/src/parser.c")))))))
+      (build-system tree-sitter-build-system)
       (home-page "https://github.com/tree-sitter/tree-sitter-ocaml")
       (synopsis "Tree-sitter OCaml grammar")
       (description
@@ -663,7 +580,7 @@ (define-public tree-sitter-ocaml
 (define-public tree-sitter-php
   (let ((commit "435fa00006c0d1515c37fbb4dd6a9de284af75ab")
         (revision "1"))
-    (package (inherit tree-sitter-c)
+    (package
       (name "tree-sitter-php")
       (version (git-version "0.19.0" revision commit))
       (source (origin
@@ -683,15 +600,7 @@ (define-public tree-sitter-php
                     (delete-file "src/parser.c")
                     (delete-file-recursively "src/tree_sitter")
                     #t))))
-    (arguments
-     (substitute-keyword-arguments (package-arguments tree-sitter-c)
-      ((#:phases phases)
-       `(modify-phases ,phases
-          (replace 'delete-dependencies
-           (lambda _
-             (delete-dependencies '("tree-sitter-cli"
-                                    "nan"
-                                    "shelljs"))))))))
+      (build-system tree-sitter-build-system)
       (home-page "https://github.com/tree-sitter/tree-sitter-php")
       (synopsis "Tree-sitter PHP grammar")
       (description
@@ -701,7 +610,7 @@ (define-public tree-sitter-php
 (define-public tree-sitter-python
   (let ((commit "ed0fe62e55dc617ed9dec8817ebf771aa7cf3c42")
         (revision "1"))
-    (package (inherit tree-sitter-c)
+    (package
       (name "tree-sitter-python")
       (version (git-version "0.19.1" revision commit))
       (source (origin
@@ -715,6 +624,7 @@ (define-public tree-sitter-python
                   "0wrfpg84mc3pzcrdi6n5fqwijkqr1nj5sqfnayb502krvqpjilal"))
                 (modules '((guix build utils)))
                 (snippet tree-sitter-delete-generated-files)))
+      (build-system tree-sitter-build-system)
       (home-page "https://github.com/tree-sitter/tree-sitter-python")
       (synopsis "Tree-sitter Python grammar")
       (description
@@ -722,7 +632,7 @@ (define-public tree-sitter-python
       (license license:expat))))
 
 (define-public tree-sitter-ruby
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-ruby")
     (version "0.19.0")
     (source (origin
@@ -736,17 +646,7 @@ (define-public tree-sitter-ruby
                 "0m3h4928rbs300wcb6776h9r88hi32rybbhcaf6rdympl5nzi83v"))
               (modules '((guix build utils)))
               (snippet tree-sitter-delete-generated-files)))
-    (arguments
-     (substitute-keyword-arguments (package-arguments tree-sitter-c)
-      ((#:phases phases)
-       `(modify-phases ,phases
-          (replace 'delete-dependencies
-           (lambda _
-             (delete-dependencies '("tree-sitter-cli"
-                                    "nan"
-                                    "node-gyp"
-                                    "prebuild"
-                                    "prebuild-install"))))))))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-ruby")
     (synopsis "Tree-sitter Ruby grammar")
     (description
@@ -754,7 +654,7 @@ (define-public tree-sitter-ruby
     (license license:expat)))
 
 (define-public tree-sitter-rust
-  (package (inherit tree-sitter-c)
+  (package
     (name "tree-sitter-rust")
     (version "0.20.1")
     (source (origin
@@ -771,6 +671,7 @@ (define-public tree-sitter-rust
     (native-inputs
      (modify-inputs (package-native-inputs tree-sitter-c)
        (prepend bc)))
+    (build-system tree-sitter-build-system)
     (home-page "https://github.com/tree-sitter/tree-sitter-rust")
     (synopsis "Tree-sitter Rust grammar")
     (description
@@ -780,7 +681,7 @@ (define-public tree-sitter-rust
 (define-public tree-sitter-typescript
   (let ((commit "111b07762e86efab9a918b7c721f720c37e76b0a")
         (revision "1"))
-    (package (inherit tree-sitter-c)
+    (package
       (name "tree-sitter-typescript")
       (version (git-version "0.20.0" revision commit))
       (source (origin
@@ -806,39 +707,40 @@ (define-public tree-sitter-typescript
                      '("typescript" "tsx"))
                     #t))))
       (inputs
-       (list `(,tree-sitter-javascript "js")))
+       (list tree-sitter-javascript))
       (arguments
-       (substitute-keyword-arguments (package-arguments tree-sitter-c)
-        ((#:phases phases)
-         #~(modify-phases #$phases
-             (replace 'build
-               (lambda _
-                 (for-each (lambda (dir)
-                             (with-directory-excursion dir
-                               (invoke "tree-sitter" "generate" "--no-bindings")))
-                           '("typescript" "tsx"))))
-             (replace 'check
-               (lambda _
-                 (for-each (lambda (dir)
-                             (with-directory-excursion dir
-                               (invoke "tree-sitter" "test")))
-                           '("typescript" "tsx"))))
-             (replace 'install
-               (lambda _
-                 (let ((lib (string-append #$output "/lib/tree-sitter/")))
-                   (mkdir-p lib)
-                   (for-each
-                    (lambda (lang)
-                      (invoke #$(cxx-for-target)
-                              "-shared"
-                              "-fPIC"
-                              "-fno-exceptions"
-                              "-O2"
-                              "-g"
-                              "-o" (string-append lib "/" lang ".so")
-                              "-xc" (string-append lang "/src/scanner.c")
-                              "-xc" (string-append lang "/src/parser.c")))
-                    '("typescript" "tsx")))))))))
+       (list
+        #:phases
+        #~(modify-phases %standard-phases
+            (replace 'build
+              (lambda _
+                (for-each (lambda (dir)
+                            (with-directory-excursion dir
+                              (invoke "tree-sitter" "generate" "--no-bindings")))
+                          '("typescript" "tsx"))))
+            (replace 'check
+              (lambda _
+                (for-each (lambda (dir)
+                            (with-directory-excursion dir
+                              (invoke "tree-sitter" "test")))
+                          '("typescript" "tsx"))))
+            (replace 'install
+              (lambda _
+                (let ((lib (string-append #$output "/lib/tree-sitter/")))
+                  (mkdir-p lib)
+                  (for-each
+                   (lambda (lang)
+                     (invoke #$(cxx-for-target)
+                             "-shared"
+                             "-fPIC"
+                             "-fno-exceptions"
+                             "-O2"
+                             "-g"
+                             "-o" (string-append lib "/" lang ".so")
+                             "-xc" (string-append lang "/src/scanner.c")
+                             "-xc" (string-append lang "/src/parser.c")))
+                   '("typescript" "tsx"))))))))
+      (build-system tree-sitter-build-system)
       (home-page "https://github.com/tree-sitter/tree-sitter-typescript")
       (synopsis "Tree-sitter Typescript grammar")
       (description
diff --git a/guix/build-system/tree-sitter.scm b/guix/build-system/tree-sitter.scm
new file mode 100644
index 0000000000..bfccea4007
--- /dev/null
+++ b/guix/build-system/tree-sitter.scm
@@ -0,0 +1,109 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2022 Pierre Langlois <[email-masked]>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system tree-sitter)
+  #:use-module (guix store)
+  #:use-module (guix utils)
+  #:use-module (guix packages)
+  #:use-module (guix gexp)
+  #:use-module (guix monads)
+  #:use-module (guix search-paths)
+  #:use-module (guix build-system)
+  #:use-module (guix build-system gnu)
+  #:use-module (guix build-system node)
+  #:use-module (ice-9 match)
+  #:export (%tree-sitter-build-system-modules
+            tree-sitter-build
+            tree-sitter-build-system))
+
+(define %tree-sitter-build-system-modules
+  ;; Build-side modules imported by default.
+  `((guix build tree-sitter-build-system)
+    ;; (guix build json)
+    ,@%node-build-system-modules))
+
+(define* (lower name
+                #:key source inputs native-inputs outputs system target
+                #:allow-other-keys
+                #:rest arguments)
+  ""
+  (define private-keywords
+    '(#:target #:inputs #:native-inputs #:outputs))
+
+  (and (not target)                    ;XXX: no cross-compilation
+       (bag
+         (name name)
+         (system system)
+         (host-inputs `(,@(if source
+                              `(("source" ,source))
+                              '())
+                        ,@(map (match-lambda
+                                 ((name package)
+                                  `(,name ,package "js")))
+                               inputs)
+                        ;; Keep the standard inputs of 'gnu-build-system'.
+                        ,@(standard-packages)))
+         (build-inputs `(("node" ,(module-ref (resolve-interface '(gnu packages node))
+                                              'node-lts))
+                         ("tree-sitter" ,(module-ref (resolve-interface '(gnu packages tree-sitter))
+                                                     'tree-sitter))
+                         ("tree-sitter-cli" ,(module-ref (resolve-interface '(gnu packages tree-sitter))
+                                                         'tree-sitter-cli))
+                         ,@native-inputs))
+         (outputs (match outputs
+                    (("out") (cons "js" outputs))
+                    (_ outputs)))
+         (build tree-sitter-build)
+         (arguments (strip-keyword-arguments private-keywords arguments)))))
+
+(define* (tree-sitter-build name inputs
+                            #:key
+                            source
+                            (phases '%standard-phases)
+                            (outputs '("out" "js"))
+                            (search-paths '())
+                            (system (%current-system))
+                            (guile #f)
+                            (imported-modules %tree-sitter-build-system-modules)
+                            (modules '((guix build utils)
+                                       (guix build tree-sitter-build-system))))
+  (define builder
+    (with-imported-modules imported-modules
+      #~(begin
+          (use-modules #$@(sexp->gexp modules))
+          (tree-sitter-build #:name #$name
+                             #:source #+source
+                             #:system #$system
+                             #:phases #$phases
+                             #:outputs #$(outputs->gexp outputs)
+                             #:search-paths '#$(sexp->gexp
+                                                (map search-path-specification->sexp
+                                                     search-paths))
+                             #:inputs #$(input-tuples->gexp inputs)))))
+
+  (mlet %store-monad ((guile (package->derivation (or guile (default-guile))
+                                                  system #:graft? #f)))
+    (gexp->derivation name builder
+                      #:system system
+                      #:guile-for-build guile)))
+
+(define tree-sitter-build-system
+  (build-system
+    (name 'tree-sitter)
+    (description "")
+    (lower lower)))
diff --git a/guix/build/tree-sitter-build-system.scm b/guix/build/tree-sitter-build-system.scm
new file mode 100644
index 0000000000..bf1d2e363d
--- /dev/null
+++ b/guix/build/tree-sitter-build-system.scm
@@ -0,0 +1,124 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2022 Pierre Langlois <[email-masked]>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build tree-sitter-build-system)
+  #:use-module ((guix build node-build-system) #:prefix node:)
+  #:use-module (guix build json)
+  #:use-module (guix build utils)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 regex)
+  #:use-module (srfi srfi-1)
+  #:export (%standard-phases
+            tree-sitter-build))
+
+(define (tree-sitter-node-inputs inputs)
+  ""
+  (alist-delete "node"
+    (alist-delete "source"
+      (filter (match-lambda
+                ((label . directory)
+                 (directory-exists? (string-append directory
+                                                   "/lib/node_modules")))
+                (_ #f))
+              inputs))))
+
+;;;
+;;; Phases.
+;;;
+
+(define* (adjust-dependencies #:key inputs #:allow-other-keys)
+  ""
+  (node:with-atomic-json-file-replacement "package.json"
+    (match-lambda
+      (('@ . pkg-meta-alist)
+       (cons '@ (map (match-lambda
+                       (("dependencies" @ . deps)
+                        '("dependencies" @))
+                       (("devDependencies" @ . deps)
+                        `("devDependencies" @
+                          ,@(map
+                             (lambda (input)
+                               `(,(car input) . "latest"))
+                             (tree-sitter-node-inputs inputs))))
+                       (other other))
+                     pkg-meta-alist))))))
+
+(define (build . _)
+  (invoke "tree-sitter" "generate" "--no-bindings")
+  #t)
+
+(define (check . _)
+  (invoke "tree-sitter" "test")
+  #t)
+
+(define* (install #:key outputs #:allow-other-keys)
+  (use-modules (guix build json)
+               (ice-9 regex))
+  (let* ((name (assoc-ref
+                (call-with-input-file "package.json" read-json)
+                "name"))
+         (lang (cond
+                ((string-match "^(@.*/)?tree-sitter-(.*)$" name)
+                 => (lambda (m)
+                      (match:substring m 2)))
+                (else #f)))
+         (lib (string-append (assoc-ref outputs "out")
+                             "/lib/tree-sitter")))
+    (mkdir-p lib)
+    (define (source-file path)
+      (if (file-exists? path)
+          path
+          #f))
+    (apply invoke
+           `("g++"
+             "-shared"
+             "-fPIC"
+             "-fno-exceptions"
+             "-O2"
+             "-g"
+             "-o" ,(string-append lib "/" lang ".so")
+             ,@(cond
+                ((source-file "src/scanner.c")
+                 => (lambda (file) (list "-xc" "-std=c99" file)))
+                ((source-file "src/scanner.cc")
+                 => (lambda (file) (list file)))
+                (else '()))
+             "-xc" "src/parser.c"))))
+
+(define* (install-js #:key inputs outputs #:allow-other-keys)
+  (invoke (search-input-file inputs "/bin/npm")
+          "--prefix" (assoc-ref outputs "js")
+          "--global"
+          "--offline"
+          "--loglevel" "info"
+          "--production"
+          ;; Skip scripts to prevent building bindings via GYP.
+          "--ignore-scripts"
+          "install" "../package.tgz"))
+
+(define %standard-phases
+  (modify-phases node:%standard-phases
+    (add-before 'patch-dependencies 'adjust-dependencies adjust-dependencies)
+    (replace 'build build)
+    (replace 'check check)
+    (replace 'install install)
+    (add-after 'install 'install-js install-js)))
+
+(define* (tree-sitter-build #:key inputs (phases %standard-phases)
+                            #:allow-other-keys #:rest args)
+  (apply node:node-build #:inputs inputs #:phases phases args))
-- 
2.34.0