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