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