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