about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-08-10 14:17:41 +0000
committerbors <bors@rust-lang.org>2025-08-10 14:17:41 +0000
commit18eeac04fc5c2a4c4a8020dbdf1c652077ad0e4e (patch)
tree3bb0db94131d71006af1bfd0151bb9d530cf1315 /src
parent7f7b8ef27d86c865a7ab20c7c42f50811c6a914d (diff)
parent934cb10f1b21be3a855951fb9b1f38094946aac8 (diff)
downloadrust-18eeac04fc5c2a4c4a8020dbdf1c652077ad0e4e.tar.gz
rust-18eeac04fc5c2a4c4a8020dbdf1c652077ad0e4e.zip
Auto merge of #145210 - Zalathar:rollup-dm4reb2, r=Zalathar
Rollup of 17 pull requests

Successful merges:

 - rust-lang/rust#141624 (unstable-book: Add stubs for environment variables; document some of the important ones)
 - rust-lang/rust#143093 (Simplify polonius location-sensitive analysis)
 - rust-lang/rust#144402 (Stabilize loongarch32 inline asm)
 - rust-lang/rust#144403 (`tests/ui/issues/`: The Issues Strike Back [4/N])
 - rust-lang/rust#144739 (Use new public libtest `ERROR_EXIT_CODE` constant in rustdoc)
 - rust-lang/rust#145089 (Improve error output when a command fails in bootstrap)
 - rust-lang/rust#145112 ([win][arm64ec] Partial fix for raw-dylib-link-ordinal on Arm64EC)
 - rust-lang/rust#145129 ([win][arm64ec] Add `/machine:arm64ec` when linking LLVM as Arm64EC)
 - rust-lang/rust#145130 (improve "Documentation problem" issue template.)
 - rust-lang/rust#145135 (Stabilize `duration_constructors_lite` feature)
 - rust-lang/rust#145145 (some `derive_more` refactors)
 - rust-lang/rust#145147 (rename `TraitRef::from_method` to `from_assoc`)
 - rust-lang/rust#145156 (Override custom Cargo `build-dir` in bootstrap)
 - rust-lang/rust#145160 (Change days-threshold to 28 in [behind-upstream])
 - rust-lang/rust#145162 (`{BTree,Hash}Map`: add "`Entry` API" section heading)
 - rust-lang/rust#145187 (Fix an unstable feature comment that wasn't a doc comment)
 - rust-lang/rust#145191 (`suggest_borrow_generic_arg`: use the correct generic args)

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/src/core/build_steps/llvm.rs7
-rw-r--r--src/bootstrap/src/core/builder/cargo.rs9
-rw-r--r--src/bootstrap/src/utils/exec.rs165
-rw-r--r--src/doc/unstable-book/src/compiler-environment-variables/COLORTERM.md5
-rw-r--r--src/doc/unstable-book/src/compiler-environment-variables/QNX_TARGET.md11
-rw-r--r--src/doc/unstable-book/src/compiler-environment-variables/SDKROOT.md6
-rw-r--r--src/doc/unstable-book/src/compiler-environment-variables/TERM.md5
-rw-r--r--src/doc/unstable-book/src/compiler-flags/terminal-urls.md13
-rw-r--r--src/doc/unstable-book/src/language-features/asm-experimental-arch.md5
-rw-r--r--src/doc/unstable-book/src/library-features/duration-constructors-lite.md11
-rw-r--r--src/doc/unstable-book/src/library-features/duration-constructors.md1
-rw-r--r--src/librustdoc/doctest.rs4
-rw-r--r--src/tools/compiletest/src/runtest.rs2
-rw-r--r--src/tools/tidy/src/features.rs35
-rw-r--r--src/tools/tidy/src/unstable_book.rs2
-rw-r--r--src/tools/unstable-book-gen/src/main.rs29
-rw-r--r--src/tools/unstable-book-gen/src/stub-env-var.md9
17 files changed, 210 insertions, 109 deletions
diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs
index 065a39a4e71..79244827059 100644
--- a/src/bootstrap/src/core/build_steps/llvm.rs
+++ b/src/bootstrap/src/core/build_steps/llvm.rs
@@ -421,6 +421,13 @@ impl Step for Llvm {
             ldflags.shared.push(" -latomic");
         }
 
+        if target.starts_with("arm64ec") {
+            // MSVC linker requires the -machine:arm64ec flag to be passed to
+            // know it's linking as Arm64EC (vs Arm64X).
+            ldflags.exe.push(" -machine:arm64ec");
+            ldflags.shared.push(" -machine:arm64ec");
+        }
+
         if target.is_msvc() {
             cfg.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded");
             cfg.static_crt(true);
diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs
index e10af2b55f9..39f46bf43af 100644
--- a/src/bootstrap/src/core/builder/cargo.rs
+++ b/src/bootstrap/src/core/builder/cargo.rs
@@ -433,6 +433,15 @@ impl Builder<'_> {
         let out_dir = self.stage_out(compiler, mode);
         cargo.env("CARGO_TARGET_DIR", &out_dir);
 
+        // Bootstrap makes a lot of assumptions about the artifacts produced in the target
+        // directory. If users override the "build directory" using `build-dir`
+        // (https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-dir), then
+        // bootstrap couldn't find these artifacts. So we forcefully override that option to our
+        // target directory here.
+        // In the future, we could attempt to read the build-dir location from Cargo and actually
+        // respect it.
+        cargo.env("CARGO_BUILD_BUILD_DIR", &out_dir);
+
         // Found with `rg "init_env_logger\("`. If anyone uses `init_env_logger`
         // from out of tree it shouldn't matter, since x.py is only used for
         // building in-tree.
diff --git a/src/bootstrap/src/utils/exec.rs b/src/bootstrap/src/utils/exec.rs
index 7527dff9cd8..03760faec69 100644
--- a/src/bootstrap/src/utils/exec.rs
+++ b/src/bootstrap/src/utils/exec.rs
@@ -80,11 +80,21 @@ impl CommandFingerprint {
     /// Helper method to format both Command and BootstrapCommand as a short execution line,
     /// without all the other details (e.g. environment variables).
     pub fn format_short_cmd(&self) -> String {
-        let program = Path::new(&self.program);
-        let mut line = vec![program.file_name().unwrap().to_str().unwrap().to_owned()];
-        line.extend(self.args.iter().map(|arg| arg.to_string_lossy().into_owned()));
-        line.extend(self.cwd.iter().map(|p| p.to_string_lossy().into_owned()));
-        line.join(" ")
+        use std::fmt::Write;
+
+        let mut cmd = self.program.to_string_lossy().to_string();
+        for arg in &self.args {
+            let arg = arg.to_string_lossy();
+            if arg.contains(' ') {
+                write!(cmd, " '{arg}'").unwrap();
+            } else {
+                write!(cmd, " {arg}").unwrap();
+            }
+        }
+        if let Some(cwd) = &self.cwd {
+            write!(cmd, " [workdir={}]", cwd.to_string_lossy()).unwrap();
+        }
+        cmd
     }
 }
 
@@ -434,8 +444,8 @@ impl From<Command> for BootstrapCommand {
 enum CommandStatus {
     /// The command has started and finished with some status.
     Finished(ExitStatus),
-    /// It was not even possible to start the command.
-    DidNotStart,
+    /// It was not even possible to start the command or wait for it to finish.
+    DidNotStartOrFinish,
 }
 
 /// Create a new BootstrapCommand. This is a helper function to make command creation
@@ -456,9 +466,9 @@ pub struct CommandOutput {
 
 impl CommandOutput {
     #[must_use]
-    pub fn did_not_start(stdout: OutputMode, stderr: OutputMode) -> Self {
+    pub fn not_finished(stdout: OutputMode, stderr: OutputMode) -> Self {
         Self {
-            status: CommandStatus::DidNotStart,
+            status: CommandStatus::DidNotStartOrFinish,
             stdout: match stdout {
                 OutputMode::Print => None,
                 OutputMode::Capture => Some(vec![]),
@@ -489,7 +499,7 @@ impl CommandOutput {
     pub fn is_success(&self) -> bool {
         match self.status {
             CommandStatus::Finished(status) => status.success(),
-            CommandStatus::DidNotStart => false,
+            CommandStatus::DidNotStartOrFinish => false,
         }
     }
 
@@ -501,7 +511,7 @@ impl CommandOutput {
     pub fn status(&self) -> Option<ExitStatus> {
         match self.status {
             CommandStatus::Finished(status) => Some(status),
-            CommandStatus::DidNotStart => None,
+            CommandStatus::DidNotStartOrFinish => None,
         }
     }
 
@@ -745,25 +755,11 @@ impl ExecutionContext {
         self.start(command, stdout, stderr).wait_for_output(self)
     }
 
-    fn fail(&self, message: &str, output: CommandOutput) -> ! {
-        if self.is_verbose() {
-            println!("{message}");
-        } else {
-            let (stdout, stderr) = (output.stdout_if_present(), output.stderr_if_present());
-            // If the command captures output, the user would not see any indication that
-            // it has failed. In this case, print a more verbose error, since to provide more
-            // context.
-            if stdout.is_some() || stderr.is_some() {
-                if let Some(stdout) = output.stdout_if_present().take_if(|s| !s.trim().is_empty()) {
-                    println!("STDOUT:\n{stdout}\n");
-                }
-                if let Some(stderr) = output.stderr_if_present().take_if(|s| !s.trim().is_empty()) {
-                    println!("STDERR:\n{stderr}\n");
-                }
-                println!("Command has failed. Rerun with -v to see more details.");
-            } else {
-                println!("Command has failed. Rerun with -v to see more details.");
-            }
+    fn fail(&self, message: &str) -> ! {
+        println!("{message}");
+
+        if !self.is_verbose() {
+            println!("Command has failed. Rerun with -v to see more details.");
         }
         exit!(1);
     }
@@ -856,7 +852,7 @@ impl<'a> DeferredCommand<'a> {
                     && command.should_cache
                 {
                     exec_ctx.command_cache.insert(fingerprint.clone(), output.clone());
-                    exec_ctx.profiler.record_execution(fingerprint.clone(), start_time);
+                    exec_ctx.profiler.record_execution(fingerprint, start_time);
                 }
 
                 output
@@ -872,6 +868,8 @@ impl<'a> DeferredCommand<'a> {
         executed_at: &'a std::panic::Location<'a>,
         exec_ctx: &ExecutionContext,
     ) -> CommandOutput {
+        use std::fmt::Write;
+
         command.mark_as_executed();
 
         let process = match process.take() {
@@ -881,79 +879,82 @@ impl<'a> DeferredCommand<'a> {
 
         let created_at = command.get_created_location();
 
-        let mut message = String::new();
+        #[allow(clippy::enum_variant_names)]
+        enum FailureReason {
+            FailedAtRuntime(ExitStatus),
+            FailedToFinish(std::io::Error),
+            FailedToStart(std::io::Error),
+        }
 
-        let output = match process {
+        let (output, fail_reason) = match process {
             Ok(child) => match child.wait_with_output() {
-                Ok(result) if result.status.success() => {
+                Ok(output) if output.status.success() => {
                     // Successful execution
-                    CommandOutput::from_output(result, stdout, stderr)
+                    (CommandOutput::from_output(output, stdout, stderr), None)
                 }
-                Ok(result) => {
-                    // Command ran but failed
-                    use std::fmt::Write;
-
-                    writeln!(
-                        message,
-                        r#"
-Command {command:?} did not execute successfully.
-Expected success, got {}
-Created at: {created_at}
-Executed at: {executed_at}"#,
-                        result.status,
+                Ok(output) => {
+                    // Command started, but then it failed
+                    let status = output.status;
+                    (
+                        CommandOutput::from_output(output, stdout, stderr),
+                        Some(FailureReason::FailedAtRuntime(status)),
                     )
-                    .unwrap();
-
-                    let output = CommandOutput::from_output(result, stdout, stderr);
-
-                    if stdout.captures() {
-                        writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap();
-                    }
-                    if stderr.captures() {
-                        writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap();
-                    }
-
-                    output
                 }
                 Err(e) => {
                     // Failed to wait for output
-                    use std::fmt::Write;
-
-                    writeln!(
-                        message,
-                        "\n\nCommand {command:?} did not execute successfully.\
-                        \nIt was not possible to execute the command: {e:?}"
+                    (
+                        CommandOutput::not_finished(stdout, stderr),
+                        Some(FailureReason::FailedToFinish(e)),
                     )
-                    .unwrap();
-
-                    CommandOutput::did_not_start(stdout, stderr)
                 }
             },
             Err(e) => {
                 // Failed to spawn the command
-                use std::fmt::Write;
-
-                writeln!(
-                    message,
-                    "\n\nCommand {command:?} did not execute successfully.\
-                    \nIt was not possible to execute the command: {e:?}"
-                )
-                .unwrap();
-
-                CommandOutput::did_not_start(stdout, stderr)
+                (CommandOutput::not_finished(stdout, stderr), Some(FailureReason::FailedToStart(e)))
             }
         };
 
-        if !output.is_success() {
+        if let Some(fail_reason) = fail_reason {
+            let mut error_message = String::new();
+            let command_str = if exec_ctx.is_verbose() {
+                format!("{command:?}")
+            } else {
+                command.fingerprint().format_short_cmd()
+            };
+            let action = match fail_reason {
+                FailureReason::FailedAtRuntime(e) => {
+                    format!("failed with exit code {}", e.code().unwrap_or(1))
+                }
+                FailureReason::FailedToFinish(e) => {
+                    format!("failed to finish: {e:?}")
+                }
+                FailureReason::FailedToStart(e) => {
+                    format!("failed to start: {e:?}")
+                }
+            };
+            writeln!(
+                error_message,
+                r#"Command `{command_str}` {action}
+Created at: {created_at}
+Executed at: {executed_at}"#,
+            )
+            .unwrap();
+            if stdout.captures() {
+                writeln!(error_message, "\n--- STDOUT vvv\n{}", output.stdout().trim()).unwrap();
+            }
+            if stderr.captures() {
+                writeln!(error_message, "\n--- STDERR vvv\n{}", output.stderr().trim()).unwrap();
+            }
+
             match command.failure_behavior {
                 BehaviorOnFailure::DelayFail => {
                     if exec_ctx.fail_fast {
-                        exec_ctx.fail(&message, output);
+                        exec_ctx.fail(&error_message);
                     }
-                    exec_ctx.add_to_delay_failure(message);
+                    exec_ctx.add_to_delay_failure(error_message);
                 }
                 BehaviorOnFailure::Exit => {
-                    exec_ctx.fail(&message, output);
+                    exec_ctx.fail(&error_message);
                 }
                 BehaviorOnFailure::Ignore => {
                     // If failures are allowed, either the error has been printed already
diff --git a/src/doc/unstable-book/src/compiler-environment-variables/COLORTERM.md b/src/doc/unstable-book/src/compiler-environment-variables/COLORTERM.md
new file mode 100644
index 00000000000..f5be63b796c
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-environment-variables/COLORTERM.md
@@ -0,0 +1,5 @@
+# `COLORTERM`
+
+This environment variable is used by [`-Zterminal-urls`] to detect if URLs are supported by the terminal emulator.
+
+[`-Zterminal-urls`]: ../compiler-flags/terminal-urls.html
diff --git a/src/doc/unstable-book/src/compiler-environment-variables/QNX_TARGET.md b/src/doc/unstable-book/src/compiler-environment-variables/QNX_TARGET.md
new file mode 100644
index 00000000000..46511598e70
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-environment-variables/QNX_TARGET.md
@@ -0,0 +1,11 @@
+# `QNX_TARGET`
+
+----
+
+This environment variable is mandatory when linking on `nto-qnx*_iosock` platforms. It is used to determine an `-L` path to pass to the QNX linker.
+
+You should [set this variable] by running `source qnxsdp-env.sh`.
+See [the QNX docs] for more background information.
+
+[set this variable]: https://www.qnx.com/developers/docs/qsc/com.qnx.doc.qsc.inst_larg_org/topic/build_server_developer_steps.html
+[the QNX docs]: https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.io_sock/topic/migrate_app.html.
diff --git a/src/doc/unstable-book/src/compiler-environment-variables/SDKROOT.md b/src/doc/unstable-book/src/compiler-environment-variables/SDKROOT.md
new file mode 100644
index 00000000000..be9ed02f54d
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-environment-variables/SDKROOT.md
@@ -0,0 +1,6 @@
+# `SDKROOT`
+
+This environment variable is used on Apple targets.
+It is passed through to the linker (currently either as `-isysroot` or `-syslibroot`).
+
+Note that this variable is not always respected. When the SDKROOT is clearly wrong (e.g. when the platform of the SDK does not match the `--target` used by rustc), this is ignored and rustc does its own detection.
diff --git a/src/doc/unstable-book/src/compiler-environment-variables/TERM.md b/src/doc/unstable-book/src/compiler-environment-variables/TERM.md
new file mode 100644
index 00000000000..9208fd378a3
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-environment-variables/TERM.md
@@ -0,0 +1,5 @@
+# `TERM`
+
+This environment variable is used by [`-Zterminal-urls`] to detect if URLs are supported by the terminal emulator.
+
+[`-Zterminal-urls`]: ../compiler-flags/terminal-urls.html
diff --git a/src/doc/unstable-book/src/compiler-flags/terminal-urls.md b/src/doc/unstable-book/src/compiler-flags/terminal-urls.md
new file mode 100644
index 00000000000..a5427978f25
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-flags/terminal-urls.md
@@ -0,0 +1,13 @@
+# `-Z terminal-urls`
+
+The tracking feature for this issue is [#125586]
+
+[#125586]: https://github.com/rust-lang/rust/issues/125586
+
+---
+
+This flag takes either a boolean or the string "auto".
+
+When enabled, use the OSC 8 hyperlink terminal specification to print hyperlinks in the compiler output.
+Use "auto" to try and autodetect whether the terminal emulator supports hyperlinks.
+Currently, "auto" only enables hyperlinks if `COLORTERM=truecolor` and `TERM=xterm-256color`.
diff --git a/src/doc/unstable-book/src/language-features/asm-experimental-arch.md b/src/doc/unstable-book/src/language-features/asm-experimental-arch.md
index 121f9493435..d9566c9f55c 100644
--- a/src/doc/unstable-book/src/language-features/asm-experimental-arch.md
+++ b/src/doc/unstable-book/src/language-features/asm-experimental-arch.md
@@ -19,7 +19,6 @@ This feature tracks `asm!` and `global_asm!` support for the following architect
 - M68k
 - CSKY
 - SPARC
-- LoongArch32
 
 ## Register classes
 
@@ -54,8 +53,6 @@ This feature tracks `asm!` and `global_asm!` support for the following architect
 | CSKY         | `freg`         | `f[0-31]`                          | `f`                  |
 | SPARC        | `reg`          | `r[2-29]`                          | `r`                  |
 | SPARC        | `yreg`         | `y`                                | Only clobbers        |
-| LoongArch32  | `reg`          | `$r1`, `$r[4-20]`, `$r[23,30]`     | `r`                  |
-| LoongArch32  | `freg`         | `$f[0-31]`                         | `f`                  |
 
 > **Notes**:
 > - NVPTX doesn't have a fixed register set, so named registers are not supported.
@@ -94,8 +91,6 @@ This feature tracks `asm!` and `global_asm!` support for the following architect
 | CSKY         | `freg`                          | None           | `f32`,                                  |
 | SPARC        | `reg`                           | None           | `i8`, `i16`, `i32`, `i64` (SPARC64 only) |
 | SPARC        | `yreg`                          | N/A            | Only clobbers                           |
-| LoongArch32  | `reg`                           | None           | `i8`, `i16`, `i32`, `f32`               |
-| LoongArch32  | `freg`                          | None           | `f32`, `f64`                            |
 
 ## Register aliases
 
diff --git a/src/doc/unstable-book/src/library-features/duration-constructors-lite.md b/src/doc/unstable-book/src/library-features/duration-constructors-lite.md
deleted file mode 100644
index 5238b84f776..00000000000
--- a/src/doc/unstable-book/src/library-features/duration-constructors-lite.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# `duration_constructors_lite`
-
-The tracking issue for this feature is: [#140881]
-
-[#140881]: https://github.com/rust-lang/rust/issues/140881
-
-------------------------
-
-Add the methods `from_mins`, `from_hours` to `Duration`.
-
-For `from_days` and `from_weeks` see [`duration_constructors`](https://github.com/rust-lang/rust/issues/120301).
diff --git a/src/doc/unstable-book/src/library-features/duration-constructors.md b/src/doc/unstable-book/src/library-features/duration-constructors.md
index 49ad78d1961..2626e0500b5 100644
--- a/src/doc/unstable-book/src/library-features/duration-constructors.md
+++ b/src/doc/unstable-book/src/library-features/duration-constructors.md
@@ -7,4 +7,3 @@ The tracking issue for this feature is: [#120301]
 ------------------------
 
 Add the methods `from_days` and `from_weeks` to `Duration`.
-For `from_mins` and `from_hours` see [duration-constructors-lite.md](./duration-constructors-lite.md)
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index 35ace656638..73ce62cdcde 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -409,9 +409,7 @@ pub(crate) fn run_tests(
         // We ensure temp dir destructor is called.
         std::mem::drop(temp_dir);
         times.display_times();
-        // FIXME(GuillaumeGomez): Uncomment the next line once #144297 has been merged.
-        // std::process::exit(test::ERROR_EXIT_CODE);
-        std::process::exit(101);
+        std::process::exit(test::ERROR_EXIT_CODE);
     }
 }
 
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index f283a625f97..debf67e2741 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -1766,7 +1766,7 @@ impl<'test> TestCx<'test> {
 
         match self.config.compare_mode {
             Some(CompareMode::Polonius) => {
-                rustc.args(&["-Zpolonius"]);
+                rustc.args(&["-Zpolonius=next"]);
             }
             Some(CompareMode::NextSolver) => {
                 rustc.args(&["-Znext-solver"]);
diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs
index fb00b3a943f..6618ba24be6 100644
--- a/src/tools/tidy/src/features.rs
+++ b/src/tools/tidy/src/features.rs
@@ -9,6 +9,7 @@
 //! * All unstable lang features have tests to ensure they are actually unstable.
 //! * Language features in a group are sorted by feature name.
 
+use std::collections::BTreeSet;
 use std::collections::hash_map::{Entry, HashMap};
 use std::ffi::OsStr;
 use std::num::NonZeroU32;
@@ -21,6 +22,7 @@ use crate::walk::{filter_dirs, filter_not_rust, walk, walk_many};
 mod tests;
 
 mod version;
+use regex::Regex;
 use version::Version;
 
 const FEATURE_GROUP_START_PREFIX: &str = "// feature-group-start";
@@ -623,3 +625,36 @@ fn map_lib_features(
         },
     );
 }
+
+fn should_document(var: &str) -> bool {
+    if var.starts_with("RUSTC_") || var.starts_with("RUST_") || var.starts_with("UNSTABLE_RUSTDOC_")
+    {
+        return true;
+    }
+    ["SDKROOT", "QNX_TARGET", "COLORTERM", "TERM"].contains(&var)
+}
+
+pub fn collect_env_vars(compiler: &Path) -> BTreeSet<String> {
+    let env_var_regex: Regex = Regex::new(r#"env::var(_os)?\("([^"]+)"#).unwrap();
+
+    let mut vars = BTreeSet::new();
+    walk(
+        compiler,
+        // skip build scripts, tests, and non-rust files
+        |path, _is_dir| {
+            filter_dirs(path)
+                || filter_not_rust(path)
+                || path.ends_with("build.rs")
+                || path.ends_with("tests.rs")
+        },
+        &mut |_entry, contents| {
+            for env_var in env_var_regex.captures_iter(contents).map(|c| c.get(2).unwrap().as_str())
+            {
+                if should_document(env_var) {
+                    vars.insert(env_var.to_owned());
+                }
+            }
+        },
+    );
+    vars
+}
diff --git a/src/tools/tidy/src/unstable_book.rs b/src/tools/tidy/src/unstable_book.rs
index 9dc9d42d466..0ed954d48de 100644
--- a/src/tools/tidy/src/unstable_book.rs
+++ b/src/tools/tidy/src/unstable_book.rs
@@ -6,6 +6,8 @@ use crate::features::{CollectedFeatures, Features, Status};
 
 pub const PATH_STR: &str = "doc/unstable-book";
 
+pub const ENV_VARS_DIR: &str = "src/compiler-environment-variables";
+
 pub const COMPILER_FLAGS_DIR: &str = "src/compiler-flags";
 
 pub const LANG_FEATURES_DIR: &str = "src/language-features";
diff --git a/src/tools/unstable-book-gen/src/main.rs b/src/tools/unstable-book-gen/src/main.rs
index 159a1d0fa17..a7c6173d88c 100644
--- a/src/tools/unstable-book-gen/src/main.rs
+++ b/src/tools/unstable-book-gen/src/main.rs
@@ -5,11 +5,11 @@ use std::env;
 use std::fs::{self, write};
 use std::path::Path;
 
-use tidy::features::{Features, collect_lang_features, collect_lib_features};
+use tidy::features::{Features, collect_env_vars, collect_lang_features, collect_lib_features};
 use tidy::t;
 use tidy::unstable_book::{
-    LANG_FEATURES_DIR, LIB_FEATURES_DIR, PATH_STR, collect_unstable_book_section_file_names,
-    collect_unstable_feature_names,
+    ENV_VARS_DIR, LANG_FEATURES_DIR, LIB_FEATURES_DIR, PATH_STR,
+    collect_unstable_book_section_file_names, collect_unstable_feature_names,
 };
 
 fn generate_stub_issue(path: &Path, name: &str, issue: u32, description: &str) {
@@ -27,6 +27,11 @@ fn generate_stub_no_issue(path: &Path, name: &str, description: &str) {
     t!(write(path, content), path);
 }
 
+fn generate_stub_env_var(path: &Path, name: &str) {
+    let content = format!(include_str!("stub-env-var.md"), name = name);
+    t!(write(path, content), path);
+}
+
 fn set_to_summary_str(set: &BTreeSet<String>, dir: &str) -> String {
     set.iter()
         .map(|ref n| format!("    - [{}]({}/{}.md)", n.replace('-', "_"), dir, n))
@@ -59,7 +64,7 @@ fn generate_summary(path: &Path, lang_features: &Features, lib_features: &Featur
     t!(write(&summary_path, content), summary_path);
 }
 
-fn generate_unstable_book_files(src: &Path, out: &Path, features: &Features) {
+fn generate_feature_files(src: &Path, out: &Path, features: &Features) {
     let unstable_features = collect_unstable_feature_names(features);
     let unstable_section_file_names = collect_unstable_book_section_file_names(src);
     t!(fs::create_dir_all(&out));
@@ -83,6 +88,16 @@ fn generate_unstable_book_files(src: &Path, out: &Path, features: &Features) {
     }
 }
 
+fn generate_env_files(src: &Path, out: &Path, env_vars: &BTreeSet<String>) {
+    let env_var_file_names = collect_unstable_book_section_file_names(src);
+    t!(fs::create_dir_all(&out));
+    for env_var in env_vars - &env_var_file_names {
+        let file_name = format!("{env_var}.md");
+        let out_file_path = out.join(&file_name);
+        generate_stub_env_var(&out_file_path, &env_var);
+    }
+}
+
 fn copy_recursive(from: &Path, to: &Path) {
     for entry in t!(fs::read_dir(from)) {
         let e = t!(entry);
@@ -112,21 +127,23 @@ fn main() {
         .into_iter()
         .filter(|&(ref name, _)| !lang_features.contains_key(name))
         .collect();
+    let env_vars = collect_env_vars(compiler_path);
 
     let doc_src_path = src_path.join(PATH_STR);
 
     t!(fs::create_dir_all(&dest_path));
 
-    generate_unstable_book_files(
+    generate_feature_files(
         &doc_src_path.join(LANG_FEATURES_DIR),
         &dest_path.join(LANG_FEATURES_DIR),
         &lang_features,
     );
-    generate_unstable_book_files(
+    generate_feature_files(
         &doc_src_path.join(LIB_FEATURES_DIR),
         &dest_path.join(LIB_FEATURES_DIR),
         &lib_features,
     );
+    generate_env_files(&doc_src_path.join(ENV_VARS_DIR), &dest_path.join(ENV_VARS_DIR), &env_vars);
 
     copy_recursive(&doc_src_path, &dest_path);
 
diff --git a/src/tools/unstable-book-gen/src/stub-env-var.md b/src/tools/unstable-book-gen/src/stub-env-var.md
new file mode 100644
index 00000000000..e8a7ddb855a
--- /dev/null
+++ b/src/tools/unstable-book-gen/src/stub-env-var.md
@@ -0,0 +1,9 @@
+# `{name}`
+
+Environment variables have no tracking issue. This environment variable has no documentation, and therefore is likely internal to the compiler and not meant for general use.
+
+See [the code][github search] for more information.
+
+[github search]: https://github.com/search?q=repo%3Arust-lang%2Frust+%22{name}%22+path%3Acompiler&type=code
+
+------------------------