xtask/commands/
r_cmd_check.rs

1use std::error::Error;
2use std::path::{Path, PathBuf};
3
4use xshell::Shell;
5
6use crate::extendrtests::path_helper::RCompatiblePath;
7use crate::extendrtests::with_absolute_path::{swap_extendr_api_path, R_FOLDER_PATH};
8
9#[derive(Debug, Clone, Copy, Eq, PartialEq)]
10pub(crate) enum RCmdCheckErrorOn {
11    Never,
12    Note,
13    Warning,
14    Error,
15}
16
17impl RCmdCheckErrorOn {
18    fn get_error_on(&self) -> &'static str {
19        match self {
20            RCmdCheckErrorOn::Never => "'never'",
21            RCmdCheckErrorOn::Note => "'note'",
22            RCmdCheckErrorOn::Warning => "'warning'",
23            RCmdCheckErrorOn::Error => "'error'",
24        }
25    }
26}
27
28pub(crate) fn run<P: AsRef<Path>>(
29    shell: &Shell,
30    no_build_vignettes: bool,
31    error_on: RCmdCheckErrorOn,
32    check_dir: Option<String>,
33    initial_path: P,
34) -> Result<(), Box<dyn Error>> {
35    let check_dir = match check_dir {
36        Some(cd) => Some(construct_check_dir_path(cd, initial_path)?),
37        _ => None,
38    };
39
40    let _document_handle = swap_extendr_api_path(shell)?;
41
42    run_r_cmd_check(shell, no_build_vignettes, error_on, check_dir)
43}
44
45fn construct_check_dir_path<S: AsRef<str>, P: AsRef<Path>>(
46    check_dir: S,
47    initial_path: P,
48) -> Result<String, Box<dyn Error>> {
49    let mut path = PathBuf::from(check_dir.as_ref());
50    if !path.is_absolute() {
51        let str_rep = path.to_string_lossy();
52        if str_rep.starts_with("./") {
53            path = PathBuf::from(str_rep.trim_start_matches("./"));
54        } else if str_rep.starts_with(r".\\") {
55            path = PathBuf::from(str_rep.trim_start_matches(r".\\"));
56        }
57        path = initial_path.as_ref().canonicalize()?.join(path);
58    }
59    Ok(path.adjust_for_r())
60}
61
62#[derive(Debug)]
63enum RCmdCheckError {
64    MissingRPackages,
65}
66impl std::fmt::Display for RCmdCheckError {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        match self {
69            RCmdCheckError::MissingRPackages => {
70                write!(f, "Missing required R-packages, please install them.")
71            }
72        }
73    }
74}
75impl Error for RCmdCheckError {}
76
77fn run_r_cmd_check(
78    shell: &Shell,
79    no_build_vignettes: bool,
80    error_on: RCmdCheckErrorOn,
81    check_dir: Option<String>,
82) -> Result<(), Box<dyn Error>> {
83    let _r_path = shell.push_dir(R_FOLDER_PATH);
84    let mut args = vec!["'--as-cran'", "'--no-manual'"];
85    if no_build_vignettes {
86        args.push("'--no-build-vignettes'");
87    }
88
89    let args = format!("c({0})", args.join(", "));
90
91    let error_on = error_on.get_error_on();
92
93    let check_dir = match check_dir {
94        Some(cd) => format!("'{}'", cd),
95        _ => "NULL".to_string(),
96    };
97
98    let has_prerequisites = shell
99        .cmd("Rscript")
100        .args([
101            "-e",
102            r#"requireNamespace("devtools");
103            requireNamespace("rcmdcheck");
104            requireNamespace("patrick");
105            requireNamespace("lobstr");
106            requireNamespace("rextendr")"#,
107        ])
108        .run()
109        .is_ok();
110
111    if !has_prerequisites {
112        println!(
113            r#"R installation is missing necessary packages.
114RScript -e 'options(repos = list(CRAN="http://cran.rstudio.com/"))'
115        -e 'install.packages("devtools")'
116        -e 'install.packages("rcmdcheck")'
117        -e 'install.packages("patrick")'
118        -e 'install.packages("lobstr")'
119        -e 'install.packages("rextendr")'
120
121Alternatively, install development version on rextendr
122Rscript -e 'options(repos = list(CRAN="http://cran.rstudio.com/"))'
123        -e 'remotes::install_github("extendr/rextendr")'"#
124        );
125        return Err(RCmdCheckError::MissingRPackages.into());
126    }
127
128    shell
129        .cmd("Rscript")
130        .arg("-e")
131        .arg(format!(
132            "rcmdcheck::rcmdcheck(args = {args}, error_on = {error_on}, check_dir = {check_dir})"
133        ))
134        .run()?;
135
136    Ok(())
137}