#
cargo/util/toml/
targets.rs

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