#
cargo/core/compiler/
artifact.rs

    1//! Generate artifact information from unit dependencies for configuring the compiler environment.
    2
    3use crate::CargoResult;
    4use crate::core::compiler::unit_graph::UnitDep;
    5use crate::core::compiler::{BuildRunner, CrateType, FileFlavor, Unit};
    6use crate::core::dependency::ArtifactKind;
    7use crate::core::{Dependency, Target, TargetKind};
    8use std::collections::{HashMap, HashSet};
    9use std::ffi::OsString;
    10
    11/// Return all environment variables for the given unit-dependencies
    12/// if artifacts are present.
    13pub fn get_env(
    14    build_runner: &BuildRunner<'_, '_>,
    15    unit: &Unit,
    16    dependencies: &[UnitDep],
    17) -> CargoResult<HashMap<String, OsString>> {
    18    let mut env = HashMap::new();
    19
    20    // Add `CARGO_BIN_EXE_` environment variables for building tests.
    21    //
    22    // These aren't built for `cargo check`, so can't use `dependencies`
    23    if unit.target.is_test() || unit.target.is_bench() {
    24        for bin_target in unit
    25            .pkg
    26            .manifest()
    27            .targets()
    28            .iter()
    29            .filter(|target| target.is_bin())
    30        {
    31            let name = bin_target
    32                .binary_filename()
    33                .unwrap_or_else(|| bin_target.name().to_string());
    34
    35            // For `cargo check` builds we do not uplift the CARGO_BIN_EXE_ artifacts to the
    36            // artifact-dir. We do not want to provide a path to a non-existent binary but we still
    37            // need to provide *something* so `env!("CARGO_BIN_EXE_...")` macros will compile.
    38            let exe_path = build_runner
    39                .files()
    40                .bin_link_for_target(bin_target, unit.kind, build_runner.bcx)?
    41                .map(|path| path.as_os_str().to_os_string())
    42                .unwrap_or_else(|| OsString::from(format!("placeholder:{name}")));
    43
    44            let key = format!("CARGO_BIN_EXE_{name}");
    45            env.insert(key, exe_path);
    46        }
    47    }
    48
    49    for unit_dep in dependencies.iter().filter(|d| d.unit.artifact.is_true()) {
    50        for artifact_path in build_runner
    51            .outputs(&unit_dep.unit)?
    52            .iter()
    53            .filter_map(|f| (f.flavor == FileFlavor::Normal).then(|| &f.path))
    54        {
    55            let artifact_type_upper = unit_artifact_type_name_upper(&unit_dep.unit);
    56            let dep_name = unit_dep.dep_name.unwrap_or(unit_dep.unit.pkg.name());
    57            let dep_name_upper = dep_name.to_uppercase().replace("-", "_");
    58
    59            let var = format!("CARGO_{}_DIR_{}", artifact_type_upper, dep_name_upper);
    60            let path = artifact_path.parent().expect("parent dir for artifacts");
    61            env.insert(var, path.to_owned().into());
    62
    63            let var_file = format!(
    64                "CARGO_{}_FILE_{}_{}",
    65                artifact_type_upper,
    66                dep_name_upper,
    67                unit_dep.unit.target.name()
    68            );
    69
    70            // In older releases, lib-targets defaulted to the name of the package. Newer releases
    71            // use the same name as default, but with dashes replaced. Hence, if the name of the
    72            // target was inferred by Cargo, we also set the env-var with the unconverted name for
    73            // backwards compatibility.
    74            let need_compat = unit_dep.unit.target.is_lib() && unit_dep.unit.target.name_inferred();
    75            if need_compat {
    76                let var_compat = format!(
    77                    "CARGO_{}_FILE_{}_{}",
    78                    artifact_type_upper,
    79                    dep_name_upper,
    80                    unit_dep.unit.pkg.name(),
    81                );
    82                if var_compat != var_file {
    83                    env.insert(var_compat, artifact_path.to_owned().into());
    84                }
    85            }
    86
    87            env.insert(var_file, artifact_path.to_owned().into());
    88
    89            // If the name of the target matches the name of the dependency, we strip the
    90            // repetition and provide the simpler env-var as well.
    91            // For backwards-compatibility of inferred names, we compare against the name of the
    92            // package as well, since that used to be the default for library targets.
    93            if unit_dep.unit.target.name() == dep_name.as_str()
    94                || (need_compat && unit_dep.unit.pkg.name() == dep_name.as_str())
    95            {
    96                let var = format!("CARGO_{}_FILE_{}", artifact_type_upper, dep_name_upper,);
    97                env.insert(var, artifact_path.to_owned().into());
    98            }
    99        }
    100    }
    101    Ok(env)
    102}
    103
    104fn unit_artifact_type_name_upper(unit: &Unit) -> &'static str {
    105    match unit.target.kind() {
    106        TargetKind::Lib(kinds) => match kinds.as_slice() {
    107            &[CrateType::Cdylib] => "CDYLIB",
    108            &[CrateType::Staticlib] => "STATICLIB",
    109            invalid => unreachable!("BUG: artifacts cannot be of type {:?}", invalid),
    110        },
    111        TargetKind::Bin => "BIN",
    112        invalid => unreachable!("BUG: artifacts cannot be of type {:?}", invalid),
    113    }
    114}
    115
    116/// Given a dependency with an artifact `artifact_dep` and a set of available `targets`
    117/// of its package, find a target for each kind of artifacts that are to be built.
    118///
    119/// Failure to match any target results in an error mentioning the parent manifests
    120/// `parent_package` name.
    121pub(crate) fn match_artifacts_kind_with_targets<'t, 'd>(
    122    artifact_dep: &'d Dependency,
    123    targets: &'t [Target],
    124    parent_package: &str,
    125) -> CargoResult<HashSet<(&'d ArtifactKind, &'t Target)>> {
    126    let mut out = HashSet::new();
    127    let artifact_requirements = artifact_dep.artifact().expect("artifact present");
    128    for artifact_kind in artifact_requirements.kinds() {
    129        let mut extend = |kind, filter: &dyn Fn(&&Target) -> bool| {
    130            let mut iter = targets.iter().filter(filter).peekable();
    131            let found = iter.peek().is_some();
    132            out.extend(std::iter::repeat(kind).zip(iter));
    133            found
    134        };
    135        let found = match artifact_kind {
    136            ArtifactKind::Cdylib => extend(artifact_kind, &|t| t.is_cdylib()),
    137            ArtifactKind::Staticlib => extend(artifact_kind, &|t| t.is_staticlib()),
    138            ArtifactKind::AllBinaries => extend(artifact_kind, &|t| t.is_bin()),
    139            ArtifactKind::SelectedBinary(bin_name) => extend(artifact_kind, &|t| {
    140                t.is_bin() && t.name() == bin_name.as_str()
    141            }),
    142        };
    143        if !found {
    144            anyhow::bail!(
    145                "dependency `{}` in package `{}` requires a `{}` artifact to be present.",
    146                artifact_dep.name_in_toml(),
    147                parent_package,
    148                artifact_kind
    149            );
    150        }
    151    }
    152    Ok(out)
    153}