rustc_cfg/
lib.rs

1//! Runs `rustc --print cfg` and parses the output
2//!
3//! *NOTE*: If you are in build script context you should prefer to use the [`CARGO_CFG_*`] env
4//! variables that Cargo sets over this crate.
5//!
6//! [`CARGO_CFG_*`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
7//!
8//! # Requirements
9//!
10//! - This crate requires `rustc` to be installed and available in the user's PATH.
11//!
12//! # How to use
13//!
14//! ```
15//! extern crate rustc_cfg;
16//!
17//! use rustc_cfg::Cfg;
18//!
19//! fn main() {
20//!     let cfg = Cfg::of("x86_64-unknown-linux-gnu").unwrap();
21//!
22//!     assert_eq!(cfg.target_arch, "x86_64");
23//!     assert!(cfg.target_family.as_ref().map(|f| f == "unix").unwrap_or(false));
24//! }
25//! ```
26
27#![deny(missing_docs)]
28#![deny(warnings)]
29
30#[macro_use]
31extern crate thiserror;
32
33use std::env;
34use std::process::Command;
35
36/// The result of parsing the output of `rustc --print cfg`
37#[cfg_attr(test, derive(Debug))]
38pub struct Cfg {
39    /// Equivalent to `cfg(target_os = "..")`
40    pub target_os: String,
41    /// Equivalent to `cfg(unix)` or `cfg(windows)`
42    pub target_family: Option<String>,
43    /// Equivalent to `cfg(target_arch = "..")`
44    pub target_arch: String,
45    /// Equivalent to `cfg(target_endian = "..")`
46    pub target_endian: String,
47    /// Equivalent to `cfg(target_pointer_width = "..")`
48    pub target_pointer_width: String,
49    /// Equivalent to `cfg(target_env = "..")`
50    pub target_env: String,
51    /// Equivalent to `cfg(target_vendor = "..")`.
52    pub target_vendor: Option<String>,
53    /// Equivalent to `cfg(target_has_atomic = "..")`
54    pub target_has_atomic: Vec<String>,
55    /// Equivalent to `cfg(target_feature = "..")`
56    pub target_feature: Vec<String>,
57    _extensible: (),
58}
59
60/// Errors
61#[derive(Debug, Error)]
62pub enum Error {
63    /// Invoking rustc failed
64    #[error("rustc invocation failed: {0}")]
65    Rustc(String),
66    /// IO error
67    #[error("IO error: {0}")]
68    Io(#[from] std::io::Error),
69    /// rustc output did not have `target_os`
70    #[error("`target_os` is missing")]
71    MissingTargetOs,
72    /// rustc output did not have `target_arch`
73    #[error("`target_arch` is missing")]
74    MissingTargetArch,
75    /// rustc output did not have `target_endian`
76    #[error("`target_endian` is missing")]
77    MissingTargetEndian,
78    /// rustc output did not have `target_pointer_width`
79    #[error("`target_pointer_width` is missing")]
80    MissingTargetPointerWidth,
81    /// rustc output did not have `target_env`
82    #[error("`target_env` is missing")]
83    MissingTargetEnv,
84}
85
86impl Cfg {
87    /// Runs `rustc --target <target> --print cfg` and returns the parsed output.
88    ///
89    /// The `target` should be a "triple" from the list of supported rustc
90    /// targets. A list of supported rustc targets can be obtained using the
91    /// `rustc --print target-list` command. This should not be confused with
92    /// Cargo targets, i.e. binaries, `[[bin]]` and a library `[lib]` define in
93    /// a package's manifest (Cargo.toml).
94    pub fn of(target: &str) -> Result<Cfg, Error> {
95        // NOTE Cargo passes RUSTC to build scripts, prefer that over plain `rustc`.
96        let output = Command::new(env::var("RUSTC").as_ref().map(|s| &**s).unwrap_or("rustc"))
97            .arg("--target")
98            .arg(target)
99            .args(&["--print", "cfg"])
100            .output()?;
101
102        if !output.status.success() {
103            return Err(Error::Rustc(
104                String::from_utf8(output.stderr)
105                    .unwrap_or_else(|_| "(output was not valid UTF-8)".to_string()),
106            ));
107        }
108
109        let spec = String::from_utf8(output.stdout)
110            .map_err(|_| Error::Rustc("Target spec from rustc was not valid UTF-8".to_string()))?;
111        let mut target_os = None;
112        let mut target_family = None;
113        let mut target_arch = None;
114        let mut target_endian = None;
115        let mut target_pointer_width = None;
116        let mut target_env = None;
117        let mut target_vendor = None;
118        let mut target_has_atomic = vec![];
119        let mut target_feature = vec![];
120
121        for entry in spec.lines() {
122            let mut parts = entry.split('=');
123
124            if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
125                match key {
126                    "target_os" => target_os = Some(value.trim_matches('"').to_string()),
127                    "target_family" => target_family = Some(value.trim_matches('"').to_string()),
128                    "target_arch" => target_arch = Some(value.trim_matches('"').to_string()),
129                    "target_endian" => target_endian = Some(value.trim_matches('"').to_string()),
130                    "target_pointer_width" => {
131                        target_pointer_width = Some(value.trim_matches('"').to_string())
132                    }
133                    "target_env" => target_env = Some(value.trim_matches('"').to_string()),
134                    "target_vendor" => target_vendor = Some(value.trim_matches('"').to_string()),
135                    "target_has_atomic" => {
136                        target_has_atomic.push(value.trim_matches('"').to_string())
137                    }
138                    "target_feature" => target_feature.push(value.trim_matches('"').to_string()),
139                    _ => {}
140                }
141            }
142        }
143
144        Ok(Cfg {
145            target_os: target_os.ok_or(Error::MissingTargetOs)?,
146            target_family,
147            target_arch: target_arch.ok_or(Error::MissingTargetArch)?,
148            target_endian: target_endian.ok_or(Error::MissingTargetEndian)?,
149            target_pointer_width: target_pointer_width.ok_or(Error::MissingTargetPointerWidth)?,
150            target_env: target_env.ok_or(Error::MissingTargetEnv)?,
151            target_vendor,
152            target_has_atomic,
153            target_feature,
154            _extensible: (),
155        })
156    }
157}
158
159#[cfg(test)]
160mod test {
161    use std::process::Command;
162
163    use super::Cfg;
164
165    #[test]
166    fn all() {
167        let output = Command::new("rustc")
168            .args(&["--print", "target-list"])
169            .output()
170            .unwrap();
171
172        let stdout = String::from_utf8(output.stdout).unwrap();
173
174        assert!(output.status.success());
175
176        for target in stdout.lines() {
177            println!("{}\n\t{:?}\n", target, Cfg::of(target));
178        }
179    }
180}