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}