use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use thiserror::Error;
use crate::artefact::target::TargetTriple;
use crate::dirs::BaseDirs;
use super::super::manifest::DependencyBinary;
use super::downloader::{DependencyArchiveDownloader, RepositoryArchiveDownloader};
use super::extractor::{DependencyArchiveExtractor, RepositoryArchiveExtractor};
use super::metadata::{archive_filename, expected_member_path};
#[derive(Debug, Error)]
pub enum DependencyBinaryInstallError {
#[error("could not determine local bin directory")]
MissingBinDir,
#[error("repository asset not found: {url}")]
NotFound {
url: String,
},
#[error("download failed for {url}: {reason}")]
Download {
url: String,
reason: String,
},
#[error("failed to extract {archive}: {reason}")]
Extraction {
archive: PathBuf,
reason: String,
},
#[error("archive did not contain expected binary {binary}")]
MissingBinaryInArchive {
binary: String,
},
#[error("failed to install binary {binary}: {reason}")]
Install {
binary: String,
reason: String,
},
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("checksum verification failed for {archive}: expected {expected}, got {actual}")]
Checksum {
archive: PathBuf,
expected: String,
actual: String,
},
}
impl DependencyBinaryInstallError {
#[must_use]
pub(crate) fn is_not_found(&self) -> bool {
matches!(self, Self::NotFound { .. })
}
}
#[cfg_attr(test, mockall::automock)]
pub trait DependencyBinaryInstaller {
fn install(
&self,
dependency: &DependencyBinary,
target: &TargetTriple,
dirs: &dyn BaseDirs,
) -> Result<PathBuf, DependencyBinaryInstallError>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RepositoryDependencyBinaryInstaller;
impl DependencyBinaryInstaller for RepositoryDependencyBinaryInstaller {
fn install(
&self,
dependency: &DependencyBinary,
target: &TargetTriple,
dirs: &dyn BaseDirs,
) -> Result<PathBuf, DependencyBinaryInstallError> {
install_with(
dependency,
target,
&InstallSupport {
dirs,
downloader: &RepositoryArchiveDownloader,
extractor: &RepositoryArchiveExtractor,
},
)
}
}
pub(crate) struct InstallSupport<'a> {
pub(crate) dirs: &'a dyn BaseDirs,
pub(crate) downloader: &'a dyn DependencyArchiveDownloader,
pub(crate) extractor: &'a dyn DependencyArchiveExtractor,
}
pub(crate) fn install_with(
dependency: &DependencyBinary,
target: &TargetTriple,
support: &InstallSupport<'_>,
) -> Result<PathBuf, DependencyBinaryInstallError> {
let bin_dir = support
.dirs
.bin_dir()
.ok_or(DependencyBinaryInstallError::MissingBinDir)?;
fs::create_dir_all(bin_dir.as_path())?;
let temp_dir = tempfile::tempdir()?;
let filename = archive_filename(dependency, target);
let archive_path = temp_dir.path().join(&filename);
support.downloader.download(&filename, &archive_path)?;
let member_path = expected_member_path(dependency, target);
let installed_path =
support
.extractor
.extract_binary(&archive_path, &member_path, bin_dir.as_path())?;
ensure_executable(&installed_path)?;
Ok(installed_path)
}
pub(crate) fn ensure_executable(path: &Path) -> Result<(), DependencyBinaryInstallError> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut permissions = fs::metadata(path)?.permissions();
permissions.set_mode(0o755);
fs::set_permissions(path, permissions).map_err(|error| {
DependencyBinaryInstallError::Install {
binary: path.display().to_string(),
reason: error.to_string(),
}
})?;
}
#[cfg(not(unix))]
let _ = path;
Ok(())
}