use crate::dependency::Dependency;
use crate::session::Session;
#[derive(Debug)]
pub enum Error {
UnknownDependencyFamily,
UnsupportedScope(InstallationScope),
UnsupportedScopes(Vec<InstallationScope>),
AnalyzedError(crate::analyze::AnalyzedError),
SessionError(crate::session::Error),
Other(String),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::UnknownDependencyFamily => write!(f, "Unknown dependency family"),
Error::UnsupportedScope(scope) => write!(f, "Unsupported scope: {:?}", scope),
Error::UnsupportedScopes(scopes) => write!(f, "Unsupported scopes: {:?}", scopes),
Error::AnalyzedError(e) => write!(f, "{}", e),
Error::SessionError(e) => write!(f, "{}", e),
Error::Other(s) => write!(f, "{}", s),
}
}
}
impl std::error::Error for Error {}
impl From<crate::analyze::AnalyzedError> for Error {
fn from(e: crate::analyze::AnalyzedError) -> Self {
Error::AnalyzedError(e)
}
}
impl From<crate::session::Error> for Error {
fn from(e: crate::session::Error) -> Self {
Error::SessionError(e)
}
}
pub struct Explanation {
pub message: String,
pub command: Option<Vec<String>>,
}
impl Explanation {
pub fn new(message: String, command: Option<Vec<String>>) -> Self {
Explanation { message, command }
}
}
impl std::fmt::Display for Explanation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.message)?;
if let Some(command) = &self.command {
write!(
f,
"\n\nRun the following command to install the dependency:\n\n"
)?;
for arg in command {
write!(f, "{} ", arg)?;
}
writeln!(f)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum InstallationScope {
Global,
User,
Vendor,
}
impl std::str::FromStr for InstallationScope {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"global" => Ok(InstallationScope::Global),
"user" => Ok(InstallationScope::User),
"vendor" => Ok(InstallationScope::Vendor),
_ => Err(Error::Other(format!("Unknown installation scope: {}", s))),
}
}
}
pub trait Installer {
fn install(&self, dep: &dyn Dependency, scope: InstallationScope) -> Result<(), Error>;
fn explain(&self, dep: &dyn Dependency, scope: InstallationScope)
-> Result<Explanation, Error>;
fn explain_some(
&self,
deps: Vec<Box<dyn Dependency>>,
scope: InstallationScope,
) -> Result<(Vec<Explanation>, Vec<Box<dyn Dependency>>), Error> {
let mut explanations = Vec::new();
let mut failed = Vec::new();
for dep in deps {
match self.explain(&*dep, scope) {
Ok(explanation) => explanations.push(explanation),
Err(Error::UnknownDependencyFamily) => failed.push(dep),
Err(e) => {
return Err(e);
}
}
}
Ok((explanations, failed))
}
fn install_some(
&self,
deps: Vec<Box<dyn Dependency>>,
scope: InstallationScope,
) -> Result<(Vec<Box<dyn Dependency>>, Vec<Box<dyn Dependency>>), Error> {
let mut installed = Vec::new();
let mut failed = Vec::new();
for dep in deps {
match self.install(&*dep, scope) {
Ok(()) => installed.push(dep),
Err(Error::UnknownDependencyFamily) => failed.push(dep),
Err(e) => {
return Err(e);
}
}
}
Ok((installed, failed))
}
}
pub struct NullInstaller;
impl NullInstaller {
pub fn new() -> Self {
NullInstaller
}
}
impl Default for NullInstaller {
fn default() -> Self {
NullInstaller::new()
}
}
impl Installer for NullInstaller {
fn install(&self, _dep: &dyn Dependency, _scope: InstallationScope) -> Result<(), Error> {
Err(Error::UnknownDependencyFamily)
}
fn explain(
&self,
_dep: &dyn Dependency,
_scope: InstallationScope,
) -> Result<Explanation, Error> {
Err(Error::UnknownDependencyFamily)
}
}
pub struct StackedInstaller<'a>(pub Vec<Box<dyn Installer + 'a>>);
impl<'a> StackedInstaller<'a> {
pub fn new(resolvers: Vec<Box<dyn Installer + 'a>>) -> Self {
Self(resolvers)
}
}
impl<'a> Installer for StackedInstaller<'a> {
fn install(&self, requirement: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> {
for sub in &self.0 {
match sub.install(requirement, scope) {
Ok(()) => {
return Ok(());
}
Err(Error::UnknownDependencyFamily) => {}
Err(e) => {
return Err(e);
}
}
}
Err(Error::UnknownDependencyFamily)
}
fn explain(
&self,
requirements: &dyn Dependency,
scope: InstallationScope,
) -> Result<Explanation, Error> {
for sub in &self.0 {
match sub.explain(requirements, scope) {
Ok(e) => {
return Ok(e);
}
Err(Error::UnknownDependencyFamily) => {}
Err(e) => {
return Err(e);
}
}
}
Err(Error::UnknownDependencyFamily)
}
}
pub fn installer_by_name<'a>(
session: &'a dyn crate::session::Session,
name: &str,
) -> Option<Box<dyn Installer + 'a>> {
match name {
#[cfg(feature = "debian")]
"apt" => Some(
Box::new(crate::debian::apt::AptInstaller::from_session(session)) as Box<dyn Installer>,
),
"cpan" => Some(
Box::new(crate::dependencies::perl::CPAN::new(session, false)) as Box<dyn Installer>,
),
"ctan" => Some(Box::new(crate::dependencies::latex::ctan(session)) as Box<dyn Installer>),
"pypi" => Some(
Box::new(crate::dependencies::python::PypiResolver::new(session)) as Box<dyn Installer>,
),
"npm" => Some(
Box::new(crate::dependencies::node::NpmResolver::new(session)) as Box<dyn Installer>,
),
"go" => {
Some(Box::new(crate::dependencies::go::GoResolver::new(session)) as Box<dyn Installer>)
}
"hackage" => Some(
Box::new(crate::dependencies::haskell::HackageResolver::new(session))
as Box<dyn Installer>,
),
"cran" => Some(Box::new(crate::dependencies::r::cran(session)) as Box<dyn Installer>),
"bioconductor" => {
Some(Box::new(crate::dependencies::r::bioconductor(session)) as Box<dyn Installer>)
}
"octave-forge" => Some(
Box::new(crate::dependencies::octave::OctaveForgeResolver::new(
session,
)) as Box<dyn Installer>,
),
"native" => {
Some(Box::new(StackedInstaller::new(native_installers(session))) as Box<dyn Installer>)
}
_ => None,
}
}
pub fn native_installers<'a>(
session: &'a dyn crate::session::Session,
) -> Vec<Box<dyn Installer + 'a>> {
[
"ctan",
"pypi",
"npm",
"go",
"hackage",
"cran",
"bioconductor",
"octave-forge",
]
.iter()
.map(|name| installer_by_name(session, name).unwrap())
.collect()
}
#[cfg(feature = "debian")]
fn apt_installer<'a>(
session: &'a dyn crate::session::Session,
#[allow(unused_variables)] dep_server_url: Option<&url::Url>,
) -> Box<dyn Installer + 'a> {
#[cfg(feature = "dep-server")]
if let Some(dep_server_url) = dep_server_url {
Box::new(
crate::debian::dep_server::DepServerAptInstaller::from_session(session, dep_server_url),
) as Box<dyn Installer + 'a>
} else {
Box::new(crate::debian::apt::AptInstaller::from_session(session))
}
#[cfg(not(feature = "dep-server"))]
{
Box::new(crate::debian::apt::AptInstaller::from_session(session))
}
}
pub fn select_installers<'a>(
session: &'a dyn crate::session::Session,
names: &[&str],
#[allow(unused_variables)] dep_server_url: Option<&url::Url>,
) -> Result<Box<dyn Installer + 'a>, String> {
let mut installers = Vec::new();
for name in names.iter() {
if name == &"apt" {
#[cfg(feature = "debian")]
installers.push(apt_installer(session, dep_server_url));
#[cfg(not(feature = "debian"))]
return Err("Apt installer not available".to_string());
} else if let Some(installer) = installer_by_name(session, name) {
installers.push(installer);
} else {
return Err(format!("Unknown installer: {}", name));
}
}
Ok(Box::new(StackedInstaller(installers)))
}
pub fn auto_installation_scope(session: &dyn crate::session::Session) -> InstallationScope {
let user = crate::session::get_user(session);
if user == "root" {
log::info!("Running as root, so using global installation scope");
InstallationScope::Global
} else if session.is_temporary() {
log::info!("Running in a temporary session, so using global installation scope");
InstallationScope::Global
} else {
log::info!("Running as user, so using user installation scope");
InstallationScope::User
}
}
pub fn auto_installer<'a>(
session: &'a dyn crate::session::Session,
#[allow(unused_variables)] scope: InstallationScope,
#[allow(unused_variables)] dep_server_url: Option<&url::Url>,
) -> Box<dyn Installer + 'a> {
let mut installers: Vec<Box<dyn Installer + 'a>> = Vec::new();
#[cfg(feature = "debian")]
if scope == InstallationScope::Global && crate::session::which(session, "apt-get").is_some() {
log::info!(
"Using global installation scope and apt-get is available, so using apt installer"
);
installers.push(apt_installer(session, dep_server_url));
}
installers.extend(native_installers(session));
Box::new(StackedInstaller::new(installers))
}
pub fn install_missing_deps(
session: &dyn Session,
installer: &dyn Installer,
scopes: &[InstallationScope],
deps: &[&dyn Dependency],
) -> Result<(), Error> {
if deps.is_empty() {
return Ok(());
}
let missing = deps
.iter()
.filter(|dep| !dep.present(session))
.collect::<Vec<_>>();
if !missing.is_empty() {
log::info!("Missing dependencies: {:?}", missing);
for dep in missing.into_iter() {
log::info!("Installing {:?}", dep);
let mut installed = false;
for scope in scopes {
match installer.install(*dep, *scope) {
Ok(()) => {
log::info!("Installed {:?}", dep);
installed = true;
break;
}
Err(Error::UnsupportedScope(_)) => {}
Err(e) => {
return Err(e);
}
}
}
if !installed {
return Err(Error::UnsupportedScopes(scopes.to_vec()));
}
}
}
Ok(())
}
pub fn explain_missing_deps(
session: &dyn Session,
installer: &dyn Installer,
deps: &[&dyn Dependency],
) -> Result<Vec<Explanation>, Error> {
if deps.is_empty() {
return Ok(vec![]);
}
let mut missing = vec![];
for dep in deps.iter() {
if !dep.present(session) {
missing.push(*dep)
}
}
if !missing.is_empty() {
let mut explanations = vec![];
for dep in missing.into_iter() {
log::info!("Explaining {:?}", dep);
explanations.push(installer.explain(dep, InstallationScope::Global)?);
}
Ok(explanations)
} else {
Ok(vec![])
}
}