#
cargo/util/toml/
targets.rs

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