use crate::dependency::Dependency;
use crate::installer::{Error, Explanation, InstallationScope, Installer};
use crate::session::Session;
use r_description::lossy::Relation;
use r_description::VersionConstraint;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RPackageDependency(Relation);
impl From<RPackageDependency> for Relation {
fn from(dep: RPackageDependency) -> Self {
dep.0
}
}
impl From<Relation> for RPackageDependency {
fn from(rel: Relation) -> Self {
Self(rel)
}
}
impl From<r_description::lossless::Relation> for RPackageDependency {
fn from(rel: r_description::lossless::Relation) -> Self {
Self(rel.into())
}
}
impl RPackageDependency {
pub fn new(package: &str, minimum_version: Option<&str>) -> Self {
if let Some(minimum_version) = minimum_version {
Self(Relation {
name: package.to_string(),
version: Some((
VersionConstraint::GreaterThanEqual,
minimum_version.parse().unwrap(),
)),
})
} else {
Self(Relation {
name: package.to_string(),
version: None,
})
}
}
pub fn simple(package: &str) -> Self {
Self(Relation {
name: package.to_string(),
version: None,
})
}
pub fn from_str(s: &str) -> Self {
if let Some((_, name, min_version)) = lazy_regex::regex_captures!("(.*) \\(>= (.*)\\)", s) {
Self::new(name, Some(min_version))
} else if !s.contains(" ") {
Self::simple(s)
} else {
panic!("Invalid R package dependency: {}", s);
}
}
}
impl Dependency for RPackageDependency {
fn family(&self) -> &'static str {
"r-package"
}
fn present(&self, _session: &dyn Session) -> bool {
todo!()
}
fn project_present(&self, _session: &dyn Session) -> bool {
todo!()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[cfg(feature = "debian")]
impl crate::dependencies::debian::IntoDebianDependency for RPackageDependency {
fn try_into_debian_dependency(
&self,
apt: &crate::debian::apt::AptManager,
) -> Option<Vec<super::debian::DebianDependency>> {
let names = apt
.get_packages_for_paths(
vec![std::path::Path::new("/usr/lib/R/site-library")
.join(&self.0.name)
.join("DESCRIPTION")
.to_str()
.unwrap()],
false,
false,
)
.unwrap();
if names.is_empty() {
return None;
}
Some(
names
.into_iter()
.map(|name| super::debian::DebianDependency::new(&name))
.collect(),
)
}
}
pub struct RResolver<'a> {
session: &'a dyn Session,
repos: String,
}
impl<'a> RResolver<'a> {
pub fn new(session: &'a dyn Session, repos: &str) -> Self {
Self {
session,
repos: repos.to_string(),
}
}
fn cmd(&self, req: &RPackageDependency) -> Vec<String> {
vec![
"R".to_string(),
"-e".to_string(),
format!("install.packages('{}', repos='{})'", req.0.name, self.repos),
]
}
}
impl<'a> Installer for RResolver<'a> {
fn install(&self, dep: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> {
let req = dep
.as_any()
.downcast_ref::<RPackageDependency>()
.ok_or(Error::UnknownDependencyFamily)?;
let args = self.cmd(req);
log::info!("RResolver({:?}): running {:?}", self.repos, args);
let mut cmd = self
.session
.command(args.iter().map(|x| x.as_str()).collect());
match scope {
InstallationScope::User => {}
InstallationScope::Global => {
cmd = cmd.user("root");
}
InstallationScope::Vendor => {
return Err(Error::UnsupportedScope(scope));
}
}
cmd.run_detecting_problems()?;
Ok(())
}
fn explain(
&self,
dep: &dyn Dependency,
_scope: InstallationScope,
) -> Result<Explanation, Error> {
if let Some(req) = dep.as_any().downcast_ref::<RPackageDependency>() {
Ok(Explanation {
message: format!("Install R package {}", req.0.name),
command: Some(self.cmd(req)),
})
} else {
Err(Error::UnknownDependencyFamily)
}
}
}
pub fn bioconductor(session: &dyn Session) -> RResolver<'_> {
RResolver::new(session, "https://hedgehog.fhcrc.org/bioconductor")
}
pub fn cran(session: &dyn Session) -> RResolver<'_> {
RResolver::new(session, "https://cran.r-project.org")
}
impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingRPackage {
fn to_dependency(&self) -> Option<Box<dyn Dependency>> {
Some(Box::new(RPackageDependency::simple(&self.package)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::buildlog::ToDependency;
#[test]
fn test_r_package_dependency_new() {
let dependency = RPackageDependency::new("dplyr", Some("1.0.0"));
assert_eq!(dependency.0.name, "dplyr");
assert!(dependency.0.version.is_some());
let (constraint, version) = dependency.0.version.unwrap();
assert_eq!(constraint, VersionConstraint::GreaterThanEqual);
assert_eq!(format!("{}", version), "1.0.0");
}
#[test]
fn test_r_package_dependency_simple() {
let dependency = RPackageDependency::simple("dplyr");
assert_eq!(dependency.0.name, "dplyr");
assert!(dependency.0.version.is_none());
}
#[test]
fn test_r_package_dependency_from_str() {
let dependency = RPackageDependency::from_str("dplyr (>= 1.0.0)");
assert_eq!(dependency.0.name, "dplyr");
assert!(dependency.0.version.is_some());
let (constraint, version) = dependency.0.version.unwrap();
assert_eq!(constraint, VersionConstraint::GreaterThanEqual);
assert_eq!(format!("{}", version), "1.0.0");
let dependency = RPackageDependency::from_str("dplyr");
assert_eq!(dependency.0.name, "dplyr");
assert!(dependency.0.version.is_none());
}
#[test]
fn test_r_package_dependency_family() {
let dependency = RPackageDependency::simple("dplyr");
assert_eq!(dependency.family(), "r-package");
}
#[test]
fn test_r_package_dependency_as_any() {
let dependency = RPackageDependency::simple("dplyr");
let any_dep = dependency.as_any();
assert!(any_dep.downcast_ref::<RPackageDependency>().is_some());
}
#[test]
fn test_missing_r_package_to_dependency() {
let problem = buildlog_consultant::problems::common::MissingRPackage {
package: "dplyr".to_string(),
minimum_version: None,
};
let dependency = problem.to_dependency();
assert!(dependency.is_some());
let dep = dependency.unwrap();
assert_eq!(dep.family(), "r-package");
let r_dep = dep.as_any().downcast_ref::<RPackageDependency>().unwrap();
assert_eq!(r_dep.0.name, "dplyr");
}
#[test]
fn test_r_resolver_new() {
let session = crate::session::plain::PlainSession::new();
let resolver = RResolver::new(&session, "https://cran.r-project.org");
assert_eq!(resolver.repos, "https://cran.r-project.org");
}
#[test]
fn test_r_resolver_cmd() {
let session = crate::session::plain::PlainSession::new();
let resolver = RResolver::new(&session, "https://cran.r-project.org");
let dependency = RPackageDependency::simple("dplyr");
let cmd = resolver.cmd(&dependency);
assert_eq!(
cmd,
vec![
"R",
"-e",
"install.packages('dplyr', repos='https://cran.r-project.org)'",
]
);
}
#[test]
fn test_bioconductor() {
let session = crate::session::plain::PlainSession::new();
let resolver = bioconductor(&session);
assert_eq!(resolver.repos, "https://hedgehog.fhcrc.org/bioconductor");
}
#[test]
fn test_cran() {
let session = crate::session::plain::PlainSession::new();
let resolver = cran(&session);
assert_eq!(resolver.repos, "https://cran.r-project.org");
}
}