Skip to content

Commit

Permalink
Add AutoCfg::probe_raw for free-form probes
Browse files Browse the repository at this point in the history
This takes an arbitrary string that will be fed to the compiler as-is,
without even interjecting `#![no_std]` like other probe methods. It also
returns a `Result` with the full `Error`, so you could choose to print
verbose details on failure, for example.
  • Loading branch information
cuviper committed Mar 28, 2024
1 parent c10bc17 commit e0a2240
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 24 deletions.
18 changes: 18 additions & 0 deletions examples/nightly.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
extern crate autocfg;

fn main() {
// Normally, cargo will set `OUT_DIR` for build scripts.
let ac = autocfg::AutoCfg::with_dir("target").unwrap();

// When this feature was stabilized, it also renamed the method to
// `chunk_by`, so it's important to *use* the feature in your probe.
let code = r#"
#![feature(slice_group_by)]
pub fn probe(slice: &[i32]) -> impl Iterator<Item = &[i32]> {
slice.group_by(|a, b| a == b)
}
"#;
if ac.probe_raw(code).is_ok() {
autocfg::emit("has_slice_group_by");
}
}
14 changes: 13 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::error;
use std::fmt;
use std::io;
use std::num;
use std::process;
use std::str;

/// A common error type for the `autocfg` crate.
Expand All @@ -20,7 +21,7 @@ impl error::Error for Error {
ErrorKind::Io(ref e) => Some(e),
ErrorKind::Num(ref e) => Some(e),
ErrorKind::Utf8(ref e) => Some(e),
ErrorKind::Other(_) => None,
ErrorKind::Process(_) | ErrorKind::Other(_) => None,
}
}
}
Expand All @@ -31,6 +32,10 @@ impl fmt::Display for Error {
ErrorKind::Io(ref e) => e.fmt(f),
ErrorKind::Num(ref e) => e.fmt(f),
ErrorKind::Utf8(ref e) => e.fmt(f),
ErrorKind::Process(ref status) => {
// Same message as the newer `ExitStatusError`
write!(f, "process exited unsuccessfully: {}", status)
}
ErrorKind::Other(s) => s.fmt(f),
}
}
Expand All @@ -40,10 +45,17 @@ impl fmt::Display for Error {
enum ErrorKind {
Io(io::Error),
Num(num::ParseIntError),
Process(process::ExitStatus),
Utf8(str::Utf8Error),
Other(&'static str),
}

pub fn from_exit(status: process::ExitStatus) -> Error {
Error {
kind: ErrorKind::Process(status),
}
}

pub fn from_io(e: io::Error) -> Error {
Error {
kind: ErrorKind::Io(e),
Expand Down
98 changes: 75 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ macro_rules! try {

use std::env;
use std::ffi::OsString;
use std::fmt::Arguments;
use std::fs;
use std::io::{stderr, Write};
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -180,11 +181,11 @@ impl AutoCfg {
};

// Sanity check with and without `std`.
if !ac.probe("").unwrap_or(false) {
ac.no_std = true;
if !ac.probe("").unwrap_or(false) {
if !ac.probe_raw("").is_ok() {
if ac.probe_raw("#![no_std]").is_ok() {
ac.no_std = true;
} else {
// Neither worked, so assume nothing...
ac.no_std = false;
let warning = b"warning: autocfg could not probe for `std`\n";
stderr().write_all(warning).ok();
}
Expand All @@ -207,7 +208,7 @@ impl AutoCfg {
///
/// See also [`set_no_std`](#method.set_no_std).
///
/// [prelude]: https://doc.rust-lang.org/reference/crates-and-source-files.html#preludes-and-no_std
/// [prelude]: https://doc.rust-lang.org/reference/names/preludes.html#the-no_std-attribute
pub fn no_std(&self) -> bool {
self.no_std
}
Expand All @@ -233,7 +234,7 @@ impl AutoCfg {
}
}

fn probe<T: AsRef<[u8]>>(&self, code: T) -> Result<bool, Error> {
fn probe_fmt<'a>(&self, source: Arguments<'a>) -> Result<(), Error> {
#[allow(deprecated)]
static ID: AtomicUsize = ATOMIC_USIZE_INIT;

Expand Down Expand Up @@ -268,14 +269,69 @@ impl AutoCfg {
let mut child = try!(command.spawn().map_err(error::from_io));
let mut stdin = child.stdin.take().expect("rustc stdin");

if self.no_std {
try!(stdin.write_all(b"#![no_std]\n").map_err(error::from_io));
}
try!(stdin.write_all(code.as_ref()).map_err(error::from_io));
try!(stdin.write_fmt(source).map_err(error::from_io));
drop(stdin);

let status = try!(child.wait().map_err(error::from_io));
Ok(status.success())
match child.wait() {
Ok(status) if status.success() => Ok(()),
Ok(status) => Err(error::from_exit(status)),
Err(error) => Err(error::from_io(error)),
}
}

fn probe<'a>(&self, code: Arguments<'a>) -> bool {
let result = if self.no_std {
self.probe_fmt(format_args!("#![no_std]\n{}", code))
} else {
self.probe_fmt(code)
};
result.is_ok()
}

/// Tests whether the given code can be compiled as a Rust library.
///
/// This will only return `Ok` if the compiler ran and exited successfully,
/// per `ExitStatus::success()`.
/// The code is passed to the compiler exactly as-is, notably not even
/// adding the [`#![no_std]`][Self::no_std] attribute like other probes.
///
/// Raw probes are useful for testing functionality that's not yet covered
/// by the rest of the `AutoCfg` API. For example, the following attribute
/// **must** be used at the crate level, so it wouldn't work within the code
/// templates used by other `probe_*` methods.
///
/// ```
/// # extern crate autocfg;
/// # // Normally, cargo will set `OUT_DIR` for build scripts.
/// # std::env::set_var("OUT_DIR", "target");
/// let ac = autocfg::new();
/// assert!(ac.probe_raw("#![no_builtins]").is_ok());
/// ```
///
/// Rust nightly features could be tested as well -- ideally including a
/// code sample to ensure the unstable feature still works as expected.
/// For example, `slice::group_by` was renamed to `chunk_by` when it was
/// stabilized, even though the feature name was unchanged, so testing the
/// `#![feature(..)]` alone wouldn't reveal that. For larger snippets,
/// [`include_str!`] may be useful to load them from separate files.
///
/// ```
/// # extern crate autocfg;
/// # // Normally, cargo will set `OUT_DIR` for build scripts.
/// # std::env::set_var("OUT_DIR", "target");
/// let ac = autocfg::new();
/// let code = r#"
/// #![feature(slice_group_by)]
/// pub fn probe(slice: &[i32]) -> impl Iterator<Item = &[i32]> {
/// slice.group_by(|a, b| a == b)
/// }
/// "#;
/// if ac.probe_raw(code).is_ok() {
/// autocfg::emit("has_slice_group_by");
/// }
/// ```
pub fn probe_raw(&self, code: &str) -> Result<(), Error> {
self.probe_fmt(format_args!("{}", code))
}

/// Tests whether the given sysroot crate can be used.
Expand All @@ -286,8 +342,8 @@ impl AutoCfg {
/// extern crate CRATE as probe;
/// ```
pub fn probe_sysroot_crate(&self, name: &str) -> bool {
self.probe(format!("extern crate {} as probe;", name)) // `as _` wasn't stabilized until Rust 1.33
.unwrap_or(false)
// Note: `as _` wasn't stabilized until Rust 1.33
self.probe(format_args!("extern crate {} as probe;", name))
}

/// Emits a config value `has_CRATE` if `probe_sysroot_crate` returns true.
Expand All @@ -305,7 +361,7 @@ impl AutoCfg {
/// pub use PATH;
/// ```
pub fn probe_path(&self, path: &str) -> bool {
self.probe(format!("pub use {};", path)).unwrap_or(false)
self.probe(format_args!("pub use {};", path))
}

/// Emits a config value `has_PATH` if `probe_path` returns true.
Expand Down Expand Up @@ -333,8 +389,7 @@ impl AutoCfg {
/// pub trait Probe: TRAIT + Sized {}
/// ```
pub fn probe_trait(&self, name: &str) -> bool {
self.probe(format!("pub trait Probe: {} + Sized {{}}", name))
.unwrap_or(false)
self.probe(format_args!("pub trait Probe: {} + Sized {{}}", name))
}

/// Emits a config value `has_TRAIT` if `probe_trait` returns true.
Expand Down Expand Up @@ -362,8 +417,7 @@ impl AutoCfg {
/// pub type Probe = TYPE;
/// ```
pub fn probe_type(&self, name: &str) -> bool {
self.probe(format!("pub type Probe = {};", name))
.unwrap_or(false)
self.probe(format_args!("pub type Probe = {};", name))
}

/// Emits a config value `has_TYPE` if `probe_type` returns true.
Expand Down Expand Up @@ -391,8 +445,7 @@ impl AutoCfg {
/// pub fn probe() { let _ = EXPR; }
/// ```
pub fn probe_expression(&self, expr: &str) -> bool {
self.probe(format!("pub fn probe() {{ let _ = {}; }}", expr))
.unwrap_or(false)
self.probe(format_args!("pub fn probe() {{ let _ = {}; }}", expr))
}

/// Emits the given `cfg` value if `probe_expression` returns true.
Expand All @@ -410,8 +463,7 @@ impl AutoCfg {
/// pub const PROBE: () = ((), EXPR).0;
/// ```
pub fn probe_constant(&self, expr: &str) -> bool {
self.probe(format!("pub const PROBE: () = ((), {}).0;", expr))
.unwrap_or(false)
self.probe(format_args!("pub const PROBE: () = ((), {}).0;", expr))
}

/// Emits the given `cfg` value if `probe_constant` returns true.
Expand Down
12 changes: 12 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ fn probe_constant() {
ac.assert_min(1, 39, ac.probe_constant(r#""test".len()"#));
}

#[test]
fn probe_raw() {
let ac = AutoCfg::for_test().unwrap();

// This attribute **must** be used at the crate level.
assert!(ac.probe_raw("#![no_builtins]").is_ok());

assert!(ac.probe_raw("#![deny(dead_code)] fn x() {}").is_err());
assert!(ac.probe_raw("#![allow(dead_code)] fn x() {}").is_ok());
assert!(ac.probe_raw("#![deny(dead_code)] pub fn x() {}").is_ok());
}

#[test]
fn dir_does_not_contain_target() {
assert!(!super::dir_contains_target(
Expand Down

0 comments on commit e0a2240

Please sign in to comment.