1#[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 7
208 }
209 }
210 None => 7, }
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 !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 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}