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")) && ext != Some(OsStr::new("rs"))
387 })
388 .filter(|e| {
389 !e.path()
390 .components()
391 .any(|x| x.as_os_str().to_string_lossy().contains("dSYM"))
392 })
393 .collect::<Vec<_>>();
394
395 let should_panics = get_attribute_candidates(&dir_entries, config, "should_panic");
396 let no_runs = get_attribute_candidates(&dir_entries, config, "no_run");
397 for dt in &dir_entries {
398 let mut tb = TestBinary::new(fix_unc_path(dt.path()), ty);
399
400 if let Some(meta) = DocTestBinaryMeta::new(dt.path()) {
401 if no_runs
402 .get(&meta.prefix)
403 .map(|x| x.contains(&meta.line))
404 .unwrap_or(false)
405 {
406 info!("Skipping no_run doctest: {}", dt.path().display());
407 continue;
408 }
409 if let Some(lines) = should_panics.get(&meta.prefix) {
410 tb.should_panic |= lines.contains(&meta.line);
411 }
412 }
413 let mut current_dir = dt.path();
414 loop {
415 if current_dir.is_dir() && current_dir.join("Cargo.toml").exists() {
416 tb.cargo_dir = Some(fix_unc_path(current_dir));
417 break;
418 }
419 match current_dir.parent() {
420 Some(s) => {
421 current_dir = s;
422 }
423 None => break,
424 }
425 }
426 result.test_binaries.push(tb);
427 }
428 }
429 Ok(())
430}
431
432fn convert_to_prefix(p: &Path) -> Option<String> {
433 let mut buffer = vec![];
434 let mut p = Some(p);
435 while let Some(path_temp) = p {
436 if let Some(name) = path_temp.file_name().and_then(|s| s.to_str()) {
439 buffer.push(name.replace(['.', '-'], "_"));
440 }
441 p = path_temp.parent();
442 }
443 if buffer.is_empty() {
444 None
445 } else {
446 buffer.reverse();
447 Some(buffer.join("_"))
448 }
449}
450
451fn is_prefix_match(prefix: &str, entry: &Path) -> bool {
452 convert_to_prefix(entry).as_deref() == Some(prefix)
453}
454
455fn get_attribute_candidates(
469 tests: &[DirEntry],
470 config: &Config,
471 attribute: &str,
472) -> HashMap<String, Vec<usize>> {
473 let mut result = HashMap::new();
474 let mut checked_files = HashSet::new();
475 let root = config.root();
476 for test in tests {
477 if let Some(test_binary) = DocTestBinaryMeta::new(test.path()) {
478 for dir_entry in get_source_walker(config) {
479 let path = dir_entry.path();
480 if path.is_file() {
481 if let Some(p) = path_relative_from(path, &root) {
482 if is_prefix_match(&test_binary.prefix, &p) && !checked_files.contains(path)
483 {
484 checked_files.insert(path.to_path_buf());
485 let lines = find_str_in_file(path, attribute).unwrap_or_default();
486 if !result.contains_key(&test_binary.prefix) {
487 result.insert(test_binary.prefix.clone(), lines);
488 } else if let Some(current_lines) = result.get_mut(&test_binary.prefix)
489 {
490 current_lines.extend_from_slice(&lines);
491 }
492 }
493 }
494 }
495 }
496 } else {
497 warn!(
498 "Invalid characters in name of doctest {}",
499 test.path().display()
500 );
501 }
502 }
503 result
504}
505
506fn find_str_in_file(file: &Path, value: &str) -> io::Result<Vec<usize>> {
507 let f = File::open(file)?;
508 let reader = BufReader::new(f);
509 let lines = reader
510 .lines()
511 .enumerate()
512 .filter(|(_, l)| l.as_ref().map(|x| x.contains(value)).unwrap_or(false))
513 .map(|(i, _)| i + 1) .collect();
515 Ok(lines)
516}
517
518fn start_cargo_command(ty: Option<RunType>) -> Command {
519 let mut test_cmd = Command::new("cargo");
520 let bootstrap = matches!(env::var("RUSTC_BOOTSTRAP").as_deref(), Ok("1"));
521 let override_toolchain = if cfg!(windows) {
522 let rustup_home = env::var("RUSTUP_HOME").unwrap_or(".rustup".into());
523 if env::var("PATH").unwrap_or_default().contains(&rustup_home) {
524 env::remove_var("RUSTUP_TOOLCHAIN");
527 false
528 } else {
529 true
530 }
531 } else {
532 true
533 };
534 if ty == Some(RunType::Doctests) {
535 if override_toolchain {
536 if let Some(toolchain) = env::var("RUSTUP_TOOLCHAIN")
537 .ok()
538 .filter(|t| t.starts_with("nightly") || bootstrap)
539 {
540 test_cmd.args([format!("+{toolchain}").as_str()]);
541 } else if !bootstrap && !is_nightly() {
542 test_cmd.args(["+nightly"]);
543 }
544 }
545 } else {
546 if override_toolchain {
547 if let Ok(toolchain) = env::var("RUSTUP_TOOLCHAIN") {
548 test_cmd.arg(format!("+{toolchain}"));
549 }
550 }
551 }
552 test_cmd
553}
554
555fn get_libdir(ty: Option<RunType>) -> Option<PathBuf> {
556 let mut test_cmd = start_cargo_command(ty);
557 test_cmd.env("RUSTC_BOOTSTRAP", "1");
558 test_cmd.args(["rustc", "-Z", "unstable-options", "--print=target-libdir"]);
559
560 let output = match test_cmd.output() {
561 Ok(output) => String::from_utf8_lossy(&output.stdout).trim().to_string(),
562 Err(e) => {
563 debug!("Unable to run cargo rustc command: {}", e);
564 warn!("Unable to get target libdir proc macro crates in the workspace may not work. Consider adding `--exclude` to remove them from compilation");
565 return None;
566 }
567 };
568 Some(PathBuf::from(output))
569}
570
571fn create_command(manifest_path: &str, config: &Config, ty: Option<RunType>) -> Command {
572 let mut test_cmd = start_cargo_command(ty);
573 if ty == Some(RunType::Doctests) {
574 test_cmd.args(["test"]);
575 } else {
576 if config.command == Mode::Test {
577 test_cmd.args(["test", "--no-run"]);
578 } else {
579 test_cmd.arg("build");
580 }
581 }
582 test_cmd.args(["--message-format", "json", "--manifest-path", manifest_path]);
583 if let Some(ty) = ty {
584 match ty {
585 RunType::Tests => test_cmd.arg("--tests"),
586 RunType::Doctests => test_cmd.arg("--doc"),
587 RunType::Benchmarks => test_cmd.arg("--benches"),
588 RunType::Examples => test_cmd.arg("--examples"),
589 RunType::AllTargets => test_cmd.arg("--all-targets"),
590 RunType::Lib => test_cmd.arg("--lib"),
591 RunType::Bins => test_cmd.arg("--bins"),
592 };
593 } else {
594 for test in &config.test_names {
595 test_cmd.arg("--test");
596 test_cmd.arg(test);
597 }
598 for test in &config.bin_names {
599 test_cmd.arg("--bin");
600 test_cmd.arg(test);
601 }
602 for test in &config.example_names {
603 test_cmd.arg("--example");
604 test_cmd.arg(test);
605 }
606 for test in &config.bench_names {
607 test_cmd.arg("--bench");
608 test_cmd.arg(test);
609 }
610 }
611 init_args(&mut test_cmd, config);
612 setup_environment(&mut test_cmd, config);
613 test_cmd
614}
615
616fn init_args(test_cmd: &mut Command, config: &Config) {
617 if config.debug {
618 test_cmd.arg("-vvv");
619 } else if config.verbose {
620 test_cmd.arg("-v");
621 }
622 if config.locked {
623 test_cmd.arg("--locked");
624 }
625 if config.frozen {
626 test_cmd.arg("--frozen");
627 }
628 if config.no_fail_fast {
629 test_cmd.arg("--no-fail-fast");
630 }
631 if let Some(profile) = config.profile.as_ref() {
632 test_cmd.arg("--profile");
633 test_cmd.arg(profile);
634 }
635 if let Some(jobs) = config.jobs {
636 test_cmd.arg("--jobs");
637 test_cmd.arg(jobs.to_string());
638 }
639 if let Some(features) = config.features.as_ref() {
640 test_cmd.arg("--features");
641 test_cmd.arg(features);
642 }
643 if config.all_features {
644 test_cmd.arg("--all-features");
645 }
646 if config.no_default_features {
647 test_cmd.arg("--no-default-features");
648 }
649 if config.all {
650 test_cmd.arg("--workspace");
651 }
652 if config.release {
653 test_cmd.arg("--release");
654 }
655 config.packages.iter().for_each(|package| {
656 test_cmd.arg("--package");
657 test_cmd.arg(package);
658 });
659 config.exclude.iter().for_each(|package| {
660 test_cmd.arg("--exclude");
661 test_cmd.arg(package);
662 });
663 test_cmd.arg("--color");
664 test_cmd.arg(config.color.to_string().to_ascii_lowercase());
665 if let Some(target) = config.target.as_ref() {
666 test_cmd.args(["--target", target]);
667 }
668 let args = vec![
669 "--target-dir".to_string(),
670 format!("{}", config.target_dir().display()),
671 ];
672 test_cmd.args(args);
673 if config.offline {
674 test_cmd.arg("--offline");
675 }
676 for feat in &config.unstable_features {
677 test_cmd.arg(format!("-Z{feat}"));
678 }
679 if config.command == Mode::Test && !config.varargs.is_empty() {
680 let mut args = vec!["--".to_string()];
681 args.extend_from_slice(&config.varargs);
682 test_cmd.args(args);
683 }
684}
685
686fn clean_doctest_folder<P: AsRef<Path>>(doctest_dir: P) {
689 if let Ok(rd) = read_dir(doctest_dir.as_ref()) {
690 rd.flat_map(Result::ok)
691 .filter(|e| {
692 e.path()
693 .components()
694 .next_back()
695 .map(|e| e.as_os_str().to_string_lossy().contains("rs"))
696 .unwrap_or(false)
697 })
698 .for_each(|e| {
699 if let Err(err) = remove_dir_all(e.path()) {
700 warn!("Failed to delete {}: {}", e.path().display(), err);
701 }
702 });
703 }
704}
705
706fn handle_llvm_flags(value: &mut String, config: &Config) {
707 if config.engine() == TraceEngine::Llvm {
708 value.push_str(llvm_coverage_rustflag());
709 } else if !config.no_dead_code {
710 value.push_str(" -Clink-dead-code ");
711 }
712}
713
714fn look_for_field_in_table(value: &Value, field: &str) -> String {
715 let table = value.as_table().unwrap();
716
717 if let Some(rustflags) = table.get(field) {
718 if rustflags.is_array() {
719 let vec_of_flags: Vec<String> = rustflags
720 .as_array()
721 .unwrap()
722 .iter()
723 .filter_map(Value::as_str)
724 .map(ToString::to_string)
725 .collect();
726
727 vec_of_flags.join(" ")
728 } else if rustflags.is_str() {
729 rustflags.as_str().unwrap().to_string()
730 } else {
731 String::new()
732 }
733 } else {
734 String::new()
735 }
736}
737
738fn look_for_field_in_file(path: &Path, section: &str, field: &str) -> Option<String> {
739 if let Ok(contents) = read_to_string(path) {
740 let value = contents.parse::<Value>().ok()?;
741
742 let value: Vec<String> = value
743 .as_table()?
744 .into_iter()
745 .map(|(s, v)| {
746 if s.as_str() == section {
747 look_for_field_in_table(v, field)
748 } else {
749 String::new()
750 }
751 })
752 .collect();
753
754 Some(value.join(" "))
755 } else {
756 None
757 }
758}
759
760fn look_for_field_in_section(path: &Path, section: &str, field: &str) -> Option<String> {
761 let mut config_path = path.join("config");
762
763 let value = look_for_field_in_file(&config_path, section, field);
764 if value.is_some() {
765 return value;
766 }
767
768 config_path.pop();
769 config_path.push("config.toml");
770
771 let value = look_for_field_in_file(&config_path, section, field);
772 if value.is_some() {
773 return value;
774 }
775
776 None
777}
778
779fn build_config_path(base: impl AsRef<Path>) -> PathBuf {
780 let mut config_path = PathBuf::from(base.as_ref());
781 config_path.push(base);
782 config_path.push(".cargo");
783
784 config_path
785}
786
787fn gather_config_field_from_section(config: &Config, section: &str, field: &str) -> String {
788 if let Some(value) =
789 look_for_field_in_section(&build_config_path(config.root()), section, field)
790 {
791 return value;
792 }
793
794 if let Ok(cargo_home_config) = env::var("CARGO_HOME") {
795 if let Some(value) =
796 look_for_field_in_section(&PathBuf::from(cargo_home_config), section, field)
797 {
798 return value;
799 }
800 }
801
802 String::new()
803}
804
805pub fn rust_flags(config: &Config) -> String {
806 const RUSTFLAGS: &str = "RUSTFLAGS";
807 let mut value = config.rustflags.clone().unwrap_or_default();
808 value.push_str(" -Cdebuginfo=2 ");
809 value.push_str("-Cstrip=none ");
810 if !config.avoid_cfg_tarpaulin {
811 value.push_str("--cfg=tarpaulin ");
812 }
813 if config.release {
814 value.push_str("-Cdebug-assertions=off ");
815 }
816 handle_llvm_flags(&mut value, config);
817 lazy_static! {
818 static ref DEBUG_INFO: Regex = Regex::new(r"\-C\s*debuginfo=\d").unwrap();
819 static ref DEAD_CODE: Regex = Regex::new(r"\-C\s*link-dead-code").unwrap();
820 }
821 if let Ok(vtemp) = env::var(RUSTFLAGS) {
822 let temp = DEBUG_INFO.replace_all(&vtemp, " ");
823 if config.no_dead_code {
824 value.push_str(&DEAD_CODE.replace_all(&temp, " "));
825 } else {
826 value.push_str(&temp);
827 }
828 } else {
829 let vtemp = gather_config_field_from_section(config, "build", "rustflags");
830 value.push_str(&DEBUG_INFO.replace_all(&vtemp, " "));
831 }
832
833 deduplicate_flags(&value)
834}
835
836pub fn rustdoc_flags(config: &Config) -> String {
837 const RUSTDOC: &str = "RUSTDOCFLAGS";
838 let common_opts = " -Cdebuginfo=2 --cfg=tarpaulin -Cstrip=none ";
839 let mut value = format!(
840 "{} --persist-doctests {} -Zunstable-options ",
841 common_opts,
842 config.doctest_dir().display()
843 );
844 if let Ok(vtemp) = env::var(RUSTDOC) {
845 if !vtemp.contains("--persist-doctests") {
846 value.push_str(vtemp.as_ref());
847 }
848 } else {
849 let vtemp = gather_config_field_from_section(config, "build", "rustdocflags");
850 value.push_str(&vtemp);
851 }
852 handle_llvm_flags(&mut value, config);
853 deduplicate_flags(&value)
854}
855
856fn deduplicate_flags(flags: &str) -> String {
857 lazy_static! {
858 static ref CFG_FLAG: Regex = Regex::new(r#"\--cfg\s+"#).unwrap();
859 static ref C_FLAG: Regex = Regex::new(r#"\-C\s+"#).unwrap();
860 static ref Z_FLAG: Regex = Regex::new(r#"\-Z\s+"#).unwrap();
861 static ref W_FLAG: Regex = Regex::new(r#"\-W\s+"#).unwrap();
862 static ref A_FLAG: Regex = Regex::new(r#"\-A\s+"#).unwrap();
863 static ref D_FLAG: Regex = Regex::new(r#"\-D\s+"#).unwrap();
864 }
865
866 let res = CFG_FLAG.replace_all(flags, "--cfg=");
868 let res = C_FLAG.replace_all(&res, "-C");
869 let res = Z_FLAG.replace_all(&res, "-Z");
870 let res = W_FLAG.replace_all(&res, "-W");
871 let res = A_FLAG.replace_all(&res, "-A");
872 let res = D_FLAG.replace_all(&res, "-D");
873
874 let mut flag_set = HashSet::new();
875 let mut result = vec![];
876 for val in res.split_whitespace() {
877 if val.starts_with("--cfg") {
878 if !flag_set.contains(&val) {
879 result.push(val);
880 flag_set.insert(val);
881 }
882 } else {
883 let id = val.split('=').next().unwrap();
884 if !flag_set.contains(id) {
885 flag_set.insert(id);
886 result.push(val);
887 }
888 }
889 }
890 result.join(" ")
891}
892
893fn setup_environment(cmd: &mut Command, config: &Config) {
894 cmd.env("LLVM_PROFILE_FILE", config.root().join(BUILD_PROFRAW));
896 cmd.env("TARPAULIN", "1");
897 let rustflags = "RUSTFLAGS";
898 let value = rust_flags(config);
899 cmd.env(rustflags, value);
900 let rustdoc = "RUSTDOCFLAGS";
902 let value = rustdoc_flags(config);
903 trace!("Setting RUSTDOCFLAGS='{}'", value);
904 cmd.env(rustdoc, value);
905 if let Ok(bootstrap) = env::var("RUSTC_BOOTSTRAP") {
906 cmd.env("RUSTC_BOOTSTRAP", bootstrap);
907 }
908}
909
910fn is_nightly() -> bool {
913 if let Some(version) = CARGO_VERSION_INFO.as_ref() {
914 version.channel == Channel::Nightly
915 } else {
916 false
917 }
918}
919
920pub fn supports_llvm_coverage() -> bool {
921 if let Some(version) = CARGO_VERSION_INFO.as_ref() {
922 version.supports_llvm_cov()
923 } else {
924 false
925 }
926}
927
928pub fn llvm_coverage_rustflag() -> &'static str {
929 match CARGO_VERSION_INFO.as_ref() {
930 Some(v) if v.minor >= 60 => " -Cinstrument-coverage ",
931 _ => " -Zinstrument-coverage ",
932 }
933}
934
935#[cfg(test)]
936mod tests {
937 use super::*;
938 use toml::toml;
939
940 #[test]
941 fn can_get_libdir() {
942 let path = get_libdir(Some(RunType::Tests)).unwrap();
943 assert!(path.exists(), "{} doesn't exist", path.display());
944 }
945
946 #[test]
947 #[cfg(not(any(windows, target_os = "macos")))]
948 fn check_dead_code_flags() {
949 let mut config = Config::default();
950 config.set_engine(TraceEngine::Ptrace);
951 assert!(rustdoc_flags(&config).contains("link-dead-code"));
952 assert!(rust_flags(&config).contains("link-dead-code"));
953
954 config.no_dead_code = true;
955 assert!(!rustdoc_flags(&config).contains("link-dead-code"));
956 assert!(!rust_flags(&config).contains("link-dead-code"));
957 }
958
959 #[test]
960 fn parse_rustflags_from_toml() {
961 let list_flags = toml! {
962 rustflags = ["--cfg=foo", "--cfg=bar"]
963 };
964 let list_flags = toml::Value::Table(list_flags);
965
966 assert_eq!(
967 look_for_field_in_table(&list_flags, "rustflags"),
968 "--cfg=foo --cfg=bar"
969 );
970
971 let string_flags = toml! {
972 rustflags = "--cfg=bar --cfg=baz"
973 };
974 let string_flags = toml::Value::Table(string_flags);
975
976 assert_eq!(
977 look_for_field_in_table(&string_flags, "rustflags"),
978 "--cfg=bar --cfg=baz"
979 );
980 }
981
982 #[test]
983 fn llvm_cov_compatible_version() {
984 let version = CargoVersionInfo {
985 major: 1,
986 minor: 50,
987 channel: Channel::Nightly,
988 };
989 assert!(version.supports_llvm_cov());
990 let version = CargoVersionInfo {
991 major: 1,
992 minor: 60,
993 channel: Channel::Stable,
994 };
995 assert!(version.supports_llvm_cov());
996 }
997
998 #[test]
999 fn llvm_cov_incompatible_version() {
1000 let mut version = CargoVersionInfo {
1001 major: 1,
1002 minor: 48,
1003 channel: Channel::Stable,
1004 };
1005 assert!(!version.supports_llvm_cov());
1006 version.channel = Channel::Beta;
1007 assert!(!version.supports_llvm_cov());
1008 version.minor = 50;
1009 assert!(!version.supports_llvm_cov());
1010 version.minor = 58;
1011 version.channel = Channel::Stable;
1012 assert!(!version.supports_llvm_cov());
1013 }
1014
1015 #[test]
1016 fn no_duplicate_flags() {
1017 assert_eq!(
1018 deduplicate_flags("--cfg=tarpaulin --cfg tarpaulin"),
1019 "--cfg=tarpaulin"
1020 );
1021 assert_eq!(
1022 deduplicate_flags("-Clink-dead-code -Zinstrument-coverage -C link-dead-code"),
1023 "-Clink-dead-code -Zinstrument-coverage"
1024 );
1025 assert_eq!(
1026 deduplicate_flags("-Clink-dead-code -Zinstrument-coverage -Zinstrument-coverage"),
1027 "-Clink-dead-code -Zinstrument-coverage"
1028 );
1029 assert_eq!(
1030 deduplicate_flags("-Clink-dead-code -Zinstrument-coverage -Cinstrument-coverage"),
1031 "-Clink-dead-code -Zinstrument-coverage -Cinstrument-coverage"
1032 );
1033
1034 assert_eq!(
1035 deduplicate_flags("--cfg=tarpaulin --cfg tarpauline --cfg=tarp"),
1036 "--cfg=tarpaulin --cfg=tarpauline --cfg=tarp"
1037 );
1038 }
1039}