1use std::collections::HashSet;
14use std::fs::{self, DirEntry};
15use std::path::{Path, PathBuf};
16
17use anyhow::Context as _;
18use cargo_util::paths;
19use cargo_util_schemas::manifest::{
20 PathValue, StringOrVec, TomlBenchTarget, TomlBinTarget, TomlExampleTarget, TomlLibTarget,
21 TomlManifest, TomlPackageBuild, TomlTarget, TomlTestTarget,
22};
23
24use crate::core::compiler::{CrateType, rustdoc::RustdocScrapeExamples};
25use crate::core::{Edition, Feature, Features, Target};
26use crate::util::{
27 closest_msg, errors::CargoResult, restricted_names, toml::deprecated_underscore,
28};
29
30const DEFAULT_TEST_DIR_NAME: &'static str = "tests";
31const DEFAULT_BENCH_DIR_NAME: &'static str = "benches";
32const DEFAULT_EXAMPLE_DIR_NAME: &'static str = "examples";
33
34const TARGET_KIND_HUMAN_LIB: &str = "library";
35const TARGET_KIND_HUMAN_BIN: &str = "binary";
36const TARGET_KIND_HUMAN_EXAMPLE: &str = "example";
37const TARGET_KIND_HUMAN_TEST: &str = "test";
38const TARGET_KIND_HUMAN_BENCH: &str = "benchmark";
39
40const TARGET_KIND_LIB: &str = "lib";
41const TARGET_KIND_BIN: &str = "bin";
42const TARGET_KIND_EXAMPLE: &str = "example";
43const TARGET_KIND_TEST: &str = "test";
44const TARGET_KIND_BENCH: &str = "bench";
45
46#[tracing::instrument(skip_all)]
47pub(super) fn to_targets(
48 features: &Features,
49 original_toml: &TomlManifest,
50 normalized_toml: &TomlManifest,
51 package_root: &Path,
52 edition: Edition,
53 metabuild: &Option<StringOrVec>,
54 warnings: &mut Vec<String>,
55) -> CargoResult<Vec<Target>> {
56 let mut targets = Vec::new();
57
58 if let Some(target) = to_lib_target(
59 original_toml.lib.as_ref(),
60 normalized_toml.lib.as_ref(),
61 package_root,
62 edition,
63 warnings,
64 )? {
65 targets.push(target);
66 }
67
68 let package = normalized_toml
69 .package
70 .as_ref()
71 .ok_or_else(|| anyhow::format_err!("manifest has no `package` (or `project`)"))?;
72
73 targets.extend(to_bin_targets(
74 features,
75 normalized_toml.bin.as_deref().unwrap_or_default(),
76 package_root,
77 edition,
78 warnings,
79 )?);
80
81 targets.extend(to_example_targets(
82 normalized_toml.example.as_deref().unwrap_or_default(),
83 package_root,
84 edition,
85 warnings,
86 )?);
87
88 targets.extend(to_test_targets(
89 normalized_toml.test.as_deref().unwrap_or_default(),
90 package_root,
91 edition,
92 warnings,
93 )?);
94
95 targets.extend(to_bench_targets(
96 normalized_toml.bench.as_deref().unwrap_or_default(),
97 package_root,
98 edition,
99 warnings,
100 )?);
101
102 if let Some(custom_build) = package.normalized_build().expect("previously normalized") {
104 if metabuild.is_some() {
105 anyhow::bail!("cannot specify both `metabuild` and `build`");
106 }
107 for script in custom_build {
108 let script_path = Path::new(script);
109 let name = format!(
110 "build-script-{}",
111 script_path
112 .file_stem()
113 .and_then(|s| s.to_str())
114 .unwrap_or("")
115 );
116 targets.push(Target::custom_build_target(
117 &name,
118 package_root.join(script_path),
119 edition,
120 ));
121 }
122 }
123 if let Some(metabuild) = metabuild {
124 let bdeps = normalized_toml.build_dependencies.as_ref();
126 for name in &metabuild.0 {
127 if !bdeps.map_or(false, |bd| bd.contains_key(name.as_str())) {
128 anyhow::bail!(
129 "metabuild package `{}` must be specified in `build-dependencies`",
130 name
131 );
132 }
133 }
134
135 targets.push(Target::metabuild_target(&format!(
136 "metabuild-{}",
137 package.normalized_name().expect("previously normalized")
138 )));
139 }
140
141 Ok(targets)
142}
143
144#[tracing::instrument(skip_all)]
145pub fn normalize_lib(
146 original_lib: Option<&TomlLibTarget>,
147 package_root: &Path,
148 package_name: &str,
149 edition: Edition,
150 autodiscover: Option<bool>,
151 warnings: &mut Vec<String>,
152) -> CargoResult<Option<TomlLibTarget>> {
153 if is_normalized(original_lib, autodiscover) {
154 let Some(mut lib) = original_lib.cloned() else {
155 return Ok(None);
156 };
157
158 validate_lib_name(&lib, warnings)?;
160
161 validate_proc_macro(&lib, TARGET_KIND_HUMAN_LIB, edition, warnings)?;
162 validate_crate_types(&lib, TARGET_KIND_HUMAN_LIB, edition, warnings)?;
163
164 if let Some(PathValue(path)) = &lib.path {
165 lib.path = Some(PathValue(paths::normalize_path(path).into()));
166 }
167
168 Ok(Some(lib))
169 } else {
170 let inferred = inferred_lib(package_root);
171 let lib = original_lib.cloned().or_else(|| {
172 inferred.as_ref().map(|lib| TomlTarget {
173 path: Some(PathValue(lib.clone())),
174 ..TomlTarget::new()
175 })
176 });
177 let Some(mut lib) = lib else { return Ok(None) };
178 lib.name
179 .get_or_insert_with(|| package_name.replace("-", "_"));
180
181 validate_lib_name(&lib, warnings)?;
183
184 validate_proc_macro(&lib, TARGET_KIND_HUMAN_LIB, edition, warnings)?;
185 validate_crate_types(&lib, TARGET_KIND_HUMAN_LIB, edition, warnings)?;
186
187 if lib.path.is_none() {
188 if let Some(inferred) = inferred {
189 lib.path = Some(PathValue(inferred));
190 } else {
191 let name = name_or_panic(&lib);
192 let legacy_path = Path::new("src").join(format!("{name}.rs"));
193 if edition == Edition::Edition2015 && package_root.join(&legacy_path).exists() {
194 warnings.push(format!(
195 "path `{}` was erroneously implicitly accepted for library `{name}`,\n\
196 please rename the file to `src/lib.rs` or set lib.path in Cargo.toml",
197 legacy_path.display(),
198 ));
199 lib.path = Some(PathValue(legacy_path));
200 } else {
201 anyhow::bail!(
202 "can't find library `{name}`, \
203 rename file to `src/lib.rs` or specify lib.path",
204 )
205 }
206 }
207 }
208
209 if let Some(PathValue(path)) = lib.path.as_ref() {
210 lib.path = Some(PathValue(paths::normalize_path(&path).into()));
211 }
212
213 Ok(Some(lib))
214 }
215}
216
217#[tracing::instrument(skip_all)]
218fn to_lib_target(
219 original_lib: Option<&TomlLibTarget>,
220 normalized_lib: Option<&TomlLibTarget>,
221 package_root: &Path,
222 edition: Edition,
223 warnings: &mut Vec<String>,
224) -> CargoResult<Option<Target>> {
225 let Some(lib) = normalized_lib else {
226 return Ok(None);
227 };
228
229 let path = lib.path.as_ref().expect("previously normalized");
230 let path = package_root.join(&path.0);
231
232 let crate_types = match (lib.crate_types(), lib.proc_macro()) {
242 (Some(kinds), _)
243 if kinds.contains(&CrateType::Dylib.as_str().to_owned())
244 && kinds.contains(&CrateType::Cdylib.as_str().to_owned()) =>
245 {
246 anyhow::bail!(format!(
247 "library `{}` cannot set the crate type of both `dylib` and `cdylib`",
248 name_or_panic(lib)
249 ));
250 }
251 (Some(kinds), _) if kinds.contains(&"proc-macro".to_string()) => {
252 warnings.push(format!(
253 "library `{}` should only specify `proc-macro = true` instead of setting `crate-type`",
254 name_or_panic(lib)
255 ));
256 if kinds.len() > 1 {
257 anyhow::bail!("cannot mix `proc-macro` crate type with others");
258 }
259 vec![CrateType::ProcMacro]
260 }
261 (Some(kinds), _) => kinds.iter().map(|s| s.into()).collect(),
262 (None, Some(true)) => vec![CrateType::ProcMacro],
263 (None, _) => vec![CrateType::Lib],
264 };
265
266 let mut target = Target::lib_target(name_or_panic(lib), crate_types, path, edition);
267 configure(lib, &mut target, TARGET_KIND_HUMAN_LIB, warnings)?;
268 target.set_name_inferred(original_lib.map_or(true, |v| v.name.is_none()));
269 Ok(Some(target))
270}
271
272#[tracing::instrument(skip_all)]
273pub fn normalize_bins(
274 toml_bins: Option<&Vec<TomlBinTarget>>,
275 package_root: &Path,
276 package_name: &str,
277 edition: Edition,
278 autodiscover: Option<bool>,
279 warnings: &mut Vec<String>,
280 errors: &mut Vec<String>,
281 has_lib: bool,
282) -> CargoResult<Vec<TomlBinTarget>> {
283 if are_normalized(toml_bins, autodiscover) {
284 let mut toml_bins = toml_bins.cloned().unwrap_or_default();
285 for bin in toml_bins.iter_mut() {
286 validate_bin_name(bin, warnings)?;
287 validate_bin_crate_types(bin, edition, warnings, errors)?;
288 validate_bin_proc_macro(bin, edition, warnings, errors)?;
289
290 if let Some(PathValue(path)) = &bin.path {
291 bin.path = Some(PathValue(paths::normalize_path(path).into()));
292 }
293 }
294 Ok(toml_bins)
295 } else {
296 let inferred = inferred_bins(package_root, package_name);
297
298 let mut bins = toml_targets_and_inferred(
299 toml_bins,
300 &inferred,
301 package_root,
302 autodiscover,
303 edition,
304 warnings,
305 TARGET_KIND_HUMAN_BIN,
306 TARGET_KIND_BIN,
307 "autobins",
308 );
309
310 for bin in &mut bins {
311 validate_bin_name(bin, warnings)?;
313
314 validate_bin_crate_types(bin, edition, warnings, errors)?;
315 validate_bin_proc_macro(bin, edition, warnings, errors)?;
316
317 let path = target_path(
318 bin,
319 &inferred,
320 TARGET_KIND_BIN,
321 package_root,
322 edition,
323 &mut |_| {
324 if let Some(legacy_path) =
325 legacy_bin_path(package_root, name_or_panic(bin), has_lib)
326 {
327 warnings.push(format!(
328 "path `{}` was erroneously implicitly accepted for binary `{}`,\n\
329 please set bin.path in Cargo.toml",
330 legacy_path.display(),
331 name_or_panic(bin)
332 ));
333 Some(legacy_path)
334 } else {
335 None
336 }
337 },
338 );
339 let path = match path {
340 Ok(path) => paths::normalize_path(&path).into(),
341 Err(e) => anyhow::bail!("{}", e),
342 };
343 bin.path = Some(PathValue(path));
344 }
345
346 Ok(bins)
347 }
348}
349
350#[tracing::instrument(skip_all)]
351fn to_bin_targets(
352 features: &Features,
353 bins: &[TomlBinTarget],
354 package_root: &Path,
355 edition: Edition,
356 warnings: &mut Vec<String>,
357) -> CargoResult<Vec<Target>> {
358 for bin in bins {
360 if bin.filename.is_some() {
363 features.require(Feature::different_binary_name())?;
364 }
365 }
366
367 validate_unique_names(&bins, TARGET_KIND_HUMAN_BIN)?;
368
369 let mut result = Vec::new();
370 for bin in bins {
371 let path = package_root.join(&bin.path.as_ref().expect("previously normalized").0);
372 let mut target = Target::bin_target(
373 name_or_panic(bin),
374 bin.filename.clone(),
375 path,
376 bin.required_features.clone(),
377 edition,
378 );
379
380 configure(bin, &mut target, TARGET_KIND_HUMAN_BIN, warnings)?;
381 result.push(target);
382 }
383 Ok(result)
384}
385
386fn legacy_bin_path(package_root: &Path, name: &str, has_lib: bool) -> Option<PathBuf> {
387 if !has_lib {
388 let rel_path = Path::new("src").join(format!("{}.rs", name));
389 if package_root.join(&rel_path).exists() {
390 return Some(rel_path);
391 }
392 }
393
394 let rel_path = Path::new("src").join("main.rs");
395 if package_root.join(&rel_path).exists() {
396 return Some(rel_path);
397 }
398
399 let default_bin_dir_name = Path::new("src").join("bin");
400 let rel_path = default_bin_dir_name.join("main.rs");
401 if package_root.join(&rel_path).exists() {
402 return Some(rel_path);
403 }
404 None
405}
406
407#[tracing::instrument(skip_all)]
408pub fn normalize_examples(
409 toml_examples: Option<&Vec<TomlExampleTarget>>,
410 package_root: &Path,
411 edition: Edition,
412 autodiscover: Option<bool>,
413 warnings: &mut Vec<String>,
414 errors: &mut Vec<String>,
415) -> CargoResult<Vec<TomlExampleTarget>> {
416 let mut inferred = || infer_from_directory(&package_root, Path::new(DEFAULT_EXAMPLE_DIR_NAME));
417
418 let targets = normalize_targets(
419 TARGET_KIND_HUMAN_EXAMPLE,
420 TARGET_KIND_EXAMPLE,
421 toml_examples,
422 &mut inferred,
423 package_root,
424 edition,
425 autodiscover,
426 warnings,
427 errors,
428 "autoexamples",
429 )?;
430
431 Ok(targets)
432}
433
434#[tracing::instrument(skip_all)]
435fn to_example_targets(
436 targets: &[TomlExampleTarget],
437 package_root: &Path,
438 edition: Edition,
439 warnings: &mut Vec<String>,
440) -> CargoResult<Vec<Target>> {
441 validate_unique_names(&targets, TARGET_KIND_EXAMPLE)?;
442
443 let mut result = Vec::new();
444 for toml in targets {
445 let path = package_root.join(&toml.path.as_ref().expect("previously normalized").0);
446 let crate_types = match toml.crate_types() {
447 Some(kinds) => kinds.iter().map(|s| s.into()).collect(),
448 None => Vec::new(),
449 };
450
451 let mut target = Target::example_target(
452 name_or_panic(&toml),
453 crate_types,
454 path,
455 toml.required_features.clone(),
456 edition,
457 );
458 configure(&toml, &mut target, TARGET_KIND_HUMAN_EXAMPLE, warnings)?;
459 result.push(target);
460 }
461
462 Ok(result)
463}
464
465#[tracing::instrument(skip_all)]
466pub fn normalize_tests(
467 toml_tests: Option<&Vec<TomlTestTarget>>,
468 package_root: &Path,
469 edition: Edition,
470 autodiscover: Option<bool>,
471 warnings: &mut Vec<String>,
472 errors: &mut Vec<String>,
473) -> CargoResult<Vec<TomlTestTarget>> {
474 let mut inferred = || infer_from_directory(&package_root, Path::new(DEFAULT_TEST_DIR_NAME));
475
476 let targets = normalize_targets(
477 TARGET_KIND_HUMAN_TEST,
478 TARGET_KIND_TEST,
479 toml_tests,
480 &mut inferred,
481 package_root,
482 edition,
483 autodiscover,
484 warnings,
485 errors,
486 "autotests",
487 )?;
488
489 Ok(targets)
490}
491
492#[tracing::instrument(skip_all)]
493fn to_test_targets(
494 targets: &[TomlTestTarget],
495 package_root: &Path,
496 edition: Edition,
497 warnings: &mut Vec<String>,
498) -> CargoResult<Vec<Target>> {
499 validate_unique_names(&targets, TARGET_KIND_TEST)?;
500
501 let mut result = Vec::new();
502 for toml in targets {
503 let path = package_root.join(&toml.path.as_ref().expect("previously normalized").0);
504 let mut target = Target::test_target(
505 name_or_panic(&toml),
506 path,
507 toml.required_features.clone(),
508 edition,
509 );
510 configure(&toml, &mut target, TARGET_KIND_HUMAN_TEST, warnings)?;
511 result.push(target);
512 }
513 Ok(result)
514}
515
516#[tracing::instrument(skip_all)]
517pub fn normalize_benches(
518 toml_benches: Option<&Vec<TomlBenchTarget>>,
519 package_root: &Path,
520 edition: Edition,
521 autodiscover: Option<bool>,
522 warnings: &mut Vec<String>,
523 errors: &mut Vec<String>,
524) -> CargoResult<Vec<TomlBenchTarget>> {
525 let mut legacy_warnings = vec![];
526 let mut legacy_bench_path = |bench: &TomlTarget| {
527 let legacy_path = Path::new("src").join("bench.rs");
528 if !(name_or_panic(bench) == "bench" && package_root.join(&legacy_path).exists()) {
529 return None;
530 }
531 legacy_warnings.push(format!(
532 "path `{}` was erroneously implicitly accepted for benchmark `{}`,\n\
533 please set bench.path in Cargo.toml",
534 legacy_path.display(),
535 name_or_panic(bench)
536 ));
537 Some(legacy_path)
538 };
539
540 let mut inferred = || infer_from_directory(&package_root, Path::new(DEFAULT_BENCH_DIR_NAME));
541
542 let targets = normalize_targets_with_legacy_path(
543 TARGET_KIND_HUMAN_BENCH,
544 TARGET_KIND_BENCH,
545 toml_benches,
546 &mut inferred,
547 package_root,
548 edition,
549 autodiscover,
550 warnings,
551 errors,
552 &mut legacy_bench_path,
553 "autobenches",
554 )?;
555 warnings.append(&mut legacy_warnings);
556
557 Ok(targets)
558}
559
560#[tracing::instrument(skip_all)]
561fn to_bench_targets(
562 targets: &[TomlBenchTarget],
563 package_root: &Path,
564 edition: Edition,
565 warnings: &mut Vec<String>,
566) -> CargoResult<Vec<Target>> {
567 validate_unique_names(&targets, TARGET_KIND_BENCH)?;
568
569 let mut result = Vec::new();
570 for toml in targets {
571 let path = package_root.join(&toml.path.as_ref().expect("previously normalized").0);
572 let mut target = Target::bench_target(
573 name_or_panic(&toml),
574 path,
575 toml.required_features.clone(),
576 edition,
577 );
578 configure(&toml, &mut target, TARGET_KIND_HUMAN_BENCH, warnings)?;
579 result.push(target);
580 }
581
582 Ok(result)
583}
584
585fn is_normalized(toml_target: Option<&TomlTarget>, autodiscover: Option<bool>) -> bool {
586 are_normalized_(toml_target.map(std::slice::from_ref), autodiscover)
587}
588
589fn are_normalized(toml_targets: Option<&Vec<TomlTarget>>, autodiscover: Option<bool>) -> bool {
590 are_normalized_(toml_targets.map(|v| v.as_slice()), autodiscover)
591}
592
593fn are_normalized_(toml_targets: Option<&[TomlTarget]>, autodiscover: Option<bool>) -> bool {
594 if autodiscover != Some(false) {
595 return false;
596 }
597
598 let Some(toml_targets) = toml_targets else {
599 return true;
600 };
601 toml_targets
602 .iter()
603 .all(|t| t.name.is_some() && t.path.is_some())
604}
605
606fn normalize_targets(
607 target_kind_human: &str,
608 target_kind: &str,
609 toml_targets: Option<&Vec<TomlTarget>>,
610 inferred: &mut dyn FnMut() -> Vec<(String, PathBuf)>,
611 package_root: &Path,
612 edition: Edition,
613 autodiscover: Option<bool>,
614 warnings: &mut Vec<String>,
615 errors: &mut Vec<String>,
616 autodiscover_flag_name: &str,
617) -> CargoResult<Vec<TomlTarget>> {
618 normalize_targets_with_legacy_path(
619 target_kind_human,
620 target_kind,
621 toml_targets,
622 inferred,
623 package_root,
624 edition,
625 autodiscover,
626 warnings,
627 errors,
628 &mut |_| None,
629 autodiscover_flag_name,
630 )
631}
632
633fn normalize_targets_with_legacy_path(
634 target_kind_human: &str,
635 target_kind: &str,
636 toml_targets: Option<&Vec<TomlTarget>>,
637 inferred: &mut dyn FnMut() -> Vec<(String, PathBuf)>,
638 package_root: &Path,
639 edition: Edition,
640 autodiscover: Option<bool>,
641 warnings: &mut Vec<String>,
642 errors: &mut Vec<String>,
643 legacy_path: &mut dyn FnMut(&TomlTarget) -> Option<PathBuf>,
644 autodiscover_flag_name: &str,
645) -> CargoResult<Vec<TomlTarget>> {
646 if are_normalized(toml_targets, autodiscover) {
647 let mut toml_targets = toml_targets.cloned().unwrap_or_default();
648 for target in toml_targets.iter_mut() {
649 validate_target_name(target, target_kind_human, target_kind, warnings)?;
651
652 validate_proc_macro(target, target_kind_human, edition, warnings)?;
653 validate_crate_types(target, target_kind_human, edition, warnings)?;
654
655 if let Some(PathValue(path)) = &target.path {
656 target.path = Some(PathValue(paths::normalize_path(path).into()));
657 }
658 }
659 Ok(toml_targets)
660 } else {
661 let inferred = inferred();
662 let toml_targets = toml_targets_and_inferred(
663 toml_targets,
664 &inferred,
665 package_root,
666 autodiscover,
667 edition,
668 warnings,
669 target_kind_human,
670 target_kind,
671 autodiscover_flag_name,
672 );
673
674 for target in &toml_targets {
675 validate_target_name(target, target_kind_human, target_kind, warnings)?;
677
678 validate_proc_macro(target, target_kind_human, edition, warnings)?;
679 validate_crate_types(target, target_kind_human, edition, warnings)?;
680 }
681
682 let mut result = Vec::new();
683 for mut target in toml_targets {
684 let path = target_path(
685 &target,
686 &inferred,
687 target_kind,
688 package_root,
689 edition,
690 legacy_path,
691 );
692 let path = match path {
693 Ok(path) => path,
694 Err(e) => {
695 errors.push(e);
696 continue;
697 }
698 };
699 target.path = Some(PathValue(paths::normalize_path(&path).into()));
700 result.push(target);
701 }
702 Ok(result)
703 }
704}
705
706fn inferred_lib(package_root: &Path) -> Option<PathBuf> {
707 let lib = Path::new("src").join("lib.rs");
708 if package_root.join(&lib).exists() {
709 Some(lib)
710 } else {
711 None
712 }
713}
714
715fn inferred_bins(package_root: &Path, package_name: &str) -> Vec<(String, PathBuf)> {
716 let main = "src/main.rs";
717 let mut result = Vec::new();
718 if package_root.join(main).exists() {
719 let main = PathBuf::from(main);
720 result.push((package_name.to_string(), main));
721 }
722 let default_bin_dir_name = Path::new("src").join("bin");
723 result.extend(infer_from_directory(package_root, &default_bin_dir_name));
724
725 result
726}
727
728fn infer_from_directory(package_root: &Path, relpath: &Path) -> Vec<(String, PathBuf)> {
729 let directory = package_root.join(relpath);
730 let entries = match fs::read_dir(directory) {
731 Err(_) => return Vec::new(),
732 Ok(dir) => dir,
733 };
734
735 entries
736 .filter_map(|e| e.ok())
737 .filter(is_not_dotfile)
738 .filter_map(|d| infer_any(package_root, &d))
739 .collect()
740}
741
742fn infer_any(package_root: &Path, entry: &DirEntry) -> Option<(String, PathBuf)> {
743 if entry.file_type().map_or(false, |t| t.is_dir()) {
744 infer_subdirectory(package_root, entry)
745 } else if entry.path().extension().and_then(|p| p.to_str()) == Some("rs") {
746 infer_file(package_root, entry)
747 } else {
748 None
749 }
750}
751
752fn infer_file(package_root: &Path, entry: &DirEntry) -> Option<(String, PathBuf)> {
753 let path = entry.path();
754 let stem = path.file_stem()?.to_str()?.to_owned();
755 let path = path
756 .strip_prefix(package_root)
757 .map(|p| p.to_owned())
758 .unwrap_or(path);
759 Some((stem, path))
760}
761
762fn infer_subdirectory(package_root: &Path, entry: &DirEntry) -> Option<(String, PathBuf)> {
763 let path = entry.path();
764 let main = path.join("main.rs");
765 let name = path.file_name()?.to_str()?.to_owned();
766 if main.exists() {
767 let main = main
768 .strip_prefix(package_root)
769 .map(|p| p.to_owned())
770 .unwrap_or(main);
771 Some((name, main))
772 } else {
773 None
774 }
775}
776
777fn is_not_dotfile(entry: &DirEntry) -> bool {
778 entry.file_name().to_str().map(|s| s.starts_with('.')) == Some(false)
779}
780
781fn toml_targets_and_inferred(
782 toml_targets: Option<&Vec<TomlTarget>>,
783 inferred: &[(String, PathBuf)],
784 package_root: &Path,
785 autodiscover: Option<bool>,
786 edition: Edition,
787 warnings: &mut Vec<String>,
788 target_kind_human: &str,
789 target_kind: &str,
790 autodiscover_flag_name: &str,
791) -> Vec<TomlTarget> {
792 let inferred_targets = inferred_to_toml_targets(inferred);
793 let mut toml_targets = match toml_targets {
794 None => {
795 if let Some(false) = autodiscover {
796 vec![]
797 } else {
798 inferred_targets
799 }
800 }
801 Some(targets) => {
802 let mut targets = targets.clone();
803
804 let target_path =
805 |target: &TomlTarget| target.path.clone().map(|p| package_root.join(p.0));
806
807 let mut seen_names = HashSet::new();
808 let mut seen_paths = HashSet::new();
809 for target in targets.iter() {
810 seen_names.insert(target.name.clone());
811 seen_paths.insert(target_path(target));
812 }
813
814 let mut rem_targets = vec![];
815 for target in inferred_targets {
816 if !seen_names.contains(&target.name) && !seen_paths.contains(&target_path(&target))
817 {
818 rem_targets.push(target);
819 }
820 }
821
822 let autodiscover = match autodiscover {
823 Some(autodiscover) => autodiscover,
824 None => {
825 if edition == Edition::Edition2015 {
826 if !rem_targets.is_empty() {
827 let mut rem_targets_str = String::new();
828 for t in rem_targets.iter() {
829 if let Some(p) = t.path.clone() {
830 rem_targets_str.push_str(&format!("* {}\n", p.0.display()))
831 }
832 }
833 warnings.push(format!(
834 "\
835An explicit [[{section}]] section is specified in Cargo.toml which currently
836disables Cargo from automatically inferring other {target_kind_human} targets.
837This inference behavior will change in the Rust 2018 edition and the following
838files will be included as a {target_kind_human} target:
839
840{rem_targets_str}
841This is likely to break cargo build or cargo test as these files may not be
842ready to be compiled as a {target_kind_human} target today. You can future-proof yourself
843and disable this warning by adding `{autodiscover_flag_name} = false` to your [package]
844section. You may also move the files to a location where Cargo would not
845automatically infer them to be a target, such as in subfolders.
846
847For more information on this warning you can consult
848https://github.com/rust-lang/cargo/issues/5330",
849 section = target_kind,
850 target_kind_human = target_kind_human,
851 rem_targets_str = rem_targets_str,
852 autodiscover_flag_name = autodiscover_flag_name,
853 ));
854 };
855 false
856 } else {
857 true
858 }
859 }
860 };
861
862 if autodiscover {
863 targets.append(&mut rem_targets);
864 }
865
866 targets
867 }
868 };
869 toml_targets.sort_unstable_by_key(|t| t.name.clone());
874 toml_targets
875}
876
877fn inferred_to_toml_targets(inferred: &[(String, PathBuf)]) -> Vec<TomlTarget> {
878 inferred
879 .iter()
880 .map(|(name, path)| TomlTarget {
881 name: Some(name.clone()),
882 path: Some(PathValue(path.clone())),
883 ..TomlTarget::new()
884 })
885 .collect()
886}
887
888fn validate_unique_names(targets: &[TomlTarget], target_kind: &str) -> CargoResult<()> {
890 let mut seen = HashSet::new();
891 for name in targets.iter().map(|e| name_or_panic(e)) {
892 if !seen.insert(name) {
893 anyhow::bail!(
894 "found duplicate {target_kind} name {name}, \
895 but all {target_kind} targets must have a unique name",
896 target_kind = target_kind,
897 name = name
898 );
899 }
900 }
901 Ok(())
902}
903
904fn configure(
905 toml: &TomlTarget,
906 target: &mut Target,
907 target_kind_human: &str,
908 warnings: &mut Vec<String>,
909) -> CargoResult<()> {
910 let t2 = target.clone();
911 target
912 .set_tested(toml.test.unwrap_or_else(|| t2.tested()))
913 .set_doc(toml.doc.unwrap_or_else(|| t2.documented()))
914 .set_doctest(toml.doctest.unwrap_or_else(|| t2.doctested()))
915 .set_benched(toml.bench.unwrap_or_else(|| t2.benched()))
916 .set_harness(toml.harness.unwrap_or_else(|| t2.harness()))
917 .set_proc_macro(toml.proc_macro().unwrap_or_else(|| t2.proc_macro()))
918 .set_doc_scrape_examples(match toml.doc_scrape_examples {
919 None => RustdocScrapeExamples::Unset,
920 Some(false) => RustdocScrapeExamples::Disabled,
921 Some(true) => RustdocScrapeExamples::Enabled,
922 })
923 .set_for_host(toml.proc_macro().unwrap_or_else(|| t2.for_host()));
924
925 if let Some(edition) = toml.edition.clone() {
926 let name = target.name();
927 warnings.push(format!(
928 "`edition` is set on {target_kind_human} `{name}` which is deprecated"
929 ));
930 target.set_edition(
931 edition
932 .parse()
933 .context("failed to parse the `edition` key")?,
934 );
935 }
936 Ok(())
937}
938
939fn target_path_not_found_error_message(
951 package_root: &Path,
952 target: &TomlTarget,
953 target_kind: &str,
954 inferred: &[(String, PathBuf)],
955) -> String {
956 fn possible_target_paths(name: &str, kind: &str, commonly_wrong: bool) -> [PathBuf; 2] {
957 let mut target_path = PathBuf::new();
958 match (kind, commonly_wrong) {
959 ("test" | "bench" | "example", true) => target_path.push(kind),
961 ("bin", true) => target_path.extend(["src", "bins"]),
962 ("test", false) => target_path.push(DEFAULT_TEST_DIR_NAME),
964 ("bench", false) => target_path.push(DEFAULT_BENCH_DIR_NAME),
965 ("example", false) => target_path.push(DEFAULT_EXAMPLE_DIR_NAME),
966 ("bin", false) => target_path.extend(["src", "bin"]),
967 _ => unreachable!("invalid target kind: {}", kind),
968 }
969
970 let target_path_file = {
971 let mut path = target_path.clone();
972 path.push(format!("{name}.rs"));
973 path
974 };
975 let target_path_subdir = {
976 target_path.extend([name, "main.rs"]);
977 target_path
978 };
979 return [target_path_file, target_path_subdir];
980 }
981
982 let target_name = name_or_panic(target);
983
984 let commonly_wrong_paths = possible_target_paths(&target_name, target_kind, true);
985 let possible_paths = possible_target_paths(&target_name, target_kind, false);
986
987 let msg = closest_msg(target_name, inferred.iter(), |(n, _p)| n, target_kind);
988 if let Some((wrong_path, possible_path)) = commonly_wrong_paths
989 .iter()
990 .zip(possible_paths.iter())
991 .filter(|(wp, _)| package_root.join(wp).exists())
992 .next()
993 {
994 let [wrong_path, possible_path] = [wrong_path, possible_path].map(|p| p.display());
995 format!(
996 "can't find `{target_name}` {target_kind} at default paths, but found a file at `{wrong_path}`.\n\
997 Perhaps rename the file to `{possible_path}` for target auto-discovery, \
998 or specify {target_kind}.path if you want to use a non-default path.{msg}",
999 )
1000 } else {
1001 let [path_file, path_dir] = possible_paths.each_ref().map(|p| p.display());
1002 format!(
1003 "can't find `{target_name}` {target_kind} at `{path_file}` or `{path_dir}`. \
1004 Please specify {target_kind}.path if you want to use a non-default path.{msg}"
1005 )
1006 }
1007}
1008
1009fn target_path(
1010 target: &TomlTarget,
1011 inferred: &[(String, PathBuf)],
1012 target_kind: &str,
1013 package_root: &Path,
1014 edition: Edition,
1015 legacy_path: &mut dyn FnMut(&TomlTarget) -> Option<PathBuf>,
1016) -> Result<PathBuf, String> {
1017 if let Some(ref path) = target.path {
1018 return Ok(path.0.clone());
1020 }
1021 let name = name_or_panic(target).to_owned();
1022
1023 let mut matching = inferred
1024 .iter()
1025 .filter(|(n, _)| n == &name)
1026 .map(|(_, p)| p.clone());
1027
1028 let first = matching.next();
1029 let second = matching.next();
1030 match (first, second) {
1031 (Some(path), None) => Ok(path),
1032 (None, None) => {
1033 if edition == Edition::Edition2015 {
1034 if let Some(path) = legacy_path(target) {
1035 return Ok(path);
1036 }
1037 }
1038 Err(target_path_not_found_error_message(
1039 package_root,
1040 target,
1041 target_kind,
1042 inferred,
1043 ))
1044 }
1045 (Some(p0), Some(p1)) => {
1046 if edition == Edition::Edition2015 {
1047 if let Some(path) = legacy_path(target) {
1048 return Ok(path);
1049 }
1050 }
1051 Err(format!(
1052 "\
1053cannot infer path for `{}` {}
1054Cargo doesn't know which to use because multiple target files found at `{}` and `{}`.",
1055 name_or_panic(target),
1056 target_kind,
1057 p0.strip_prefix(package_root).unwrap_or(&p0).display(),
1058 p1.strip_prefix(package_root).unwrap_or(&p1).display(),
1059 ))
1060 }
1061 (None, Some(_)) => unreachable!(),
1062 }
1063}
1064
1065#[tracing::instrument(skip_all)]
1067pub fn normalize_build(
1068 build: Option<&TomlPackageBuild>,
1069 package_root: &Path,
1070) -> CargoResult<Option<TomlPackageBuild>> {
1071 const BUILD_RS: &str = "build.rs";
1072 match build {
1073 None => {
1074 let build_rs = package_root.join(BUILD_RS);
1077 if build_rs.is_file() {
1078 Ok(Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned())))
1079 } else {
1080 Ok(Some(TomlPackageBuild::Auto(false)))
1081 }
1082 }
1083 Some(TomlPackageBuild::Auto(false)) => Ok(build.cloned()),
1085 Some(TomlPackageBuild::SingleScript(build_file)) => {
1086 let build_file = paths::normalize_path(Path::new(build_file));
1087 let build = build_file.into_os_string().into_string().expect(
1088 "`build_file` started as a String and `normalize_path` shouldn't have changed that",
1089 );
1090 Ok(Some(TomlPackageBuild::SingleScript(build)))
1091 }
1092 Some(TomlPackageBuild::Auto(true)) => {
1093 Ok(Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned())))
1094 }
1095 Some(TomlPackageBuild::MultipleScript(_scripts)) => Ok(build.cloned()),
1096 }
1097}
1098
1099fn name_or_panic(target: &TomlTarget) -> &str {
1100 target
1101 .name
1102 .as_deref()
1103 .unwrap_or_else(|| panic!("target name is required"))
1104}
1105
1106fn validate_lib_name(target: &TomlTarget, warnings: &mut Vec<String>) -> CargoResult<()> {
1107 validate_target_name(target, TARGET_KIND_HUMAN_LIB, TARGET_KIND_LIB, warnings)?;
1108 let name = name_or_panic(target);
1109 if name.contains('-') {
1110 anyhow::bail!("library target names cannot contain hyphens: {}", name)
1111 }
1112
1113 Ok(())
1114}
1115
1116fn validate_bin_name(bin: &TomlTarget, warnings: &mut Vec<String>) -> CargoResult<()> {
1117 validate_target_name(bin, TARGET_KIND_HUMAN_BIN, TARGET_KIND_BIN, warnings)?;
1118 let name = name_or_panic(bin).to_owned();
1119 if restricted_names::is_conflicting_artifact_name(&name) {
1120 anyhow::bail!(
1121 "the binary target name `{name}` is forbidden, \
1122 it conflicts with cargo's build directory names",
1123 )
1124 }
1125
1126 Ok(())
1127}
1128
1129fn validate_target_name(
1130 target: &TomlTarget,
1131 target_kind_human: &str,
1132 target_kind: &str,
1133 warnings: &mut Vec<String>,
1134) -> CargoResult<()> {
1135 match target.name {
1136 Some(ref name) => {
1137 if name.trim().is_empty() {
1138 anyhow::bail!("{} target names cannot be empty", target_kind_human)
1139 }
1140 if cfg!(windows) && restricted_names::is_windows_reserved(name) {
1141 warnings.push(format!(
1142 "{} target `{}` is a reserved Windows filename, \
1143 this target will not work on Windows platforms",
1144 target_kind_human, name
1145 ));
1146 }
1147 }
1148 None => anyhow::bail!(
1149 "{} target {}.name is required",
1150 target_kind_human,
1151 target_kind
1152 ),
1153 }
1154
1155 Ok(())
1156}
1157
1158fn validate_bin_proc_macro(
1159 target: &TomlTarget,
1160 edition: Edition,
1161 warnings: &mut Vec<String>,
1162 errors: &mut Vec<String>,
1163) -> CargoResult<()> {
1164 if target.proc_macro() == Some(true) {
1165 let name = name_or_panic(target);
1166 errors.push(format!(
1167 "the target `{}` is a binary and can't have `proc-macro` \
1168 set `true`",
1169 name
1170 ));
1171 } else {
1172 validate_proc_macro(target, TARGET_KIND_HUMAN_BIN, edition, warnings)?;
1173 }
1174 Ok(())
1175}
1176
1177fn validate_proc_macro(
1178 target: &TomlTarget,
1179 kind: &str,
1180 edition: Edition,
1181 warnings: &mut Vec<String>,
1182) -> CargoResult<()> {
1183 deprecated_underscore(
1184 &target.proc_macro2,
1185 &target.proc_macro,
1186 "proc-macro",
1187 name_or_panic(target),
1188 format!("{kind} target").as_str(),
1189 edition,
1190 warnings,
1191 )
1192}
1193
1194fn validate_bin_crate_types(
1195 target: &TomlTarget,
1196 edition: Edition,
1197 warnings: &mut Vec<String>,
1198 errors: &mut Vec<String>,
1199) -> CargoResult<()> {
1200 if let Some(crate_types) = target.crate_types() {
1201 if !crate_types.is_empty() {
1202 let name = name_or_panic(target);
1203 errors.push(format!(
1204 "the target `{}` is a binary and can't have any \
1205 crate-types set (currently \"{}\")",
1206 name,
1207 crate_types.join(", ")
1208 ));
1209 } else {
1210 validate_crate_types(target, TARGET_KIND_HUMAN_BIN, edition, warnings)?;
1211 }
1212 }
1213 Ok(())
1214}
1215
1216fn validate_crate_types(
1217 target: &TomlTarget,
1218 kind: &str,
1219 edition: Edition,
1220 warnings: &mut Vec<String>,
1221) -> CargoResult<()> {
1222 deprecated_underscore(
1223 &target.crate_type2,
1224 &target.crate_type,
1225 "crate-type",
1226 name_or_panic(target),
1227 format!("{kind} target").as_str(),
1228 edition,
1229 warnings,
1230 )
1231}