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