use std::{env::consts::DLL_EXTENSION, fs::create_dir_all, path::Path, process::ExitCode}; use clap::Args; use git2::Repository; use tempfile::tempdir; use crate::console::{error, fail, info, update}; use crate::settings::Settings; mod cc; mod languages; use languages::{InstallInfo, get_install_info}; #[derive(Debug, Args)] pub struct Install { languages: Vec, } impl Install { pub fn run( self, Settings { parser_dir, compiler, }: Settings, ) -> anyhow::Result { let src_dir = tempdir()?; create_dir_all(&parser_dir)?; // have to use a canonical path bc compiler runs elsewhere let parser_dir = parser_dir.canonicalize()?; let mut languages = self.languages; languages.sort(); languages.dedup(); let mut errors = 0; for language in &languages { if let Some(info) = get_install_info(language) { if let Err(Error::Fatal(_)) = install_with_status( language, info, &parser_dir, src_dir.as_ref(), compiler.as_deref(), ) { return Ok(ExitCode::FAILURE); } else { errors += 1; } } else { error!("unknown language {language}"); errors += 1; } } Ok(if errors > 0 { ExitCode::FAILURE } else { ExitCode::SUCCESS }) } } fn install_with_status( language: &str, install_info: &InstallInfo, parser_dir: &Path, src_dir: &Path, compiler: Option<&str>, ) -> Result { info!("Installing", "{language}"); let res = install(language, install_info, parser_dir, src_dir, compiler); match &res { Err(e) => fail!("{e}"), Ok(revision) => update!("Installed", "{language} {revision:.7}"), } res } #[derive(Debug, thiserror::Error)] enum Error { #[error("couldn't clone `{0}`")] Clone(&'static str), #[error(transparent)] Git(#[from] git2::Error), #[error(transparent)] Compile(cc::Error), #[error(transparent)] Fatal(cc::Error), } impl From for Error { fn from(e: cc::Error) -> Self { use cc::Error as E; match e { E::NoCompiler | E::BadCompiler { .. } | E::EmptyCompiler => Self::Fatal(e), _ => Self::Compile(e), } } } fn install( language: &str, InstallInfo { url, files }: &InstallInfo, parser_dir: &Path, src_dir: &Path, compiler: Option<&str>, ) -> Result { let repo_path = src_dir.join(language); let repo = Repository::clone(url, &repo_path).map_err(|_| Error::Clone(url))?; let version = repo.head()?.peel_to_commit()?.id(); let output_path = parser_dir.join(language).with_extension(DLL_EXTENSION); let mut build = cc::Build::new(); build.compiler(compiler).dir(&repo_path).input_files(files); build.compile(&output_path)?; Ok(version) }