From c773b19540a30a6ffa80ec11c1013616b99c2633 Mon Sep 17 00:00:00 2001 From: Urgau Date: Fri, 3 May 2024 19:17:29 +0200 Subject: [PATCH 1/3] Make `rustc_session::config::Input` clone-able --- compiler/rustc_session/src/config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index f2bdabbf394..7dd9fdf60f9 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -798,6 +798,7 @@ pub enum DumpSolverProofTree { Never, } +#[derive(Clone)] pub enum Input { /// Load source code from a file. File(PathBuf), From 1193e965a3d3169f5543e5f5de680a4b9feea1d9 Mon Sep 17 00:00:00 2001 From: Urgau Date: Wed, 1 May 2024 23:42:59 +0200 Subject: [PATCH 2/3] Add `-` (stdin) support to rustdoc --- src/doc/rustdoc/src/command-line-arguments.md | 6 +++ src/librustdoc/config.rs | 50 ++++++++++++++----- src/librustdoc/core.rs | 4 +- src/librustdoc/doctest.rs | 4 +- src/librustdoc/lib.rs | 7 ++- src/librustdoc/markdown.rs | 14 ++++-- tests/run-make/stdin-rustdoc/rmake.rs | 25 ++++++++++ 7 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 tests/run-make/stdin-rustdoc/rmake.rs diff --git a/src/doc/rustdoc/src/command-line-arguments.md b/src/doc/rustdoc/src/command-line-arguments.md index 822f341b370..9edfb873f0b 100644 --- a/src/doc/rustdoc/src/command-line-arguments.md +++ b/src/doc/rustdoc/src/command-line-arguments.md @@ -417,6 +417,12 @@ When `rustdoc` receives this flag, it will print an extra "Version (version)" in the crate root's docs. You can use this flag to differentiate between different versions of your library's documentation. +## `-`: load source code from the standard input + +If you specify `-` as the INPUT on the command line, then `rustdoc` will read the +source code from stdin (standard input stream) until the EOF, instead of the file +system with an otherwise specified path. + ## `@path`: load command-line flags from a path If you specify `@path` on the command-line, then it will open `path` and read diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 465f969435d..c3236d4c9c5 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -1,6 +1,8 @@ use std::collections::BTreeMap; use std::ffi::OsStr; use std::fmt; +use std::io; +use std::io::Read; use std::path::PathBuf; use std::str::FromStr; @@ -9,14 +11,14 @@ use rustc_session::config::{ self, parse_crate_types_from_list, parse_externs, parse_target_triple, CrateType, }; use rustc_session::config::{get_cmd_lint_options, nightly_options}; -use rustc_session::config::{ - CodegenOptions, ErrorOutputType, Externs, JsonUnusedExterns, UnstableOptions, -}; +use rustc_session::config::{CodegenOptions, ErrorOutputType, Externs, Input}; +use rustc_session::config::{JsonUnusedExterns, UnstableOptions}; use rustc_session::getopts; use rustc_session::lint::Level; use rustc_session::search_paths::SearchPath; use rustc_session::EarlyDiagCtxt; use rustc_span::edition::Edition; +use rustc_span::FileName; use rustc_target::spec::TargetTriple; use crate::core::new_dcx; @@ -60,7 +62,7 @@ impl TryFrom<&str> for OutputFormat { pub(crate) struct Options { // Basic options / Options passed directly to rustc /// The crate root or Markdown file to load. - pub(crate) input: PathBuf, + pub(crate) input: Input, /// The name of the crate being documented. pub(crate) crate_name: Option, /// Whether or not this is a bin crate @@ -179,7 +181,7 @@ impl fmt::Debug for Options { } f.debug_struct("Options") - .field("input", &self.input) + .field("input", &self.input.source_name()) .field("crate_name", &self.crate_name) .field("bin_crate", &self.bin_crate) .field("proc_macro_crate", &self.proc_macro_crate) @@ -322,6 +324,23 @@ impl RenderOptions { } } +/// Create the input (string or file path) +/// +/// Warning: Return an unrecoverable error in case of error! +fn make_input(early_dcx: &EarlyDiagCtxt, input: &str) -> Input { + if input == "-" { + let mut src = String::new(); + if io::stdin().read_to_string(&mut src).is_err() { + // Immediately stop compilation if there was an issue reading + // the input (for example if the input stream is not UTF-8). + early_dcx.early_fatal("couldn't read from stdin, as it did not contain valid UTF-8"); + } + Input::Str { name: FileName::anon_source_code(&src), input: src } + } else { + Input::File(PathBuf::from(input)) + } +} + impl Options { /// Parses the given command-line for options. If an error message or other early-return has /// been printed, returns `Err` with the exit code. @@ -450,15 +469,16 @@ impl Options { let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); - let input = PathBuf::from(if describe_lints { + let input = if describe_lints { "" // dummy, this won't be used - } else if matches.free.is_empty() { - dcx.fatal("missing file operand"); - } else if matches.free.len() > 1 { - dcx.fatal("too many file operands"); } else { - &matches.free[0] - }); + match matches.free.as_slice() { + [] => dcx.fatal("missing file operand"), + [input] => input, + _ => dcx.fatal("too many file operands"), + } + }; + let input = make_input(early_dcx, &input); let externs = parse_externs(early_dcx, matches, &unstable_opts); let extern_html_root_urls = match parse_extern_html_roots(matches) { @@ -797,7 +817,11 @@ impl Options { /// Returns `true` if the file given as `self.input` is a Markdown file. pub(crate) fn markdown_input(&self) -> bool { - self.input.extension().is_some_and(|e| e == "md" || e == "markdown") + self.input + .opt_path() + .map(std::path::Path::extension) + .flatten() + .is_some_and(|e| e == "md" || e == "markdown") } } diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 25b78d9598d..feb03b9a823 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -32,7 +32,7 @@ use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions}; use crate::formats::cache::Cache; use crate::passes::{self, Condition::*}; -pub(crate) use rustc_session::config::{Input, Options, UnstableOptions}; +pub(crate) use rustc_session::config::{Options, UnstableOptions}; pub(crate) struct DocContext<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, @@ -204,8 +204,6 @@ pub(crate) fn create_config( // Add the doc cfg into the doc build. cfgs.push("doc".to_string()); - let input = Input::File(input); - // By default, rustdoc ignores all lints. // Specifically unblock lints relevant to documentation or the lint machinery itself. let mut lints_to_show = vec![ diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 0ad4c9c2346..a4a960219bd 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -93,8 +93,6 @@ pub(crate) fn run( dcx: &rustc_errors::DiagCtxt, options: RustdocOptions, ) -> Result<(), ErrorGuaranteed> { - let input = config::Input::File(options.input.clone()); - let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name; // See core::create_config for what's going on here. @@ -140,7 +138,7 @@ pub(crate) fn run( opts: sessopts, crate_cfg: cfgs, crate_check_cfg: options.check_cfgs.clone(), - input, + input: options.input.clone(), output_file: None, output_dir: None, file_loader: None, diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index f2a7518b4ce..fab0e22612f 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -733,10 +733,15 @@ fn main_args( (true, true) => return wrap_return(&diag, markdown::test(options)), (true, false) => return doctest::run(&diag, options), (false, true) => { - let input = options.input.clone(); let edition = options.edition; let config = core::create_config(options, &render_options, using_internal_features); + use rustc_session::config::Input; + let input = match &config.input { + Input::File(path) => path.clone(), + Input::Str { .. } => unreachable!("only path to markdown are supported"), + }; + // `markdown::render` can invoke `doctest::make_test`, which // requires session globals and a thread pool, so we use // `run_compiler`. diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index dcd2cf02a30..7289ed56dc7 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -144,8 +144,14 @@ pub(crate) fn render>( /// Runs any tests/code examples in the markdown file `input`. pub(crate) fn test(options: Options) -> Result<(), String> { - let input_str = read_to_string(&options.input) - .map_err(|err| format!("{input}: {err}", input = options.input.display()))?; + use rustc_session::config::Input; + let input_str = match &options.input { + Input::File(path) => { + read_to_string(&path).map_err(|err| format!("{}: {err}", path.display()))? + } + Input::Str { name: _, input } => input.clone(), + }; + let mut opts = GlobalTestOptions::default(); opts.no_crate_inject = true; @@ -155,12 +161,12 @@ pub(crate) fn test(options: Options) -> Result<(), String> { generate_args_file(&file_path, &options)?; let mut collector = Collector::new( - options.input.display().to_string(), + options.input.filestem().to_string(), options.clone(), true, opts, None, - Some(options.input), + options.input.opt_path().map(ToOwned::to_owned), options.enable_per_target_ignores, file_path, ); diff --git a/tests/run-make/stdin-rustdoc/rmake.rs b/tests/run-make/stdin-rustdoc/rmake.rs new file mode 100644 index 00000000000..584a610ed63 --- /dev/null +++ b/tests/run-make/stdin-rustdoc/rmake.rs @@ -0,0 +1,25 @@ +//! This test checks rustdoc `-` (stdin) handling + +use run_make_support::{rustdoc, tmp_dir}; + +static INPUT: &str = r#" +//! ``` +//! dbg!(()); +//! ``` +pub struct F; +"#; + +fn main() { + let tmp_dir = tmp_dir(); + let out_dir = tmp_dir.join("doc"); + + // rustdoc - + rustdoc().arg("-").out_dir(&out_dir).stdin(INPUT).run(); + assert!(out_dir.join("rust_out/struct.F.html").try_exists().unwrap()); + + // rustdoc --test - + rustdoc().arg("--test").arg("-").stdin(INPUT).run(); + + // rustdoc file.rs - + rustdoc().arg("file.rs").arg("-").run_fail(); +} From 7e1dc742d620123f251c696c8aae864a226d1c89 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 4 May 2024 17:52:04 +0200 Subject: [PATCH 3/3] Simplify `markdown_input` fn and subsequent logic --- src/librustdoc/config.rs | 7 +++---- src/librustdoc/lib.rs | 15 +++++---------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index c3236d4c9c5..e1448ed5739 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -3,6 +3,7 @@ use std::ffi::OsStr; use std::fmt; use std::io; use std::io::Read; +use std::path::Path; use std::path::PathBuf; use std::str::FromStr; @@ -816,12 +817,10 @@ impl Options { } /// Returns `true` if the file given as `self.input` is a Markdown file. - pub(crate) fn markdown_input(&self) -> bool { + pub(crate) fn markdown_input(&self) -> Option<&Path> { self.input .opt_path() - .map(std::path::Path::extension) - .flatten() - .is_some_and(|e| e == "md" || e == "markdown") + .filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown")) } } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index fab0e22612f..0650afb90c7 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -730,18 +730,13 @@ fn main_args( core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts); match (options.should_test, options.markdown_input()) { - (true, true) => return wrap_return(&diag, markdown::test(options)), - (true, false) => return doctest::run(&diag, options), - (false, true) => { + (true, Some(_)) => return wrap_return(&diag, markdown::test(options)), + (true, None) => return doctest::run(&diag, options), + (false, Some(input)) => { + let input = input.to_owned(); let edition = options.edition; let config = core::create_config(options, &render_options, using_internal_features); - use rustc_session::config::Input; - let input = match &config.input { - Input::File(path) => path.clone(), - Input::Str { .. } => unreachable!("only path to markdown are supported"), - }; - // `markdown::render` can invoke `doctest::make_test`, which // requires session globals and a thread pool, so we use // `run_compiler`. @@ -752,7 +747,7 @@ fn main_args( }), ); } - (false, false) => {} + (false, None) => {} } // need to move these items separately because we lose them by the time the closure is called,