1use crate::config::*;
2use crate::errors::RunError;
3use crate::path_utils::{fix_unc_path, get_source_walker};
4use cargo_metadata::{diagnostic::DiagnosticLevel, CargoOpt, Message, Metadata, MetadataCommand};
5use lazy_static::lazy_static;
6use regex::Regex;
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::env;
10use std::ffi::OsStr;
11use std::fs::{read_dir, read_to_string, remove_dir_all, remove_file, File};
12use std::io;
13use std::io::{BufRead, BufReader};
14use std::path::{Component, Path, PathBuf};
15use std::process::{Command, Stdio};
16use toml::Value;
17use tracing::{debug, error, info, trace, warn};
18use walkdir::{DirEntry, WalkDir};
19
20const BUILD_PROFRAW: &str = "build_rs_cov.profraw";
21
22cfg_if::cfg_if! {
23 if #[cfg(target_os = "windows")] {
24 pub const LD_PATH_VAR: &'static str ="PATH";
25 } else if #[cfg(any(target_os = "macos", target_os = "ios"))] {
26 pub const LD_PATH_VAR: &'static str = "DYLD_LIBRARY_PATH";
27 } else {
28 pub const LD_PATH_VAR: &'static str = "LD_LIBRARY_PATH";
29 }
30}
31
32#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
33enum Channel {
34 Stable,
35 Beta,
36 Nightly,
37}
38
39#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
40struct CargoVersionInfo {
41 major: usize,
42 minor: usize,
43 channel: Channel,
44}
45
46impl CargoVersionInfo {
47 fn supports_llvm_cov(&self) -> bool {
48 (self.minor >= 50 && self.channel == Channel::Nightly) || self.minor >= 60
49 }
50}
51
52#[derive(Clone, Debug, Default)]
53pub struct CargoOutput {
54 pub test_binaries: Vec<TestBinary>,
56 pub binaries: Vec<PathBuf>,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
62pub struct TestBinary {
63 path: PathBuf,
64 ty: Option<RunType>,
65 cargo_dir: Option<PathBuf>,
66 pkg_name: Option<String>,
67 pkg_version: Option<String>,
68 pkg_authors: Option<Vec<String>>,
69 should_panic: bool,
70 pub(crate) linker_paths: Vec<PathBuf>,
74}
75
76#[derive(Clone, Debug)]
77struct DocTestBinaryMeta {
78 prefix: String,
79 line: usize,
80}
81
82impl TestBinary {
83 pub fn new(path: PathBuf, ty: Option<RunType>) -> Self {
84 Self {
85 path,
86 ty,
87 pkg_name: None,
88 pkg_version: None,
89 pkg_authors: None,
90 cargo_dir: None,
91 should_panic: false,
92 linker_paths: vec![],
93 }
94 }
95
96 pub fn path(&self) -> &Path {
97 &self.path
98 }
99
100 pub fn run_type(&self) -> Option<RunType> {
101 self.ty
102 }
103
104 pub fn manifest_dir(&self) -> &Option<PathBuf> {
105 &self.cargo_dir
106 }
107
108 pub fn pkg_name(&self) -> &Option<String> {
109 &self.pkg_name
110 }
111
112 pub fn pkg_version(&self) -> &Option<String> {
113 &self.pkg_version
114 }
115
116 pub fn pkg_authors(&self) -> &Option<Vec<String>> {
117 &self.pkg_authors
118 }
119
120 pub fn has_linker_paths(&self) -> bool {
121 !self.linker_paths.is_empty()
122 }
123
124 pub fn is_test_type(&self) -> bool {
125 matches!(self.ty, None | Some(RunType::Tests))
126 }
127
128 pub fn ld_library_path(&self) -> String {
131 cfg_if::cfg_if! {
132 if #[cfg(windows)] {
133 const PATH_SEP: &str = ";";
134 } else {
135 const PATH_SEP: &str = ":";
136 }
137 }
138
139 let mut new_vals = self
140 .linker_paths
141 .iter()
142 .map(|x| x.display().to_string())
143 .collect::<Vec<String>>()
144 .join(PATH_SEP);
145 if let Ok(ld) = env::var(LD_PATH_VAR) {
146 new_vals.push_str(PATH_SEP);
147 new_vals.push_str(ld.as_str());
148 }
149 new_vals
150 }
151
152 pub fn should_panic(&self) -> bool {
155 self.should_panic
156 }
157
158 pub fn file_name(&self) -> String {
161 self.path
162 .file_name()
163 .map(|x| x.to_string_lossy().to_string())
164 .unwrap_or_default()
165 }
166}
167
168impl DocTestBinaryMeta {
169 fn new<P: AsRef<Path>>(test: P) -> Option<Self> {
170 if let Some(Component::Normal(folder)) = test.as_ref().components().nth_back(1) {
171 let temp = folder.to_string_lossy();
172 let file_end = temp.rfind("rs").map(|i| i + 2)?;
173 let end = temp.rfind('_')?;
174 if end > file_end + 1 {
175 let line = temp[(file_end + 1)..end].parse::<usize>().ok()?;
176 Some(Self {
177 prefix: temp[..file_end].to_string(),
178 line,
179 })
180 } else {
181 None
182 }
183 } else {
184 None
185 }
186 }
187}
188
189lazy_static! {
190 static ref CARGO_VERSION_INFO: Option<CargoVersionInfo> = {
191 let version_info = Regex::new(
192 r"cargo (\d)\.(\d+)\.\d+([\-betanightly]*)(\.[[:alnum:]]+)?",
193 )
194 .unwrap();
195 Command::new("cargo")
196 .arg("--version")
197 .output()
198 .map(|x| {
199 let s = String::from_utf8_lossy(&x.stdout);
200 if let Some(cap) = version_info.captures(&s) {
201 let major = cap[1].parse().unwrap();
202 let minor = cap[2].parse().unwrap();
203 let channel = match &cap[3] {
206 "-nightly" => Channel::Nightly,
207 "-beta" => Channel::Beta,
208 _ => Channel::Stable,
209 };
210 Some(CargoVersionInfo {
211 major,
212 minor,
213 channel,
214 })
215 } else {
216 None
217 }
218 })
219 .unwrap_or(None)
220 };
221}
222
223pub fn get_tests(config: &Config) -> Result<CargoOutput, RunError> {
224 let mut result = CargoOutput::default();
225 if config.force_clean() {
226 let cleanup_dir = if config.release {
227 config.target_dir().join("release")
228 } else {
229 config.target_dir().join("debug")
230 };
231 info!("Cleaning project");
232 if cleanup_dir.exists() {
233 if let Err(e) = remove_dir_all(cleanup_dir) {
234 error!("Cargo clean failed: {e}");
235 }
236 }
237 }
238 let man_binding = config.manifest();
239 let manifest = man_binding.as_path().to_str().unwrap_or("Cargo.toml");
240 let metadata = MetadataCommand::new()
241 .manifest_path(manifest)
242 .features(CargoOpt::AllFeatures)
243 .exec()
244 .map_err(|e| RunError::Cargo(e.to_string()))?;
245
246 for ty in &config.run_types {
247 run_cargo(&metadata, manifest, config, Some(*ty), &mut result)?;
248 }
249 if config.has_named_tests() {
250 run_cargo(&metadata, manifest, config, None, &mut result)?;
251 } else if config.run_types.is_empty() {
252 let ty = if config.command == Mode::Test {
253 Some(RunType::Tests)
254 } else {
255 None
256 };
257 run_cargo(&metadata, manifest, config, ty, &mut result)?;
258 }
259 let _ = remove_file(config.root().join(BUILD_PROFRAW));
261 Ok(result)
262}
263
264fn run_cargo(
265 metadata: &Metadata,
266 manifest: &str,
267 config: &Config,
268 ty: Option<RunType>,
269 result: &mut CargoOutput,
270) -> Result<(), RunError> {
271 let mut cmd = create_command(manifest, config, ty);
272 if ty != Some(RunType::Doctests) {
273 cmd.stdout(Stdio::piped());
274 } else {
275 clean_doctest_folder(config.doctest_dir());
276 cmd.stdout(Stdio::null());
277 }
278 trace!("Running command {:?}", cmd);
279 let mut child = cmd.spawn().map_err(|e| RunError::Cargo(e.to_string()))?;
280 let update_from = result.test_binaries.len();
281 let mut paths = match get_libdir(ty) {
282 Some(path) => vec![path],
283 None => vec![],
284 };
285
286 if ty != Some(RunType::Doctests) {
287 let mut package_ids = vec![None; result.test_binaries.len()];
288 let reader = std::io::BufReader::new(child.stdout.take().unwrap());
289 let mut error = None;
290 for msg in Message::parse_stream(reader) {
291 match msg {
292 Ok(Message::CompilerArtifact(art)) => {
293 if let Some(path) = art.executable.as_ref() {
294 if !art.profile.test && config.command == Mode::Test {
295 result.binaries.push(PathBuf::from(path));
296 continue;
297 }
298 result
299 .test_binaries
300 .push(TestBinary::new(fix_unc_path(path.as_std_path()), ty));
301 package_ids.push(Some(art.package_id.clone()));
302 }
303 }
304 Ok(Message::CompilerMessage(m)) => match m.message.level {
305 DiagnosticLevel::Error | DiagnosticLevel::Ice => {
306 let msg = if let Some(rendered) = m.message.rendered {
307 rendered
308 } else {
309 format!("{}: {}", m.target.name, m.message.message)
310 };
311 error = Some(RunError::TestCompile(msg));
312 break;
313 }
314 _ => {}
315 },
316 Ok(Message::BuildScriptExecuted(bs))
317 if !(bs.linked_libs.is_empty() && bs.linked_paths.is_empty()) =>
318 {
319 let temp_paths = bs.linked_paths.iter().filter_map(|x| {
320 if x.as_std_path().exists() {
321 Some(x.as_std_path().to_path_buf())
322 } else if let Some(index) = x.as_str().find('=') {
323 Some(PathBuf::from(&x.as_str()[(index + 1)..]))
324 } else {
325 warn!("Couldn't resolve linker path: {}", x.as_str());
326 None
327 }
328 });
329 for p in temp_paths {
330 if !paths.contains(&p) {
331 paths.push(p);
332 }
333 }
334 }
335 Err(e) => {
336 error!("Error parsing cargo messages {e}");
337 }
338 _ => {}
339 }
340 }
341 debug!("Linker paths: {:?}", paths);
342 for bin in result.test_binaries.iter_mut().skip(update_from) {
343 bin.linker_paths = paths.clone();
344 }
345 let status = child.wait().unwrap();
346 if let Some(error) = error {
347 return Err(error);
348 }
349 if !status.success() {
350 return Err(RunError::Cargo("cargo run failed".to_string()));
351 };
352 for (res, package) in result
353 .test_binaries
354 .iter_mut()
355 .zip(package_ids.iter())
356 .filter(|(_, b)| b.is_some())
357 {
358 if let Some(package) = package {
359 let package = &metadata[package];
360 res.cargo_dir = package
361 .manifest_path
362 .parent()
363 .map(|x| fix_unc_path(x.as_std_path()));
364 res.pkg_name = Some(package.name.clone());
365 res.pkg_version = Some(package.version.to_string());
366 res.pkg_authors = Some(package.authors.clone());
367 }
368 }
369 child.wait().map_err(|e| RunError::Cargo(e.to_string()))?;
370 } else {
371 let out = child
374 .wait_with_output()
375 .map_err(|e| RunError::Cargo(e.to_string()))?;
376 if !out.status.success() {
377 error!("Building doctests failed");
378 return Err(RunError::Cargo("Building doctest failed".to_string()));
379 }
380 let walker = WalkDir::new(config.doctest_dir()).into_iter();
381 let dir_entries = walker
382 .filter_map(Result::ok)
383 .filter(|e| matches!(e.metadata(), Ok(ref m) if m.is_file() && m.len() != 0))
384 .filter(|e| {
385 let ext = e.path().extension();
386 ext != Some(OsStr::new("pdb"))
387 && ext != Some(OsStr::new("rs"))
388 && ext != Some(OsStr::new("rlib"))
389 })
390 .filter(|e| {
391 !e.path()
392 .components()
393 .any(|x| x.as_os_str().to_string_lossy().contains("dSYM"))
394 })
395 .collect::<Vec<_>>();
396
397 let should_panics = get_attribute_candidates(&dir_entries, config, "should_panic");
398 let no_runs = get_attribute_candidates(&dir_entries, config, "no_run");
399 for dt in &dir_entries {
400 let mut tb = TestBinary::new(fix_unc_path(dt.path()), ty);
401
402 if let Some(meta) = DocTestBinaryMeta::new(dt.path()) {
403 if no_runs
404 .get(&meta.prefix)
405 .map(|x| x.contains(&meta.line))
406 .unwrap_or(false)
407 {
408 info!("Skipping no_run doctest: {}", dt.path().display());
409 continue;
410 }
411 if let Some(lines) = should_panics.get(&meta.prefix) {
412 tb.should_panic |= lines.contains(&meta.line);
413 }
414 }
415 let mut current_dir = dt.path();
416 loop {
417 if current_dir.is_dir() && current_dir.join("Cargo.toml").exists() {
418 tb.cargo_dir = Some(fix_unc_path(current_dir));
419 break;
420 }
421 match current_dir.parent() {
422 Some(s) => {
423 current_dir = s;
424 }
425 None => break,
426 }
427 }
428 result.test_binaries.push(tb);
429 }
430 }
431 Ok(())
432}
433
434fn convert_to_prefix(p: &Path) -> Option<String> {
435 let mut buffer = vec![];
436 let mut p = Some(p);
437 while let Some(path_temp) = p {
438 if let Some(name) = path_temp.file_name().and_then(|s| s.to_str()) {
441 buffer.push(name.replace(['.', '-'], "_"));
442 }
443 p = path_temp.parent();
444 }
445 if buffer.is_empty() {
446 None
447 } else {
448 buffer.reverse();
449 Some(buffer.join("_"))
450 }
451}
452
453fn is_prefix_match(prefix: &str, entry: &Path) -> bool {
454 convert_to_prefix(entry).as_deref() == Some(prefix)
455}
456
457fn get_attribute_candidates(
471 tests: &[DirEntry],
472 config: &Config,
473 attribute: &str,
474) -> HashMap<String, Vec<usize>> {
475 let mut result = HashMap::new();
476 let mut checked_files = HashSet::new();
477 let root = config.root();
478 for test in tests {
479 if let Some(test_binary) = DocTestBinaryMeta::new(test.path()) {
480 for dir_entry in get_source_walker(config) {
481 let path = dir_entry.path();
482 if path.is_file() {
483 if let Some(p) = path_relative_from(path, &root) {
484 if is_prefix_match(&test_binary.prefix, &p) && !checked_files.contains(path)
485 {
486 checked_files.insert(path.to_path_buf());
487 let lines = find_str_in_file(path, attribute).unwrap_or_default();
488 if !result.contains_key(&test_binary.prefix) {
489 result.insert(test_binary.prefix.clone(), lines);
490 } else if let Some(current_lines) = result.get_mut(&test_binary.prefix)
491 {
492 current_lines.extend_from_slice(&lines);
493 }
494 }
495 }
496 }
497 }
498 } else {
499 warn!(
500 "Invalid characters in name of doctest {}",
501 test.path().display()
502 );
503 }
504 }
505 result
506}
507
508fn find_str_in_file(file: &Path, value: &str) -> io::Result<Vec<usize>> {
509 let f = File::open(file)?;
510 let reader = BufReader::new(f);
511 let lines = reader
512 .lines()
513 .enumerate()
514 .filter(|(_, l)| l.as_ref().map(|x| x.contains(value)).unwrap_or(false))
515 .map(|(i, _)| i + 1) .collect();
517 Ok(lines)
518}
519
520fn start_cargo_command(ty: Option<RunType>) -> Command {
521 let mut test_cmd = Command::new("cargo");
522 let bootstrap = matches!(env::var("RUSTC_BOOTSTRAP").as_deref(), Ok("1"));
523 let override_toolchain = if cfg!(windows) {
524 let rustup_home = env::var("RUSTUP_HOME").unwrap_or(".rustup".into());
525 if env::var("PATH").unwrap_or_default().contains(&rustup_home) {
526 env::remove_var("RUSTUP_TOOLCHAIN");
529 false
530 } else {
531 true
532 }
533 } else {
534 true
535 };
536 if ty == Some(RunType::Doctests) {
537 if override_toolchain {
538 if let Some(toolchain) = env::var("RUSTUP_TOOLCHAIN")
539 .ok()
540 .filter(|t| t.starts_with("nightly") || bootstrap)
541 {
542 test_cmd.args([format!("+{toolchain}").as_str()]);
543 } else if !bootstrap && !is_nightly() {
544 test_cmd.args(["+nightly"]);
545 }
546 }
547 } else {
548 if override_toolchain {
549 if let Ok(toolchain) = env::var("RUSTUP_TOOLCHAIN") {
550 test_cmd.arg(format!("+{toolchain}"));
551 }
552 }
553 }
554 test_cmd
555}
556
557fn get_libdir(ty: Option<RunType>) -> Option<PathBuf> {
558 let mut test_cmd = start_cargo_command(ty);
559 test_cmd.env("RUSTC_BOOTSTRAP", "1");
560 test_cmd.args(["rustc", "-Z", "unstable-options", "--print=target-libdir"]);
561
562 let output = match test_cmd.output() {
563 Ok(output) => String::from_utf8_lossy(&output.stdout).trim().to_string(),
564 Err(e) => {
565 debug!("Unable to run cargo rustc command: {}", e);
566 warn!("Unable to get target libdir proc macro crates in the workspace may not work. Consider adding `--exclude` to remove them from compilation");
567 return None;
568 }
569 };
570 Some(PathBuf::from(output))
571}
572
573fn create_command(manifest_path: &str, config: &Config, ty: Option<RunType>) -> Command {
574 let mut test_cmd = start_cargo_command(ty);
575 if ty == Some(RunType::Doctests) {
576 test_cmd.args(["test"]);
577 } else {
578 if config.command == Mode::Test {
579 test_cmd.args(["test", "--no-run"]);
580 } else {
581 test_cmd.arg("build");
582 }
583 }
584 test_cmd.args(["--message-format", "json", "--manifest-path", manifest_path]);
585 if let Some(ty) = ty {
586 match ty {
587 RunType::Tests => test_cmd.arg("--tests"),
588 RunType::Doctests => test_cmd.arg("--doc"),
589 RunType::Benchmarks => test_cmd.arg("--benches"),
590 RunType::Examples => test_cmd.arg("--examples"),
591 RunType::AllTargets => test_cmd.arg("--all-targets"),
592 RunType::Lib => test_cmd.arg("--lib"),
593 RunType::Bins => test_cmd.arg("--bins"),
594 };
595 } else {
596 for test in &config.test_names {
597 test_cmd.arg("--test");
598 test_cmd.arg(test);
599 }
600 for test in &config.bin_names {
601 test_cmd.arg("--bin");
602 test_cmd.arg(test);
603 }
604 for test in &config.example_names {
605 test_cmd.arg("--example");
606 test_cmd.arg(test);
607 }
608 for test in &config.bench_names {
609 test_cmd.arg("--bench");
610 test_cmd.arg(test);
611 }
612 }
613 init_args(&mut test_cmd, config);
614 setup_environment(&mut test_cmd, config);
615 test_cmd
616}
617
618fn init_args(test_cmd: &mut Command, config: &Config) {
619 if config.debug {
620 test_cmd.arg("-vvv");
621 } else if config.verbose {
622 test_cmd.arg("-v");
623 }
624 if config.locked {
625 test_cmd.arg("--locked");
626 }
627 if config.frozen {
628 test_cmd.arg("--frozen");
629 }
630 if config.no_fail_fast {
631 test_cmd.arg("--no-fail-fast");
632 }
633 if let Some(profile) = config.profile.as_ref() {
634 test_cmd.arg("--profile");
635 test_cmd.arg(profile);
636 }
637 if let Some(jobs) = config.jobs {
638 test_cmd.arg("--jobs");
639 test_cmd.arg(jobs.to_string());
640 }
641 if let Some(features) = config.features.as_ref() {
642 test_cmd.arg("--features");
643 test_cmd.arg(features);
644 }
645 if config.all_features {
646 test_cmd.arg("--all-features");
647 }
648 if config.no_default_features {
649 test_cmd.arg("--no-default-features");
650 }
651 if config.all {
652 test_cmd.arg("--workspace");
653 }
654 if config.release {
655 test_cmd.arg("--release");
656 }
657 config.packages.iter().for_each(|package| {
658 test_cmd.arg("--package");
659 test_cmd.arg(package);
660 });
661 config.exclude.iter().for_each(|package| {
662 test_cmd.arg("--exclude");
663 test_cmd.arg(package);
664 });
665 test_cmd.arg("--color");
666 test_cmd.arg(config.color.to_string().to_ascii_lowercase());
667 if let Some(target) = config.target.as_ref() {
668 test_cmd.args(["--target", target]);
669 }
670 let args = vec![
671 "--target-dir".to_string(),
672 format!("{}", config.target_dir().display()),
673 ];
674 test_cmd.args(args);
675 if config.offline {
676 test_cmd.arg("--offline");
677 }
678 for feat in &config.unstable_features {
679 test_cmd.arg(format!("-Z{feat}"));
680 }
681 if config.command == Mode::Test && !config.varargs.is_empty() {
682 let mut args = vec!["--".to_string()];
683 args.extend_from_slice(&config.varargs);
684 test_cmd.args(args);
685 }
686}
687
688fn clean_doctest_folder<P: AsRef<Path>>(doctest_dir: P) {
691 if let Ok(rd) = read_dir(doctest_dir.as_ref()) {
692 rd.flat_map(Result::ok)
693 .filter(|e| {
694 e.path()
695 .components()
696 .next_back()
697 .map(|e| e.as_os_str().to_string_lossy().contains("rs"))
698 .unwrap_or(false)
699 })
700 .for_each(|e| {
701 if let Err(err) = remove_dir_all(e.path()) {
702 warn!("Failed to delete {}: {}", e.path().display(), err);
703 }
704 });
705 }
706}
707
708fn handle_llvm_flags(value: &mut String, config: &Config) {
709 if config.engine() == TraceEngine::Llvm {
710 value.push_str(llvm_coverage_rustflag());
711 } else if !config.no_dead_code {
712 value.push_str(" -Clink-dead-code ");
713 }
714}
715
716fn look_for_field_in_table(value: &Value, field: &str) -> String {
717 let table = value.as_table().unwrap();
718
719 if let Some(rustflags) = table.get(field) {
720 if rustflags.is_array() {
721 let vec_of_flags: Vec<String> = rustflags
722 .as_array()
723 .unwrap()
724 .iter()
725 .filter_map(Value::as_str)
726 .map(ToString::to_string)
727 .collect();
728
729 vec_of_flags.join(" ")
730 } else if rustflags.is_str() {
731 rustflags.as_str().unwrap().to_string()
732 } else {
733 String::new()
734 }
735 } else {
736 String::new()
737 }
738}
739
740fn look_for_field_in_file(path: &Path, section: &str, field: &str) -> Option<String> {
741 if let Ok(contents) = read_to_string(path) {
742 let value = contents.parse::<Value>().ok()?;
743
744 let value: Vec<String> = value
745 .as_table()?
746 .into_iter()
747 .map(|(s, v)| {
748 if s.as_str() == section {
749 look_for_field_in_table(v, field)
750 } else {
751 String::new()
752 }
753 })
754 .collect();
755
756 Some(value.join(" "))
757 } else {
758 None
759 }
760}
761
762fn look_for_field_in_section(path: &Path, section: &str, field: &str) -> Option<String> {
763 let mut config_path = path.join("config");
764
765 let value = look_for_field_in_file(&config_path, section, field);
766 if value.is_some() {
767 return value;
768 }
769
770 config_path.pop();
771 config_path.push("config.toml");
772
773 let value = look_for_field_in_file(&config_path, section, field);
774 if value.is_some() {
775 return value;
776 }
777
778 None
779}
780
781fn build_config_path(base: impl AsRef<Path>) -> PathBuf {
782 let mut config_path = PathBuf::from(base.as_ref());
783 config_path.push(base);
784 config_path.push(".cargo");
785
786 config_path
787}
788
789fn gather_config_field_from_section(config: &Config, section: &str, field: &str) -> String {
790 if let Some(value) =
791 look_for_field_in_section(&build_config_path(config.root()), section, field)
792 {
793 return value;
794 }
795
796 if let Ok(cargo_home_config) = env::var("CARGO_HOME") {
797 if let Some(value) =
798 look_for_field_in_section(&PathBuf::from(cargo_home_config), section, field)
799 {
800 return value;
801 }
802 }
803
804 String::new()
805}
806
807pub fn rust_flags(config: &Config) -> String {
808 const RUSTFLAGS: &str = "RUSTFLAGS";
809 let mut value = config.rustflags.clone().unwrap_or_default();
810 value.push_str(" -Cdebuginfo=2 ");
811 value.push_str("-Cstrip=none ");
812 if !config.avoid_cfg_tarpaulin {
813 value.push_str("--cfg=tarpaulin ");
814 }
815 if config.release {
816 value.push_str("-Cdebug-assertions=off ");
817 }
818 handle_llvm_flags(&mut value, config);
819 lazy_static! {
820 static ref DEBUG_INFO: Regex = Regex::new(r"\-C\s*debuginfo=\d").unwrap();
821 static ref DEAD_CODE: Regex = Regex::new(r"\-C\s*link-dead-code").unwrap();
822 }
823 if let Ok(vtemp) = env::var(RUSTFLAGS) {
824 let temp = DEBUG_INFO.replace_all(&vtemp, " ");
825 if config.no_dead_code {
826 value.push_str(&DEAD_CODE.replace_all(&temp, " "));
827 } else {
828 value.push_str(&temp);
829 }
830 } else {
831 let vtemp = gather_config_field_from_section(config, "build", "rustflags");
832 value.push_str(&DEBUG_INFO.replace_all(&vtemp, " "));
833 }
834
835 deduplicate_flags(&value)
836}
837
838pub fn rustdoc_flags(config: &Config) -> String {
839 const RUSTDOC: &str = "RUSTDOCFLAGS";
840 let common_opts = " -Cdebuginfo=2 --cfg=tarpaulin -Cstrip=none ";
841 let mut value = format!(
842 "{} --persist-doctests {} -Zunstable-options ",
843 common_opts,
844 config.doctest_dir().display()
845 );
846 if let Ok(vtemp) = env::var(RUSTDOC) {
847 if !vtemp.contains("--persist-doctests") {
848 value.push_str(vtemp.as_ref());
849 }
850 } else {
851 let vtemp = gather_config_field_from_section(config, "build", "rustdocflags");
852 value.push_str(&vtemp);
853 }
854 handle_llvm_flags(&mut value, config);
855 deduplicate_flags(&value)
856}
857
858fn deduplicate_flags(flags: &str) -> String {
859 lazy_static! {
860 static ref CFG_FLAG: Regex = Regex::new(r#"\--cfg\s+"#).unwrap();
861 static ref C_FLAG: Regex = Regex::new(r#"\-C\s+"#).unwrap();
862 static ref Z_FLAG: Regex = Regex::new(r#"\-Z\s+"#).unwrap();
863 static ref W_FLAG: Regex = Regex::new(r#"\-W\s+"#).unwrap();
864 static ref A_FLAG: Regex = Regex::new(r#"\-A\s+"#).unwrap();
865 static ref D_FLAG: Regex = Regex::new(r#"\-D\s+"#).unwrap();
866 }
867
868 let res = CFG_FLAG.replace_all(flags, "--cfg=");
870 let res = C_FLAG.replace_all(&res, "-C");
871 let res = Z_FLAG.replace_all(&res, "-Z");
872 let res = W_FLAG.replace_all(&res, "-W");
873 let res = A_FLAG.replace_all(&res, "-A");
874 let res = D_FLAG.replace_all(&res, "-D");
875
876 let mut flag_set = HashSet::new();
877 let mut result = vec![];
878 for val in res.split_whitespace() {
879 if val.starts_with("--cfg") {
880 if !flag_set.contains(&val) {
881 result.push(val);
882 flag_set.insert(val);
883 }
884 } else {
885 let id = val.split('=').next().unwrap();
886 if !flag_set.contains(id) {
887 flag_set.insert(id);
888 result.push(val);
889 }
890 }
891 }
892 result.join(" ")
893}
894
895fn setup_environment(cmd: &mut Command, config: &Config) {
896 cmd.env("LLVM_PROFILE_FILE", config.root().join(BUILD_PROFRAW));
898 cmd.env("TARPAULIN", "1");
899 let rustflags = "RUSTFLAGS";
900 let value = rust_flags(config);
901 cmd.env(rustflags, value);
902 let rustdoc = "RUSTDOCFLAGS";
904 let value = rustdoc_flags(config);
905 trace!("Setting RUSTDOCFLAGS='{}'", value);
906 cmd.env(rustdoc, value);
907 if let Ok(bootstrap) = env::var("RUSTC_BOOTSTRAP") {
908 cmd.env("RUSTC_BOOTSTRAP", bootstrap);
909 }
910}
911
912fn is_nightly() -> bool {
915 if let Some(version) = CARGO_VERSION_INFO.as_ref() {
916 version.channel == Channel::Nightly
917 } else {
918 false
919 }
920}
921
922pub fn supports_llvm_coverage() -> bool {
923 if let Some(version) = CARGO_VERSION_INFO.as_ref() {
924 version.supports_llvm_cov()
925 } else {
926 false
927 }
928}
929
930pub fn llvm_coverage_rustflag() -> &'static str {
931 match CARGO_VERSION_INFO.as_ref() {
932 Some(v) if v.minor >= 60 => " -Cinstrument-coverage ",
933 _ => " -Zinstrument-coverage ",
934 }
935}
936
937#[cfg(test)]
938mod tests {
939 use super::*;
940 use toml::toml;
941
942 #[test]
943 fn can_get_libdir() {
944 let path = get_libdir(Some(RunType::Tests)).unwrap();
945 assert!(path.exists(), "{} doesn't exist", path.display());
946 }
947
948 #[test]
949 #[cfg(not(any(windows, target_os = "macos")))]
950 fn check_dead_code_flags() {
951 let mut config = Config::default();
952 config.set_engine(TraceEngine::Ptrace);
953 assert!(rustdoc_flags(&config).contains("link-dead-code"));
954 assert!(rust_flags(&config).contains("link-dead-code"));
955
956 config.no_dead_code = true;
957 assert!(!rustdoc_flags(&config).contains("link-dead-code"));
958 assert!(!rust_flags(&config).contains("link-dead-code"));
959 }
960
961 #[test]
962 fn parse_rustflags_from_toml() {
963 let list_flags = toml! {
964 rustflags = ["--cfg=foo", "--cfg=bar"]
965 };
966 let list_flags = toml::Value::Table(list_flags);
967
968 assert_eq!(
969 look_for_field_in_table(&list_flags, "rustflags"),
970 "--cfg=foo --cfg=bar"
971 );
972
973 let string_flags = toml! {
974 rustflags = "--cfg=bar --cfg=baz"
975 };
976 let string_flags = toml::Value::Table(string_flags);
977
978 assert_eq!(
979 look_for_field_in_table(&string_flags, "rustflags"),
980 "--cfg=bar --cfg=baz"
981 );
982 }
983
984 #[test]
985 fn llvm_cov_compatible_version() {
986 let version = CargoVersionInfo {
987 major: 1,
988 minor: 50,
989 channel: Channel::Nightly,
990 };
991 assert!(version.supports_llvm_cov());
992 let version = CargoVersionInfo {
993 major: 1,
994 minor: 60,
995 channel: Channel::Stable,
996 };
997 assert!(version.supports_llvm_cov());
998 }
999
1000 #[test]
1001 fn llvm_cov_incompatible_version() {
1002 let mut version = CargoVersionInfo {
1003 major: 1,
1004 minor: 48,
1005 channel: Channel::Stable,
1006 };
1007 assert!(!version.supports_llvm_cov());
1008 version.channel = Channel::Beta;
1009 assert!(!version.supports_llvm_cov());
1010 version.minor = 50;
1011 assert!(!version.supports_llvm_cov());
1012 version.minor = 58;
1013 version.channel = Channel::Stable;
1014 assert!(!version.supports_llvm_cov());
1015 }
1016
1017 #[test]
1018 fn no_duplicate_flags() {
1019 assert_eq!(
1020 deduplicate_flags("--cfg=tarpaulin --cfg tarpaulin"),
1021 "--cfg=tarpaulin"
1022 );
1023 assert_eq!(
1024 deduplicate_flags("-Clink-dead-code -Zinstrument-coverage -C link-dead-code"),
1025 "-Clink-dead-code -Zinstrument-coverage"
1026 );
1027 assert_eq!(
1028 deduplicate_flags("-Clink-dead-code -Zinstrument-coverage -Zinstrument-coverage"),
1029 "-Clink-dead-code -Zinstrument-coverage"
1030 );
1031 assert_eq!(
1032 deduplicate_flags("-Clink-dead-code -Zinstrument-coverage -Cinstrument-coverage"),
1033 "-Clink-dead-code -Zinstrument-coverage -Cinstrument-coverage"
1034 );
1035
1036 assert_eq!(
1037 deduplicate_flags("--cfg=tarpaulin --cfg tarpauline --cfg=tarp"),
1038 "--cfg=tarpaulin --cfg=tarpauline --cfg=tarp"
1039 );
1040 }
1041}