cargo/util/context/
target.rs

1use super::{ConfigKey, ConfigRelativePath, GlobalContext, OptValue, PathAndArgs, StringList, CV};
2use crate::core::compiler::{BuildOutput, LibraryPath, LinkArgTarget};
3use crate::util::CargoResult;
4use serde::Deserialize;
5use std::collections::{BTreeMap, HashMap};
6use std::path::{Path, PathBuf};
7use std::rc::Rc;
8
9/// Config definition of a `[target.'cfg(…)']` table.
10///
11/// This is a subset of `TargetConfig`.
12#[derive(Debug, Deserialize)]
13pub struct TargetCfgConfig {
14    pub runner: OptValue<PathAndArgs>,
15    pub rustflags: OptValue<StringList>,
16    pub linker: OptValue<ConfigRelativePath>,
17    // This is here just to ignore fields from normal `TargetConfig` because
18    // all `[target]` tables are getting deserialized, whether they start with
19    // `cfg(` or not.
20    #[serde(flatten)]
21    pub other: BTreeMap<String, toml::Value>,
22}
23
24/// Config definition of a `[target]` table or `[host]`.
25#[derive(Debug, Clone, Default)]
26pub struct TargetConfig {
27    /// Process to run as a wrapper for `cargo run`, `test`, and `bench` commands.
28    pub runner: OptValue<PathAndArgs>,
29    /// Additional rustc flags to pass.
30    pub rustflags: OptValue<StringList>,
31    /// Additional rustdoc flags to pass.
32    pub rustdocflags: OptValue<StringList>,
33    /// The path of the linker for this target.
34    pub linker: OptValue<ConfigRelativePath>,
35    /// Build script override for the given library name.
36    ///
37    /// Any package with a `links` value for the given library name will skip
38    /// running its build script and instead use the given output from the
39    /// config file.
40    pub links_overrides: Rc<BTreeMap<String, BuildOutput>>,
41}
42
43/// Loads all of the `target.'cfg()'` tables.
44pub(super) fn load_target_cfgs(
45    gctx: &GlobalContext,
46) -> CargoResult<Vec<(String, TargetCfgConfig)>> {
47    // Load all [target] tables, filter out the cfg() entries.
48    let mut result = Vec::new();
49    // Use a BTreeMap so the keys are sorted. This is important for
50    // deterministic ordering of rustflags, which affects fingerprinting and
51    // rebuilds. We may perhaps one day wish to ensure a deterministic
52    // ordering via the order keys were defined in files perhaps.
53    let target: BTreeMap<String, TargetCfgConfig> = gctx.get("target")?;
54    tracing::debug!("Got all targets {:#?}", target);
55    for (key, cfg) in target {
56        if let Ok(platform) = key.parse::<cargo_platform::Platform>() {
57            let mut warnings = Vec::new();
58            platform.check_cfg_keywords(&mut warnings, &Path::new(".cargo/config.toml"));
59            for w in warnings {
60                gctx.shell().warn(w)?;
61            }
62        }
63        if key.starts_with("cfg(") {
64            // Unfortunately this is not able to display the location of the
65            // unused key. Using config::Value<toml::Value> doesn't work. One
66            // solution might be to create a special "Any" type, but I think
67            // that will be quite difficult with the current design.
68            for other_key in cfg.other.keys() {
69                gctx.shell().warn(format!(
70                    "unused key `{}` in [target] config table `{}`",
71                    other_key, key
72                ))?;
73            }
74            result.push((key, cfg));
75        }
76    }
77    Ok(result)
78}
79
80/// Returns true if the `[target]` table should be applied to host targets.
81pub(super) fn get_target_applies_to_host(gctx: &GlobalContext) -> CargoResult<bool> {
82    if gctx.cli_unstable().target_applies_to_host {
83        if let Ok(target_applies_to_host) = gctx.get::<bool>("target-applies-to-host") {
84            Ok(target_applies_to_host)
85        } else {
86            Ok(!gctx.cli_unstable().host_config)
87        }
88    } else if gctx.cli_unstable().host_config {
89        anyhow::bail!(
90            "the -Zhost-config flag requires the -Ztarget-applies-to-host flag to be set"
91        );
92    } else {
93        Ok(true)
94    }
95}
96
97/// Loads a single `[host]` table for the given triple.
98pub(super) fn load_host_triple(gctx: &GlobalContext, triple: &str) -> CargoResult<TargetConfig> {
99    if gctx.cli_unstable().host_config {
100        let host_triple_prefix = format!("host.{}", triple);
101        let host_triple_key = ConfigKey::from_str(&host_triple_prefix);
102        let host_prefix = match gctx.get_cv(&host_triple_key)? {
103            Some(_) => host_triple_prefix,
104            None => "host".to_string(),
105        };
106        load_config_table(gctx, &host_prefix)
107    } else {
108        Ok(TargetConfig::default())
109    }
110}
111
112/// Loads a single `[target]` table for the given triple.
113pub(super) fn load_target_triple(gctx: &GlobalContext, triple: &str) -> CargoResult<TargetConfig> {
114    load_config_table(gctx, &format!("target.{}", triple))
115}
116
117/// Loads a single table for the given prefix.
118fn load_config_table(gctx: &GlobalContext, prefix: &str) -> CargoResult<TargetConfig> {
119    // This needs to get each field individually because it cannot fetch the
120    // struct all at once due to `links_overrides`. Can't use `serde(flatten)`
121    // because it causes serde to use `deserialize_map` which means the config
122    // deserializer does not know which keys to deserialize, which means
123    // environment variables would not work.
124    let runner: OptValue<PathAndArgs> = gctx.get(&format!("{prefix}.runner"))?;
125    let rustflags: OptValue<StringList> = gctx.get(&format!("{prefix}.rustflags"))?;
126    let rustdocflags: OptValue<StringList> = gctx.get(&format!("{prefix}.rustdocflags"))?;
127    let linker: OptValue<ConfigRelativePath> = gctx.get(&format!("{prefix}.linker"))?;
128    // Links do not support environment variables.
129    let target_key = ConfigKey::from_str(prefix);
130    let links_overrides = match gctx.get_table(&target_key)? {
131        Some(links) => parse_links_overrides(&target_key, links.val)?,
132        None => BTreeMap::new(),
133    };
134    Ok(TargetConfig {
135        runner,
136        rustflags,
137        rustdocflags,
138        linker,
139        links_overrides: Rc::new(links_overrides),
140    })
141}
142
143fn parse_links_overrides(
144    target_key: &ConfigKey,
145    links: HashMap<String, CV>,
146) -> CargoResult<BTreeMap<String, BuildOutput>> {
147    let mut links_overrides = BTreeMap::new();
148
149    for (lib_name, value) in links {
150        // Skip these keys, it shares the namespace with `TargetConfig`.
151        match lib_name.as_str() {
152            // `ar` is a historical thing.
153            "ar" | "linker" | "runner" | "rustflags" | "rustdocflags" => continue,
154            _ => {}
155        }
156        let mut output = BuildOutput::default();
157        let table = value.table(&format!("{}.{}", target_key, lib_name))?.0;
158        // We require deterministic order of evaluation, so we must sort the pairs by key first.
159        let mut pairs = Vec::new();
160        for (k, value) in table {
161            pairs.push((k, value));
162        }
163        pairs.sort_by_key(|p| p.0);
164        for (key, value) in pairs {
165            match key.as_str() {
166                "rustc-flags" => {
167                    let flags = value.string(key)?;
168                    let whence = format!("target config `{}.{}` (in {})", target_key, key, flags.1);
169                    let (paths, links) = BuildOutput::parse_rustc_flags(flags.0, &whence)?;
170                    output
171                        .library_paths
172                        .extend(paths.into_iter().map(LibraryPath::External));
173                    output.library_links.extend(links);
174                }
175                "rustc-link-lib" => {
176                    let list = value.list(key)?;
177                    output
178                        .library_links
179                        .extend(list.iter().map(|v| v.0.clone()));
180                }
181                "rustc-link-search" => {
182                    let list = value.list(key)?;
183                    output.library_paths.extend(
184                        list.iter()
185                            .map(|v| PathBuf::from(&v.0))
186                            .map(LibraryPath::External),
187                    );
188                }
189                "rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
190                    let args = extra_link_args(LinkArgTarget::Cdylib, key, value)?;
191                    output.linker_args.extend(args);
192                }
193                "rustc-link-arg-bins" => {
194                    let args = extra_link_args(LinkArgTarget::Bin, key, value)?;
195                    output.linker_args.extend(args);
196                }
197                "rustc-link-arg" => {
198                    let args = extra_link_args(LinkArgTarget::All, key, value)?;
199                    output.linker_args.extend(args);
200                }
201                "rustc-link-arg-tests" => {
202                    let args = extra_link_args(LinkArgTarget::Test, key, value)?;
203                    output.linker_args.extend(args);
204                }
205                "rustc-link-arg-benches" => {
206                    let args = extra_link_args(LinkArgTarget::Bench, key, value)?;
207                    output.linker_args.extend(args);
208                }
209                "rustc-link-arg-examples" => {
210                    let args = extra_link_args(LinkArgTarget::Example, key, value)?;
211                    output.linker_args.extend(args);
212                }
213                "rustc-cfg" => {
214                    let list = value.list(key)?;
215                    output.cfgs.extend(list.iter().map(|v| v.0.clone()));
216                }
217                "rustc-check-cfg" => {
218                    let list = value.list(key)?;
219                    output.check_cfgs.extend(list.iter().map(|v| v.0.clone()));
220                }
221                "rustc-env" => {
222                    for (name, val) in value.table(key)?.0 {
223                        let val = val.string(name)?.0;
224                        output.env.push((name.clone(), val.to_string()));
225                    }
226                }
227                "warning" | "rerun-if-changed" | "rerun-if-env-changed" => {
228                    anyhow::bail!("`{}` is not supported in build script overrides", key);
229                }
230                _ => {
231                    let val = value.string(key)?.0;
232                    output.metadata.push((key.clone(), val.to_string()));
233                }
234            }
235        }
236        links_overrides.insert(lib_name, output);
237    }
238    Ok(links_overrides)
239}
240
241fn extra_link_args<'a>(
242    link_type: LinkArgTarget,
243    key: &str,
244    value: &'a CV,
245) -> CargoResult<impl Iterator<Item = (LinkArgTarget, String)> + 'a> {
246    let args = value.list(key)?;
247    Ok(args.iter().map(move |v| (link_type.clone(), v.0.clone())))
248}