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