From 6dab92c6917a70193fa2c40746072a4618e26520 Mon Sep 17 00:00:00 2001 From: Anders429 Date: Sat, 13 Feb 2021 23:43:29 -0800 Subject: [PATCH 01/11] Parse rustc channel. --- src/lib.rs | 10 +++++- src/version.rs | 92 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 70 insertions(+), 32 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index de50135..d7e9407 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,6 +73,7 @@ mod error; pub use error::Error; mod version; +pub use version::Channel; use version::Version; #[cfg(test)] @@ -84,6 +85,7 @@ pub struct AutoCfg { out_dir: PathBuf, rustc: PathBuf, rustc_version: Version, + rustc_channel: Channel, target: Option, no_std: bool, rustflags: Option>, @@ -155,7 +157,7 @@ impl AutoCfg { pub fn with_dir>(dir: T) -> Result { let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); let rustc: PathBuf = rustc.into(); - let rustc_version = try!(Version::from_rustc(&rustc)); + let (rustc_version, rustc_channel) = try!(version::version_and_channel_from_rustc(&rustc)); let target = env::var_os("TARGET"); @@ -194,6 +196,7 @@ impl AutoCfg { out_dir: dir, rustc: rustc, rustc_version: rustc_version, + rustc_channel: rustc_channel, target: target, no_std: false, rustflags: rustflags, @@ -404,6 +407,11 @@ impl AutoCfg { emit(cfg); } } + + /// Returns the current `rustc` [`Channel`]. + pub fn rustc_channel(&self) -> Channel { + self.rustc_channel + } } fn mangle(s: &str) -> String { diff --git a/src/version.rs b/src/version.rs index 378c21e..f7a40d0 100644 --- a/src/version.rs +++ b/src/version.rs @@ -4,6 +4,19 @@ use std::str; use super::{error, Error}; +/// The channel the current compiler was released on. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Channel { + /// Stable channel. + Stable, + /// Beta channel, one release ahead of stable. + Beta, + /// Nightly channel, released every night. + Nightly, + /// Dev channel. + Dev, +} + /// A version structure for making relative comparisons. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Version { @@ -21,40 +34,57 @@ impl Version { patch: patch, } } +} + +pub fn version_and_channel_from_rustc(rustc: &Path) -> Result<(Version, Channel), Error> { + // Get rustc's verbose version + let output = try!(Command::new(rustc) + .args(&["--version", "--verbose"]) + .output() + .map_err(error::from_io)); + if !output.status.success() { + return Err(error::from_str("could not execute rustc")); + } + let output = try!(str::from_utf8(&output.stdout).map_err(error::from_utf8)); - pub fn from_rustc(rustc: &Path) -> Result { - // Get rustc's verbose version - let output = try!(Command::new(rustc) - .args(&["--version", "--verbose"]) - .output() - .map_err(error::from_io)); - if !output.status.success() { - return Err(error::from_str("could not execute rustc")); + // Find the release line in the verbose version output. + let release = match output.lines().find(|line| line.starts_with("release: ")) { + Some(line) => &line["release: ".len()..], + None => return Err(error::from_str("could not find rustc release")), + }; + + let mut release_split = release.split('-'); + let version = match release_split.next() { + Some(version) => version, + None => return Err(error::from_str("could not parse rustc release")), + }; + let channel = match release_split.next() { + Some(channel) => { + if channel.starts_with("beta") { + Channel::Beta + } else if channel.starts_with("nightly") { + Channel::Nightly + } else if channel.starts_with("dev") { + Channel::Dev + } else { + return Err(error::from_str("could not parse rustc channel")); + } } - let output = try!(str::from_utf8(&output.stdout).map_err(error::from_utf8)); - - // Find the release line in the verbose version output. - let release = match output.lines().find(|line| line.starts_with("release: ")) { - Some(line) => &line["release: ".len()..], - None => return Err(error::from_str("could not find rustc release")), - }; - - // Strip off any extra channel info, e.g. "-beta.N", "-nightly" - let version = match release.find('-') { - Some(i) => &release[..i], - None => release, - }; - - // Split the version into semver components. - let mut iter = version.splitn(3, '.'); - let major = try!(iter.next().ok_or(error::from_str("missing major version"))); - let minor = try!(iter.next().ok_or(error::from_str("missing minor version"))); - let patch = try!(iter.next().ok_or(error::from_str("missing patch version"))); - - Ok(Version::new( + None => Channel::Stable, + }; + + // Split the version into semver components. + let mut iter = version.splitn(3, '.'); + let major = try!(iter.next().ok_or(error::from_str("missing major version"))); + let minor = try!(iter.next().ok_or(error::from_str("missing minor version"))); + let patch = try!(iter.next().ok_or(error::from_str("missing patch version"))); + + Ok(( + Version::new( try!(major.parse().map_err(error::from_num)), try!(minor.parse().map_err(error::from_num)), try!(patch.parse().map_err(error::from_num)), - )) - } + ), + channel, + )) } From 415147512a4059bcbf771291906ead6451fc0171 Mon Sep 17 00:00:00 2001 From: Anders429 Date: Sun, 14 Feb 2021 01:12:46 -0800 Subject: [PATCH 02/11] Probing for minimum rustc channel. --- src/lib.rs | 22 ++++++++++++++++++++++ src/tests.rs | 14 ++++++++++++++ src/version.rs | 21 +++++++++++++++++++-- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d7e9407..56b9e7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -412,6 +412,28 @@ impl AutoCfg { pub fn rustc_channel(&self) -> Channel { self.rustc_channel } + + /// Tests whether the current `rustc` [`Channel`] supports the features of the requested + /// `channel`. + pub fn probe_rustc_channel(&self, channel: Channel) -> bool { + self.rustc_channel >= channel + } + + /// Emits a `cfg` value of the form `rustc_channel_, like `rustc_channel_nightly`, if + /// the current `rustc` [`Channel`] supports the features of the requested `channel`. + pub fn emit_rustc_channel(&self, channel: Channel) { + if self.probe_rustc_channel(channel) { + emit(&format!( + "rustc_channel_{}", + match channel { + Channel::Stable => "stable", + Channel::Beta => "beta", + Channel::Nightly => "nightly", + Channel::Dev => "dev", + } + )) + } + } } fn mangle(s: &str) -> String { diff --git a/src/tests.rs b/src/tests.rs index 4c67462..dbcf14a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,4 +1,5 @@ use super::AutoCfg; +use super::Channel; use std::env; impl AutoCfg { @@ -30,6 +31,12 @@ fn autocfg_version() { assert!(ac.probe_rustc_version(1, 0)); } +#[test] +fn autocfg_channel() { + let ac = AutoCfg::for_test().unwrap(); + assert!(ac.probe_rustc_channel(Channel::Stable)); +} + #[test] fn version_cmp() { use super::version::Version; @@ -43,6 +50,13 @@ fn version_cmp() { assert!(Version::new(2, 0, 0) > v123); } +#[test] +fn channel_cmp() { + assert!(Channel::Stable < Channel::Beta); + assert!(Channel::Beta < Channel::Nightly); + assert!(Channel::Nightly < Channel::Dev); +} + #[test] fn probe_add() { let ac = AutoCfg::for_test().unwrap(); diff --git a/src/version.rs b/src/version.rs index f7a40d0..68b05dc 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::path::Path; use std::process::Command; use std::str; @@ -5,18 +6,34 @@ use std::str; use super::{error, Error}; /// The channel the current compiler was released on. +/// +/// `Channel`s are orderable by their available features. The more features a channel supports, the +/// higher it is ordered. Specifically, channels are ordered as follows: `Stable < Beta < Nightly < +/// Dev`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Channel { /// Stable channel. Stable, - /// Beta channel, one release ahead of stable. + /// Beta channel. Beta, - /// Nightly channel, released every night. + /// Nightly channel. Nightly, /// Dev channel. Dev, } +impl PartialOrd for Channel { + fn partial_cmp(&self, other: &Self) -> Option { + (*self as u8).partial_cmp(&(*other as u8)) + } +} + +impl Ord for Channel { + fn cmp(&self, other: &Self) -> Ordering { + (*self as u8).cmp(&(*other as u8)) + } +} + /// A version structure for making relative comparisons. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Version { From 53d2d1204365d0f83617a48e3f3450f51f2879d9 Mon Sep 17 00:00:00 2001 From: Anders429 Date: Sun, 14 Feb 2021 08:35:17 -0800 Subject: [PATCH 03/11] Probe nightly features. --- src/lib.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 56b9e7b..b9eb3e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -434,6 +434,23 @@ impl AutoCfg { )) } } + + /// Tests whether `feature` is an available feature gate. + pub fn probe_feature(&self, feature: &str) -> bool { + if self.probe_rustc_channel(Channel::Nightly) { + self.probe(format!("#![feature({})]", mangle(feature))).unwrap_or(false) + } else { + // Features are only supported on nightly channels. + false + } + } + + /// Emits the given `cfg` if `feature` is an available feature gate. + pub fn emit_feature_cfg(&self, feature: &str, cfg: &str) { + if self.probe_feature(feature) { + emit(cfg) + } + } } fn mangle(s: &str) -> String { From 7cdb0070d9997c6f95ba0eb6a9f4cb1f89125409 Mon Sep 17 00:00:00 2001 From: Anders429 Date: Sun, 14 Feb 2021 09:18:46 -0800 Subject: [PATCH 04/11] Setting and unsetting nightly features. --- src/lib.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index b9eb3e4..605022a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,7 @@ macro_rules! try { }; } +use std::collections::HashSet; use std::env; use std::ffi::OsString; use std::fs; @@ -88,6 +89,7 @@ pub struct AutoCfg { rustc_channel: Channel, target: Option, no_std: bool, + features: HashSet, rustflags: Option>, } @@ -199,6 +201,7 @@ impl AutoCfg { rustc_channel: rustc_channel, target: target, no_std: false, + features: HashSet::new(), rustflags: rustflags, }; @@ -258,6 +261,9 @@ impl AutoCfg { if self.no_std { try!(stdin.write_all(b"#![no_std]\n").map_err(error::from_io)); } + for feature in &self.features { + try!(stdin.write_all(format!("#![feature({})]\n", feature).as_bytes()).map_err(error::from_io)); + } try!(stdin.write_all(code.as_ref()).map_err(error::from_io)); drop(stdin); @@ -451,6 +457,26 @@ impl AutoCfg { emit(cfg) } } + + /// Sets `feature` as a gated feature for all probes. + /// + /// This adds `#![feature()]` at the start of all probes on nightly `rustc` channels. + /// Multiple features can be enabled at once. If `feature` is not valid, it is not enabled. + /// + /// On non-nightly channels this does nothing. + pub fn set_feature(&mut self, feature: &str) { + if self.probe_feature(feature) { + self.features.insert(feature.to_owned()); + } + } + + /// Removes `feature` as a gated feature for all probes, if it is set. + /// + /// This removes `#![feature()]` from the start of all probes on nightly `rustc` + /// channels. If the feature was not set, this does nothing. + pub fn unset_feature(&mut self, feature: &str) { + self.features.remove(feature); + } } fn mangle(s: &str) -> String { From d771bbce2a66ecdaff094a5b9b645f20be1f40ef Mon Sep 17 00:00:00 2001 From: Anders429 Date: Sun, 14 Feb 2021 11:41:05 -0800 Subject: [PATCH 05/11] Mangle features. --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 605022a..5720ae2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -466,7 +466,7 @@ impl AutoCfg { /// On non-nightly channels this does nothing. pub fn set_feature(&mut self, feature: &str) { if self.probe_feature(feature) { - self.features.insert(feature.to_owned()); + self.features.insert(mangle(feature)); } } @@ -475,7 +475,7 @@ impl AutoCfg { /// This removes `#![feature()]` from the start of all probes on nightly `rustc` /// channels. If the feature was not set, this does nothing. pub fn unset_feature(&mut self, feature: &str) { - self.features.remove(feature); + self.features.remove(mangle(feature)); } } From 317cd14071b0debdd584648d785f58518e4de5d2 Mon Sep 17 00:00:00 2001 From: Anders429 Date: Sun, 14 Feb 2021 22:07:27 -0800 Subject: [PATCH 06/11] Mangle features. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5720ae2..d372929 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -475,7 +475,7 @@ impl AutoCfg { /// This removes `#![feature()]` from the start of all probes on nightly `rustc` /// channels. If the feature was not set, this does nothing. pub fn unset_feature(&mut self, feature: &str) { - self.features.remove(mangle(feature)); + self.features.remove(&mangle(feature)); } } From 85e51ceb51f153b8cf2d5cdeed2ac2dff0496910 Mon Sep 17 00:00:00 2001 From: Anders429 Date: Sun, 14 Feb 2021 23:04:27 -0800 Subject: [PATCH 07/11] Test channel probing. --- src/lib.rs | 7 +++++-- src/tests.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d372929..6d6f2c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -262,7 +262,9 @@ impl AutoCfg { try!(stdin.write_all(b"#![no_std]\n").map_err(error::from_io)); } for feature in &self.features { - try!(stdin.write_all(format!("#![feature({})]\n", feature).as_bytes()).map_err(error::from_io)); + try!(stdin + .write_all(format!("#![feature({})]\n", feature).as_bytes()) + .map_err(error::from_io)); } try!(stdin.write_all(code.as_ref()).map_err(error::from_io)); drop(stdin); @@ -444,7 +446,8 @@ impl AutoCfg { /// Tests whether `feature` is an available feature gate. pub fn probe_feature(&self, feature: &str) -> bool { if self.probe_rustc_channel(Channel::Nightly) { - self.probe(format!("#![feature({})]", mangle(feature))).unwrap_or(false) + self.probe(format!("#![feature({})]", mangle(feature))) + .unwrap_or(false) } else { // Features are only supported on nightly channels. false diff --git a/src/tests.rs b/src/tests.rs index dbcf14a..c2a1776 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -16,6 +16,12 @@ impl AutoCfg { assert_eq!(self.probe_rustc_version(major, minor), probe_result); } + fn assert_on_channel(&self, channel: Channel, probe_result: bool) { + if self.rustc_channel == channel { + assert!(probe_result); + } + } + fn for_test() -> Result { match env::var_os("TESTS_TARGET_DIR") { Some(d) => Self::with_dir(d), @@ -146,6 +152,42 @@ fn probe_constant() { ac.assert_min(1, 39, ac.probe_constant(r#""test".len()"#)); } +#[test] +fn probe_stable() { + let ac = AutoCfg::for_test().unwrap(); + + ac.assert_on_channel(Channel::Stable, ac.probe_rustc_channel(Channel::Stable)); + ac.assert_on_channel(Channel::Beta, ac.probe_rustc_channel(Channel::Stable)); + ac.assert_on_channel(Channel::Nightly, ac.probe_rustc_channel(Channel::Stable)); +} + +#[test] +fn probe_beta() { + let ac = AutoCfg::for_test().unwrap(); + + ac.assert_on_channel(Channel::Stable, !ac.probe_rustc_channel(Channel::Beta)); + ac.assert_on_channel(Channel::Beta, ac.probe_rustc_channel(Channel::Beta)); + ac.assert_on_channel(Channel::Nightly, ac.probe_rustc_channel(Channel::Beta)); +} + +#[test] +fn probe_nightly() { + let ac = AutoCfg::for_test().unwrap(); + + ac.assert_on_channel(Channel::Stable, !ac.probe_rustc_channel(Channel::Nightly)); + ac.assert_on_channel(Channel::Beta, !ac.probe_rustc_channel(Channel::Nightly)); + ac.assert_on_channel(Channel::Nightly, ac.probe_rustc_channel(Channel::Nightly)); +} + +#[test] +fn probe_dev() { + let ac = AutoCfg::for_test().unwrap(); + + ac.assert_on_channel(Channel::Stable, !ac.probe_rustc_channel(Channel::Dev)); + ac.assert_on_channel(Channel::Beta, !ac.probe_rustc_channel(Channel::Dev)); + ac.assert_on_channel(Channel::Nightly, !ac.probe_rustc_channel(Channel::Dev)); +} + #[test] fn dir_does_not_contain_target() { assert!(!super::dir_contains_target( From 0a0af272704595e7d4c0a99850a74368f2c1748a Mon Sep 17 00:00:00 2001 From: Anders429 Date: Mon, 15 Feb 2021 09:00:33 -0800 Subject: [PATCH 08/11] Test probing features. --- src/tests.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tests.rs b/src/tests.rs index c2a1776..d629afc 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -188,6 +188,17 @@ fn probe_dev() { ac.assert_on_channel(Channel::Nightly, !ac.probe_rustc_channel(Channel::Dev)); } +#[test] +fn probe_feature() { + let ac = AutoCfg::for_test().unwrap(); + + assert!(!ac.probe_feature("nonexistant_feature_abcdefg")); + // rust1 is the feature gate for Rust 1.0. + ac.assert_on_channel(Channel::Stable, !ac.probe_feature("rust1")); + ac.assert_on_channel(Channel::Beta, !ac.probe_feature("rust1")); + ac.assert_on_channel(Channel::Nightly, ac.probe_feature("rust1")); +} + #[test] fn dir_does_not_contain_target() { assert!(!super::dir_contains_target( From b0e0028b44fe3505edbc74af3b2535befa4e87bf Mon Sep 17 00:00:00 2001 From: Anders429 Date: Mon, 15 Feb 2021 09:21:20 -0800 Subject: [PATCH 09/11] Test feature setting and unsetting. --- src/tests.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tests.rs b/src/tests.rs index d629afc..fcdff82 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,6 @@ use super::AutoCfg; use super::Channel; +use super::Version; use std::env; impl AutoCfg { @@ -45,7 +46,6 @@ fn autocfg_channel() { #[test] fn version_cmp() { - use super::version::Version; let v123 = Version::new(1, 2, 3); assert!(Version::new(1, 0, 0) < v123); @@ -199,6 +199,24 @@ fn probe_feature() { ac.assert_on_channel(Channel::Nightly, ac.probe_feature("rust1")); } +#[test] +fn set_feature() { + let mut ac = AutoCfg::for_test().unwrap(); + let step_trait = ac.core_std("iter::Step"); + + ac.set_feature("step_trait"); + // As of Rust 1.50, the Step trait is experimental and therefore unaccessible on stable. Setting + // the feature should not allow access. + if ac.rustc_version <= Version::new(1, 50, 0) { + ac.assert_on_channel(Channel::Stable, !ac.probe_trait(&step_trait)); + ac.assert_on_channel(Channel::Beta, !ac.probe_trait(&step_trait)); + } + // The trait should be available on nightly, since the feature is set. + ac.assert_on_channel(Channel::Nightly, ac.probe_trait(&step_trait)); + ac.unset_feature("step_trait"); + ac.assert_on_channel(Channel::Nightly, !ac.probe_trait(&step_trait)); +} + #[test] fn dir_does_not_contain_target() { assert!(!super::dir_contains_target( From 470f1f4078dd3e4e26c2828088edd361ffea5fa7 Mon Sep 17 00:00:00 2001 From: Anders429 Date: Mon, 15 Feb 2021 09:36:09 -0800 Subject: [PATCH 10/11] Test feature setting and unsetting. --- src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests.rs b/src/tests.rs index fcdff82..a76f695 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,6 +1,6 @@ use super::AutoCfg; use super::Channel; -use super::Version; +use super::version::Version; use std::env; impl AutoCfg { From a7bb4d0271c605b2a1a7a0c68448f6983447978b Mon Sep 17 00:00:00 2001 From: Anders429 Date: Mon, 15 Feb 2021 10:08:25 -0800 Subject: [PATCH 11/11] Fix formatting. --- src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index a76f695..26499bf 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,6 +1,6 @@ +use super::version::Version; use super::AutoCfg; use super::Channel; -use super::version::Version; use std::env; impl AutoCfg { @@ -202,7 +202,7 @@ fn probe_feature() { #[test] fn set_feature() { let mut ac = AutoCfg::for_test().unwrap(); - let step_trait = ac.core_std("iter::Step"); + let step_trait = ac.core_std("iter::Step"); ac.set_feature("step_trait"); // As of Rust 1.50, the Step trait is experimental and therefore unaccessible on stable. Setting