use anyhow::{Context, Result};
use chrono::Datelike;
use serde::Deserialize;
use tar::Archive;
use zstd::Decoder;
use std::{
env,
fs::{self, File},
io::BufReader,
path::Path,
process::Command,
};
#[derive(Deserialize)]
pub struct Config {
pub languages: Vec<Language>,
}
#[derive(Deserialize)]
pub struct Language {
pub name: String,
}
impl Language {
pub fn compile(&self, languages_dir: &Path) -> Result<()> {
let path = languages_dir.join(&self.name).join("src");
let has_scanner = path.join("scanner.c").exists() || path.join("scanner.cc").exists();
let scanner_is_cpp = path.join("scanner.cc").exists();
let mut build = cc::Build::new();
let parser_path = path.join("parser.c");
let build = build.include(&path).flag_if_supported("-w").flag_if_supported("-s").flag_if_supported("-O2").file(&parser_path);
rerun_if_changed(&parser_path);
if has_scanner && !scanner_is_cpp {
let scanner_path = path.join("scanner.c");
rerun_if_changed(&scanner_path);
build.file(&scanner_path);
} else if scanner_is_cpp {
let mut build = cc::Build::new();
let scanner_path = path.join("scanner.cc");
rerun_if_changed(&scanner_path);
build
.cpp(true)
.include(&path)
.flag_if_supported("-w")
.flag_if_supported("-s")
.flag_if_supported("-O2")
.flag_if_supported("-fvisibility=default")
.flag_if_supported("-fno-exceptions")
.flag_if_supported("-fno-rtti")
.file(&scanner_path)
.compile(&format!("{}-scanner", self.name));
}
build.compile(&format!("{}-parser", self.name));
Ok(())
}
}
fn rerun_if_changed(path: impl AsRef<Path>) {
println!("cargo:rerun-if-changed={}", path.as_ref().to_str().unwrap());
}
fn extract_languages(languages_dir: &Path) -> Result<()> {
if languages_dir.exists() {
println!("Languages directory already exists. Skipping extraction.");
return Ok(());
}
println!("Extracting languages...");
let languages_archive = Path::new("languages.tar.zst");
let file = File::open(languages_archive).context("Failed to open languages.tar.zst")?;
let zstd_decoder = Decoder::new(file)?;
let buf_reader = BufReader::new(zstd_decoder);
let mut archive = Archive::new(buf_reader);
archive.unpack(".").context("Failed to extract languages archive")?;
println!("Extraction completed successfully.");
Ok(())
}
fn main() -> Result<()> {
let date = chrono::Utc::now();
let profile = env::var("PROFILE").unwrap();
let languages_dir = Path::new("languages");
let output = Command::new("git").args(&["rev-parse", "--short=10", "HEAD"]).output().unwrap();
let output_full = Command::new("git").args(&["rev-parse", "HEAD"]).output().unwrap();
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
println!("cargo:rustc-env=GIT_HASH={}", String::from_utf8(output.stdout).unwrap());
println!("cargo:rustc-env=GIT_HASH_FULL={}", String::from_utf8(output_full.stdout).unwrap());
println!("cargo:rustc-env=BUILD_DATE={}-{}-{}", date.year(), date.month(), date.day());
match profile.as_str() {
"debug" => println!("cargo:rustc-env=PROFILE=debug"),
"release" => println!("cargo:rustc-env=PROFILE=release"),
_ => println!("cargo:rustc-env=PROFILE=none"),
}
match std::env::current_dir() {
Ok(current_dir) => println!("Current working directory: {:?}", current_dir),
Err(e) => println!("Failed to get current directory: {}", e),
}
for entry in fs::read_dir(".")? {
let entry = entry?;
println!("{:?}", entry.path());
}
if profile.as_str() == "release" {
extract_languages(languages_dir)?;
let config = fs::read_to_string("languages.toml")?;
let config: Config = toml::from_str(&config)?;
for lang in config.languages {
lang.compile(languages_dir).with_context(|| format!("Failed to compile language: {}", lang.name))?;
}
}
Ok(println!("cargo:rerun-if-changed=languages.tar.zst"))
}