lux_lib/build/
treesitter_parser.rs1use crate::build::backend::{BuildBackend, BuildInfo, RunBuildArgs};
2use crate::fs;
3use crate::lua_rockspec::TreesitterParserBuildSpec;
4use crate::lua_version::LuaVersionUnset;
5use crate::tree::InstallTree;
6use miette::Diagnostic;
7use std::num::ParseIntError;
8use std::path::{Path, PathBuf};
9use thiserror::Error;
10use tracing::info_span;
11use tree_sitter_generate::GenerateError;
12
13const DEFAULT_GENERATE_ABI_VERSION: usize = tree_sitter::LANGUAGE_VERSION;
14
15#[derive(Error, Debug, Diagnostic)]
16#[non_exhaustive]
17pub enum Error {
18 #[error(transparent)]
19 #[diagnostic(transparent)]
20 LuaVersionUnset(#[from] LuaVersionUnset),
21 #[error("invalid TREE_SITTER_LANGUAGE_VERSION")]
22 #[diagnostic(help(
23 "check the value of the TREE_SITTER_LANGUAGE_VERSION environment variable."
24 ))]
25 ParseAbiVersion(#[from] ParseIntError),
26 #[error("error generating tree-sitter grammar")]
27 #[diagnostic(help("check the grammar source files for errors."))]
28 Generate(#[from] GenerateError),
29 #[error("error compiling the tree-sitter grammar: {0}")]
30 #[diagnostic(help("run `lx debug toolchains` to check available build tools."))]
31 TreesitterCompileError(String),
32 #[error(transparent)]
33 #[diagnostic(transparent)]
34 Fs(#[from] fs::Error),
35}
36
37impl BuildBackend for TreesitterParserBuildSpec {
38 type Err = Error;
39
40 #[tracing::instrument(name = "treesitter_parser::run", skip_all, level = "debug")]
41 async fn run<T>(self, args: RunBuildArgs<'_, T>) -> Result<BuildInfo, Self::Err>
42 where
43 T: InstallTree,
44 {
45 let package = args.package;
46 let tree = args.tree;
47 let layout = tree.layout_for(package);
48 let build_dir = args.build_dir;
49 let build_dir = self
50 .location
51 .map(|dir| build_dir.join(dir))
52 .unwrap_or(build_dir.to_path_buf());
53 if self.generate {
54 let span = info_span!("Generating tree-sitter parser");
55 let _enter = span.enter();
56 let abi_version = match std::env::var("TREE_SITTER_LANGUAGE_VERSION") {
57 Ok(v) => v.parse()?,
58 Err(_) => DEFAULT_GENERATE_ABI_VERSION,
59 };
60 tracing::debug!("ABI version: {abi_version}");
61 let out_path: Option<PathBuf> = None;
62 let grammar_path: Option<PathBuf> = None;
63 let mut diagnostics = Vec::new();
64 tree_sitter_generate::generate_parser_in_directory(
65 &build_dir,
66 out_path,
67 grammar_path,
68 abi_version,
69 None,
70 None,
71 true,
72 tree_sitter_generate::OptLevel::default(),
73 &mut diagnostics,
74 )?;
75 for diagnostic in diagnostics {
76 tracing::debug!("tree-sitter-{}: {diagnostic}", &self.lang);
77 }
78 }
79 if self.parser {
80 build_parser(&build_dir, &layout.etc.join("parser"), &self.lang).await?;
81 }
82
83 let queries_dir = layout.etc.join("queries").join(&self.lang);
84 install_queries(&build_dir, &queries_dir, &self.lang, self.queries).await?;
85
86 Ok(BuildInfo::default())
87 }
88}
89
90fn is_query_file(path: &Path) -> bool {
91 path.extension().is_some_and(|ext| ext == "scm")
92}
93
94#[tracing::instrument(level = "trace")]
95async fn install_inline_queries(
96 queries_dir: &Path,
97 queries: std::collections::HashMap<PathBuf, String>,
98) -> Result<(), Error> {
99 fs::tokio::create_dir_all(queries_dir).await?;
100 for (path, content) in queries {
101 let dest = queries_dir.join(path);
102 fs::tokio::write(&dest, content).await?;
103 }
104 Ok(())
105}
106
107#[tracing::instrument(level = "trace")]
108async fn install_source_queries(
109 source_queries_dir: &Path,
110 queries_dir: &Path,
111) -> Result<(), Error> {
112 fs::tokio::create_dir_all(queries_dir).await?;
113 let mut entries = fs::tokio::read_dir(source_queries_dir).await?;
114 while let Some(entry) = entries
115 .next_entry()
116 .await
117 .map_err(|source| fs::Error::ReadDir {
118 path: source_queries_dir.to_path_buf(),
119 source,
120 })?
121 {
122 let path = entry.path();
123 if let Some(filename) = path.file_name().filter(|_| is_query_file(&path)) {
124 let dest = queries_dir.join(filename);
125 fs::tokio::copy(&path, &dest).await?;
126 }
127 }
128 Ok(())
129}
130
131#[tracing::instrument(level = "trace")]
132async fn install_queries(
133 build_dir: &Path,
134 queries_dir: &Path,
135 lang: &str,
136 queries: std::collections::HashMap<PathBuf, String>,
137) -> Result<(), Error> {
138 if !queries.is_empty() {
139 install_inline_queries(queries_dir, queries).await
140 } else {
141 let source_queries_dir = build_dir.join("queries");
142 let lang_queries_dir = source_queries_dir.join(lang);
143 if source_queries_dir.is_dir() && !lang_queries_dir.is_dir() {
144 install_source_queries(&source_queries_dir, queries_dir).await
145 } else {
146 Ok(())
147 }
148 }
149}
150
151#[tracing::instrument(
152 name = "Compiling tree-sitter parser",
153 level = "info",
154 skip_all,
155 fields(language = lang),
156)]
157async fn build_parser(build_dir: &Path, parser_dir: &Path, lang: &str) -> Result<(), Error> {
158 fs::tokio::create_dir_all(parser_dir).await?;
159 let loader = tree_sitter_loader::Loader::with_parser_lib_path(build_dir.to_path_buf());
160 let output_path = parser_dir.join(format!("{}.{}", lang, std::env::consts::DLL_EXTENSION));
161 std::env::set_var("CROSS_RUNNER", "");
167 loader
168 .compile_parser_at_path(build_dir, output_path, &[])
169 .map_err(|err| Error::TreesitterCompileError(err.to_string()))
170}