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