cargo/util/context/
target.rs

1use super::{ConfigKey, ConfigRelativePath, GlobalContext, OptValue, PathAndArgs, StringList, CV};
2use crate::core::compiler::{BuildOutput, 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.library_paths.extend(paths);
171                    output.library_links.extend(links);
172                }
173                "rustc-link-lib" => {
174                    let list = value.list(key)?;
175                    output
176                        .library_links
177                        .extend(list.iter().map(|v| v.0.clone()));
178                }
179                "rustc-link-search" => {
180                    let list = value.list(key)?;
181                    output
182                        .library_paths
183                        .extend(list.iter().map(|v| PathBuf::from(&v.0)));
184                }
185                "rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
186                    let args = extra_link_args(LinkArgTarget::Cdylib, key, value)?;
187                    output.linker_args.extend(args);
188                }
189                "rustc-link-arg-bins" => {
190                    let args = extra_link_args(LinkArgTarget::Bin, key, value)?;
191                    output.linker_args.extend(args);
192                }
193                "rustc-link-arg" => {
194                    let args = extra_link_args(LinkArgTarget::All, key, value)?;
195                    output.linker_args.extend(args);
196                }
197                "rustc-link-arg-tests" => {
198                    let args = extra_link_args(LinkArgTarget::Test, key, value)?;
199                    output.linker_args.extend(args);
200                }
201                "rustc-link-arg-benches" => {
202                    let args = extra_link_args(LinkArgTarget::Bench, key, value)?;
203                    output.linker_args.extend(args);
204                }
205                "rustc-link-arg-examples" => {
206                    let args = extra_link_args(LinkArgTarget::Example, key, value)?;
207                    output.linker_args.extend(args);
208                }
209                "rustc-cfg" => {
210                    let list = value.list(key)?;
211                    output.cfgs.extend(list.iter().map(|v| v.0.clone()));
212                }
213                "rustc-check-cfg" => {
214                    let list = value.list(key)?;
215                    output.check_cfgs.extend(list.iter().map(|v| v.0.clone()));
216                }
217                "rustc-env" => {
218                    for (name, val) in value.table(key)?.0 {
219                        let val = val.string(name)?.0;
220                        output.env.push((name.clone(), val.to_string()));
221                    }
222                }
223                "warning" | "rerun-if-changed" | "rerun-if-env-changed" => {
224                    anyhow::bail!("`{}` is not supported in build script overrides", key);
225                }
226                _ => {
227                    let val = value.string(key)?.0;
228                    output.metadata.push((key.clone(), val.to_string()));
229                }
230            }
231        }
232        links_overrides.insert(lib_name, output);
233    }
234    Ok(links_overrides)
235}
236
237fn extra_link_args<'a>(
238    link_type: LinkArgTarget,
239    key: &str,
240    value: &'a CV,
241) -> CargoResult<impl Iterator<Item = (LinkArgTarget, String)> + 'a> {
242    let args = value.list(key)?;
243    Ok(args.iter().map(move |v| (link_type.clone(), v.0.clone())))
244}