Skip to main content

cargo/context/
target.rs

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