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