chrysopoeia/src/install.rs

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)
}