Skip to main content

cargo/compiler/
artifact.rs

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