cli/installer/
cargo_plugin_installer.rs

1//! # cargo_plugin_installer
2//!
3//! Installs cargo plugins automatically if needed.
4//!
5
6#[cfg(test)]
7#[path = "cargo_plugin_installer_test.rs"]
8mod cargo_plugin_installer_test;
9
10use crate::command;
11use crate::error::CargoMakeError;
12use crate::installer::crate_version_check;
13use crate::toolchain::wrap_command;
14use crate::types::ToolchainSpecifier;
15use std::process::Command;
16use strip_ansi_escapes::strip_str;
17
18fn is_crate_in_list_output_legacy(crate_name: &str, output: &str) -> bool {
19    let lines: Vec<&str> = output.split(' ').collect();
20    for mut line in lines {
21        line = line.trim();
22
23        debug!("Checking (legacy): {}", &line);
24
25        if line.contains(crate_name) && crate_name.contains(line) {
26            debug!("Found installed cratei (legacy).");
27
28            return true;
29        }
30    }
31
32    false
33}
34
35fn is_crate_in_list_output(crate_name: &str, output: &str) -> bool {
36    let lines: Vec<&str> = output.split('\n').collect();
37    for mut line in lines {
38        line = line.trim();
39
40        let words: Vec<&str> = line.split(' ').collect();
41        let plugin_name = words[0].trim();
42        let found = crate_name.eq(plugin_name);
43        debug!(
44            "Checking Line: {}\nPlugin: <{}> Expected: <{}> Sizes: {}/{} Found: {}",
45            &line,
46            &plugin_name,
47            &crate_name,
48            plugin_name.len(),
49            crate_name.len(),
50            found
51        );
52
53        if found {
54            debug!("Found installed crate.");
55
56            return true;
57        }
58    }
59
60    false
61}
62
63fn is_crate_installed(
64    toolchain: &Option<ToolchainSpecifier>,
65    crate_name: &str,
66) -> Result<bool, CargoMakeError> {
67    debug!("Getting list of installed cargo commands.");
68
69    let mut command_struct = match toolchain {
70        Some(ref toolchain_string) => {
71            let command_spec = wrap_command(toolchain_string, "cargo", &None);
72            let mut cmd = Command::new(command_spec.command);
73            cmd.args(command_spec.args.unwrap());
74
75            cmd
76        }
77        None => Command::new("cargo"),
78    };
79
80    let result = command_struct.arg("--list").output();
81
82    match result {
83        Ok(output) => {
84            let exit_code = command::get_exit_code(Ok(output.status), false);
85            command::validate_exit_code(exit_code)?;
86
87            let stdout = strip_str(String::from_utf8_lossy(&output.stdout));
88            let crate_name_trimmed = crate_name.trim();
89            Ok(is_crate_in_list_output(&crate_name_trimmed, &stdout)
90                || is_crate_in_list_output_legacy(&crate_name_trimmed, &stdout))
91        }
92        Err(error) => {
93            error!(
94                "Unable to check if crate is installed: {} {:#?}",
95                crate_name, &error
96            );
97            Ok(false)
98        }
99    }
100}
101
102fn should_skip_crate_name(args: &Option<Vec<String>>) -> bool {
103    match *args {
104        Some(ref args_vec) => args_vec.contains(&"--git".to_string()),
105        None => false,
106    }
107}
108
109pub(crate) fn get_install_crate_args(
110    crate_name: &str,
111    force: bool,
112    args: &Option<Vec<String>>,
113    version_option: &Option<String>,
114    install_command: &Option<String>,
115) -> Vec<String> {
116    let install_command_str = match install_command {
117        Some(value) => value.clone(),
118        None => "install".to_string(),
119    };
120    let mut install_args = vec![install_command_str.to_string()];
121
122    if force {
123        install_args.push("--force".to_string());
124    }
125
126    match *args {
127        Some(ref args_vec) => {
128            for arg in args_vec.iter() {
129                install_args.push(arg.to_string());
130            }
131        }
132        None => debug!("No crate installation args defined."),
133    };
134
135    let skip_crate_name = should_skip_crate_name(&args);
136    if !skip_crate_name {
137        // add locked flags
138        if let Some(version) = version_option {
139            if envmnt::is("CARGO_MAKE_CRATE_INSTALLATION_LOCKED") {
140                install_args.push("--locked".to_string());
141                install_args.push("--version".to_string());
142                install_args.push(version.to_string());
143            }
144        }
145
146        install_args.push(crate_name.to_string());
147    }
148
149    install_args
150}
151
152pub(crate) fn install_crate(
153    toolchain: &Option<ToolchainSpecifier>,
154    cargo_command: Option<&str>,
155    crate_name: &str,
156    args: &Option<Vec<String>>,
157    validate: bool,
158    min_version: &Option<String>,
159    install_command: &Option<String>,
160    allow_force: &Option<bool>,
161) -> Result<(), CargoMakeError> {
162    let installed = match cargo_command {
163        Some(cargo_command) => is_crate_installed(&toolchain, cargo_command),
164        None => Ok(false),
165    }?;
166    let mut force = false;
167    let allow_force_value = allow_force.unwrap_or(true);
168    let run_installation = if !installed {
169        true
170    } else if toolchain.is_none() {
171        match *min_version {
172            Some(ref version) => {
173                if crate_version_check::is_min_version_valid(&crate_name, version, None) {
174                    false
175                } else {
176                    force = allow_force_value;
177                    true
178                }
179            }
180            None => false,
181        }
182    } else {
183        false
184    };
185
186    if run_installation {
187        let install_args =
188            get_install_crate_args(crate_name, force, args, &min_version, install_command);
189
190        match toolchain {
191            Some(ref toolchain_string) => {
192                let command_spec = wrap_command(&toolchain_string, "cargo", &Some(install_args));
193                command::run_command(&command_spec.command, &command_spec.args, validate)?
194            }
195            None => command::run_command("cargo", &Some(install_args), validate)?,
196        };
197    }
198    Ok(())
199}