lux_lib/build/
treesitter_parser.rs

1use crate::config::{Config, LuaVersionUnset};
2use crate::lua_installation::LuaInstallation;
3use crate::lua_rockspec::Build;
4use crate::lua_rockspec::{BuildInfo, TreesitterParserBuildSpec};
5use crate::progress::{Progress, ProgressBar};
6use crate::tree::{RockLayout, Tree};
7use std::collections::HashMap;
8use std::io;
9use std::num::ParseIntError;
10use std::path::{Path, PathBuf};
11use thiserror::Error;
12use tree_sitter_generate::GenerateError;
13
14use super::external_dependency::ExternalDependencyInfo;
15
16const DEFAULT_GENERATE_ABI_VERSION: usize = tree_sitter::LANGUAGE_VERSION;
17
18#[derive(Error, Debug)]
19pub enum TreesitterBuildError {
20    #[error(transparent)]
21    LuaVersionUnset(#[from] LuaVersionUnset),
22    #[error("failed to initialise the tree-sitter loader: {0}")]
23    Loader(String),
24    #[error("invalid TREE_SITTER_LANGUAGE_VERSION: {0}")]
25    ParseAbiVersion(#[from] ParseIntError),
26    #[error("error generating tree-sitter grammar: {0}")]
27    Generate(#[from] GenerateError),
28    #[error("error compiling the tree-sitter grammar: {0}")]
29    TreesitterCompileError(String),
30    #[error("error creating directory {dir}: {err}")]
31    CreateDir { dir: PathBuf, err: io::Error },
32    #[error("error writing query file: {0}")]
33    WriteQuery(io::Error),
34}
35
36impl Build for TreesitterParserBuildSpec {
37    type Err = TreesitterBuildError;
38
39    async fn run(
40        self,
41        output_paths: &RockLayout,
42        _no_install: bool,
43        _lua: &LuaInstallation,
44        _external_dependencies: &HashMap<String, ExternalDependencyInfo>,
45        _config: &Config,
46        _tree: &Tree,
47        build_dir: &Path,
48        progress: &Progress<ProgressBar>,
49    ) -> Result<BuildInfo, Self::Err> {
50        let build_dir = self
51            .location
52            .map(|dir| build_dir.join(dir))
53            .unwrap_or(build_dir.to_path_buf());
54        if self.generate {
55            progress.map(|b| b.set_message("📖 ✍Generating tree-sitter grammar..."));
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            tree_sitter_generate::generate_parser_in_directory(
61                &build_dir,
62                None,
63                None,
64                abi_version,
65                None,
66                None,
67            )?;
68        }
69        progress.map(|b| b.set_message("🌳 Building tree-sitter parser..."));
70        if self.parser {
71            let parser_dir = output_paths.etc.join("parser");
72            tokio::fs::create_dir_all(&parser_dir)
73                .await
74                .map_err(|err| TreesitterBuildError::CreateDir {
75                    dir: parser_dir.clone(),
76                    err,
77                })?;
78            let loader = tree_sitter_loader::Loader::with_parser_lib_path(build_dir.clone());
79            let output_path =
80                parser_dir.join(format!("{}.{}", self.lang, std::env::consts::DLL_EXTENSION));
81            // HACK(vhyrro): `tree-sitter-loader` will only use a temp directory instead of a
82            // lockfile if a `CROSS_RUNNER` env variable is set (why??). We should probably make a
83            // PR fixing this with a flag. This should make-do for now: theoretically, this could
84            // break since it's on an async thread, but in practice this will never be executed
85            // many times during the lifetime of the `lx` binary.
86            std::env::set_var("CROSS_RUNNER", "");
87            loader
88                .compile_parser_at_path(&build_dir, output_path, &[])
89                .map_err(|err| TreesitterBuildError::TreesitterCompileError(err.to_string()))?;
90        }
91
92        let queries_dir = output_paths.etc.join("queries");
93        if !self.queries.is_empty() {
94            tokio::fs::create_dir_all(&queries_dir)
95                .await
96                .map_err(|err| TreesitterBuildError::CreateDir {
97                    dir: queries_dir.clone(),
98                    err,
99                })?;
100        }
101        for (path, content) in self.queries {
102            let dest = queries_dir.join(path);
103            tokio::fs::write(&dest, content)
104                .await
105                .map_err(TreesitterBuildError::WriteQuery)?;
106        }
107
108        Ok(BuildInfo::default())
109    }
110}