mirror of
https://github.com/rust-lang/rust.git
synced 2025-04-17 06:26:55 +00:00
Auto merge of #138521 - cuviper:stable-next, r=cuviper
[stable] Release 1.85.1 - [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 `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/) Added backports to fix CI: - Remove latest Windows SDK from 32-bit CI #137753 - Do not install rustup on Rust for Linux job #137947 cc `@rust-lang/release` r? cuviper
This commit is contained in:
commit
4eb161250e
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@ -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.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"
|
||||
|
||||
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
|
||||
|
11
RELEASES.md
11
RELEASES.md
@ -1,3 +1,14 @@
|
||||
Version 1.85.1 (2025-03-18)
|
||||
==========================
|
||||
|
||||
<a id="1.85.1"></a>
|
||||
|
||||
- [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 `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/)
|
||||
|
||||
Version 1.85.0 (2025-02-20)
|
||||
==========================
|
||||
|
||||
|
@ -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;
|
||||
|
||||
@ -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)
|
||||
{
|
||||
@ -149,11 +154,38 @@ 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
|
||||
rustc_target::target_features::all_rust_features()
|
||||
.map(|(a, b)| (a.to_string(), b.compute_toggleability(target)))
|
||||
.collect()
|
||||
// HACK: rustdoc would like to pretend that we have all the target features, so we
|
||||
// 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<String, Stability<_>> = 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
|
||||
|
@ -2402,7 +2402,7 @@ pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
|
||||
/// # 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
|
||||
|
@ -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};
|
||||
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<File> {
|
||||
let path = maybe_verbatim(path)?;
|
||||
Self::open_native(&path, opts)
|
||||
}
|
||||
|
||||
fn open_native(path: &[u16], opts: &OpenOptions) -> io::Result<File> {
|
||||
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();
|
||||
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() };
|
||||
|
||||
// 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::<c::FILE_RENAME_INFO>() bytes, which would be the case with
|
||||
// 0 or 1 character paths + a null byte.
|
||||
let struct_size = mem::size_of::<c::FILE_RENAME_INFO>()
|
||||
.max(mem::offset_of!(c::FILE_RENAME_INFO, FileName) + new.len() * mem::size_of::<u16>());
|
||||
// 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<u32, _> = ((new.len() - 1) * 2).try_into()
|
||||
else {
|
||||
return Err(err).io_result();
|
||||
};
|
||||
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::<c::FILE_RENAME_INFO>())
|
||||
.unwrap();
|
||||
|
||||
let struct_size: u32 = struct_size.try_into().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::<c::FILE_RENAME_INFO>();
|
||||
if file_rename_info.is_null() {
|
||||
return Err(io::ErrorKind::OutOfMemory.into());
|
||||
}
|
||||
|
||||
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(),
|
||||
))
|
||||
};
|
||||
(&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,
|
||||
});
|
||||
|
||||
OwnedHandle::try_from(handle).map_err(|_| io::Error::last_os_error())
|
||||
};
|
||||
(&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);
|
||||
|
||||
// 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<c::FILE_ATTRIBUTE_TAG_INFO> =
|
||||
MaybeUninit::uninit();
|
||||
new.as_ptr().copy_to_nonoverlapping(
|
||||
(&raw mut (*file_rename_info).FileName).cast::<u16>(),
|
||||
new.len(),
|
||||
);
|
||||
}
|
||||
|
||||
let result = unsafe {
|
||||
cvt(c::GetFileInformationByHandleEx(
|
||||
handle.as_raw_handle(),
|
||||
c::FileAttributeTagInfo,
|
||||
file_attribute_tag_info.as_mut_ptr().cast(),
|
||||
mem::size_of::<c::FILE_ATTRIBUTE_TAG_INFO>().try_into().unwrap(),
|
||||
))
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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::<c::FILE_RENAME_INFO>(),
|
||||
)
|
||||
.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());
|
||||
}
|
||||
|
||||
// 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::<c_void>(),
|
||||
struct_size,
|
||||
))
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
cvt(unsafe {
|
||||
c::SetFileInformationByHandle(
|
||||
handle.as_raw_handle(),
|
||||
c::FileRenameInfo,
|
||||
(&raw const *file_rename_info).cast::<c_void>(),
|
||||
f.as_raw_handle(),
|
||||
c::FileRenameInfoEx,
|
||||
file_rename_info.cast::<c_void>(),
|
||||
struct_size,
|
||||
)
|
||||
})?;
|
||||
};
|
||||
unsafe { dealloc(file_rename_info.cast::<u8>(), 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(())
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
]
|
||||
|
@ -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" }
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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 <<EOF > linux/kernel/configs/rfl-for-rust-ci.config
|
||||
# CONFIG_WERROR is not set
|
||||
|
@ -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<String>,
|
||||
}
|
||||
|
||||
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,91 +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);
|
||||
|
||||
// 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_args.push(format!("@{}", doctest.global_opts.args_file.display()));
|
||||
|
||||
if let Some(sysroot) = &rustdoc_options.maybe_sysroot {
|
||||
compiler_args.push(format!("--sysroot={}", sysroot.display()));
|
||||
}
|
||||
|
||||
compiler_args.extend_from_slice(&["--edition".to_owned(), doctest.edition.to_string()]);
|
||||
if langstr.test_harness {
|
||||
compiler_args.push("--test".to_owned());
|
||||
}
|
||||
if rustdoc_options.json_unused_externs.is_enabled() && !langstr.compile_fail {
|
||||
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_args.push("--emit=metadata".to_owned());
|
||||
}
|
||||
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_args.extend_from_slice(&["--error-format".to_owned(), "short".to_owned()]);
|
||||
}
|
||||
if unicode {
|
||||
compiler_args
|
||||
.extend_from_slice(&["--error-format".to_owned(), "human-unicode".to_owned()]);
|
||||
}
|
||||
|
||||
match color_config {
|
||||
ColorConfig::Never => {
|
||||
compiler_args.extend_from_slice(&["--color".to_owned(), "never".to_owned()]);
|
||||
}
|
||||
ColorConfig::Always => {
|
||||
compiler_args.extend_from_slice(&["--color".to_owned(), "always".to_owned()]);
|
||||
}
|
||||
ColorConfig::Auto => {
|
||||
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.arg(format!("@{}", doctest.global_opts.args_file.display()));
|
||||
compiler.args(&compiler_args);
|
||||
|
||||
if let Some(sysroot) = &rustdoc_options.maybe_sysroot {
|
||||
compiler.arg(format!("--sysroot={}", sysroot.display()));
|
||||
}
|
||||
|
||||
compiler.arg("--edition").arg(doctest.edition.to_string());
|
||||
if !doctest.is_multiple_tests {
|
||||
// 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() {
|
||||
// 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_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);
|
||||
}
|
||||
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("-o").arg(&output_file);
|
||||
if langstr.test_harness {
|
||||
compiler.arg("--test");
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
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.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")
|
||||
}
|
||||
});
|
||||
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");
|
||||
}
|
||||
if unicode {
|
||||
compiler.arg("--error-format").arg("human-unicode");
|
||||
}
|
||||
|
||||
match color_config {
|
||||
ColorConfig::Never => {
|
||||
compiler.arg("--color").arg("never");
|
||||
}
|
||||
ColorConfig::Always => {
|
||||
compiler.arg("--color").arg("always");
|
||||
}
|
||||
ColorConfig::Auto => {
|
||||
compiler.arg("--color").arg(if supports_color { "always" } else { "never" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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();
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
compiler.arg("-");
|
||||
compiler.stdin(Stdio::piped());
|
||||
compiler.stderr(Stdio::piped());
|
||||
@ -605,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");
|
||||
@ -683,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);
|
||||
}
|
||||
}
|
||||
@ -691,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(),
|
||||
@ -977,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);
|
||||
|
@ -14,6 +14,7 @@ pub(crate) struct DocTestRunner {
|
||||
crate_attrs: FxIndexSet<String>,
|
||||
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())
|
||||
}}
|
||||
",
|
||||
)
|
||||
|
@ -1 +1 @@
|
||||
1.85.0
|
||||
1.85.1
|
||||
|
@ -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())
|
||||
|
@ -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"
|
||||
|
@ -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::*;
|
||||
| ^^^^ you might be missing crate `test`
|
||||
|
|
||||
help: consider importing the `test` crate
|
||||
|
|
||||
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
|
||||
|
@ -0,0 +1,23 @@
|
||||
|
||||
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 undeclared crate or module `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
|
||||
|
17
tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs
Normal file
17
tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs
Normal file
@ -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
|
||||
|
||||
/// <https://github.com/rust-lang/rust/pull/137899#discussion_r1976743383>
|
||||
///
|
||||
/// ```rust
|
||||
/// use test::*;
|
||||
/// ```
|
||||
pub mod m {}
|
28
tests/rustdoc-ui/target-feature-stability.rs
Normal file
28
tests/rustdoc-ui/target-feature-stability.rs
Normal file
@ -0,0 +1,28 @@
|
||||
//! This is a regression test for <https://github.com/rust-lang/rust/issues/137366>, ensuring
|
||||
//! 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
|
||||
//@ 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)]
|
||||
#![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 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() {}
|
Loading…
Reference in New Issue
Block a user