Skip to main content

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