diff --git a/Cargo.lock b/Cargo.lock index 2ea32a8012030..dda8c03e256f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3293,6 +3293,14 @@ dependencies = [ "winapi", ] +[[package]] +name = "replace-version-placeholder" +version = "0.1.0" +dependencies = [ + "tidy", + "walkdir", +] + [[package]] name = "rls" version = "1.41.0" diff --git a/Cargo.toml b/Cargo.toml index ffc886d47f399..1e83f05e0ca74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "src/tools/jsondocck", "src/tools/html-checker", "src/tools/bump-stage0", + "src/tools/replace-version-placeholder", "src/tools/lld-wrapper", ] diff --git a/compiler/rustc_feature/src/accepted.rs b/compiler/rustc_feature/src/accepted.rs index 37b2b0ecad772..fb1ad8d6beb3d 100644 --- a/compiler/rustc_feature/src/accepted.rs +++ b/compiler/rustc_feature/src/accepted.rs @@ -187,7 +187,7 @@ declare_features! ( /// especially around globs and shadowing (RFC 1560). (accepted, item_like_imports, "1.15.0", Some(35120), None), /// Allows `'a: { break 'a; }`. - (accepted, label_break_value, "1.65.0", Some(48594), None), + (accepted, label_break_value, "CURRENT_RUSTC_VERSION", Some(48594), None), /// Allows `if/while p && let q = r && ...` chains. (accepted, let_chains, "1.64.0", Some(53667), None), /// Allows `break {expr}` with a value inside `loop`s. diff --git a/compiler/rustc_passes/src/lib_features.rs b/compiler/rustc_passes/src/lib_features.rs index 868887c66cdec..70b6bfd1e582d 100644 --- a/compiler/rustc_passes/src/lib_features.rs +++ b/compiler/rustc_passes/src/lib_features.rs @@ -54,6 +54,14 @@ impl<'tcx> LibFeatureCollector<'tcx> { } } } + const VERSION_PLACEHOLDER: &str = "CURRENT_RUSTC_VERSION"; + + if let Some(s) = since && s.as_str() == VERSION_PLACEHOLDER { + let version = option_env!("CFG_VERSION").unwrap_or(""); + let version = version.split(' ').next().unwrap(); + since = Some(Symbol::intern(&version)); + } + if let Some(feature) = feature { // This additional check for stability is to make sure we // don't emit additional, irrelevant errors for malformed diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index 08fbb79fa654c..b8486af00186b 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -95,8 +95,8 @@ impl *const T { /// /// This is a bit safer than `as` because it wouldn't silently change the type if the code is /// refactored. - #[stable(feature = "ptr_const_cast", since = "1.65.0")] - #[rustc_const_stable(feature = "ptr_const_cast", since = "1.65.0")] + #[stable(feature = "ptr_const_cast", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "ptr_const_cast", since = "CURRENT_RUSTC_VERSION")] pub const fn cast_mut(self) -> *mut T { self as _ } diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index 8467469053151..a183614055934 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -100,8 +100,8 @@ impl *mut T { /// coercion. /// /// [`cast_mut`]: #method.cast_mut - #[stable(feature = "ptr_const_cast", since = "1.65.0")] - #[rustc_const_stable(feature = "ptr_const_cast", since = "1.65.0")] + #[stable(feature = "ptr_const_cast", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "ptr_const_cast", since = "CURRENT_RUSTC_VERSION")] pub const fn cast_const(self) -> *const T { self as _ } diff --git a/library/std/src/backtrace.rs b/library/std/src/backtrace.rs index 5cf6ec8178928..354200d4c95e9 100644 --- a/library/std/src/backtrace.rs +++ b/library/std/src/backtrace.rs @@ -58,7 +58,7 @@ //! `RUST_LIB_BACKTRACE` or `RUST_BACKTRACE` at runtime might not actually change //! how backtraces are captured. -#![stable(feature = "backtrace", since = "1.65.0")] +#![stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] #[cfg(test)] mod tests; @@ -104,7 +104,7 @@ use crate::vec::Vec; /// previous point in time. In some instances the `Backtrace` type may /// internally be empty due to configuration. For more information see /// `Backtrace::capture`. -#[stable(feature = "backtrace", since = "1.65.0")] +#[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] #[must_use] pub struct Backtrace { inner: Inner, @@ -112,21 +112,21 @@ pub struct Backtrace { /// The current status of a backtrace, indicating whether it was captured or /// whether it is empty for some other reason. -#[stable(feature = "backtrace", since = "1.65.0")] +#[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] #[non_exhaustive] #[derive(Debug, PartialEq, Eq)] pub enum BacktraceStatus { /// Capturing a backtrace is not supported, likely because it's not /// implemented for the current platform. - #[stable(feature = "backtrace", since = "1.65.0")] + #[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] Unsupported, /// Capturing a backtrace has been disabled through either the /// `RUST_LIB_BACKTRACE` or `RUST_BACKTRACE` environment variables. - #[stable(feature = "backtrace", since = "1.65.0")] + #[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] Disabled, /// A backtrace has been captured and the `Backtrace` should print /// reasonable information when rendered. - #[stable(feature = "backtrace", since = "1.65.0")] + #[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] Captured, } @@ -173,7 +173,7 @@ enum BytesOrWide { Wide(Vec), } -#[stable(feature = "backtrace", since = "1.65.0")] +#[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] impl fmt::Debug for Backtrace { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { let capture = match &self.inner { @@ -289,7 +289,7 @@ impl Backtrace { /// /// To forcibly capture a backtrace regardless of environment variables, use /// the `Backtrace::force_capture` function. - #[stable(feature = "backtrace", since = "1.65.0")] + #[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] #[inline(never)] // want to make sure there's a frame here to remove pub fn capture() -> Backtrace { if !Backtrace::enabled() { @@ -308,7 +308,7 @@ impl Backtrace { /// Note that capturing a backtrace can be an expensive operation on some /// platforms, so this should be used with caution in performance-sensitive /// parts of code. - #[stable(feature = "backtrace", since = "1.65.0")] + #[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] #[inline(never)] // want to make sure there's a frame here to remove pub fn force_capture() -> Backtrace { Backtrace::create(Backtrace::force_capture as usize) @@ -316,8 +316,8 @@ impl Backtrace { /// Forcibly captures a disabled backtrace, regardless of environment /// variable configuration. - #[stable(feature = "backtrace", since = "1.65.0")] - #[rustc_const_stable(feature = "backtrace", since = "1.65.0")] + #[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] pub const fn disabled() -> Backtrace { Backtrace { inner: Inner::Disabled } } @@ -361,7 +361,7 @@ impl Backtrace { /// Returns the status of this backtrace, indicating whether this backtrace /// request was unsupported, disabled, or a stack trace was actually /// captured. - #[stable(feature = "backtrace", since = "1.65.0")] + #[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] #[must_use] pub fn status(&self) -> BacktraceStatus { match self.inner { @@ -381,7 +381,7 @@ impl<'a> Backtrace { } } -#[stable(feature = "backtrace", since = "1.65.0")] +#[stable(feature = "backtrace", since = "CURRENT_RUSTC_VERSION")] impl fmt::Display for Backtrace { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { let capture = match &self.inner { diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index ba732cd7742f3..c4710e8faecbe 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -647,6 +647,7 @@ impl<'a> Builder<'a> { test::CrateRustdocJsonTypes, test::Linkcheck, test::TierCheck, + test::ReplacePlaceholderTest, test::Cargotest, test::Cargo, test::Rls, @@ -746,7 +747,12 @@ impl<'a> Builder<'a> { install::Src, install::Rustc ), - Kind::Run => describe!(run::ExpandYamlAnchors, run::BuildManifest, run::BumpStage0), + Kind::Run => describe!( + run::ExpandYamlAnchors, + run::BuildManifest, + run::BumpStage0, + run::ReplaceVersionPlaceholder, + ), // These commands either don't use paths, or they're special-cased in Build::build() Kind::Clean | Kind::Format | Kind::Setup => vec![], } diff --git a/src/bootstrap/run.rs b/src/bootstrap/run.rs index 25abe7a72fdc8..511872903d14e 100644 --- a/src/bootstrap/run.rs +++ b/src/bootstrap/run.rs @@ -103,3 +103,25 @@ impl Step for BumpStage0 { builder.run(&mut cmd); } } + +#[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] +pub struct ReplaceVersionPlaceholder; + +impl Step for ReplaceVersionPlaceholder { + type Output = (); + const ONLY_HOSTS: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path("src/tools/replace-version-placeholder") + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(ReplaceVersionPlaceholder); + } + + fn run(self, builder: &Builder<'_>) -> Self::Output { + let mut cmd = builder.tool_cmd(Tool::ReplaceVersionPlaceholder); + cmd.arg(&builder.src); + builder.run(&mut cmd); + } +} diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index c759d9b88e2ff..9cbdb3aca3223 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -2527,6 +2527,43 @@ impl Step for TierCheck { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct ReplacePlaceholderTest; + +impl Step for ReplacePlaceholderTest { + type Output = (); + const ONLY_HOSTS: bool = true; + const DEFAULT: bool = true; + + /// Ensure the version placeholder replacement tool builds + fn run(self, builder: &Builder<'_>) { + builder.info("build check for version replacement placeholder"); + + // Test the version placeholder replacement tool itself. + let bootstrap_host = builder.config.build; + let compiler = builder.compiler(0, bootstrap_host); + let cargo = tool::prepare_tool_cargo( + builder, + compiler, + Mode::ToolBootstrap, + bootstrap_host, + "test", + "src/tools/replace-version-placeholder", + SourceType::InTree, + &[], + ); + try_run(builder, &mut cargo.into()); + } + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path("src/tools/replace-version-placeholder") + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(Self); + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct LintDocs { pub compiler: Compiler, diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 570da20e7d60d..5bb40014eb97f 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -378,6 +378,7 @@ bootstrap_tool!( JsonDocCk, "src/tools/jsondocck", "jsondocck"; HtmlChecker, "src/tools/html-checker", "html-checker"; BumpStage0, "src/tools/bump-stage0", "bump-stage0"; + ReplaceVersionPlaceholder, "src/tools/replace-version-placeholder", "replace-version-placeholder"; ); #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] diff --git a/src/tools/replace-version-placeholder/Cargo.toml b/src/tools/replace-version-placeholder/Cargo.toml new file mode 100644 index 0000000000000..346ce6bd1dbfd --- /dev/null +++ b/src/tools/replace-version-placeholder/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "replace-version-placeholder" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tidy = { path = "../tidy" } +walkdir = "2" diff --git a/src/tools/replace-version-placeholder/src/main.rs b/src/tools/replace-version-placeholder/src/main.rs new file mode 100644 index 0000000000000..146e53f2e9a0f --- /dev/null +++ b/src/tools/replace-version-placeholder/src/main.rs @@ -0,0 +1,30 @@ +use std::path::PathBuf; +use tidy::{t, walk}; + +pub const VERSION_PLACEHOLDER: &str = "CURRENT_RUSTC_VERSION"; + +fn main() { + let root_path: PathBuf = std::env::args_os().nth(1).expect("need path to root of repo").into(); + let version_path = root_path.join("src").join("version"); + let version_str = t!(std::fs::read_to_string(&version_path), version_path); + let version_str = version_str.trim(); + walk::walk( + &root_path, + &mut |path| { + walk::filter_dirs(path) + // We exempt these as they require the placeholder + // for their operation + || path.ends_with("compiler/rustc_passes/src/lib_features.rs") + || path.ends_with("src/tools/tidy/src/features/version.rs") + || path.ends_with("src/tools/replace-version-placeholder") + }, + &mut |entry, contents| { + if !contents.contains(VERSION_PLACEHOLDER) { + return; + } + let new_contents = contents.replace(VERSION_PLACEHOLDER, version_str); + let path = entry.path(); + t!(std::fs::write(&path, new_contents), path); + }, + ); +} diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index 2f22c081a54b5..de292d3305db8 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -175,6 +175,36 @@ pub fn check( tidy_error!(bad, "Found {} features without a gate test.", gate_untested.len()); } + let (version, channel) = get_version_and_channel(src_path); + + let all_features_iter = features + .iter() + .map(|feat| (feat, "lang")) + .chain(lib_features.iter().map(|feat| (feat, "lib"))); + for ((feature_name, feature), kind) in all_features_iter { + let since = if let Some(since) = feature.since { since } else { continue }; + if since > version && since != Version::CurrentPlaceholder { + tidy_error!( + bad, + "The stabilization version {since} of {kind} feature `{feature_name}` is newer than the current {version}" + ); + } + if channel == "nightly" && since == version { + tidy_error!( + bad, + "The stabilization version {since} of {kind} feature `{feature_name}` is written out but should be {}", + version::VERSION_PLACEHOLDER + ); + } + if channel != "nightly" && since == Version::CurrentPlaceholder { + tidy_error!( + bad, + "The placeholder use of {kind} feature `{feature_name}` is not allowed on the {} channel", + version::VERSION_PLACEHOLDER + ); + } + } + if *bad { return CollectedFeatures { lib: lib_features, lang: features }; } @@ -195,6 +225,14 @@ pub fn check( CollectedFeatures { lib: lib_features, lang: features } } +fn get_version_and_channel(src_path: &Path) -> (Version, String) { + let version_str = t!(std::fs::read_to_string(src_path.join("version"))); + let version_str = version_str.trim(); + let version = t!(std::str::FromStr::from_str(&version_str).map_err(|e| format!("{e:?}"))); + let channel_str = t!(std::fs::read_to_string(src_path.join("ci").join("channel"))); + (version, channel_str.trim().to_owned()) +} + fn format_features<'a>( features: &'a Features, family: &'a str, diff --git a/src/tools/tidy/src/features/version.rs b/src/tools/tidy/src/features/version.rs index 620be2f985230..0830c226caf41 100644 --- a/src/tools/tidy/src/features/version.rs +++ b/src/tools/tidy/src/features/version.rs @@ -5,14 +5,22 @@ use std::str::FromStr; #[cfg(test)] mod tests; +pub const VERSION_PLACEHOLDER: &str = "CURRENT_RUSTC_VERSION"; + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Version { - parts: [u32; 3], +pub enum Version { + Explicit { parts: [u32; 3] }, + CurrentPlaceholder, } impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad(&format!("{}.{}.{}", self.parts[0], self.parts[1], self.parts[2])) + match self { + Version::Explicit { parts } => { + f.pad(&format!("{}.{}.{}", parts[0], parts[1], parts[2])) + } + Version::CurrentPlaceholder => f.pad(&format!("CURRENT")), + } } } @@ -32,6 +40,9 @@ impl FromStr for Version { type Err = ParseVersionError; fn from_str(s: &str) -> Result { + if s == VERSION_PLACEHOLDER { + return Ok(Version::CurrentPlaceholder); + } let mut iter = s.split('.').map(|part| Ok(part.parse()?)); let mut part = || iter.next().unwrap_or(Err(ParseVersionError::WrongNumberOfParts)); @@ -43,6 +54,6 @@ impl FromStr for Version { return Err(ParseVersionError::WrongNumberOfParts); } - Ok(Self { parts }) + Ok(Version::Explicit { parts }) } } diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs index 09848462ae207..12d3bdcd76f82 100644 --- a/src/tools/tidy/src/lib.rs +++ b/src/tools/tidy/src/lib.rs @@ -3,12 +3,14 @@ //! This library contains the tidy lints and exposes it //! to be used by tools. -use std::fs::File; -use std::io::Read; -use walkdir::{DirEntry, WalkDir}; - -use std::path::Path; - +use walk::{filter_dirs, walk, walk_many, walk_no_read}; + +/// A helper macro to `unwrap` a result except also print out details like: +/// +/// * The expression that failed +/// * The error itself +/// * (optionally) a path connected to the error (e.g. failure to open a file) +#[macro_export] macro_rules! t { ($e:expr, $p:expr) => { match $e { @@ -28,7 +30,8 @@ macro_rules! t { macro_rules! tidy_error { ($bad:expr, $fmt:expr) => ({ *$bad = true; - eprintln!("tidy error: {}", $fmt); + eprint!("tidy error: "); + eprintln!($fmt); }); ($bad:expr, $fmt:expr, $($arg:tt)*) => ({ *$bad = true; @@ -52,59 +55,4 @@ pub mod target_specific_tests; pub mod ui_tests; pub mod unit_tests; pub mod unstable_book; - -fn filter_dirs(path: &Path) -> bool { - let skip = [ - "tidy-test-file", - "compiler/rustc_codegen_cranelift", - "compiler/rustc_codegen_gcc", - "src/llvm-project", - "library/backtrace", - "library/portable-simd", - "library/stdarch", - "src/tools/cargo", - "src/tools/clippy", - "src/tools/miri", - "src/tools/rls", - "src/tools/rust-analyzer", - "src/tools/rust-installer", - "src/tools/rustfmt", - "src/doc/book", - // Filter RLS output directories - "target/rls", - ]; - skip.iter().any(|p| path.ends_with(p)) -} - -fn walk_many( - paths: &[&Path], - skip: &mut dyn FnMut(&Path) -> bool, - f: &mut dyn FnMut(&DirEntry, &str), -) { - for path in paths { - walk(path, skip, f); - } -} - -fn walk(path: &Path, skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&DirEntry, &str)) { - let mut contents = String::new(); - walk_no_read(path, skip, &mut |entry| { - contents.clear(); - if t!(File::open(entry.path()), entry.path()).read_to_string(&mut contents).is_err() { - contents.clear(); - } - f(&entry, &contents); - }); -} - -fn walk_no_read(path: &Path, skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&DirEntry)) { - let walker = WalkDir::new(path).into_iter().filter_entry(|e| !skip(e.path())); - for entry in walker { - if let Ok(entry) = entry { - if entry.file_type().is_dir() { - continue; - } - f(&entry); - } - } -} +pub mod walk; diff --git a/src/tools/tidy/src/walk.rs b/src/tools/tidy/src/walk.rs new file mode 100644 index 0000000000000..6dca55dfa9fa9 --- /dev/null +++ b/src/tools/tidy/src/walk.rs @@ -0,0 +1,65 @@ +use std::fs::File; +use std::io::Read; +use walkdir::{DirEntry, WalkDir}; + +use std::path::Path; + +pub fn filter_dirs(path: &Path) -> bool { + let skip = [ + "tidy-test-file", + "compiler/rustc_codegen_cranelift", + "compiler/rustc_codegen_gcc", + "src/llvm-project", + "library/backtrace", + "library/portable-simd", + "library/stdarch", + "src/tools/cargo", + "src/tools/clippy", + "src/tools/miri", + "src/tools/rls", + "src/tools/rust-analyzer", + "src/tools/rust-installer", + "src/tools/rustfmt", + "src/doc/book", + // Filter RLS output directories + "target/rls", + ]; + skip.iter().any(|p| path.ends_with(p)) +} + +pub fn walk_many( + paths: &[&Path], + skip: &mut dyn FnMut(&Path) -> bool, + f: &mut dyn FnMut(&DirEntry, &str), +) { + for path in paths { + walk(path, skip, f); + } +} + +pub fn walk(path: &Path, skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&DirEntry, &str)) { + let mut contents = String::new(); + walk_no_read(path, skip, &mut |entry| { + contents.clear(); + if t!(File::open(entry.path()), entry.path()).read_to_string(&mut contents).is_err() { + contents.clear(); + } + f(&entry, &contents); + }); +} + +pub(crate) fn walk_no_read( + path: &Path, + skip: &mut dyn FnMut(&Path) -> bool, + f: &mut dyn FnMut(&DirEntry), +) { + let walker = WalkDir::new(path).into_iter().filter_entry(|e| !skip(e.path())); + for entry in walker { + if let Ok(entry) = entry { + if entry.file_type().is_dir() { + continue; + } + f(&entry); + } + } +}