use std::{env, fs, path::PathBuf, process::Command};
use cc::Build;
use url::Url;
struct GrammarSpec {
library_name: &'static str, repo_url: Url,
rev: &'static str,
subdir: &'static str,
source: Vec<&'static str>,
}
fn exec(command: &str, args: &[&str]) -> Result<(), Box<dyn std::error::Error>> {
let status = Command::new(command).args(args).status().map_err(|e| {
format!(
"Failed to execute command [[ {command} {} ]]. {e}",
args.join(" ")
)
})?;
if !status.success() {
return Err(format!(
"Command finished with exit status {}: {command} {}",
status.code().unwrap_or(-1),
args.join(" ")
)
.into());
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let grammar_specs = vec![
GrammarSpec {
library_name: "markdown",
repo_url: Url::parse("https://github.com/tree-sitter-grammars/tree-sitter-markdown")
.unwrap(),
rev: "7462bb66ac7e90312082269007fac2772fe5efd1",
subdir: "tree-sitter-markdown",
source: vec!["src/parser.c", "src/scanner.c"],
},
GrammarSpec {
library_name: "markdown-inline",
repo_url: Url::parse("https://github.com/tree-sitter-grammars/tree-sitter-markdown")
.unwrap(),
rev: "7462bb66ac7e90312082269007fac2772fe5efd1",
subdir: "tree-sitter-markdown-inline",
source: vec!["src/parser.c", "src/scanner.c"],
},
GrammarSpec {
library_name: "json",
repo_url: Url::parse("https://github.com/tree-sitter/tree-sitter-json.git").unwrap(),
rev: "ee35a6ebefcef0c5c416c0d1ccec7370cfca5a24",
subdir: ".",
source: vec!["src/parser.c"],
},
];
let out_dir = env::var("OUT_DIR")?;
let grammars_dir = PathBuf::from(&out_dir);
for spec in grammar_specs {
let repo_name = spec
.repo_url
.path_segments()
.and_then(|mut segments| segments.next_back())
.ok_or("Invalid repository URL")?
.trim_end_matches(".git");
let repo_dir = grammars_dir.join(repo_name);
if !repo_dir.join("__ok__").exists() {
let orig_current_dir = env::current_dir()?;
_ = fs::remove_dir_all(&repo_dir);
fs::create_dir_all(&repo_dir)?;
env::set_current_dir(&repo_dir)?;
exec("git", &["init", "-q"])?;
exec("git", &["remote", "add", "origin", spec.repo_url.as_str()])?;
exec("git", &["fetch", "-q", "--depth", "1", "origin", spec.rev])?;
exec("git", &["checkout", "-q", "FETCH_HEAD"])?;
fs::write(repo_dir.join("__ok__"), b"")?;
env::set_current_dir(&orig_current_dir)?;
}
let include = repo_dir.join(spec.subdir).join("src");
let source_files: Vec<PathBuf> = spec
.source
.iter()
.map(|p| repo_dir.join(spec.subdir).join(p))
.collect();
Build::new()
.opt_level(3)
.include(include)
.files(&source_files)
.compile(spec.library_name);
source_files
.iter()
.for_each(|p| println!("cargo::rerun-if-changed={}", p.to_string_lossy()));
}
Ok(())
}