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