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