cargo/util/toml/
targets.rs

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