Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AutoCfg::probe_raw for free-form probes #59

Merged
merged 2 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
16 changes: 16 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,22 @@ fn probe_constant() {
ac.assert_min(1, 39, ac.probe_constant(r#""test".len()"#));
}

#[test]
fn probe_raw() {
let ac = AutoCfg::for_test().unwrap();
let prefix = if ac.no_std { "#![no_std]\n" } else { "" };
let f = |s| format!("{}{}", prefix, s);

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

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

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