#[cfg(feature = "static-grammar-libs")]
use anyhow::bail;
#[cfg(feature = "static-grammar-libs")]
use thiserror::Error;
#[cfg(feature = "static-grammar-libs")]
use cargo_emit::{rerun_if_changed, rerun_if_env_changed};
#[cfg(feature = "static-grammar-libs")]
use rayon::prelude::*;
#[cfg(feature = "static-grammar-libs")]
use std::{
env,
fmt::Display,
fs,
path::{Path, PathBuf},
vec,
};
use anyhow::Result;
use std::fmt::Write;
#[cfg(feature = "static-grammar-libs")]
#[derive(Debug, Default)]
struct GrammarCompileInfo<'a> {
display_name: &'a str,
path: PathBuf,
c_sources: Vec<&'a str>,
cpp_sources: Vec<&'a str>,
include_paths: Option<Vec<PathBuf>>,
}
#[cfg(feature = "static-grammar-libs")]
struct CompileParams {
pub include_dirs: Vec<PathBuf>,
pub c_sources: Vec<PathBuf>,
pub cpp_sources: Vec<PathBuf>,
pub display_name: String,
}
#[cfg(feature = "static-grammar-libs")]
#[derive(Debug, Error)]
enum CompileParamError {
#[error("Subdirectory for grammar {0} was not found")]
SubdirectoryNotFound(String),
#[error("Source files {source_files:?} not found for {grammar}")]
SourceFilesNotFound {
grammar: String,
source_files: Vec<String>,
},
}
#[cfg(feature = "static-grammar-libs")]
const BUILD_ENV_VARS: &[&str] = &["CC", "CXX", "LD_LIBRARY_PATH", "PATH"];
#[cfg(feature = "static-grammar-libs")]
fn codegen_language_map<T: ToString + Display>(languages: &[T]) -> String {
let body: String = languages.iter().fold(String::new(), |mut buffer, lang| {
writeln!(buffer, "\"{lang}\" => tree_sitter_{lang},").unwrap();
buffer
});
let map_decl = format!(
"\nstatic LANGUAGES: phf::Map<&'static str, unsafe extern \"C\" fn() -> Language> = phf_map! {{\n {body}\n }};\n");
map_decl
}
#[cfg(feature = "static-grammar-libs")]
fn compile_grammar(
includes: &[PathBuf],
c_sources: &[PathBuf],
cpp_sources: &[PathBuf],
output_name: &str,
) -> Result<(), cc::Error> {
if !c_sources.is_empty() {
cc::Build::new()
.includes(includes)
.files(c_sources)
.flag_if_supported("-std=c11")
.warnings(false)
.extra_warnings(false)
.try_compile(&format!("{output_name}-cc-diffsitter"))?;
}
if !cpp_sources.is_empty() {
cc::Build::new()
.cpp(true)
.includes(includes)
.files(cpp_sources)
.flag_if_supported("-std=c++17")
.warnings(false)
.extra_warnings(false)
.try_compile(&format!("{}-cxx-diffsitter", &output_name))?;
}
Ok(())
}
#[cfg(feature = "static-grammar-libs")]
fn extra_cargo_directives() {
for &env_var in BUILD_ENV_VARS {
rerun_if_env_changed!(env_var);
}
}
#[cfg(feature = "static-grammar-libs")]
fn preprocess_compile_info(grammar: &GrammarCompileInfo) -> CompileParams {
let dir = grammar.path.join("src");
let include_dirs = if let Some(includes) = grammar.include_paths.clone() {
includes.clone()
} else {
vec![dir.clone()]
};
let cpp_sources: Vec<_> = grammar
.cpp_sources
.iter()
.map(|&filename| dir.join(filename))
.collect();
let c_sources: Vec<_> = grammar
.c_sources
.iter()
.map(|&filename| dir.join(filename))
.collect();
CompileParams {
include_dirs,
c_sources,
cpp_sources,
display_name: grammar.display_name.into(),
}
}
#[cfg(feature = "static-grammar-libs")]
fn verify_compile_params(compile_params: &CompileParams) -> Result<(), CompileParamError> {
for include_dir in &compile_params.include_dirs {
if !include_dir.exists() {
return Err(CompileParamError::SubdirectoryNotFound(
compile_params.display_name.to_string(),
));
}
}
let missing_sources = compile_params
.c_sources
.iter()
.chain(compile_params.cpp_sources.iter())
.filter_map(|file| {
if file.exists() {
None
} else {
Some(file.to_string_lossy().to_string())
}
})
.collect::<Vec<String>>();
if !missing_sources.is_empty() {
return Err(CompileParamError::SourceFilesNotFound {
grammar: compile_params.display_name.to_string(),
source_files: missing_sources,
});
}
Ok(())
}
#[cfg(feature = "static-grammar-libs")]
fn grammars() -> Vec<GrammarCompileInfo<'static>> {
let grammars = vec![
GrammarCompileInfo {
display_name: "rust",
path: PathBuf::from("grammars/tree-sitter-rust"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "cpp",
path: PathBuf::from("grammars/tree-sitter-cpp"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "python",
path: PathBuf::from("grammars/tree-sitter-python"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "bash",
path: PathBuf::from("grammars/tree-sitter-bash"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "ocaml",
path: PathBuf::from("grammars/tree-sitter-ocaml/grammars/ocaml"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "go",
path: PathBuf::from("grammars/tree-sitter-go"),
c_sources: vec!["parser.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "ruby",
path: PathBuf::from("grammars/tree-sitter-ruby"),
c_sources: vec!["parser.c"],
cpp_sources: vec!["scanner.cc"],
..GrammarCompileInfo::default()
},
GrammarCompileInfo {
display_name: "java",
path: PathBuf::from("grammars/tree-sitter-java"),
c_sources: vec!["parser.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "c_sharp",
path: PathBuf::from("grammars/tree-sitter-c-sharp"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "css",
path: PathBuf::from("grammars/tree-sitter-css"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "php",
path: PathBuf::from("grammars/tree-sitter-php/php"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "json",
path: PathBuf::from("grammars/tree-sitter-json"),
c_sources: vec!["parser.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "hcl",
path: PathBuf::from("grammars/tree-sitter-hcl"),
c_sources: vec!["parser.c"],
cpp_sources: vec!["scanner.cc"],
..Default::default()
},
GrammarCompileInfo {
display_name: "typescript",
path: PathBuf::from("grammars/tree-sitter-typescript/typescript"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "tsx",
path: PathBuf::from("grammars/tree-sitter-typescript/tsx"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "c",
path: PathBuf::from("grammars/tree-sitter-c"),
c_sources: vec!["parser.c"],
..Default::default()
},
GrammarCompileInfo {
display_name: "markdown",
path: PathBuf::from("grammars/tree-sitter-markdown/tree-sitter-markdown"),
c_sources: vec!["parser.c", "scanner.c"],
..Default::default()
}, ];
grammars
}
#[cfg(feature = "static-grammar-libs")]
fn compile_static_grammars() -> Result<()> {
let grammars = grammars();
let mut codegen = String::from(
r"
use tree_sitter::Language;
use phf::phf_map;
",
);
let mut languages = Vec::with_capacity(grammars.len());
let compile_params: Vec<CompileParams> = grammars.iter().map(preprocess_compile_info).collect();
compile_params
.iter()
.map(verify_compile_params)
.collect::<Result<Vec<_>, CompileParamError>>()?;
compile_params
.par_iter()
.map(|p| {
compile_grammar(
&p.include_dirs,
&p.c_sources[..],
&p.cpp_sources[..],
&p.display_name,
)
})
.collect::<Result<Vec<_>, _>>()?;
for params in &compile_params {
let language = ¶ms.display_name;
writeln!(
codegen,
"extern \"C\" {{ pub fn tree_sitter_{language}() -> Language; }}"
)?;
languages.push(language.as_str());
for source in params.c_sources.iter().chain(params.cpp_sources.iter()) {
if let Some(grammar_path) = &source.as_path().to_str() {
rerun_if_changed!((*grammar_path).to_string());
} else {
bail!("Path to grammar for {} is not a valid string", language);
}
}
}
extra_cargo_directives();
codegen += &codegen_language_map(&languages[..]);
let codegen_out_dir = env::var_os("OUT_DIR").unwrap();
let codegen_path = Path::new(&codegen_out_dir).join("generated_grammar.rs");
fs::write(codegen_path, codegen)?;
Ok(())
}
fn main() -> anyhow::Result<()> {
#[cfg(feature = "static-grammar-libs")]
compile_static_grammars()?;
#[cfg(feature = "better-build-info")]
shadow_rs::ShadowBuilder::builder()
.build_pattern(shadow_rs::BuildPattern::RealTime)
.build()
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
Ok(())
}