1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#![doc(html_root_url = "https://docs.rs/feature-probe/0.1.1")]
//! To support multiple versions of Rust, it's often necessary to conditionally
//! compile parts of our libraries or programs. It's possible to allow users to
//! specify what features to enable, but detection is better, because users get
//! all the features that their version of Rust supports. And while we could check
//! the rustc version, it's better to probe for individual features. That way,
//! code will work both on nightly, and on stable releases after particular features
//! stabilize, without changes.
//!
//! ## Usage
//!
//! It’s [on crates.io](https://crates.io/crates/feature-probe), so you can add
//!
//! ```toml
//! [build-dependencies]
//! feature-probe = "0.1.1"
//! ```
//!
//! Then add to your `build.rs`:
//!
//! ```no_compile
//! extern crate feature_probe;
//!
//! use feature_probe::Probe;
//! ```
//!
//! Then you can probe for features such as types or expressions. For example:
//!
//! ```no_compile
//! fn main () {
//!     let probe = Probe::new();
//!
//!     if probe.probe_type("i128") {
//!         println!("cargo:rustc-cfg=int_128");
//!     }
//!
//!     if probe.probe_type("::std::ops::RangeInclusive<u64>") {
//!         println!("cargo:rustc-cfg=inclusive_range");
//!     }
//! }
//! ```
//!
//! This crate supports Rust version 1.16.0 and later.

use std::env;
use std::ffi::OsString;
use std::io::{self, Write};
use std::process::{Command, Stdio};

/// A probe object, which is used for probing for features.
///
/// Create this with [`ProbeProbeo::new`](#method.new), and then probe with
/// one of the probing methods.
#[derive(Debug)]
pub struct Probe {
    rustc:   OsString,
    out_dir: OsString,
}

impl Probe {
    /// Creates a new [`Probe`](struct.Probe.html) object with a default
    /// configuration.
    ///
    /// In particular, it consults the environment variable `"RUSTC"` to determine
    /// what Rust compiler to use, and the environment variable `"OUT_DIR"` to
    /// determine where to put object files. If these are not set, they default to
    /// the values `"rustc"` and `"target"`, respectively.
    ///
    /// # Panics
    ///
    /// If the child `rustc` cannot be started or communicated with.
    ///
    /// # Examples
    ///
    /// ```
    /// use feature_probe::Probe;
    ///
    /// let probe = Probe::new();
    /// assert!( probe.probe_type("u32") );
    /// ```
    pub fn new() -> Self {
        Probe {
            rustc:   env_var_or("RUSTC",   "rustc"),
            out_dir: env_var_or("OUT_DIR", "target"),
        }
    }

    /// Probes for the existence of the given type by name.
    ///
    /// # Panics
    ///
    /// If the child `rustc` cannot be started or communicated with.
    ///
    /// # Examples
    ///
    /// ```
    /// use feature_probe::Probe;
    ///
    /// let probe = Probe::new();
    /// assert!(   probe.probe_type("u32") );
    /// assert!( ! probe.probe_type("u512") );
    /// ```
    pub fn probe_type(&self, type_name: &str) -> bool {
        self.probe(&format!("pub type T = {}; fn main() {{ }}", type_name))
    }

    /// Probes whether the given expression can be compiled.
    ///
    /// # Examples
    ///
    /// ```
    /// use feature_probe::Probe;
    ///
    /// let probe = Probe::new();
    /// assert!(   probe.probe_expression("3 + 4") );
    /// assert!( ! probe.probe_expression("3 + true") );
    pub fn probe_expression(&self, expression: &str) -> bool {
        self.probe(&format!("fn main() {{ {}; }}", expression))
    }

    /// Probes for whether a whole program can be compiled.
    ///
    /// # Panics
    ///
    /// If the child `rustc` cannot be started or communicated with.
    ///
    /// # Examples
    ///
    /// ```
    /// # extern crate feature_probe;
    /// # fn main() {
    /// use feature_probe::Probe;
    ///
    /// let probe = Probe::new();
    /// assert!(   probe.probe("fn main() { }") );
    /// assert!( ! probe.probe("fn main(args: Vec<String>) { }") );
    /// # }
    /// ```
    pub fn probe(&self, code: &str) -> bool {
        self.probe_result(code).expect("Probe::probe")
    }

    /// Probes for whether a whole program can be compiled.
    ///
    /// # Examples
    ///
    /// ```
    /// # extern crate feature_probe;
    /// # fn main() {
    /// use feature_probe::Probe;
    ///
    /// let probe = Probe::new();
    /// assert_eq!( probe.probe_result("fn main() { }").unwrap(),                  true );
    /// assert_eq!( probe.probe_result("fn main(args: Vec<String>) { }").unwrap(), false );
    /// # }
    /// ```
    pub fn probe_result(&self, code: &str) -> io::Result<bool> {
        let mut child = Command::new(&self.rustc)
            .arg("--out-dir")
            .arg(&self.out_dir)
            .arg("--emit=obj")
            .arg("-")
            .stdin(Stdio::piped())
            .spawn()?;

        child
            .stdin
            .as_mut().unwrap()
            .write_all(code.as_bytes())?;

        Ok(child.wait()?.success())
    }
}

impl Default for Probe {
    fn default() -> Self {
        Probe::new()
    }
}

fn env_var_or(var: &str, default: &str) -> OsString {
    env::var_os(var).unwrap_or_else(|| default.into())
}