nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1{
2 lib,
3 buildPythonPackage,
4 setuptools,
5 pytestCheckHook,
6 tree-sitter,
7 symlinkJoin,
8 writeTextDir,
9 # `name`: grammar derivation pname in the format of `tree-sitter-<lang>`
10 name,
11 grammarDrv,
12}:
13let
14 # Map nix style `0-unstable-YYYY-MM-DD` version identifiers to a PEP 440
15 # compatible form (`0+unstableYYYYMMDD`).
16 version = lib.pipe grammarDrv [
17 lib.getVersion
18 (lib.splitString "-")
19 (
20 parts:
21 let
22 version = lib.head parts;
23 metadata = lib.join "" (lib.tail parts);
24 in
25 if (metadata == "") then version else "${version}+${metadata}"
26 )
27 ];
28 snakeCaseName = lib.replaceStrings [ "-" ] [ "_" ] name;
29 drvPrefix = "python-${name}";
30 # If the name of the grammar attribute differs from the grammar's symbol name,
31 # it could cause a symbol mismatch at load time. This manually curated collection
32 # of overrides ensures the binding can find the correct symbol
33 langIdentOverrides = {
34 tree_sitter_org_nvim = "tree_sitter_org";
35 };
36 langIdent = langIdentOverrides.${snakeCaseName} or snakeCaseName;
37in
38buildPythonPackage {
39 inherit version;
40 pname = drvPrefix;
41 pyproject = true;
42 build-system = [ setuptools ];
43
44 src = symlinkJoin {
45 name = "${drvPrefix}-source";
46 paths = [
47 (writeTextDir "${snakeCaseName}/__init__.py" ''
48 # AUTO-GENERATED DO NOT EDIT
49
50 # preload the parser object before importing c binding
51 # this way we can avoid dynamic linker kicking in when
52 # downstream code imports this python module
53 import ctypes
54 import sys
55 import os
56 parser = "${grammarDrv}/parser"
57 try:
58 ctypes.CDLL(parser, mode=ctypes.RTLD_GLOBAL) # cached
59 except OSError as e:
60 raise ImportError(f"cannot load tree-sitter parser object from {parser}: {e}")
61
62 # expose binding
63 from ._binding import language
64 __all__ = ["language"]
65 '')
66 (writeTextDir "${snakeCaseName}/binding.c" ''
67 // AUTO-GENERATED DO NOT EDIT
68
69 #include <Python.h>
70
71 typedef struct TSLanguage TSLanguage;
72
73 TSLanguage *${langIdent}(void);
74
75 static PyObject* _binding_language(PyObject *self, PyObject *args) {
76 return PyLong_FromVoidPtr(${langIdent}());
77 }
78
79 static PyMethodDef methods[] = {
80 {"language", _binding_language, METH_NOARGS,
81 "Get the tree-sitter language for this grammar."},
82 {NULL, NULL, 0, NULL}
83 };
84
85 static struct PyModuleDef module = {
86 .m_base = PyModuleDef_HEAD_INIT,
87 .m_name = "_binding",
88 .m_doc = NULL,
89 .m_size = -1,
90 .m_methods = methods
91 };
92
93 PyMODINIT_FUNC PyInit__binding(void) {
94 return PyModule_Create(&module);
95 }
96 '')
97 (writeTextDir "setup.py" ''
98 # AUTO-GENERATED DO NOT EDIT
99
100 from platform import system
101 from setuptools import Extension, setup
102
103
104 setup(
105 packages=["${snakeCaseName}"],
106 ext_package="${snakeCaseName}",
107 ext_modules=[
108 Extension(
109 name="_binding",
110 sources=["${snakeCaseName}/binding.c"],
111 extra_objects = ["${grammarDrv}/parser"],
112 extra_compile_args=(
113 ["-std=c11"] if system() != 'Windows' else []
114 ),
115 )
116 ],
117 )
118 '')
119 (writeTextDir "pyproject.toml" ''
120 # AUTO-GENERATED DO NOT EDIT
121
122 [build-system]
123 requires = ["setuptools", "wheel"]
124 build-backend = "setuptools.build_meta"
125
126 [project]
127 name="${snakeCaseName}"
128 description = "${langIdent} grammar for tree-sitter"
129 version = "${version}"
130 keywords = ["parsing", "incremental", "python"]
131 classifiers = [
132 "Development Status :: 4 - Beta",
133 "Intended Audience :: Developers",
134 "Topic :: Software Development :: Compilers",
135 "Topic :: Text Processing :: Linguistic",
136 ]
137
138 requires-python = ">=3.8"
139 license = "MIT"
140 readme = "README.md"
141
142 [project.optional-dependencies]
143 core = ["tree-sitter~=0.21"]
144
145 [tool.cibuildwheel]
146 build = "cp38-*"
147 build-frontend = "build"
148 '')
149 (writeTextDir "tests/test_language.py" ''
150 # AUTO-GENERATED DO NOT EDIT
151
152 from ${snakeCaseName} import language
153 from tree_sitter import Language, Parser
154
155 # This test only checks that the binding can load the grammar from the compiled shared object.
156 # It does not verify the grammar itself; that is tested in
157 # `pkgs/development/tools/parsing/tree-sitter/grammar.nix`.
158
159 def test_language():
160 lang = Language(language())
161 assert lang is not None
162 parser = Parser(lang)
163 tree = parser.parse(bytes("", "utf-8"))
164 assert tree is not None
165 '')
166 ];
167 };
168
169 preCheck = ''
170 # https://github.com/NixOS/nixpkgs/issues/255262
171 rm -r ${snakeCaseName}
172 '';
173
174 nativeCheckInputs = [
175 tree-sitter
176 pytestCheckHook
177 ];
178
179 pythonImportsCheck = [ snakeCaseName ];
180
181 meta = {
182 description = "Python bindings for ${name}";
183 license = lib.licenses.mit;
184 maintainers = with lib.maintainers; [
185 a-jay98
186 adfaure
187 mightyiam
188 stepbrobd
189 ];
190 };
191}