1use crate::util::data_structures::{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::compiler::{CrateType, rustdoc::RustdocScrapeExamples};
26use crate::util::{closest_msg, errors::CargoResult, restricted_names};
27use crate::workspace::parser::deprecated_underscore;
28use crate::workspace::{Edition, Feature, Features, Target};
29
30const DEFAULT_TEST_DIR_NAME: &'static str = "tests";
31const DEFAULT_BENCH_DIR_NAME: &'static str = "benches";
32const DEFAULT_EXAMPLE_DIR_NAME: &'static str = "examples";
33
34const TARGET_KIND_HUMAN_LIB: &str = "library";
35const TARGET_KIND_HUMAN_BIN: &str = "binary";
36const TARGET_KIND_HUMAN_EXAMPLE: &str = "example";
37const TARGET_KIND_HUMAN_TEST: &str = "test";
38const TARGET_KIND_HUMAN_BENCH: &str = "benchmark";
39
40const TARGET_KIND_LIB: &str = "lib";
41const TARGET_KIND_BIN: &str = "bin";
42const TARGET_KIND_EXAMPLE: &str = "example";
43const TARGET_KIND_TEST: &str = "test";
44const TARGET_KIND_BENCH: &str = "bench";
45
46#[tracing::instrument(skip_all)]
47pub(super) fn to_targets(
48 features: &Features,
49 original_toml: &TomlManifest,
50 normalized_toml: &TomlManifest,
51 package_root: &Path,
52 edition: Edition,
53 metabuild: &Option<StringOrVec>,
54 warnings: &mut Vec<String>,
55) -> CargoResult<Vec<Target>> {
56 let mut targets = Vec::new();
57
58 if let Some(target) = to_lib_target(
59 original_toml.lib.as_ref(),
60 normalized_toml.lib.as_ref(),
61 package_root,
62 edition,
63 warnings,
64 )? {
65 targets.push(target);
66 }
67
68 let package = normalized_toml
69 .package
70 .as_ref()
71 .ok_or_else(|| anyhow::format_err!("manifest has no `package` (or `project`)"))?;
72
73 targets.extend(to_bin_targets(
74 features,
75 normalized_toml.bin.as_deref().unwrap_or_default(),
76 package_root,
77 edition,
78 warnings,
79 )?);
80
81 targets.extend(to_example_targets(
82 normalized_toml.example.as_deref().unwrap_or_default(),
83 package_root,
84 edition,
85 warnings,
86 )?);
87
88 targets.extend(to_test_targets(
89 normalized_toml.test.as_deref().unwrap_or_default(),
90 package_root,
91 edition,
92 warnings,
93 )?);
94
95 targets.extend(to_bench_targets(
96 normalized_toml.bench.as_deref().unwrap_or_default(),
97 package_root,
98 edition,
99 warnings,
100 )?);
101
102 if let Some(custom_build) = package.normalized_build().expect("previously normalized") {
104 if metabuild.is_some() {
105 anyhow::bail!("cannot specify both `metabuild` and `build`");
106 }
107 validate_unique_build_scripts(custom_build)?;
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::with_capacity(bins.len());
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::with_capacity(targets.len());
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::with_capacity(targets.len());
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::with_capacity(targets.len());
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::default();
809 let mut seen_paths = HashSet::default();
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::default();
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 validate_unique_build_scripts(scripts: &[String]) -> CargoResult<()> {
907 let mut seen = HashMap::default();
908 for script in scripts {
909 let stem = Path::new(script).file_stem().unwrap().to_str().unwrap();
910 seen.entry(stem)
911 .or_insert_with(Vec::new)
912 .push(script.as_str());
913 }
914 let mut conflict_file_stem = false;
915 let mut err_msg = String::from(
916 "found build scripts with duplicate file stems, but all build scripts must have a unique file stem",
917 );
918 for (stem, paths) in seen {
919 if paths.len() > 1 {
920 conflict_file_stem = true;
921 write!(&mut err_msg, "\n for stem `{stem}`: {}", paths.join(", "))?;
922 }
923 }
924 if conflict_file_stem {
925 anyhow::bail!(err_msg);
926 }
927 Ok(())
928}
929
930fn configure(
931 toml: &TomlTarget,
932 target: &mut Target,
933 target_kind_human: &str,
934 warnings: &mut Vec<String>,
935) -> CargoResult<()> {
936 let t2 = target.clone();
937 target
938 .set_tested(toml.test.unwrap_or_else(|| t2.tested()))
939 .set_doc(toml.doc.unwrap_or_else(|| t2.documented()))
940 .set_doctest(toml.doctest.unwrap_or_else(|| t2.doctested()))
941 .set_benched(toml.bench.unwrap_or_else(|| t2.benched()))
942 .set_harness(toml.harness.unwrap_or_else(|| t2.harness()))
943 .set_proc_macro(toml.proc_macro().unwrap_or_else(|| t2.proc_macro()))
944 .set_doc_scrape_examples(match toml.doc_scrape_examples {
945 None => RustdocScrapeExamples::Unset,
946 Some(false) => RustdocScrapeExamples::Disabled,
947 Some(true) => RustdocScrapeExamples::Enabled,
948 })
949 .set_for_host(toml.proc_macro().unwrap_or_else(|| t2.for_host()));
950
951 if let Some(edition) = toml.edition.clone() {
952 let name = target.name();
953 warnings.push(format!(
954 "`edition` is set on {target_kind_human} `{name}` which is deprecated"
955 ));
956 target.set_edition(
957 edition
958 .parse()
959 .context("failed to parse the `edition` key")?,
960 );
961 }
962 Ok(())
963}
964
965fn target_path_not_found_error_message(
977 package_root: &Path,
978 target: &TomlTarget,
979 target_kind: &str,
980 inferred: &[(String, PathBuf)],
981) -> String {
982 fn possible_target_paths(name: &str, kind: &str, commonly_wrong: bool) -> [PathBuf; 2] {
983 let mut target_path = PathBuf::new();
984 match (kind, commonly_wrong) {
985 ("test" | "bench" | "example", true) => target_path.push(kind),
987 ("bin", true) => target_path.extend(["src", "bins"]),
988 ("test", false) => target_path.push(DEFAULT_TEST_DIR_NAME),
990 ("bench", false) => target_path.push(DEFAULT_BENCH_DIR_NAME),
991 ("example", false) => target_path.push(DEFAULT_EXAMPLE_DIR_NAME),
992 ("bin", false) => target_path.extend(["src", "bin"]),
993 _ => unreachable!("invalid target kind: {}", kind),
994 }
995
996 let target_path_file = {
997 let mut path = target_path.clone();
998 path.push(format!("{name}.rs"));
999 path
1000 };
1001 let target_path_subdir = {
1002 target_path.extend([name, "main.rs"]);
1003 target_path
1004 };
1005 return [target_path_file, target_path_subdir];
1006 }
1007
1008 let target_name = name_or_panic(target);
1009
1010 let commonly_wrong_paths = possible_target_paths(&target_name, target_kind, true);
1011 let possible_paths = possible_target_paths(&target_name, target_kind, false);
1012
1013 let msg = closest_msg(target_name, inferred.iter(), |(n, _p)| n, target_kind);
1014 if let Some((wrong_path, possible_path)) = commonly_wrong_paths
1015 .iter()
1016 .zip(possible_paths.iter())
1017 .filter(|(wp, _)| package_root.join(wp).exists())
1018 .next()
1019 {
1020 let [wrong_path, possible_path] = [wrong_path, possible_path].map(|p| p.display());
1021 format!(
1022 "can't find `{target_name}` {target_kind} at default paths, but found a file at `{wrong_path}`.\n\
1023 Perhaps rename the file to `{possible_path}` for target auto-discovery, \
1024 or specify {target_kind}.path if you want to use a non-default path.{msg}",
1025 )
1026 } else {
1027 let [path_file, path_dir] = possible_paths.each_ref().map(|p| p.display());
1028 format!(
1029 "can't find `{target_name}` {target_kind} at `{path_file}` or `{path_dir}`. \
1030 Please specify {target_kind}.path if you want to use a non-default path.{msg}"
1031 )
1032 }
1033}
1034
1035fn target_path(
1036 target: &TomlTarget,
1037 inferred: &[(String, PathBuf)],
1038 target_kind: &str,
1039 package_root: &Path,
1040 edition: Edition,
1041 legacy_path: &mut dyn FnMut(&TomlTarget) -> Option<PathBuf>,
1042) -> Result<PathBuf, String> {
1043 if let Some(ref path) = target.path {
1044 return Ok(path.0.clone());
1046 }
1047 let name = name_or_panic(target).to_owned();
1048
1049 let mut matching = inferred
1050 .iter()
1051 .filter(|(n, _)| n == &name)
1052 .map(|(_, p)| p.clone());
1053
1054 let first = matching.next();
1055 let second = matching.next();
1056 match (first, second) {
1057 (Some(path), None) => Ok(path),
1058 (None, None) => {
1059 if edition == Edition::Edition2015 {
1060 if let Some(path) = legacy_path(target) {
1061 return Ok(path);
1062 }
1063 }
1064 Err(target_path_not_found_error_message(
1065 package_root,
1066 target,
1067 target_kind,
1068 inferred,
1069 ))
1070 }
1071 (Some(p0), Some(p1)) => {
1072 if edition == Edition::Edition2015 {
1073 if let Some(path) = legacy_path(target) {
1074 return Ok(path);
1075 }
1076 }
1077 Err(format!(
1078 "\
1079cannot infer path for `{}` {}
1080Cargo doesn't know which to use because multiple target files found at `{}` and `{}`.",
1081 name_or_panic(target),
1082 target_kind,
1083 p0.strip_prefix(package_root).unwrap_or(&p0).display(),
1084 p1.strip_prefix(package_root).unwrap_or(&p1).display(),
1085 ))
1086 }
1087 (None, Some(_)) => unreachable!(),
1088 }
1089}
1090
1091#[tracing::instrument(skip_all)]
1093pub fn normalize_build(
1094 build: Option<&TomlPackageBuild>,
1095 package_root: &Path,
1096) -> CargoResult<Option<TomlPackageBuild>> {
1097 const BUILD_RS: &str = "build.rs";
1098 match build {
1099 None => {
1100 let build_rs = package_root.join(BUILD_RS);
1103 if build_rs.is_file() {
1104 Ok(Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned())))
1105 } else {
1106 Ok(Some(TomlPackageBuild::Auto(false)))
1107 }
1108 }
1109 Some(TomlPackageBuild::Auto(false)) => Ok(build.cloned()),
1111 Some(TomlPackageBuild::SingleScript(build_file)) => {
1112 let build_file = paths::normalize_path(Path::new(build_file));
1113 let build = build_file.into_os_string().into_string().expect(
1114 "`build_file` started as a String and `normalize_path` shouldn't have changed that",
1115 );
1116 Ok(Some(TomlPackageBuild::SingleScript(build)))
1117 }
1118 Some(TomlPackageBuild::Auto(true)) => {
1119 Ok(Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned())))
1120 }
1121 Some(TomlPackageBuild::MultipleScript(_scripts)) => Ok(build.cloned()),
1122 }
1123}
1124
1125fn name_or_panic(target: &TomlTarget) -> &str {
1126 target
1127 .name
1128 .as_deref()
1129 .unwrap_or_else(|| panic!("target name is required"))
1130}
1131
1132fn validate_lib_name(target: &TomlTarget, warnings: &mut Vec<String>) -> CargoResult<()> {
1133 validate_target_name(target, TARGET_KIND_HUMAN_LIB, TARGET_KIND_LIB, warnings)?;
1134 let name = name_or_panic(target);
1135 if name.contains('-') {
1136 anyhow::bail!("library target names cannot contain hyphens: {}", name)
1137 }
1138
1139 Ok(())
1140}
1141
1142fn validate_bin_name(bin: &TomlTarget, warnings: &mut Vec<String>) -> CargoResult<()> {
1143 validate_target_name(bin, TARGET_KIND_HUMAN_BIN, TARGET_KIND_BIN, warnings)?;
1144 let name = name_or_panic(bin).to_owned();
1145 if restricted_names::is_conflicting_artifact_name(&name) {
1146 anyhow::bail!(
1147 "the binary target name `{name}` is forbidden, \
1148 it conflicts with cargo's build directory names",
1149 )
1150 }
1151
1152 Ok(())
1153}
1154
1155fn validate_target_name(
1156 target: &TomlTarget,
1157 target_kind_human: &str,
1158 target_kind: &str,
1159 warnings: &mut Vec<String>,
1160) -> CargoResult<()> {
1161 match target.name {
1162 Some(ref name) => {
1163 if name.trim().is_empty() {
1164 anyhow::bail!("{} target names cannot be empty", target_kind_human)
1165 }
1166 if cfg!(windows) && restricted_names::is_windows_reserved(name) {
1167 warnings.push(format!(
1168 "{} target `{}` is a reserved Windows filename, \
1169 this target will not work on Windows platforms",
1170 target_kind_human, name
1171 ));
1172 }
1173 }
1174 None => anyhow::bail!(
1175 "{} target {}.name is required",
1176 target_kind_human,
1177 target_kind
1178 ),
1179 }
1180
1181 Ok(())
1182}
1183
1184fn validate_bin_proc_macro(
1185 target: &TomlTarget,
1186 edition: Edition,
1187 warnings: &mut Vec<String>,
1188 errors: &mut Vec<String>,
1189) -> CargoResult<()> {
1190 if target.proc_macro() == Some(true) {
1191 let name = name_or_panic(target);
1192 errors.push(format!(
1193 "the target `{}` is a binary and can't have `proc-macro` \
1194 set `true`",
1195 name
1196 ));
1197 } else {
1198 validate_proc_macro(target, TARGET_KIND_HUMAN_BIN, edition, warnings)?;
1199 }
1200 Ok(())
1201}
1202
1203fn validate_proc_macro(
1204 target: &TomlTarget,
1205 kind: &str,
1206 edition: Edition,
1207 warnings: &mut Vec<String>,
1208) -> CargoResult<()> {
1209 deprecated_underscore(
1210 &target.proc_macro2,
1211 &target.proc_macro,
1212 "proc-macro",
1213 name_or_panic(target),
1214 format!("{kind} target").as_str(),
1215 edition,
1216 warnings,
1217 )
1218}
1219
1220fn validate_bin_crate_types(
1221 target: &TomlTarget,
1222 edition: Edition,
1223 warnings: &mut Vec<String>,
1224 errors: &mut Vec<String>,
1225) -> CargoResult<()> {
1226 if let Some(crate_types) = target.crate_types() {
1227 if !crate_types.is_empty() {
1228 let name = name_or_panic(target);
1229 errors.push(format!(
1230 "the target `{}` is a binary and can't have any \
1231 crate-types set (currently \"{}\")",
1232 name,
1233 crate_types.join(", ")
1234 ));
1235 } else {
1236 validate_crate_types(target, TARGET_KIND_HUMAN_BIN, edition, warnings)?;
1237 }
1238 }
1239 Ok(())
1240}
1241
1242fn validate_crate_types(
1243 target: &TomlTarget,
1244 kind: &str,
1245 edition: Edition,
1246 warnings: &mut Vec<String>,
1247) -> CargoResult<()> {
1248 deprecated_underscore(
1249 &target.crate_type2,
1250 &target.crate_type,
1251 "crate-type",
1252 name_or_panic(target),
1253 format!("{kind} target").as_str(),
1254 edition,
1255 warnings,
1256 )
1257}