133 lines
3.1 KiB
Rust
133 lines
3.1 KiB
Rust
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<String>,
|
|
}
|
|
|
|
impl Install {
|
|
pub fn run(
|
|
self,
|
|
Settings {
|
|
parser_dir,
|
|
compiler,
|
|
}: Settings,
|
|
) -> anyhow::Result<ExitCode> {
|
|
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<languages::Revision, Error> {
|
|
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<cc::Error> 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<git2::Oid, Error> {
|
|
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)
|
|
}
|