cli/
version.rs

1//! # version
2//!
3//! Checks if the currently running version is the most up to date version and
4//! if not, it will print a notification message.
5//!
6
7#[cfg(test)]
8#[path = "version_test.rs"]
9mod version_test;
10
11use crate::cache;
12use crate::command;
13use crate::types::{Cache, CliArgs, GlobalConfig};
14use semver::Version;
15use std::process::Command;
16use std::time::{SystemTime, UNIX_EPOCH};
17
18static VERSION: &str = env!("CARGO_PKG_VERSION");
19
20fn get_version_from_output(line: &str) -> Option<String> {
21    let parts: Vec<&str> = line.split(' ').collect();
22
23    if parts.len() >= 3 {
24        let version_part = parts[2];
25        let version = str::replace(version_part, "\"", "");
26
27        Some(version)
28    } else {
29        None
30    }
31}
32
33fn get_latest_version() -> Option<String> {
34    let result = Command::new("cargo")
35        .arg("search")
36        .arg("cargo-make")
37        .arg("--limit=1")
38        .output();
39
40    match result {
41        Ok(output) => {
42            let exit_code = command::get_exit_code(Ok(output.status), false);
43            if exit_code == 0 {
44                let stdout = String::from_utf8_lossy(&output.stdout);
45                let lines: Vec<&str> = stdout.split('\n').collect();
46
47                let mut output = None;
48                for mut line in lines {
49                    line = line.trim();
50
51                    debug!("Checking: {}", &line);
52
53                    if line.starts_with("cargo-make = ") {
54                        output = get_version_from_output(line);
55
56                        break;
57                    }
58                }
59
60                output
61            } else {
62                None
63            }
64        }
65        _ => None,
66    }
67}
68
69fn parse(version_string: &str, allow_partial_version_string: bool) -> Result<Version, ()> {
70    match Version::parse(version_string) {
71        Ok(version) => Ok(version),
72        Err(_) => {
73            if allow_partial_version_string {
74                match lenient_semver::parse(version_string) {
75                    Ok(version) => Ok(version),
76                    Err(_) => Err(()),
77                }
78            } else {
79                Err(())
80            }
81        }
82    }
83}
84
85pub(crate) fn is_same(
86    version_string1: &str,
87    version_string2: &str,
88    allow_partial_version_string: bool,
89    default_result: bool,
90) -> bool {
91    let version1 = parse(version_string1, allow_partial_version_string);
92    match version1 {
93        Ok(values1) => {
94            let version2 = parse(version_string2, allow_partial_version_string);
95
96            match version2 {
97                Ok(values2) => {
98                    values1.major == values2.major
99                        && values1.minor == values2.minor
100                        && values1.patch == values2.patch
101                }
102                _ => default_result,
103            }
104        }
105        _ => default_result,
106    }
107}
108
109pub(crate) fn is_newer(
110    old_string: &str,
111    new_string: &str,
112    allow_partial_version_string: bool,
113    default_result: bool,
114) -> bool {
115    let old_version = parse(old_string, allow_partial_version_string);
116    match old_version {
117        Ok(old_values) => {
118            let new_version = parse(new_string, allow_partial_version_string);
119
120            match new_version {
121                Ok(new_values) => {
122                    if new_values.major > old_values.major {
123                        true
124                    } else if new_values.major == old_values.major {
125                        if new_values.minor > old_values.minor {
126                            true
127                        } else {
128                            new_values.minor == old_values.minor
129                                && new_values.patch > old_values.patch
130                        }
131                    } else {
132                        false
133                    }
134                }
135                _ => default_result,
136            }
137        }
138        _ => default_result,
139    }
140}
141
142pub(crate) fn is_newer_found(version_string: &str) -> bool {
143    debug!("Checking Version: {}", &version_string);
144
145    is_newer(&VERSION, &version_string, false, false)
146}
147
148fn print_notification(latest_string: &str) {
149    warn!("#####################################################################");
150    warn!("#                                                                   #");
151    warn!("#                                                                   #");
152    warn!("#                  NEW CARGO-MAKE VERSION FOUND!!!                  #");
153    warn!(
154        "#{:^67}#",
155        format!("Current: {}, Latest: {}", VERSION, latest_string)
156    );
157    warn!("#    Run 'cargo install --force cargo-make' to get latest version   #");
158    warn!("#                                                                   #");
159    warn!("#                                                                   #");
160    warn!("#####################################################################");
161}
162
163fn get_now_as_seconds() -> u64 {
164    let now = SystemTime::now();
165    match now.duration_since(UNIX_EPOCH) {
166        Ok(duration) => duration.as_secs(),
167        _ => 0,
168    }
169}
170
171fn has_amount_of_days_passed_from_last_check(days: u64, last_check_seconds: u64) -> bool {
172    let now_seconds = get_now_as_seconds();
173    if now_seconds > 0 && days > 0 {
174        if last_check_seconds > now_seconds {
175            false
176        } else {
177            let diff_seconds = now_seconds - last_check_seconds;
178            let minimum_diff_seconds = days * 24 * 60 * 60;
179
180            diff_seconds >= minimum_diff_seconds
181        }
182    } else {
183        true
184    }
185}
186
187fn has_amount_of_days_passed(days: u64, cache_data: &Cache) -> bool {
188    match cache_data.last_update_check {
189        Some(last_check_seconds) => {
190            has_amount_of_days_passed_from_last_check(days, last_check_seconds)
191        }
192        None => true,
193    }
194}
195
196fn get_days(global_config: &GlobalConfig) -> u64 {
197    match global_config.update_check_minimum_interval {
198        Some(ref value) => {
199            if value == "always" {
200                0
201            } else if value == "daily" {
202                1
203            } else if value == "monthly" {
204                30
205            } else {
206                // default to weekly
207                7
208            }
209        }
210        None => 7, // default to weekly
211    }
212}
213
214fn should_check_overdue(global_config: &GlobalConfig) -> bool {
215    let days = get_days(global_config);
216
217    if days > 0 {
218        let cache_data = cache::load();
219        has_amount_of_days_passed(days, &cache_data)
220    } else {
221        true
222    }
223}
224
225fn should_check_for_args(cli_args: &CliArgs, global_config: &GlobalConfig, is_ci: bool) -> bool {
226    // only run check for updates if we are not in a CI env and user didn't ask to skip the check
227    !cli_args.disable_check_for_updates
228        && !is_ci
229        && !envmnt::is_or("CARGO_MAKE_DISABLE_UPDATE_CHECK", false)
230        && should_check_overdue(&global_config)
231}
232
233pub(crate) fn should_check(cli_args: &CliArgs, global_config: &GlobalConfig) -> bool {
234    // only run check for updates if we are not in a CI env and user didn't ask to skip the check
235    should_check_for_args(cli_args, global_config, ci_info::is_ci())
236}
237
238pub(crate) fn check() {
239    let latest = get_latest_version();
240
241    let mut cache_data = cache::load();
242    let now = get_now_as_seconds();
243    if now > 0 {
244        cache_data.last_update_check = Some(now);
245        cache::store(&cache_data);
246    }
247
248    match latest {
249        Some(value) => {
250            if is_newer_found(&value) {
251                print_notification(&value);
252            }
253        }
254        None => (),
255    }
256}