1#[cfg_attr(test, double)]
6use crate::env::Environment;
7use crate::vars::{
8 ENV_CARGO_BUILD_DEP_INFO_BASEDIR, ENV_CARGO_BUILD_JOBS, ENV_CARGO_BUILD_PIPELINING,
9 ENV_CARGO_BUILD_TARGET, ENV_CARGO_CACHE_RUSTC_INFO, ENV_CARGO_HOME, ENV_CARGO_HTTP_CAINFO,
10 ENV_CARGO_HTTP_CHECK_REVOKE, ENV_CARGO_HTTP_DEBUG, ENV_CARGO_HTTP_LOW_SPEED_LIMIT,
11 ENV_CARGO_HTTP_MULTIPLEXING, ENV_CARGO_HTTP_SSL_VERSION, ENV_CARGO_HTTP_USER_AGENT,
12 ENV_CARGO_INCREMENTAL, ENV_CARGO_NET_GIT_FETCH_WITH_CLI, ENV_CARGO_NET_OFFLINE,
13 ENV_CARGO_NET_RETRY, ENV_CARGO_TARGET_DIR, ENV_CARGO_TERM_COLOR, ENV_CARGO_TERM_VERBOSE,
14 ENV_HTTPS_PROXY, ENV_HTTP_TIMEOUT, ENV_RUSTC, ENV_RUSTC_WRAPPER, ENV_RUSTDOC, ENV_RUSTDOCFLAGS,
15 ENV_RUSTFLAGS, ENV_TERM,
16};
17#[cfg(test)]
18use mockall_double::double;
19use std::{
20 collections::HashMap,
21 ffi::OsStr,
22 path::{Path, PathBuf},
23 process::Command,
24 time::Duration,
25};
26use url::Url;
27
28fn str_env(command: &mut Command, clean_env: bool, value: Option<&impl AsRef<OsStr>>, env: &str) {
31 if clean_env || value.is_some() {
32 command.env_remove(env);
33 }
34
35 if let Some(v) = value {
36 command.env(env, v);
37 }
38}
39
40fn strv_env(command: &mut Command, clean_env: bool, values: &[String], env: &str, sep: &str) {
43 if clean_env || !values.is_empty() {
44 command.env_remove(env);
45 }
46
47 if values.is_empty() {
48 str_env(command, clean_env, None as Option<&String>, env);
49 } else {
50 let v = values.join(sep);
51 str_env(command, clean_env, Some(&v), env);
52 }
53}
54
55fn onezero_env(command: &mut Command, clean_env: bool, value: Option<bool>, env: &str) {
58 if clean_env || value.is_some() {
59 command.env_remove(env);
60 }
61
62 if let Some(val) = value {
63 let v = val as u8;
64 str_env(command, clean_env, Some(&v.to_string()), env);
65 }
66}
67
68fn duration_env(command: &mut Command, clean_env: bool, value: Option<&Duration>, env: &str) {
71 if clean_env || value.is_some() {
72 command.env_remove(env);
73 }
74
75 if let Some(val) = value {
76 str_env(command, clean_env, Some(&val.as_secs().to_string()), env);
77 }
78}
79
80fn u64_env(command: &mut Command, clean_env: bool, value: Option<&u64>, env: &str) {
83 if clean_env || value.is_some() {
84 command.env_remove(env);
85 }
86
87 if let Some(val) = value {
88 str_env(command, clean_env, Some(&val.to_string()), env);
89 }
90}
91
92#[derive(Clone, Debug)]
94pub struct CargoBuilder {
95 working_dir: PathBuf,
96 clean_env: bool,
97 cargo_path: PathBuf,
98
99 home: Option<PathBuf>,
100 target_dir: Option<PathBuf>,
101 rustc: Option<PathBuf>,
102 rustc_wrapper: Option<PathBuf>,
103 rustdoc: Option<PathBuf>,
104 rustdocflags: Vec<String>,
105 rustflags: Vec<String>,
106 incremental: Option<bool>,
107 cache_rustc_info: Option<bool>,
108 term: Option<String>,
109
110 build_jobs: Option<u64>,
111 target: Option<String>,
112 dep_info_basedir: Option<PathBuf>,
113 pipelining: Option<bool>,
114
115 http_debug: Option<bool>,
116 http_proxy: Option<String>,
117 http_timeout: Option<Duration>,
118 http_cainfo: Option<PathBuf>,
119 http_check_revoke: Option<bool>,
120 http_ssl_version: Option<String>,
121 http_low_speed_limit: Option<u64>,
122 http_multiplexing: Option<bool>,
123 http_user_agent: Option<String>,
124 net_retry: Option<u64>,
125 net_git_fetch_with_cli: Option<bool>,
126 net_offline: Option<bool>,
127 registries: HashMap<String, Url>,
128 term_verbose: Option<bool>,
129 term_color: Option<bool>,
130
131 profile: String,
132 locked: bool,
133}
134
135impl CargoBuilder {
136 pub fn new(env: &Environment, working_dir: &Path, clean_env: bool) -> Self {
141 let cargo_path = env.cargo().to_owned();
142 let profile = env.profile().to_owned();
143 Self {
144 working_dir: working_dir.to_owned(),
145 clean_env,
146 cargo_path,
147 home: None,
148 target_dir: None,
149 rustc: None,
150 rustc_wrapper: None,
151 rustdoc: None,
152 rustdocflags: Vec::default(),
153 rustflags: Vec::default(),
154 incremental: None,
155 cache_rustc_info: None,
156 term: None,
157 build_jobs: None,
158 target: None,
159 dep_info_basedir: None,
160 pipelining: None,
161 http_debug: None,
162 http_proxy: None,
163 http_timeout: None,
164 http_cainfo: None,
165 http_check_revoke: None,
166 http_ssl_version: None,
167 http_low_speed_limit: None,
168 http_multiplexing: None,
169 http_user_agent: None,
170 net_retry: None,
171 net_git_fetch_with_cli: None,
172 net_offline: None,
173 registries: HashMap::default(),
174 term_verbose: None,
175 term_color: None,
176 profile,
177 locked: env.locked(),
178 }
179 }
180
181 pub fn construct(&mut self) -> Command {
183 let mut command = Command::new(&self.cargo_path);
184
185 command.current_dir(&self.working_dir);
186
187 str_env(
190 &mut command,
191 self.clean_env,
192 self.home.as_ref(),
193 ENV_CARGO_HOME,
194 );
195 str_env(
196 &mut command,
197 self.clean_env,
198 self.target_dir.as_ref(),
199 ENV_CARGO_TARGET_DIR,
200 );
201 str_env(&mut command, self.clean_env, self.rustc.as_ref(), ENV_RUSTC);
202 str_env(
203 &mut command,
204 self.clean_env,
205 self.rustc_wrapper.as_ref(),
206 ENV_RUSTC_WRAPPER,
207 );
208 str_env(
209 &mut command,
210 self.clean_env,
211 self.rustdoc.as_ref(),
212 ENV_RUSTDOC,
213 );
214 strv_env(
215 &mut command,
216 self.clean_env,
217 &self.rustdocflags,
218 ENV_RUSTDOCFLAGS,
219 " ",
220 );
221 strv_env(
222 &mut command,
223 self.clean_env,
224 &self.rustflags,
225 ENV_RUSTFLAGS,
226 " ",
227 );
228 onezero_env(
229 &mut command,
230 self.clean_env,
231 self.incremental,
232 ENV_CARGO_INCREMENTAL,
233 );
234 onezero_env(
235 &mut command,
236 self.clean_env,
237 self.cache_rustc_info,
238 ENV_CARGO_CACHE_RUSTC_INFO,
239 );
240
241 str_env(
242 &mut command,
243 self.clean_env,
244 self.http_proxy.as_ref(),
245 ENV_HTTPS_PROXY,
246 );
247 duration_env(
248 &mut command,
249 self.clean_env,
250 self.http_timeout.as_ref(),
251 ENV_HTTP_TIMEOUT,
252 );
253 str_env(&mut command, self.clean_env, self.term.as_ref(), ENV_TERM);
254
255 u64_env(
258 &mut command,
259 self.clean_env,
260 self.build_jobs.as_ref(),
261 ENV_CARGO_BUILD_JOBS,
262 );
263 str_env(
264 &mut command,
265 self.clean_env,
266 self.target.as_ref(),
267 ENV_CARGO_BUILD_TARGET,
268 );
269 str_env(
270 &mut command,
271 self.clean_env,
272 self.dep_info_basedir.as_ref(),
273 ENV_CARGO_BUILD_DEP_INFO_BASEDIR,
274 );
275 onezero_env(
276 &mut command,
277 self.clean_env,
278 self.pipelining,
279 ENV_CARGO_BUILD_PIPELINING,
280 );
281
282 onezero_env(
283 &mut command,
284 self.clean_env,
285 self.http_debug,
286 ENV_CARGO_HTTP_DEBUG,
287 );
288 str_env(
289 &mut command,
290 self.clean_env,
291 self.http_cainfo.as_ref(),
292 ENV_CARGO_HTTP_CAINFO,
293 );
294 onezero_env(
295 &mut command,
296 self.clean_env,
297 self.http_check_revoke,
298 ENV_CARGO_HTTP_CHECK_REVOKE,
299 );
300 str_env(
301 &mut command,
302 self.clean_env,
303 self.http_ssl_version.as_ref(),
304 ENV_CARGO_HTTP_SSL_VERSION,
305 );
306 u64_env(
307 &mut command,
308 self.clean_env,
309 self.http_low_speed_limit.as_ref(),
310 ENV_CARGO_HTTP_LOW_SPEED_LIMIT,
311 );
312 onezero_env(
313 &mut command,
314 self.clean_env,
315 self.http_multiplexing,
316 ENV_CARGO_HTTP_MULTIPLEXING,
317 );
318 str_env(
319 &mut command,
320 self.clean_env,
321 self.http_user_agent.as_ref(),
322 ENV_CARGO_HTTP_USER_AGENT,
323 );
324 u64_env(
325 &mut command,
326 self.clean_env,
327 self.net_retry.as_ref(),
328 ENV_CARGO_NET_RETRY,
329 );
330 onezero_env(
331 &mut command,
332 self.clean_env,
333 self.net_git_fetch_with_cli,
334 ENV_CARGO_NET_GIT_FETCH_WITH_CLI,
335 );
336 onezero_env(
337 &mut command,
338 self.clean_env,
339 self.net_offline,
340 ENV_CARGO_NET_OFFLINE,
341 );
342
343 for (name, index) in self.registries.iter() {
345 str_env(
346 &mut command,
347 self.clean_env,
348 Some(&index.as_str()),
349 &format!(
350 "CARGO_REGISTRY_{}_INDEX",
351 name.to_ascii_uppercase().replace('-', "_")
352 ),
353 );
354 }
355
356 onezero_env(
357 &mut command,
358 self.clean_env,
359 self.term_verbose,
360 ENV_CARGO_TERM_VERBOSE,
361 );
362 onezero_env(
363 &mut command,
364 self.clean_env,
365 self.term_color,
366 ENV_CARGO_TERM_COLOR,
367 );
368
369 command.arg("build").arg("-vv");
370
371 if self.profile == "release" {
372 command.arg("--release");
373 }
374
375 if self.locked {
376 command.arg("--locked");
377 }
378
379 command
380 }
381
382 pub fn cargo_path(&mut self, cargo: &Path) -> &mut Self {
384 self.cargo_path = cargo.to_owned();
385 self
386 }
387
388 pub fn home(&mut self, home: &Path) -> &mut Self {
390 self.home = Some(home.to_owned());
391 self
392 }
393
394 pub fn target_dir(&mut self, target_dir: &Path) -> &mut Self {
396 self.target_dir = Some(target_dir.to_owned());
397 self
398 }
399
400 pub fn rustc(&mut self, rustc: &Path) -> &mut Self {
402 self.rustc = Some(rustc.to_owned());
403 self
404 }
405
406 pub fn rustc_wrapper(&mut self, rustc_wrapper: &Path) -> &mut Self {
408 self.rustc_wrapper = Some(rustc_wrapper.to_owned());
409 self
410 }
411
412 pub fn rustdoc(&mut self, rustdoc: &Path) -> &mut Self {
414 self.rustdoc = Some(rustdoc.to_owned());
415 self
416 }
417
418 pub fn add_rustdoc_flag(&mut self, rustdoc_flag: &str) -> &mut Self {
420 self.rustdocflags.push(rustdoc_flag.to_owned());
421 self
422 }
423
424 pub fn add_rustdoc_flags(&mut self, rustdoc_flags: &[&str]) -> &mut Self {
426 for flag in rustdoc_flags {
427 self.rustdocflags.push((*flag).to_owned());
428 }
429 self
430 }
431
432 pub fn add_rust_flag(&mut self, rust_flag: &str) -> &mut Self {
434 self.rustflags.push(rust_flag.to_owned());
435 self
436 }
437
438 pub fn add_rust_flags(&mut self, rust_flags: &[&str]) -> &mut Self {
440 for flag in rust_flags {
441 self.rustflags.push((*flag).to_owned());
442 }
443 self
444 }
445
446 pub fn incremental(&mut self, incremental: bool) -> &mut Self {
448 self.incremental = Some(incremental);
449 self
450 }
451
452 pub fn cache_rustc_info(&mut self, cache_rustc_info: bool) -> &mut Self {
454 self.cache_rustc_info = Some(cache_rustc_info);
455 self
456 }
457
458 pub fn term(&mut self, term: &str) -> &mut Self {
460 self.term = Some(term.to_owned());
461 self
462 }
463
464 pub fn build_jobs(&mut self, build_jobs: u64) -> &mut Self {
466 self.build_jobs = Some(build_jobs);
467 self
468 }
469
470 pub fn target(&mut self, target: &str) -> &mut Self {
472 self.target = Some(target.to_owned());
473 self
474 }
475
476 pub fn dep_info_basedir(&mut self, dep_info_basedir: &Path) -> &mut Self {
478 self.dep_info_basedir = Some(dep_info_basedir.to_owned());
479 self
480 }
481
482 pub fn pipelining(&mut self, pipelining: bool) -> &mut Self {
484 self.pipelining = Some(pipelining);
485 self
486 }
487
488 pub fn http_debug(&mut self, http_debug: bool) -> &mut Self {
490 self.http_debug = Some(http_debug);
491 self
492 }
493
494 pub fn http_proxy(&mut self, http_proxy: &str) -> &mut Self {
496 self.http_proxy = Some(http_proxy.to_owned());
497 self
498 }
499
500 pub fn http_timeout(&mut self, http_timeout: Duration) -> &mut Self {
502 self.http_timeout = Some(http_timeout);
503 self
504 }
505
506 pub fn http_cainfo(&mut self, http_cainfo: &Path) -> &mut Self {
508 self.http_cainfo = Some(http_cainfo.to_owned());
509 self
510 }
511
512 pub fn http_check_revoke(&mut self, http_check_revoke: bool) -> &mut Self {
514 self.http_check_revoke = Some(http_check_revoke);
515 self
516 }
517
518 pub fn http_ssl_version(&mut self, http_ssl_version: &str) -> &mut Self {
520 self.http_ssl_version = Some(http_ssl_version.to_owned());
521 self
522 }
523
524 pub fn http_low_speed_limit(&mut self, http_low_speed_limit: u64) -> &mut Self {
526 self.http_low_speed_limit = Some(http_low_speed_limit);
527 self
528 }
529
530 pub fn http_multiplexing(&mut self, http_multiplexing: bool) -> &mut Self {
532 self.http_multiplexing = Some(http_multiplexing);
533 self
534 }
535
536 pub fn http_user_agent(&mut self, http_user_agent: String) -> &mut Self {
538 self.http_user_agent = Some(http_user_agent);
539 self
540 }
541
542 pub fn net_retry(&mut self, net_retry: u64) -> &mut Self {
544 self.net_retry = Some(net_retry);
545 self
546 }
547
548 pub fn net_git_fetch_with_cli(&mut self, net_git_fetch_with_cli: bool) -> &mut Self {
550 self.net_git_fetch_with_cli = Some(net_git_fetch_with_cli);
551 self
552 }
553
554 pub fn net_offline(&mut self, net_offline: bool) -> &mut Self {
556 self.net_offline = Some(net_offline);
557 self
558 }
559
560 pub fn add_registry(&mut self, name: String, index: Url) -> &mut Self {
562 self.registries.insert(name, index);
563 self
564 }
565
566 pub fn term_verbose(&mut self, term_verbose: bool) -> &mut Self {
568 self.term_verbose = Some(term_verbose);
569 self
570 }
571
572 pub fn term_color(&mut self, term_color: bool) -> &mut Self {
574 self.term_color = Some(term_color);
575 self
576 }
577
578 pub fn profile(&mut self, profile: String) -> &mut Self {
580 self.profile = profile;
581 self
582 }
583
584 pub fn locked(&mut self, locked: bool) -> &mut Self {
586 self.locked = locked;
587 self
588 }
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594 use std::str::FromStr;
595
596 fn init_mock_env(cargo_path: &str, profile: &str, cargo_locked: bool) -> Environment {
597 let mut mock = Environment::default();
598
599 mock.expect_cargo()
600 .return_const(PathBuf::from_str(cargo_path).expect("Fail"));
601 mock.expect_profile().return_const(profile.to_string());
602 mock.expect_locked().return_const(cargo_locked);
603 mock
604 }
605
606 #[test]
607 fn cargo_builder() {
608 const EXPECTED_OUTPUT_DIR: &str = "/path_to_output_directory";
609 const EXPECTED_CARGO_PATH: &str = "/path_to_cargo";
610 const EXPECTED_PROFILE: &str = "debug";
611 const EXPECTED_CARGO_LOCKED: bool = true;
612
613 let mock = init_mock_env(EXPECTED_CARGO_PATH, EXPECTED_PROFILE, EXPECTED_CARGO_LOCKED);
614 let cargo_builder = CargoBuilder::new(&mock, &Path::new(EXPECTED_OUTPUT_DIR), false);
615 let actual_dir = cargo_builder.working_dir.to_str().expect("Fail");
616 let actual_cargo_path = cargo_builder.cargo_path.to_str().expect("Fail");
617
618 assert_eq!(actual_dir, EXPECTED_OUTPUT_DIR);
619 assert_eq!(actual_cargo_path, EXPECTED_CARGO_PATH);
620 assert_eq!(cargo_builder.profile, EXPECTED_PROFILE);
621 assert_eq!(cargo_builder.locked, true);
622 }
623
624 #[test]
625 fn construct_cargo_command() {
626 let mock = init_mock_env("/path_to_cargo", "debug", true);
627 let mut cargo_builder =
628 CargoBuilder::new(&mock, &Path::new("/path_to_output_directory"), false);
629 let cmd = cargo_builder.construct();
630 let actual_cmd = format!("{:?}", cmd);
631 let expected_cmd =
632 "cd \"/path_to_output_directory\" && \"/path_to_cargo\" \"build\" \"-vv\" \"--locked\"";
633
634 assert_eq!(actual_cmd, expected_cmd);
635 }
636
637 #[test]
638 fn change_cargo_path() {
639 let expected_cargo_path = "/path_to_cargo";
640 let expected_changed_cargo_path = "/changed/path_to_cargo";
641 let mock = init_mock_env(expected_cargo_path, "debug", true);
642 let mut cargo_builder =
643 CargoBuilder::new(&mock, &Path::new("/path_to_output_directory"), false);
644 let mut actual_cargo_path = cargo_builder.cargo_path.to_str().expect("Fail");
645
646 assert_eq!(actual_cargo_path, expected_cargo_path);
647
648 cargo_builder.cargo_path(Path::new(expected_changed_cargo_path));
649 actual_cargo_path = cargo_builder.cargo_path.to_str().expect("Fail");
650
651 assert_eq!(actual_cargo_path, expected_changed_cargo_path);
652 }
653
654 #[test]
655 fn change_target() {
656 let expected_target = "x86_64-unknown-linux-gnu";
657 let mock = init_mock_env("/path_to_cargo", "debug", true);
658 let mut cargo_builder =
659 CargoBuilder::new(&mock, &Path::new("/path_to_output_directory"), false);
660 let mut actual_target = cargo_builder.target.as_ref();
661
662 assert_eq!(actual_target, None);
663
664 cargo_builder.target(expected_target);
665 actual_target = cargo_builder.target.as_ref();
666
667 assert_eq!(actual_target.expect("Fail"), expected_target);
668 }
669
670 #[test]
671 fn change_rust_flags() {
672 let expected_rust_flags = ["-D", "warnings", "-C"];
673 let mock = init_mock_env("/path_to_cargo", "debug", true);
674 let mut cargo_builder =
675 CargoBuilder::new(&mock, &Path::new("/path_to_output_directory"), false);
676 let mut rust_flags: &Vec<String> = cargo_builder.rustflags.as_ref();
677
678 assert_eq!(rust_flags.is_empty(), true);
679
680 cargo_builder.add_rust_flags(&expected_rust_flags);
681 rust_flags = cargo_builder.rustflags.as_ref();
682
683 assert_eq!(rust_flags.as_slice(), expected_rust_flags);
684 }
685}