From e0a22408361b3be650b9fd6542d2b860abd7656b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 28 Mar 2024 15:13:02 -0700 Subject: [PATCH 1/2] Add `AutoCfg::probe_raw` for free-form probes 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. --- examples/nightly.rs | 18 +++++++++ src/error.rs | 14 ++++++- src/lib.rs | 98 ++++++++++++++++++++++++++++++++++----------- src/tests.rs | 12 ++++++ 4 files changed, 118 insertions(+), 24 deletions(-) create mode 100644 examples/nightly.rs diff --git a/examples/nightly.rs b/examples/nightly.rs new file mode 100644 index 0000000..955e63d --- /dev/null +++ b/examples/nightly.rs @@ -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 { + slice.group_by(|a, b| a == b) + } + "#; + if ac.probe_raw(code).is_ok() { + autocfg::emit("has_slice_group_by"); + } +} diff --git a/src/error.rs b/src/error.rs index 4624835..7839ee9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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. @@ -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, } } } @@ -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), } } @@ -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), diff --git a/src/lib.rs b/src/lib.rs index 920e8a5..4147fe2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; @@ -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(); } @@ -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 } @@ -233,7 +234,7 @@ impl AutoCfg { } } - fn probe>(&self, code: T) -> Result { + fn probe_fmt<'a>(&self, source: Arguments<'a>) -> Result<(), Error> { #[allow(deprecated)] static ID: AtomicUsize = ATOMIC_USIZE_INIT; @@ -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 { + /// 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. @@ -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. @@ -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. @@ -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. @@ -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. @@ -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. @@ -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. diff --git a/src/tests.rs b/src/tests.rs index d3b1fbb..5f436f8 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -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( From e01e12038958fa20f20a6a662dfa92f46de41b7b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 28 Mar 2024 15:37:12 -0700 Subject: [PATCH 2/2] Fix the probe_raw test on no_std --- src/tests.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index 5f436f8..c2468a6 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -136,13 +136,17 @@ fn probe_constant() { #[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("#![no_builtins]").is_ok()); + assert!(ac.probe_raw(&f("#![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()); + 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]