From a28fc0b44aea48cc3e5e69528154297903d7f02c Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Thu, 20 Feb 2025 22:48:39 +0300 Subject: [PATCH 01/16] skip submodule updating logics on tarballs Signed-off-by: onur-ozkan (cherry picked from commit d2203ad59c67a6acb2968ea77e1e9dea5530e518) --- src/bootstrap/src/core/config/config.rs | 2 +- src/bootstrap/src/lib.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 0587408e987ef..053330d7417c6 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -2668,7 +2668,7 @@ impl Config { /// used instead to provide a nice error to the user if the submodule is /// missing. pub(crate) fn update_submodule(&self, relative_path: &str) { - if !self.submodules() { + if self.rust_info.is_from_tarball() || !self.submodules() { return; } diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index ccc115a279fb5..e5d2fc790fbec 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -471,6 +471,10 @@ impl Build { /// The given `err_hint` will be shown to the user if the submodule is not /// checked out and submodule management is disabled. pub fn require_submodule(&self, submodule: &str, err_hint: Option<&str>) { + if self.rust_info().is_from_tarball() { + return; + } + // When testing bootstrap itself, it is much faster to ignore // submodules. Almost all Steps work fine without their submodules. if cfg!(test) && !self.config.submodules() { From a280bac772426be9af66370b8e0ea2bd5c2d22e1 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Sun, 23 Feb 2025 08:23:51 +0300 Subject: [PATCH 02/16] downgrade bootstrap `cc` Signed-off-by: onur-ozkan (cherry picked from commit e4ca11f87ffca8c63aa56d45b46e62b6acc58bd7) --- src/bootstrap/Cargo.lock | 4 ++-- src/bootstrap/Cargo.toml | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock index c9697e670b777..b10ea346d6f9f 100644 --- a/src/bootstrap/Cargo.lock +++ b/src/bootstrap/Cargo.lock @@ -84,9 +84,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.0" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8" +checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" dependencies = [ "shlex", ] diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml index d8775a67e1939..68e0591c6b7b7 100644 --- a/src/bootstrap/Cargo.toml +++ b/src/bootstrap/Cargo.toml @@ -36,7 +36,9 @@ test = false # Most of the time updating these dependencies requires modifications to the # bootstrap codebase(e.g., https://github.com/rust-lang/rust/issues/124565); # otherwise, some targets will fail. That's why these dependencies are explicitly pinned. -cc = "=1.2.0" +# +# Do not upgrade this crate unless https://github.com/rust-lang/cc-rs/issues/1317 is fixed. +cc = "=1.1.22" cmake = "=0.1.48" build_helper = { path = "../build_helper" } From e616d37b33950b0b86d45fd6a34fe9755d7f9715 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Mon, 24 Feb 2025 03:48:12 +0000 Subject: [PATCH 03/16] Windows: Use MoveFileEx by default in `fs:rename` (cherry picked from commit 0dfe2ae3fb72c50ea369286131c73daede13d7e5) --- library/std/src/sys/pal/windows/fs.rs | 185 +++++++++----------------- 1 file changed, 60 insertions(+), 125 deletions(-) diff --git a/library/std/src/sys/pal/windows/fs.rs b/library/std/src/sys/pal/windows/fs.rs index f8493c21ad444..a0234febdcb6e 100644 --- a/library/std/src/sys/pal/windows/fs.rs +++ b/library/std/src/sys/pal/windows/fs.rs @@ -1,10 +1,10 @@ use super::api::{self, WinError}; use super::{IoResult, to_u16s}; -use crate::alloc::{alloc, handle_alloc_error}; +use crate::alloc::{Layout, alloc, dealloc, handle_alloc_error}; use crate::borrow::Cow; use crate::ffi::{OsStr, OsString, c_void}; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; -use crate::mem::{self, MaybeUninit}; +use crate::mem::{self, MaybeUninit, offset_of}; use crate::os::windows::io::{AsHandle, BorrowedHandle}; use crate::os::windows::prelude::*; use crate::path::{Path, PathBuf}; @@ -296,6 +296,10 @@ impl OpenOptions { impl File { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { let path = maybe_verbatim(path)?; + Self::open_native(&path, opts) + } + + fn open_native(path: &[u16], opts: &OpenOptions) -> io::Result { let creation = opts.get_creation_mode()?; let handle = unsafe { c::CreateFileW( @@ -1234,141 +1238,72 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { let old = maybe_verbatim(old)?; let new = maybe_verbatim(new)?; - let new_len_without_nul_in_bytes = (new.len() - 1).try_into().unwrap(); - - // The last field of FILE_RENAME_INFO, the file name, is unsized, - // and FILE_RENAME_INFO has two padding bytes. - // Therefore we need to make sure to not allocate less than - // size_of::() bytes, which would be the case with - // 0 or 1 character paths + a null byte. - let struct_size = mem::size_of::() - .max(mem::offset_of!(c::FILE_RENAME_INFO, FileName) + new.len() * mem::size_of::()); - - let struct_size: u32 = struct_size.try_into().unwrap(); - - let create_file = |extra_access, extra_flags| { - let handle = unsafe { - HandleOrInvalid::from_raw_handle(c::CreateFileW( - old.as_ptr(), - c::SYNCHRONIZE | c::DELETE | extra_access, - c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE, - ptr::null(), - c::OPEN_EXISTING, - c::FILE_ATTRIBUTE_NORMAL | c::FILE_FLAG_BACKUP_SEMANTICS | extra_flags, - ptr::null_mut(), - )) - }; - - OwnedHandle::try_from(handle).map_err(|_| io::Error::last_os_error()) - }; - - // The following code replicates `MoveFileEx`'s behavior as reverse-engineered from its disassembly. - // If `old` refers to a mount point, we move it instead of the target. - let handle = match create_file(c::FILE_READ_ATTRIBUTES, c::FILE_FLAG_OPEN_REPARSE_POINT) { - Ok(handle) => { - let mut file_attribute_tag_info: MaybeUninit = - MaybeUninit::uninit(); - - let result = unsafe { - cvt(c::GetFileInformationByHandleEx( - handle.as_raw_handle(), - c::FileAttributeTagInfo, - file_attribute_tag_info.as_mut_ptr().cast(), - mem::size_of::().try_into().unwrap(), - )) + if unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) } == 0 { + let err = api::get_last_error(); + // if `MoveFileExW` fails with ERROR_ACCESS_DENIED then try to move + // the file while ignoring the readonly attribute. + // This is accomplished by calling `SetFileInformationByHandle` with `FileRenameInfoEx`. + if err == WinError::ACCESS_DENIED { + let mut opts = OpenOptions::new(); + opts.access_mode(c::DELETE); + opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS); + let Ok(f) = File::open_native(&old, &opts) else { return Err(err).io_result() }; + + // Calculate the layout of the `FILE_RENAME_INFO` we pass to `SetFileInformation` + // This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size. + let Ok(new_len_without_nul_in_bytes): Result = ((new.len() - 1) * 2).try_into() + else { + return Err(err).io_result(); }; - - if let Err(err) = result { - if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) - || err.raw_os_error() == Some(c::ERROR_INVALID_FUNCTION as _) - { - // `GetFileInformationByHandleEx` documents that not all underlying drivers support all file information classes. - // Since we know we passed the correct arguments, this means the underlying driver didn't understand our request; - // `MoveFileEx` proceeds by reopening the file without inhibiting reparse point behavior. - None - } else { - Some(Err(err)) - } - } else { - // SAFETY: The struct has been initialized by GetFileInformationByHandleEx - let file_attribute_tag_info = unsafe { file_attribute_tag_info.assume_init() }; - let file_type = FileType::new( - file_attribute_tag_info.FileAttributes, - file_attribute_tag_info.ReparseTag, - ); - - if file_type.is_symlink() { - // The file is a mount point, junction point or symlink so - // don't reopen the file so that the link gets renamed. - Some(Ok(handle)) - } else { - // Otherwise reopen the file without inhibiting reparse point behavior. - None + let offset: u32 = offset_of!(c::FILE_RENAME_INFO, FileName).try_into().unwrap(); + let struct_size = offset + new_len_without_nul_in_bytes + 2; + let layout = + Layout::from_size_align(struct_size as usize, align_of::()) + .unwrap(); + + // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename. + let file_rename_info; + unsafe { + file_rename_info = alloc(layout).cast::(); + if file_rename_info.is_null() { + handle_alloc_error(layout); } - } - } - // The underlying driver may not support `FILE_FLAG_OPEN_REPARSE_POINT`: Retry without it. - Err(err) if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) => None, - Err(err) => Some(Err(err)), - } - .unwrap_or_else(|| create_file(0, 0))?; - let layout = core::alloc::Layout::from_size_align( - struct_size as _, - mem::align_of::(), - ) - .unwrap(); - - let file_rename_info = unsafe { alloc(layout) } as *mut c::FILE_RENAME_INFO; - - if file_rename_info.is_null() { - handle_alloc_error(layout); - } - - // SAFETY: file_rename_info is a non-null pointer pointing to memory allocated by the global allocator. - let mut file_rename_info = unsafe { Box::from_raw(file_rename_info) }; - - // SAFETY: We have allocated enough memory for a full FILE_RENAME_INFO struct and a filename. - unsafe { - (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 { - Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, - }); - - (&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut()); - (&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes); - - new.as_ptr() - .copy_to_nonoverlapping((&raw mut (*file_rename_info).FileName) as *mut u16, new.len()); - } + (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 { + Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS + | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, + }); - // We don't use `set_file_information_by_handle` here as `FILE_RENAME_INFO` is used for both `FileRenameInfo` and `FileRenameInfoEx`. - let result = unsafe { - cvt(c::SetFileInformationByHandle( - handle.as_raw_handle(), - c::FileRenameInfoEx, - (&raw const *file_rename_info).cast::(), - struct_size, - )) - }; + (&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut()); + // Don't include the NULL in the size + (&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes); - if let Err(err) = result { - if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) { - // FileRenameInfoEx and FILE_RENAME_FLAG_POSIX_SEMANTICS were added in Windows 10 1607; retry with FileRenameInfo. - file_rename_info.Anonymous.ReplaceIfExists = 1; + new.as_ptr().copy_to_nonoverlapping( + (&raw mut (*file_rename_info).FileName).cast::(), + new.len(), + ); + } - cvt(unsafe { + let result = unsafe { c::SetFileInformationByHandle( - handle.as_raw_handle(), - c::FileRenameInfo, - (&raw const *file_rename_info).cast::(), + f.as_raw_handle(), + c::FileRenameInfoEx, + file_rename_info.cast::(), struct_size, ) - })?; + }; + unsafe { dealloc(file_rename_info.cast::(), layout) }; + if result == 0 { + if api::get_last_error() == WinError::DIR_NOT_EMPTY { + return Err(WinError::DIR_NOT_EMPTY).io_result(); + } else { + return Err(err).io_result(); + } + } } else { - return Err(err); + return Err(err).io_result(); } } - Ok(()) } From 3aa36cf951e5f89646741f17b55e2f6c2e290d66 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Thu, 6 Mar 2025 14:18:18 +0000 Subject: [PATCH 04/16] Return OutOfMemoryError and update docs (cherry picked from commit 3cb53df1feaba73b84344c8c0e3dc4120ad8c95b) --- library/std/src/fs.rs | 2 +- library/std/src/sys/pal/windows/fs.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 9b752ed14437c..f390fabad29dc 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -2402,7 +2402,7 @@ pub fn symlink_metadata>(path: P) -> io::Result { /// # Platform-specific behavior /// /// This function currently corresponds to the `rename` function on Unix -/// and the `SetFileInformationByHandle` function on Windows. +/// and the `MoveFileExW` or `SetFileInformationByHandle` function on Windows. /// /// Because of this, the behavior when both `from` and `to` exist differs. On /// Unix, if `from` is a directory, `to` must also be an (empty) directory. If diff --git a/library/std/src/sys/pal/windows/fs.rs b/library/std/src/sys/pal/windows/fs.rs index a0234febdcb6e..7fedf15dad5c4 100644 --- a/library/std/src/sys/pal/windows/fs.rs +++ b/library/std/src/sys/pal/windows/fs.rs @@ -1,6 +1,6 @@ use super::api::{self, WinError}; use super::{IoResult, to_u16s}; -use crate::alloc::{Layout, alloc, dealloc, handle_alloc_error}; +use crate::alloc::{Layout, alloc, dealloc}; use crate::borrow::Cow; use crate::ffi::{OsStr, OsString, c_void}; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; @@ -1266,7 +1266,7 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { unsafe { file_rename_info = alloc(layout).cast::(); if file_rename_info.is_null() { - handle_alloc_error(layout); + return Err(io::ErrorKind::OutOfMemory.into()); } (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 { From b30cc1ef494a224b4e817ece96155fd3417f53fb Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sun, 2 Mar 2025 14:33:29 -0700 Subject: [PATCH 05/16] doctests: fix merging on stable Fixes #137898 The generated multi-test harness relies on nightly-only APIs, so the only way to run it on stable is to enable them. Since tests that use crate attrs don't be merged, there's no way to use nightly-only features on it anyway. (cherry picked from commit 5d6eeea5f969a5a3478de3904e9e02df68ce8b89) --- src/librustdoc/doctest.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 009e9662933ae..91794cc37b5ea 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -525,7 +525,12 @@ fn run_test( } compiler.arg("--edition").arg(doctest.edition.to_string()); - if !doctest.is_multiple_tests { + if doctest.is_multiple_tests { + // The merged test harness uses the `test` crate, so we need to actually allow it. + // This will not expose nightly features on stable, because crate attrs disable + // merging, and `#![feature]` is required to be a crate attr. + compiler.env("RUSTC_BOOTSTRAP", "1"); + } else { // Setting these environment variables is unneeded if this is a merged doctest. compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", &doctest.test_opts.path); compiler.env( From 23322f301d57d31b64118bb4b2a3f071ff07fb3f Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sun, 2 Mar 2025 19:37:40 -0700 Subject: [PATCH 06/16] doctests: build test bundle and harness separately This prevents the included test case from getting at nightly-only features when run on stable. The harness builds with RUSTC_BOOTSTRAP, but the bundle doesn't. (cherry picked from commit 9cf531d26f474917f21a750d8b5fb61bbbae8faa) --- src/librustdoc/doctest.rs | 175 +++++++++++++----- src/librustdoc/doctest/runner.rs | 37 ++-- tests/run-make/doctests-merge/rmake.rs | 1 - tests/rustdoc-ui/doctest/doctest-output.rs | 2 +- ...iled-doctest-test-crate.edition2015.stdout | 28 +++ ...iled-doctest-test-crate.edition2024.stdout | 25 +++ .../doctest/failed-doctest-test-crate.rs | 17 ++ 7 files changed, 221 insertions(+), 64 deletions(-) create mode 100644 tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout create mode 100644 tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout create mode 100644 tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 91794cc37b5ea..881d0b6dcbe2a 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -95,7 +95,7 @@ pub(crate) fn generate_args_file(file_path: &Path, options: &RustdocOptions) -> .map_err(|error| format!("failed to create args file: {error:?}"))?; // We now put the common arguments into the file we created. - let mut content = vec!["--crate-type=bin".to_string()]; + let mut content = vec![]; for cfg in &options.cfgs { content.push(format!("--cfg={cfg}")); @@ -488,12 +488,18 @@ pub(crate) struct RunnableDocTest { line: usize, edition: Edition, no_run: bool, - is_multiple_tests: bool, + merged_test_code: Option, } impl RunnableDocTest { - fn path_for_merged_doctest(&self) -> PathBuf { - self.test_opts.outdir.path().join(format!("doctest_{}.rs", self.edition)) + fn path_for_merged_doctest_bundle(&self) -> PathBuf { + self.test_opts.outdir.path().join(format!("doctest_bundle_{}.rs", self.edition)) + } + fn path_for_merged_doctest_runner(&self) -> PathBuf { + self.test_opts.outdir.path().join(format!("doctest_runner_{}.rs", self.edition)) + } + fn is_multiple_tests(&self) -> bool { + self.merged_test_code.is_some() } } @@ -512,96 +518,108 @@ fn run_test( let rust_out = add_exe_suffix("rust_out".to_owned(), &rustdoc_options.target); let output_file = doctest.test_opts.outdir.path().join(rust_out); - let rustc_binary = rustdoc_options - .test_builder - .as_deref() - .unwrap_or_else(|| rustc_interface::util::rustc_path().expect("found rustc")); - let mut compiler = wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary); + // Common arguments used for compiling the doctest runner. + // On merged doctests, the compiler is invoked twice: once for the test code itself, + // and once for the runner wrapper (which needs to use `#![feature]` on stable). + let mut compiler_args = vec![]; - compiler.arg(format!("@{}", doctest.global_opts.args_file.display())); + compiler_args.push(format!("@{}", doctest.global_opts.args_file.display())); if let Some(sysroot) = &rustdoc_options.maybe_sysroot { - compiler.arg(format!("--sysroot={}", sysroot.display())); + compiler_args.push(format!("--sysroot={}", sysroot.display())); } - compiler.arg("--edition").arg(doctest.edition.to_string()); - if doctest.is_multiple_tests { - // The merged test harness uses the `test` crate, so we need to actually allow it. - // This will not expose nightly features on stable, because crate attrs disable - // merging, and `#![feature]` is required to be a crate attr. - compiler.env("RUSTC_BOOTSTRAP", "1"); - } else { - // Setting these environment variables is unneeded if this is a merged doctest. - compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", &doctest.test_opts.path); - compiler.env( - "UNSTABLE_RUSTDOC_TEST_LINE", - format!("{}", doctest.line as isize - doctest.full_test_line_offset as isize), - ); - } - compiler.arg("-o").arg(&output_file); + compiler_args.extend_from_slice(&["--edition".to_owned(), doctest.edition.to_string()]); if langstr.test_harness { - compiler.arg("--test"); + compiler_args.push("--test".to_owned()); } if rustdoc_options.json_unused_externs.is_enabled() && !langstr.compile_fail { - compiler.arg("--error-format=json"); - compiler.arg("--json").arg("unused-externs"); - compiler.arg("-W").arg("unused_crate_dependencies"); - compiler.arg("-Z").arg("unstable-options"); + compiler_args.push("--error-format=json".to_owned()); + compiler_args.extend_from_slice(&["--json".to_owned(), "unused-externs".to_owned()]); + compiler_args.extend_from_slice(&["-W".to_owned(), "unused_crate_dependencies".to_owned()]); + compiler_args.extend_from_slice(&["-Z".to_owned(), "unstable-options".to_owned()]); } if doctest.no_run && !langstr.compile_fail && rustdoc_options.persist_doctests.is_none() { // FIXME: why does this code check if it *shouldn't* persist doctests // -- shouldn't it be the negation? - compiler.arg("--emit=metadata"); + compiler_args.push("--emit=metadata".to_owned()); } - compiler.arg("--target").arg(match &rustdoc_options.target { - TargetTuple::TargetTuple(s) => s, - TargetTuple::TargetJson { path_for_rustdoc, .. } => { - path_for_rustdoc.to_str().expect("target path must be valid unicode") - } - }); + compiler_args.extend_from_slice(&[ + "--target".to_owned(), + match &rustdoc_options.target { + TargetTuple::TargetTuple(s) => s.clone(), + TargetTuple::TargetJson { path_for_rustdoc, .. } => { + path_for_rustdoc.to_str().expect("target path must be valid unicode").to_owned() + } + }, + ]); if let ErrorOutputType::HumanReadable(kind, color_config) = rustdoc_options.error_format { let short = kind.short(); let unicode = kind == HumanReadableErrorType::Unicode; if short { - compiler.arg("--error-format").arg("short"); + compiler_args.extend_from_slice(&["--error-format".to_owned(), "short".to_owned()]); } if unicode { - compiler.arg("--error-format").arg("human-unicode"); + compiler_args + .extend_from_slice(&["--error-format".to_owned(), "human-unicode".to_owned()]); } match color_config { ColorConfig::Never => { - compiler.arg("--color").arg("never"); + compiler_args.extend_from_slice(&["--color".to_owned(), "never".to_owned()]); } ColorConfig::Always => { - compiler.arg("--color").arg("always"); + compiler_args.extend_from_slice(&["--color".to_owned(), "always".to_owned()]); } ColorConfig::Auto => { - compiler.arg("--color").arg(if supports_color { "always" } else { "never" }); + compiler_args.extend_from_slice(&[ + "--color".to_owned(), + if supports_color { "always" } else { "never" }.to_owned(), + ]); } } } + let rustc_binary = rustdoc_options + .test_builder + .as_deref() + .unwrap_or_else(|| rustc_interface::util::rustc_path().expect("found rustc")); + let mut compiler = wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary); + + compiler.args(&compiler_args); + // If this is a merged doctest, we need to write it into a file instead of using stdin // because if the size of the merged doctests is too big, it'll simply break stdin. - if doctest.is_multiple_tests { + if doctest.is_multiple_tests() { // It makes the compilation failure much faster if it is for a combined doctest. compiler.arg("--error-format=short"); - let input_file = doctest.path_for_merged_doctest(); + let input_file = doctest.path_for_merged_doctest_bundle(); if std::fs::write(&input_file, &doctest.full_test_code).is_err() { // If we cannot write this file for any reason, we leave. All combined tests will be // tested as standalone tests. return Err(TestFailure::CompileError); } - compiler.arg(input_file); if !rustdoc_options.nocapture { // If `nocapture` is disabled, then we don't display rustc's output when compiling // the merged doctests. compiler.stderr(Stdio::null()); } + // bundled tests are an rlib, loaded by a separate runner executable + compiler + .arg("--crate-type=lib") + .arg("--out-dir") + .arg(doctest.test_opts.outdir.path()) + .arg(input_file); } else { + compiler.arg("--crate-type=bin").arg("-o").arg(&output_file); + // Setting these environment variables is unneeded if this is a merged doctest. + compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", &doctest.test_opts.path); + compiler.env( + "UNSTABLE_RUSTDOC_TEST_LINE", + format!("{}", doctest.line as isize - doctest.full_test_line_offset as isize), + ); compiler.arg("-"); compiler.stdin(Stdio::piped()); compiler.stderr(Stdio::piped()); @@ -610,8 +628,65 @@ fn run_test( debug!("compiler invocation for doctest: {compiler:?}"); let mut child = compiler.spawn().expect("Failed to spawn rustc process"); - let output = if doctest.is_multiple_tests { + let output = if let Some(merged_test_code) = &doctest.merged_test_code { + // compile-fail tests never get merged, so this should always pass let status = child.wait().expect("Failed to wait"); + + // the actual test runner is a separate component, built with nightly-only features; + // build it now + let runner_input_file = doctest.path_for_merged_doctest_runner(); + + let mut runner_compiler = + wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary); + // the test runner does not contain any user-written code, so this doesn't allow + // the user to exploit nightly-only features on stable + runner_compiler.env("RUSTC_BOOTSTRAP", "1"); + runner_compiler.args(compiler_args); + runner_compiler.args(&["--crate-type=bin", "-o"]).arg(&output_file); + let mut extern_path = std::ffi::OsString::from(format!( + "--extern=doctest_bundle_{edition}=", + edition = doctest.edition + )); + for extern_str in &rustdoc_options.extern_strs { + if let Some((_cratename, path)) = extern_str.split_once('=') { + // Direct dependencies of the tests themselves are + // indirect dependencies of the test runner. + // They need to be in the library search path. + let dir = Path::new(path) + .parent() + .filter(|x| x.components().count() > 0) + .unwrap_or(Path::new(".")); + runner_compiler.arg("-L").arg(dir); + } + } + let output_bundle_file = doctest + .test_opts + .outdir + .path() + .join(format!("libdoctest_bundle_{edition}.rlib", edition = doctest.edition)); + extern_path.push(&output_bundle_file); + runner_compiler.arg(extern_path); + runner_compiler.arg(&runner_input_file); + if std::fs::write(&runner_input_file, &merged_test_code).is_err() { + // If we cannot write this file for any reason, we leave. All combined tests will be + // tested as standalone tests. + return Err(TestFailure::CompileError); + } + if !rustdoc_options.nocapture { + // If `nocapture` is disabled, then we don't display rustc's output when compiling + // the merged doctests. + runner_compiler.stderr(Stdio::null()); + } + runner_compiler.arg("--error-format=short"); + debug!("compiler invocation for doctest runner: {runner_compiler:?}"); + + let status = if !status.success() { + status + } else { + let mut child_runner = runner_compiler.spawn().expect("Failed to spawn rustc process"); + child_runner.wait().expect("Failed to wait") + }; + process::Output { status, stdout: Vec::new(), stderr: Vec::new() } } else { let stdin = child.stdin.as_mut().expect("Failed to open stdin"); @@ -688,7 +763,7 @@ fn run_test( cmd.arg(&output_file); } else { cmd = Command::new(&output_file); - if doctest.is_multiple_tests { + if doctest.is_multiple_tests() { cmd.env("RUSTDOC_DOCTEST_BIN_PATH", &output_file); } } @@ -696,7 +771,7 @@ fn run_test( cmd.current_dir(run_directory); } - let result = if doctest.is_multiple_tests || rustdoc_options.nocapture { + let result = if doctest.is_multiple_tests() || rustdoc_options.nocapture { cmd.status().map(|status| process::Output { status, stdout: Vec::new(), @@ -982,7 +1057,7 @@ fn doctest_run_fn( line: scraped_test.line, edition: scraped_test.edition(&rustdoc_options), no_run: scraped_test.no_run(&rustdoc_options), - is_multiple_tests: false, + merged_test_code: None, }; let res = run_test(runnable_test, &rustdoc_options, doctest.supports_color, report_unused_externs); diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs index 234f40c6c1ab2..58efa35711a0e 100644 --- a/src/librustdoc/doctest/runner.rs +++ b/src/librustdoc/doctest/runner.rs @@ -14,6 +14,7 @@ pub(crate) struct DocTestRunner { crate_attrs: FxIndexSet, ids: String, output: String, + output_merged_tests: String, supports_color: bool, nb_tests: usize, } @@ -24,6 +25,7 @@ impl DocTestRunner { crate_attrs: FxIndexSet::default(), ids: String::new(), output: String::new(), + output_merged_tests: String::new(), supports_color: true, nb_tests: 0, } @@ -55,7 +57,8 @@ impl DocTestRunner { scraped_test, ignore, self.nb_tests, - &mut self.output + &mut self.output, + &mut self.output_merged_tests, ), )); self.supports_color &= doctest.supports_color; @@ -78,9 +81,11 @@ impl DocTestRunner { " .to_string(); + let mut code_prefix = String::new(); + for crate_attr in &self.crate_attrs { - code.push_str(crate_attr); - code.push('\n'); + code_prefix.push_str(crate_attr); + code_prefix.push('\n'); } if opts.attrs.is_empty() { @@ -88,15 +93,16 @@ impl DocTestRunner { // lints that are commonly triggered in doctests. The crate-level test attributes are // commonly used to make tests fail in case they trigger warnings, so having this there in // that case may cause some tests to pass when they shouldn't have. - code.push_str("#![allow(unused)]\n"); + code_prefix.push_str("#![allow(unused)]\n"); } // Next, any attributes that came from the crate root via #![doc(test(attr(...)))]. for attr in &opts.attrs { - code.push_str(&format!("#![{attr}]\n")); + code_prefix.push_str(&format!("#![{attr}]\n")); } code.push_str("extern crate test;\n"); + writeln!(code, "extern crate doctest_bundle_{edition} as doctest_bundle;").unwrap(); let test_args = test_args.iter().fold(String::new(), |mut x, arg| { write!(x, "{arg:?}.to_string(),").unwrap(); @@ -161,12 +167,12 @@ the same process\"); std::process::Termination::report(test::test_main(test_args, Vec::from(TESTS), None)) }}", nb_tests = self.nb_tests, - output = self.output, + output = self.output_merged_tests, ids = self.ids, ) .expect("failed to generate test code"); let runnable_test = RunnableDocTest { - full_test_code: code, + full_test_code: format!("{code_prefix}{code}", code = self.output), full_test_line_offset: 0, test_opts: test_options, global_opts: opts.clone(), @@ -174,7 +180,7 @@ std::process::Termination::report(test::test_main(test_args, Vec::from(TESTS), N line: 0, edition, no_run: false, - is_multiple_tests: true, + merged_test_code: Some(code), }; let ret = run_test(runnable_test, rustdoc_options, self.supports_color, |_: UnusedExterns| {}); @@ -189,14 +195,15 @@ fn generate_mergeable_doctest( ignore: bool, id: usize, output: &mut String, + output_merged_tests: &mut String, ) -> String { let test_id = format!("__doctest_{id}"); if ignore { // We generate nothing else. - writeln!(output, "mod {test_id} {{\n").unwrap(); + writeln!(output, "pub mod {test_id} {{}}\n").unwrap(); } else { - writeln!(output, "mod {test_id} {{\n{}{}", doctest.crates, doctest.maybe_crate_attrs) + writeln!(output, "pub mod {test_id} {{\n{}{}", doctest.crates, doctest.maybe_crate_attrs) .unwrap(); if doctest.has_main_fn { output.push_str(&doctest.everything_else); @@ -216,11 +223,17 @@ fn main() {returns_result} {{ ) .unwrap(); } + writeln!( + output, + "\npub fn __main_fn() -> impl std::process::Termination {{ main() }} \n}}\n" + ) + .unwrap(); } let not_running = ignore || scraped_test.langstr.no_run; writeln!( - output, + output_merged_tests, " +mod {test_id} {{ pub const TEST: test::TestDescAndFn = test::TestDescAndFn::new_doctest( {test_name:?}, {ignore}, {file:?}, {line}, {no_run}, {should_panic}, test::StaticTestFn( @@ -242,7 +255,7 @@ test::StaticTestFn( if let Some(bin_path) = crate::__doctest_mod::doctest_path() {{ test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id})) }} else {{ - test::assert_test_result(self::main()) + test::assert_test_result(doctest_bundle::{test_id}::__main_fn()) }} ", ) diff --git a/tests/run-make/doctests-merge/rmake.rs b/tests/run-make/doctests-merge/rmake.rs index a25da7403e24b..a88b050c50fa8 100644 --- a/tests/run-make/doctests-merge/rmake.rs +++ b/tests/run-make/doctests-merge/rmake.rs @@ -8,7 +8,6 @@ fn test_and_compare(input_file: &str, stdout_file: &str, edition: &str, dep: &Pa let output = cmd .input(input_file) .arg("--test") - .arg("-Zunstable-options") .edition(edition) .arg("--test-args=--test-threads=1") .extern_("foo", dep.display().to_string()) diff --git a/tests/rustdoc-ui/doctest/doctest-output.rs b/tests/rustdoc-ui/doctest/doctest-output.rs index fb4ab06800019..04bd1813b4c81 100644 --- a/tests/rustdoc-ui/doctest/doctest-output.rs +++ b/tests/rustdoc-ui/doctest/doctest-output.rs @@ -2,7 +2,7 @@ //@[edition2015]edition:2015 //@[edition2015]aux-build:extern_macros.rs //@[edition2015]compile-flags:--test --test-args=--test-threads=1 -//@[edition2024]edition:2015 +//@[edition2024]edition:2024 //@[edition2024]aux-build:extern_macros.rs //@[edition2024]compile-flags:--test --test-args=--test-threads=1 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout new file mode 100644 index 0000000000000..ce767fb8443d8 --- /dev/null +++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout @@ -0,0 +1,28 @@ + +running 1 test +test $DIR/failed-doctest-test-crate.rs - m (line 14) ... FAILED + +failures: + +---- $DIR/failed-doctest-test-crate.rs - m (line 14) stdout ---- +error[E0432]: unresolved import `test` + --> $DIR/failed-doctest-test-crate.rs:15:5 + | +LL | use test::*; + | ^^^^ use of unresolved module or unlinked crate `test` + | +help: you might be missing a crate named `test`, add it to your project and import it in your code + | +LL + extern crate test; + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0432`. +Couldn't compile the test. + +failures: + $DIR/failed-doctest-test-crate.rs - m (line 14) + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout new file mode 100644 index 0000000000000..80642e93bbde7 --- /dev/null +++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout @@ -0,0 +1,25 @@ + +running 1 test +test $DIR/failed-doctest-test-crate.rs - m (line 14) ... FAILED + +failures: + +---- $DIR/failed-doctest-test-crate.rs - m (line 14) stdout ---- +error[E0432]: unresolved import `test` + --> $DIR/failed-doctest-test-crate.rs:15:5 + | +LL | use test::*; + | ^^^^ use of unresolved module or unlinked crate `test` + | + = help: you might be missing a crate named `test` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0432`. +Couldn't compile the test. + +failures: + $DIR/failed-doctest-test-crate.rs - m (line 14) + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs new file mode 100644 index 0000000000000..6966d3df11ce7 --- /dev/null +++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs @@ -0,0 +1,17 @@ +// FIXME: if/when the output of the test harness can be tested on its own, this test should be +// adapted to use that, and that normalize line can go away + +//@ revisions: edition2015 edition2024 +//@[edition2015]edition:2015 +//@[edition2024]edition:2024 +//@ compile-flags:--test +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ failure-status: 101 + +/// +/// +/// ```rust +/// use test::*; +/// ``` +pub mod m {} From eb2bae3fa62bc7c7bf30b576075e8aefed08b862 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 12 Mar 2025 13:43:25 -0700 Subject: [PATCH 07/16] Bless rustdoc-ui differences These changed in the stable backport because we don't have the rewording that came in #133154. --- .../doctest/failed-doctest-test-crate.edition2015.stdout | 4 ++-- .../doctest/failed-doctest-test-crate.edition2024.stdout | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout index ce767fb8443d8..5bbf3ebfab6cc 100644 --- a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout +++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout @@ -9,9 +9,9 @@ error[E0432]: unresolved import `test` --> $DIR/failed-doctest-test-crate.rs:15:5 | LL | use test::*; - | ^^^^ use of unresolved module or unlinked crate `test` + | ^^^^ you might be missing crate `test` | -help: you might be missing a crate named `test`, add it to your project and import it in your code +help: consider importing the `test` crate | LL + extern crate test; | diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout index 80642e93bbde7..463b0fa95361b 100644 --- a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout +++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout @@ -9,9 +9,7 @@ error[E0432]: unresolved import `test` --> $DIR/failed-doctest-test-crate.rs:15:5 | LL | use test::*; - | ^^^^ use of unresolved module or unlinked crate `test` - | - = help: you might be missing a crate named `test` + | ^^^^ use of undeclared crate or module `test` error: aborting due to 1 previous error From d4de520fc458f67758ad7d7fb024e065f840f61e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 25 Feb 2025 20:26:12 +0100 Subject: [PATCH 08/16] rustdoc: disable forbidden #[target_feature] check (cherry picked from commit b6f22400002f7921feed13e35852e3041cf2b145) --- compiler/rustc_codegen_ssa/src/target_features.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 7e80d014ea282..d673cd6e6b4f8 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -149,8 +149,11 @@ pub(crate) fn provide(providers: &mut Providers) { assert_eq!(cnum, LOCAL_CRATE); let target = &tcx.sess.target; if tcx.sess.opts.actually_rustdoc { - // rustdoc needs to be able to document functions that use all the features, so - // whitelist them all + // HACK: rustdoc would like to pretend that we have all the target features, so we + // have to merge all the lists into one. The result has a "random" stability + // (depending on the order in which we consider features); all places that check + // target stability are expected to check `actually_rustdoc` and do nothing when + // that is set. rustc_target::target_features::all_rust_features() .map(|(a, b)| (a.to_string(), b.compute_toggleability(target))) .collect() From c13521b739511d4a0cb7d6941e93eadd9e59aaf9 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 25 Feb 2025 20:38:13 +0100 Subject: [PATCH 09/16] also fix potential issues with mixed stable/unstable target features in rustdoc (cherry picked from commit 039af88e09f4f4beb47406f4771bffc2e61d800a) --- .../rustc_codegen_ssa/src/target_features.rs | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index d673cd6e6b4f8..bffcdca299b21 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -10,7 +10,7 @@ use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; use rustc_session::parse::feature_err; use rustc_span::{Span, Symbol, sym}; -use rustc_target::target_features; +use rustc_target::target_features::{self, Stability}; use crate::errors; @@ -150,13 +150,37 @@ pub(crate) fn provide(providers: &mut Providers) { let target = &tcx.sess.target; if tcx.sess.opts.actually_rustdoc { // HACK: rustdoc would like to pretend that we have all the target features, so we - // have to merge all the lists into one. The result has a "random" stability - // (depending on the order in which we consider features); all places that check - // target stability are expected to check `actually_rustdoc` and do nothing when - // that is set. - rustc_target::target_features::all_rust_features() - .map(|(a, b)| (a.to_string(), b.compute_toggleability(target))) - .collect() + // have to merge all the lists into one. To ensure an unstable target never prevents + // a stable one from working, we merge the stability info of all instances of the + // same target feature name, with the "most stable" taking precedence. And then we + // hope that this doesn't cause issues anywhere else in the compiler... + let mut result: UnordMap> = Default::default(); + for (name, stability) in rustc_target::target_features::all_rust_features() { + use std::collections::hash_map::Entry; + match result.entry(name.to_owned()) { + Entry::Vacant(vacant_entry) => { + vacant_entry.insert(stability.compute_toggleability(target)); + } + Entry::Occupied(mut occupied_entry) => { + // Merge the two stabilities, "more stable" taking precedence. + match (occupied_entry.get(), &stability) { + (Stability::Stable { .. }, _) + | ( + Stability::Unstable { .. }, + Stability::Unstable { .. } | Stability::Forbidden { .. }, + ) + | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => { + // The stability in the entry is at least as good as the new one, just keep it. + } + _ => { + // Overwrite stabilite. + occupied_entry.insert(stability.compute_toggleability(target)); + } + } + } + } + } + result } else { tcx.sess .target From 207a543c2188e1d75de96135116a827fc5b53dfa Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 28 Feb 2025 16:51:53 +0100 Subject: [PATCH 10/16] add test (cherry picked from commit dc04c0ca48c7285d74a0489354ed7d013dc25799) --- tests/rustdoc-ui/target-feature-stability.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/rustdoc-ui/target-feature-stability.rs diff --git a/tests/rustdoc-ui/target-feature-stability.rs b/tests/rustdoc-ui/target-feature-stability.rs new file mode 100644 index 0000000000000..4ade9690310e3 --- /dev/null +++ b/tests/rustdoc-ui/target-feature-stability.rs @@ -0,0 +1,18 @@ +//! This is a regression test for , ensuring +//! that we can use the `neon` target feature on ARM-32 targets in rustdoc despite there +//! being a "forbidden" feature of the same name for aarch64, and rustdoc merging the +//! target features of all targets. +//@ check-pass +//@ compile-flags: --target armv7-unknown-linux-gnueabihf + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![feature(arm_target_feature)] +#![no_core] + +#[lang = "sized"] +pub trait Sized {} + +// `fp-armv8` is "forbidden" on aarch64 as we tie it to `neon`. +#[target_feature(enable = "fp-armv8")] +pub fn fun() {} From 541b2c60097f3c25ec175d22c33852a7fc3fa3a7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 28 Feb 2025 16:56:36 +0100 Subject: [PATCH 11/16] also skip abi_required_features check in rustdoc (cherry picked from commit 4c939db0e775df21a0b409b7603eaaf0056e8f86) --- .../rustc_codegen_ssa/src/target_features.rs | 15 ++++++++++----- tests/rustdoc-ui/target-feature-stability.rs | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index bffcdca299b21..0c53a731221db 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -65,11 +65,16 @@ pub(crate) fn from_target_feature_attr( // Only allow target features whose feature gates have been enabled // and which are permitted to be toggled. if let Err(reason) = stability.toggle_allowed(/*enable*/ true) { - tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr { - span: item.span(), - feature, - reason, - }); + // We skip this error in rustdoc, where we want to allow all target features of + // all targets, so we can't check their ABI compatibility and anyway we are not + // generating code so "it's fine". + if !tcx.sess.opts.actually_rustdoc { + tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr { + span: item.span(), + feature, + reason, + }); + } } else if let Some(nightly_feature) = stability.requires_nightly() && !rust_features.enabled(nightly_feature) { diff --git a/tests/rustdoc-ui/target-feature-stability.rs b/tests/rustdoc-ui/target-feature-stability.rs index 4ade9690310e3..17fa3ccfe3e89 100644 --- a/tests/rustdoc-ui/target-feature-stability.rs +++ b/tests/rustdoc-ui/target-feature-stability.rs @@ -1,9 +1,13 @@ //! This is a regression test for , ensuring -//! that we can use the `neon` target feature on ARM-32 targets in rustdoc despite there +//! that we can use the `neon` target feature on ARM32 targets in rustdoc despite there //! being a "forbidden" feature of the same name for aarch64, and rustdoc merging the //! target features of all targets. //@ check-pass -//@ compile-flags: --target armv7-unknown-linux-gnueabihf +//@ revisions: arm aarch64 +//@[arm] compile-flags: --target armv7-unknown-linux-gnueabihf +//@[arm] needs-llvm-components: arm +//@[aarch64] compile-flags: --target aarch64-unknown-none-softfloat +//@[aarch64] needs-llvm-components: aarch64 #![crate_type = "lib"] #![feature(no_core, lang_items)] @@ -15,4 +19,10 @@ pub trait Sized {} // `fp-armv8` is "forbidden" on aarch64 as we tie it to `neon`. #[target_feature(enable = "fp-armv8")] -pub fn fun() {} +pub fn fun1() {} + +// This would usually be rejected as it changes the ABI. +// But we disable that check in rustdoc since we are building "for all targets" and the +// check can't really handle that. +#[target_feature(enable = "soft-float")] +pub fn fun2() {} From eb5c32de041ef7721312559bfd8fdd639be092aa Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 14 Mar 2025 14:42:36 -0700 Subject: [PATCH 12/16] Release 1.85.1 --- RELEASES.md | 11 +++++++++++ src/version | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 038d7ca639f20..c4959ec0c3465 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,14 @@ +Version 1.85.1 (2025-03-18) +========================== + + + +- [Fix the doctest-merging feature of the 2024 Edition.](https://github.com/rust-lang/rust/pull/137899/) +- [Relax some `target_feature` checks when generating docs.](https://github.com/rust-lang/rust/pull/137632/) +- [Fix errors in `fs::rename` on Windows 1607.](https://github.com/rust-lang/rust/pull/137528/) +- [Downgrade bootstrap `cc` to fix custom targets.](https://github.com/rust-lang/rust/pull/137460/) +- [Skip submodule updates when building Rust from a source tarball.](https://github.com/rust-lang/rust/pull/137338/) + Version 1.85.0 (2025-02-20) ========================== diff --git a/src/version b/src/version index f288d11142d11..8510ffad03679 100644 --- a/src/version +++ b/src/version @@ -1 +1 @@ -1.85.0 +1.85.1 From 3c37eba27c8df47eb452280e5ac8608e937a93bc Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Thu, 27 Feb 2025 21:50:30 +0000 Subject: [PATCH 13/16] Remove Win SDK 10.0.26100.0 from CI (cherry picked from commit 25617c7e695d716d0ecb3cf2366d371441505e47) --- .github/workflows/ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c78ac2816cdb..1aaf194d4d1e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -189,6 +189,20 @@ jobs: - name: ensure the stable version number is correct run: src/ci/scripts/verify-stable-version-number.sh + # Temporary fix to unblock CI + # Remove the latest Windows SDK for 32-bit Windows MSVC builds. + # See issue https://github.com/rust-lang/rust/issues/137733 for more details. + - name: Remove Windows SDK 10.0.26100.0 + shell: powershell + if: ${{ matrix.name == 'i686-msvc-1' || matrix.name == 'i686-msvc-2' || matrix.name == 'dist-i686-msvc' }} + run: | + $kits = (Get-ItemProperty -path 'HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots').KitsRoot10 + $sdk_version = "10.0.26100.0" + + foreach ($kind in 'Bin', 'Lib', 'Include') { + Remove-Item -Force -Recurse $kits\$kind\$sdk_version -ErrorAction Continue + } + - name: run the build # Redirect stderr to stdout to avoid reordering the two streams in the GHA logs. run: src/ci/scripts/run-build-from-ci.sh 2>&1 From 0e8fd2dbef616d9f8ca1c34b189d2ed459028975 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 14 Mar 2025 18:27:50 -0700 Subject: [PATCH 14/16] Use `matrix.image`, and `i686-msvc` isn't split here --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1aaf194d4d1e2..88d2642fd8c3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -194,7 +194,7 @@ jobs: # See issue https://github.com/rust-lang/rust/issues/137733 for more details. - name: Remove Windows SDK 10.0.26100.0 shell: powershell - if: ${{ matrix.name == 'i686-msvc-1' || matrix.name == 'i686-msvc-2' || matrix.name == 'dist-i686-msvc' }} + if: ${{ matrix.image == 'i686-msvc' || matrix.image == 'dist-i686-msvc' }} run: | $kits = (Get-ItemProperty -path 'HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots').KitsRoot10 $sdk_version = "10.0.26100.0" From c990a19a41f0a1f75a7513508983d971ead53b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 3 Mar 2025 19:49:35 +0100 Subject: [PATCH 15/16] Do not use rustup to build Rust for Linux (cherry picked from commit e3117e6e1834838bce446517d7541dda395032d8) --- src/ci/docker/scripts/rfl-build.sh | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/ci/docker/scripts/rfl-build.sh b/src/ci/docker/scripts/rfl-build.sh index 8776e0f0be901..dbd0eff2079b6 100755 --- a/src/ci/docker/scripts/rfl-build.sh +++ b/src/ci/docker/scripts/rfl-build.sh @@ -8,16 +8,10 @@ LINUX_VERSION=v6.13-rc1 ../x.py build --stage 2 library rustdoc clippy rustfmt ../x.py build --stage 0 cargo -# Install rustup so that we can use the built toolchain easily, and also -# install bindgen in an easy way. -curl --proto '=https' --tlsv1.2 -sSf -o rustup.sh https://sh.rustup.rs -sh rustup.sh -y --default-toolchain none +BUILD_DIR=$(realpath ./build/x86_64-unknown-linux-gnu) -source /cargo/env - -BUILD_DIR=$(realpath ./build) -rustup toolchain link local "${BUILD_DIR}"/x86_64-unknown-linux-gnu/stage2 -rustup default local +# Provide path to rustc, rustdoc, clippy-driver and rustfmt to RfL +export PATH=${PATH}:${BUILD_DIR}/stage2/bin mkdir -p rfl cd rfl @@ -33,10 +27,14 @@ git -C linux fetch --depth 1 origin ${LINUX_VERSION} git -C linux checkout FETCH_HEAD # Install bindgen -"${BUILD_DIR}"/x86_64-unknown-linux-gnu/stage0/bin/cargo install \ +"${BUILD_DIR}"/stage0/bin/cargo install \ --version $(linux/scripts/min-tool-version.sh bindgen) \ + --root ${BUILD_DIR}/bindgen \ bindgen-cli +# Provide path to bindgen to RfL +export PATH=${PATH}:${BUILD_DIR}/bindgen/bin + # Configure Rust for Linux cat < linux/kernel/configs/rfl-for-rust-ci.config # CONFIG_WERROR is not set From 4d11490950147b333cb13c0efb579127e37f7104 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 15 Mar 2025 07:31:28 -0700 Subject: [PATCH 16/16] Specify `std:fs::rename` in relnotes --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index c4959ec0c3465..ecf76c63cd995 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -5,7 +5,7 @@ Version 1.85.1 (2025-03-18) - [Fix the doctest-merging feature of the 2024 Edition.](https://github.com/rust-lang/rust/pull/137899/) - [Relax some `target_feature` checks when generating docs.](https://github.com/rust-lang/rust/pull/137632/) -- [Fix errors in `fs::rename` on Windows 1607.](https://github.com/rust-lang/rust/pull/137528/) +- [Fix errors in `std::fs::rename` on Windows 1607.](https://github.com/rust-lang/rust/pull/137528/) - [Downgrade bootstrap `cc` to fix custom targets.](https://github.com/rust-lang/rust/pull/137460/) - [Skip submodule updates when building Rust from a source tarball.](https://github.com/rust-lang/rust/pull/137338/)