diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 9e7b69ec45faa..b3c87a725080f 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -53,12 +53,18 @@ impl TryFrom<&str> for OutputFormat { } } +/// Either an input crate, markdown file, or nothing (--merge=finalize). +pub(crate) enum InputMode { + /// The `--merge=finalize` step does not need an input crate to rustdoc. + NoInputMergeFinalize, + /// A crate or markdown file. + HasFile(Input), +} + /// Configuration options for rustdoc. #[derive(Clone)] pub(crate) struct Options { // Basic options / Options passed directly to rustc - /// The crate root or Markdown file to load. - 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 +185,6 @@ impl fmt::Debug for Options { } f.debug_struct("Options") - .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) @@ -289,6 +294,12 @@ pub(crate) struct RenderOptions { /// This field is only used for the JSON output. If it's set to true, no file will be created /// and content will be displayed in stdout directly. pub(crate) output_to_stdout: bool, + /// Whether we should read or write rendered cross-crate info in the doc root. + pub(crate) should_merge: ShouldMerge, + /// Path to crate-info for external crates. + pub(crate) include_parts_dir: Vec, + /// Where to write crate-info + pub(crate) parts_out_dir: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -348,7 +359,7 @@ impl Options { early_dcx: &mut EarlyDiagCtxt, matches: &getopts::Matches, args: Vec, - ) -> Option<(Options, RenderOptions)> { + ) -> Option<(InputMode, Options, RenderOptions)> { // Check for unstable options. nightly_options::check_nightly_options(early_dcx, matches, &opts()); @@ -478,15 +489,17 @@ impl Options { let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); let input = if describe_lints { - "" // dummy, this won't be used + InputMode::HasFile(make_input(early_dcx, "")) } else { match matches.free.as_slice() { + [] if matches.opt_str("merge").as_deref() == Some("finalize") => { + InputMode::NoInputMergeFinalize + } [] => dcx.fatal("missing file operand"), - [input] => input, + [input] => InputMode::HasFile(make_input(early_dcx, 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) { @@ -494,6 +507,16 @@ impl Options { Err(err) => dcx.fatal(err), }; + let parts_out_dir = + match matches.opt_str("parts-out-dir").map(|p| PathToParts::from_flag(p)).transpose() { + Ok(parts_out_dir) => parts_out_dir, + Err(e) => dcx.fatal(e), + }; + let include_parts_dir = match parse_include_parts_dir(matches) { + Ok(include_parts_dir) => include_parts_dir, + Err(e) => dcx.fatal(e), + }; + let default_settings: Vec> = vec![ matches .opt_str("default-theme") @@ -735,6 +758,10 @@ impl Options { let extern_html_root_takes_precedence = matches.opt_present("extern-html-root-takes-precedence"); let html_no_source = matches.opt_present("html-no-source"); + let should_merge = match parse_merge(matches) { + Ok(result) => result, + Err(e) => dcx.fatal(format!("--merge option error: {e}")), + }; if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) { dcx.struct_warn( @@ -751,7 +778,6 @@ impl Options { let unstable_features = rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); let options = Options { - input, bin_crate, proc_macro_crate, error_format, @@ -823,16 +849,17 @@ impl Options { no_emit_shared: false, html_no_source, output_to_stdout, + should_merge, + include_parts_dir, + parts_out_dir, }; - Some((options, render_options)) + Some((input, options, render_options)) } +} - /// Returns `true` if the file given as `self.input` is a Markdown file. - pub(crate) fn markdown_input(&self) -> Option<&Path> { - self.input - .opt_path() - .filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown")) - } +/// Returns `true` if the file given as `self.input` is a Markdown file. +pub(crate) fn markdown_input(input: &Input) -> Option<&Path> { + input.opt_path().filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown")) } fn parse_remap_path_prefix( @@ -900,3 +927,71 @@ fn parse_extern_html_roots( } Ok(externs) } + +/// Path directly to crate-info file. +/// +/// For example, `/home/user/project/target/doc.parts//crate-info`. +#[derive(Clone, Debug)] +pub(crate) struct PathToParts(pub(crate) PathBuf); + +impl PathToParts { + fn from_flag(path: String) -> Result { + let mut path = PathBuf::from(path); + // check here is for diagnostics + if path.exists() && !path.is_dir() { + Err(format!( + "--parts-out-dir and --include-parts-dir expect directories, found: {}", + path.display(), + )) + } else { + // if it doesn't exist, we'll create it. worry about that in write_shared + path.push("crate-info"); + Ok(PathToParts(path)) + } + } +} + +/// Reports error if --include-parts-dir / crate-info is not a file +fn parse_include_parts_dir(m: &getopts::Matches) -> Result, String> { + let mut ret = Vec::new(); + for p in m.opt_strs("include-parts-dir") { + let p = PathToParts::from_flag(p)?; + // this is just for diagnostic + if !p.0.is_file() { + return Err(format!("--include-parts-dir expected {} to be a file", p.0.display())); + } + ret.push(p); + } + Ok(ret) +} + +/// Controls merging of cross-crate information +#[derive(Debug, Clone)] +pub(crate) struct ShouldMerge { + /// Should we append to existing cci in the doc root + pub(crate) read_rendered_cci: bool, + /// Should we write cci to the doc root + pub(crate) write_rendered_cci: bool, +} + +/// Extracts read_rendered_cci and write_rendered_cci from command line arguments, or +/// reports an error if an invalid option was provided +fn parse_merge(m: &getopts::Matches) -> Result { + match m.opt_str("merge").as_deref() { + // default = read-write + None => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }), + Some("none") if m.opt_present("include-parts-dir") => { + Err("--include-parts-dir not allowed if --merge=none") + } + Some("none") => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: false }), + Some("shared") if m.opt_present("parts-out-dir") || m.opt_present("include-parts-dir") => { + Err("--parts-out-dir and --include-parts-dir not allowed if --merge=shared") + } + Some("shared") => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }), + Some("finalize") if m.opt_present("parts-out-dir") => { + Err("--parts-out-dir not allowed if --merge=finalize") + } + Some("finalize") => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: true }), + Some(_) => Err("argument to --merge must be `none`, `shared`, or `finalize`"), + } +} diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 2cde0ac5c5354..4fafef657925f 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -20,7 +20,7 @@ use rustc_interface::interface; use rustc_lint::{late_lint_mod, MissingDoc}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; -use rustc_session::config::{self, CrateType, ErrorOutputType, ResolveDocLinks}; +use rustc_session::config::{self, CrateType, ErrorOutputType, Input, ResolveDocLinks}; pub(crate) use rustc_session::config::{Options, UnstableOptions}; use rustc_session::{lint, Session}; use rustc_span::symbol::sym; @@ -177,8 +177,8 @@ pub(crate) fn new_dcx( /// Parse, resolve, and typecheck the given crate. pub(crate) fn create_config( + input: Input, RustdocOptions { - input, crate_name, proc_macro_crate, error_format, diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 8b6588ea75c83..05ef72892012d 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -19,7 +19,7 @@ use rustc_errors::{ColorConfig, DiagCtxtHandle, ErrorGuaranteed, FatalError}; use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::CRATE_HIR_ID; use rustc_interface::interface; -use rustc_session::config::{self, CrateType, ErrorOutputType}; +use rustc_session::config::{self, CrateType, ErrorOutputType, Input}; use rustc_session::lint; use rustc_span::edition::Edition; use rustc_span::symbol::sym; @@ -88,7 +88,11 @@ fn get_doctest_dir() -> io::Result { TempFileBuilder::new().prefix("rustdoctest").tempdir() } -pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<(), ErrorGuaranteed> { +pub(crate) fn run( + dcx: DiagCtxtHandle<'_>, + input: Input, + options: RustdocOptions, +) -> Result<(), ErrorGuaranteed> { let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name; // See core::create_config for what's going on here. @@ -135,7 +139,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<() opts: sessopts, crate_cfg: cfgs, crate_check_cfg: options.check_cfgs.clone(), - input: options.input.clone(), + input: input.clone(), output_file: None, output_dir: None, file_loader: None, diff --git a/src/librustdoc/doctest/markdown.rs b/src/librustdoc/doctest/markdown.rs index 9a237f8684d8f..4f83bd5e8826a 100644 --- a/src/librustdoc/doctest/markdown.rs +++ b/src/librustdoc/doctest/markdown.rs @@ -3,6 +3,7 @@ use std::fs::read_to_string; use std::sync::{Arc, Mutex}; +use rustc_session::config::Input; use rustc_span::FileName; use tempfile::tempdir; @@ -69,9 +70,8 @@ impl DocTestVisitor for MdCollector { } /// Runs any tests/code examples in the markdown file `options.input`. -pub(crate) fn test(options: Options) -> Result<(), String> { - use rustc_session::config::Input; - let input_str = match &options.input { +pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> { + let input_str = match input { Input::File(path) => { read_to_string(path).map_err(|err| format!("{}: {err}", path.display()))? } @@ -79,7 +79,7 @@ pub(crate) fn test(options: Options) -> Result<(), String> { }; // Obviously not a real crate name, but close enough for purposes of doctests. - let crate_name = options.input.filestem().to_string(); + let crate_name = input.filestem().to_string(); let temp_dir = tempdir().map_err(|error| format!("failed to create temporary directory: {error:?}"))?; let args_file = temp_dir.path().join("rustdoc-cfgs"); @@ -96,8 +96,7 @@ pub(crate) fn test(options: Options) -> Result<(), String> { let mut md_collector = MdCollector { tests: vec![], cur_path: vec![], - filename: options - .input + filename: input .opt_path() .map(ToOwned::to_owned) .map(FileName::from) diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 58a228b74fcf3..a9b9377c0b972 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -16,12 +16,11 @@ use tracing::info; use super::print_item::{full_path, item_path, print_item}; use super::sidebar::{print_sidebar, sidebar_module_like, ModuleLike, Sidebar}; -use super::write_shared::write_shared; use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath}; use crate::clean::types::ExternalLocation; use crate::clean::utils::has_doc_flag; use crate::clean::{self, ExternalCrate}; -use crate::config::{ModuleSorting, RenderOptions}; +use crate::config::{ModuleSorting, RenderOptions, ShouldMerge}; use crate::docfs::{DocFS, PathError}; use crate::error::Error; use crate::formats::cache::Cache; @@ -30,6 +29,7 @@ use crate::formats::FormatRenderer; use crate::html::escape::Escape; use crate::html::format::{join_with_double_colon, Buffer}; use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap}; +use crate::html::render::write_shared::write_shared; use crate::html::url_parts_builder::UrlPartsBuilder; use crate::html::{layout, sources, static_files}; use crate::scrape_examples::AllCallLocations; @@ -128,8 +128,10 @@ pub(crate) struct SharedContext<'tcx> { pub(crate) span_correspondence_map: FxHashMap, /// The [`Cache`] used during rendering. pub(crate) cache: Cache, - pub(crate) call_locations: AllCallLocations, + /// Controls whether we read / write to cci files in the doc root. Defaults read=true, + /// write=true + should_merge: ShouldMerge, } impl SharedContext<'_> { @@ -551,6 +553,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { span_correspondence_map: matches, cache, call_locations, + should_merge: options.should_merge, }; let dst = output; @@ -640,92 +643,96 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { ); shared.fs.write(final_file, v)?; - // Generating settings page. - page.title = "Settings"; - page.description = "Settings of Rustdoc"; - page.root_path = "./"; - page.rust_logo = true; + // if to avoid writing help, settings files to doc root unless we're on the final invocation + if shared.should_merge.write_rendered_cci { + // Generating settings page. + page.title = "Settings"; + page.description = "Settings of Rustdoc"; + page.root_path = "./"; + page.rust_logo = true; - let sidebar = "

Settings

"; - let v = layout::render( - &shared.layout, - &page, - sidebar, - |buf: &mut Buffer| { - write!( - buf, - "
\ -

Rustdoc settings

\ - \ - \ - Back\ - \ - \ -
\ - \ - ", - static_root_path = page.get_static_root_path(), - settings_js = static_files::STATIC_FILES.settings_js, - ); - // Pre-load all theme CSS files, so that switching feels seamless. - // - // When loading settings.html as a popover, the equivalent HTML is - // generated in main.js. - for file in &shared.style_files { - if let Ok(theme) = file.basename() { - write!( - buf, - "", - root_path = page.static_root_path.unwrap_or(""), - suffix = page.resource_suffix, - ); + let sidebar = "

Settings

"; + let v = layout::render( + &shared.layout, + &page, + sidebar, + |buf: &mut Buffer| { + write!( + buf, + "
\ +

Rustdoc settings

\ + \ + \ + Back\ + \ + \ +
\ + \ + ", + static_root_path = page.get_static_root_path(), + settings_js = static_files::STATIC_FILES.settings_js, + ); + // Pre-load all theme CSS files, so that switching feels seamless. + // + // When loading settings.html as a popover, the equivalent HTML is + // generated in main.js. + for file in &shared.style_files { + if let Ok(theme) = file.basename() { + write!( + buf, + "", + root_path = page.static_root_path.unwrap_or(""), + suffix = page.resource_suffix, + ); + } } - } - }, - &shared.style_files, - ); - shared.fs.write(settings_file, v)?; + }, + &shared.style_files, + ); + shared.fs.write(settings_file, v)?; - // Generating help page. - page.title = "Help"; - page.description = "Documentation for Rustdoc"; - page.root_path = "./"; - page.rust_logo = true; + // Generating help page. + page.title = "Help"; + page.description = "Documentation for Rustdoc"; + page.root_path = "./"; + page.rust_logo = true; - let sidebar = "

Help

"; - let v = layout::render( - &shared.layout, - &page, - sidebar, - |buf: &mut Buffer| { - write!( - buf, - "
\ -

Rustdoc help

\ - \ - \ - Back\ - \ - \ -
\ - ", - ) - }, - &shared.style_files, - ); - shared.fs.write(help_file, v)?; + let sidebar = "

Help

"; + let v = layout::render( + &shared.layout, + &page, + sidebar, + |buf: &mut Buffer| { + write!( + buf, + "
\ +

Rustdoc help

\ + \ + \ + Back\ + \ + \ +
\ + ", + ) + }, + &shared.style_files, + ); + shared.fs.write(help_file, v)?; + } - if shared.layout.scrape_examples_extension { + // if to avoid writing files to doc root unless we're on the final invocation + if shared.layout.scrape_examples_extension && shared.should_merge.write_rendered_cci { page.title = "About scraped examples"; page.description = "How the scraped examples feature works in Rustdoc"; let v = layout::render( diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 3b8eda08372e6..a402d79950492 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -61,6 +61,7 @@ use tracing::{debug, info}; pub(crate) use self::context::*; pub(crate) use self::span_map::{collect_spans_and_sources, LinkFromSrc}; +pub(crate) use self::write_shared::*; use crate::clean::{self, ItemId, RenderedLink}; use crate::error::Error; use crate::formats::cache::Cache; diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index e8d12320f82fd..dc1a8cca6bc8f 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -39,7 +39,7 @@ use serde::{Deserialize, Serialize, Serializer}; use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode}; use crate::clean::{Crate, Item, ItemId, ItemKind}; -use crate::config::{EmitType, RenderOptions}; +use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge}; use crate::docfs::PathError; use crate::error::Error; use crate::formats::cache::Cache; @@ -50,12 +50,11 @@ use crate::html::layout; use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; use crate::html::render::search_index::{build_index, SerializedSearchIndex}; use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate}; -use crate::html::render::{AssocItemLink, ImplRenderingParameters}; +use crate::html::render::{AssocItemLink, ImplRenderingParameters, StylePath}; use crate::html::static_files::{self, suffix_path}; use crate::visit::DocVisitor; use crate::{try_err, try_none}; -/// Write cross-crate information files, static files, invocation-specific files, etc. to disk pub(crate) fn write_shared( cx: &mut Context<'_>, krate: &Crate, @@ -70,13 +69,14 @@ pub(crate) fn write_shared( let SerializedSearchIndex { index, desc } = build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx); - write_search_desc(cx, &krate, &desc)?; // does not need to be merged; written unconditionally + write_search_desc(cx, &krate, &desc)?; // does not need to be merged let crate_name = krate.name(cx.tcx()); let crate_name = crate_name.as_str(); // rand let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand" let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?; let info = CrateInfo { + version: CrateInfoVersion::V1, src_files_js: SourcesPart::get(cx, &crate_name_json)?, search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?, all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?, @@ -85,47 +85,103 @@ pub(crate) fn write_shared( type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?, }; - let crates = vec![info]; // we have info from just one crate. rest will found in out dir + if let Some(parts_out_dir) = &opt.parts_out_dir { + create_parents(&parts_out_dir.0)?; + try_err!( + fs::write(&parts_out_dir.0, serde_json::to_string(&info).unwrap()), + &parts_out_dir.0 + ); + } - write_static_files(cx, &opt)?; - let dst = &cx.dst; - if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) { - if cx.include_sources { - write_rendered_cci::(SourcesPart::blank, dst, &crates)?; - } - write_rendered_cci::(SearchIndexPart::blank, dst, &crates)?; - write_rendered_cci::(AllCratesPart::blank, dst, &crates)?; - } - write_rendered_cci::(TraitAliasPart::blank, dst, &crates)?; - write_rendered_cci::(TypeAliasPart::blank, dst, &crates)?; - match &opt.index_page { - Some(index_page) if opt.enable_index_page => { - let mut md_opts = opt.clone(); - md_opts.output = cx.dst.clone(); - md_opts.external_html = cx.shared.layout.external_html.clone(); - try_err!( - crate::markdown::render(&index_page, md_opts, cx.shared.edition()), - &index_page - ); - } - None if opt.enable_index_page => { - write_rendered_cci::(|| CratesIndexPart::blank(cx), dst, &crates)?; + let mut crates = CrateInfo::read_many(&opt.include_parts_dir)?; + crates.push(info); + + if opt.should_merge.write_rendered_cci { + write_not_crate_specific( + &crates, + &cx.dst, + opt, + &cx.shared.style_files, + cx.shared.layout.css_file_extension.as_deref(), + &cx.shared.resource_suffix, + cx.include_sources, + )?; + match &opt.index_page { + Some(index_page) if opt.enable_index_page => { + let mut md_opts = opt.clone(); + md_opts.output = cx.dst.clone(); + md_opts.external_html = cx.shared.layout.external_html.clone(); + try_err!( + crate::markdown::render(&index_page, md_opts, cx.shared.edition()), + &index_page + ); + } + None if opt.enable_index_page => { + write_rendered_cci::( + || CratesIndexPart::blank(cx), + &cx.dst, + &crates, + &opt.should_merge, + )?; + } + _ => {} // they don't want an index page } - _ => {} // they don't want an index page } Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); Ok(()) } -/// Writes the static files, the style files, and the css extensions -fn write_static_files(cx: &mut Context<'_>, options: &RenderOptions) -> Result<(), Error> { - let static_dir = cx.dst.join("static.files"); +/// Writes files that are written directly to the `--out-dir`, without the prefix from the current +/// crate. These are the rendered cross-crate files that encode info from multiple crates (e.g. +/// search index), and the static files. +pub(crate) fn write_not_crate_specific( + crates: &[CrateInfo], + dst: &Path, + opt: &RenderOptions, + style_files: &[StylePath], + css_file_extension: Option<&Path>, + resource_suffix: &str, + include_sources: bool, +) -> Result<(), Error> { + write_rendered_cross_crate_info(crates, dst, opt, include_sources)?; + write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?; + Ok(()) +} - cx.shared.fs.create_dir_all(&static_dir).map_err(|e| PathError::new(e, "static.files"))?; +fn write_rendered_cross_crate_info( + crates: &[CrateInfo], + dst: &Path, + opt: &RenderOptions, + include_sources: bool, +) -> Result<(), Error> { + let m = &opt.should_merge; + if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) { + if include_sources { + write_rendered_cci::(SourcesPart::blank, dst, &crates, m)?; + } + write_rendered_cci::(SearchIndexPart::blank, dst, &crates, m)?; + write_rendered_cci::(AllCratesPart::blank, dst, &crates, m)?; + } + write_rendered_cci::(TraitAliasPart::blank, dst, &crates, m)?; + write_rendered_cci::(TypeAliasPart::blank, dst, &crates, m)?; + Ok(()) +} + +/// Writes the static files, the style files, and the css extensions. +/// Have to be careful about these, because they write to the root out dir. +fn write_static_files( + dst: &Path, + opt: &RenderOptions, + style_files: &[StylePath], + css_file_extension: Option<&Path>, + resource_suffix: &str, +) -> Result<(), Error> { + let static_dir = dst.join("static.files"); + try_err!(fs::create_dir_all(&static_dir), &static_dir); // Handle added third-party themes - for entry in &cx.shared.style_files { + for entry in style_files { let theme = entry.basename()?; let extension = try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path); @@ -136,22 +192,24 @@ fn write_static_files(cx: &mut Context<'_>, options: &RenderOptions) -> Result<( } let bytes = try_err!(fs::read(&entry.path), &entry.path); - let filename = format!("{theme}{suffix}.{extension}", suffix = cx.shared.resource_suffix); - cx.shared.fs.write(cx.dst.join(filename), bytes)?; + let filename = format!("{theme}{resource_suffix}.{extension}"); + let dst_filename = dst.join(filename); + try_err!(fs::write(&dst_filename, bytes), &dst_filename); } // When the user adds their own CSS files with --extend-css, we write that as an // invocation-specific file (that is, with a resource suffix). - if let Some(ref css) = cx.shared.layout.css_file_extension { + if let Some(css) = css_file_extension { let buffer = try_err!(fs::read_to_string(css), css); - let path = static_files::suffix_path("theme.css", &cx.shared.resource_suffix); - cx.shared.fs.write(cx.dst.join(path), buffer)?; + let path = static_files::suffix_path("theme.css", resource_suffix); + let dst_path = dst.join(path); + try_err!(fs::write(&dst_path, buffer), &dst_path); } - if options.emit.is_empty() || options.emit.contains(&EmitType::Toolchain) { + if opt.emit.is_empty() || opt.emit.contains(&EmitType::Toolchain) { static_files::for_each(|f: &static_files::StaticFile| { let filename = static_dir.join(f.output_filename()); - cx.shared.fs.write(filename, f.minified()) + fs::write(&filename, f.minified()).map_err(|e| PathError::new(e, &filename)) })?; } @@ -186,7 +244,8 @@ fn write_search_desc( /// Contains pre-rendered contents to insert into the CCI template #[derive(Serialize, Deserialize, Clone, Debug)] -struct CrateInfo { +pub(crate) struct CrateInfo { + version: CrateInfoVersion, src_files_js: PartsAndLocations, search_index_js: PartsAndLocations, all_crates: PartsAndLocations, @@ -195,6 +254,33 @@ struct CrateInfo { type_impl: PartsAndLocations, } +impl CrateInfo { + /// Read all of the crate info from its location on the filesystem + pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result, Error> { + parts_paths + .iter() + .map(|parts_path| { + let path = &parts_path.0; + let parts = try_err!(fs::read(&path), &path); + let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path); + Ok::<_, Error>(parts) + }) + .collect::, Error>>() + } +} + +/// Version for the format of the crate-info file. +/// +/// This enum should only ever have one variant, representing the current version. +/// Gives pretty good error message about expecting the current version on deserialize. +/// +/// Must be incremented (V2, V3, etc.) upon any changes to the search index or CrateInfo, +/// to provide better diagnostics about including an invalid file. +#[derive(Serialize, Deserialize, Clone, Debug)] +enum CrateInfoVersion { + V1, +} + /// Paths (relative to the doc root) and their pre-merge contents #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(transparent)] @@ -900,10 +986,14 @@ fn create_parents(path: &Path) -> Result<(), Error> { fn read_template_or_blank( mut make_blank: F, path: &Path, + should_merge: &ShouldMerge, ) -> Result, Error> where F: FnMut() -> SortedTemplate, { + if !should_merge.read_rendered_cci { + return Ok(make_blank()); + } match fs::read_to_string(&path) { Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)), Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()), @@ -916,6 +1006,7 @@ fn write_rendered_cci( mut make_blank: F, dst: &Path, crates_info: &[CrateInfo], + should_merge: &ShouldMerge, ) -> Result<(), Error> where F: FnMut() -> SortedTemplate, @@ -924,7 +1015,8 @@ where for (path, parts) in get_path_parts::(dst, crates_info) { create_parents(&path)?; // read previous rendered cci from storage, append to them - let mut template = read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path)?; + let mut template = + read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?; for part in parts { template.append(part); } diff --git a/src/librustdoc/html/render/write_shared/tests.rs b/src/librustdoc/html/render/write_shared/tests.rs index e282cd99e43cc..a235f1d37243a 100644 --- a/src/librustdoc/html/render/write_shared/tests.rs +++ b/src/librustdoc/html/render/write_shared/tests.rs @@ -1,3 +1,4 @@ +use crate::config::ShouldMerge; use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; use crate::html::render::sorted_template::{Html, SortedTemplate}; use crate::html::render::write_shared::*; @@ -192,16 +193,17 @@ fn read_template_test() { let path = path.path().join("file.html"); let make_blank = || SortedTemplate::::from_before_after("
", "
"); - let template = read_template_or_blank(make_blank, &path).unwrap(); + let should_merge = ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }; + let template = read_template_or_blank(make_blank, &path, &should_merge).unwrap(); assert_eq!(but_last_line(&template.to_string()), "
"); fs::write(&path, template.to_string()).unwrap(); - let mut template = read_template_or_blank(make_blank, &path).unwrap(); + let mut template = read_template_or_blank(make_blank, &path, &should_merge).unwrap(); template.append("".to_string()); fs::write(&path, template.to_string()).unwrap(); - let mut template = read_template_or_blank(make_blank, &path).unwrap(); + let mut template = read_template_or_blank(make_blank, &path, &should_merge).unwrap(); template.append("
".to_string()); fs::write(&path, template.to_string()).unwrap(); - let template = read_template_or_blank(make_blank, &path).unwrap(); + let template = read_template_or_blank(make_blank, &path, &should_merge).unwrap(); assert_eq!(but_last_line(&template.to_string()), "

"); } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index f25acbe080ada..6649e1721a48b 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -603,6 +603,33 @@ fn opts() -> Vec { "path to function call information (for displaying examples in the documentation)", ) }), + unstable("merge", |o| { + o.optopt( + "", + "merge", + "Controls how rustdoc handles files from previously documented crates in the doc root + none = Do not write cross-crate information to the --out-dir + shared = Append current crate's info to files found in the --out-dir + finalize = Write current crate's info and --include-parts-dir info to the --out-dir, overwriting conflicting files", + "none|shared|finalize", + ) + }), + unstable("parts-out-dir", |o| { + o.optopt( + "", + "parts-out-dir", + "Writes trait implementations and other info for the current crate to provided path. Only use with --merge=none", + "path/to/doc.parts/", + ) + }), + unstable("include-parts-dir", |o| { + o.optmulti( + "", + "include-parts-dir", + "Includes trait implementations and other crate info from provided path. Only use with --merge=finalize", + "path/to/doc.parts/", + ) + }), // deprecated / removed options unstable("disable-minification", |o| o.optflagmulti("", "disable-minification", "removed")), stable("plugin-path", |o| { @@ -697,6 +724,32 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>( } } +/// Renders and writes cross-crate info files, like the search index. This function exists so that +/// we can run rustdoc without a crate root in the `--merge=finalize` mode. Cross-crate info files +/// discovered via `--include-parts-dir` are combined and written to the doc root. +fn run_merge_finalize(opt: config::RenderOptions) -> Result<(), error::Error> { + assert!( + opt.should_merge.write_rendered_cci, + "config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize" + ); + assert!( + !opt.should_merge.read_rendered_cci, + "config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize" + ); + let crates = html::render::CrateInfo::read_many(&opt.include_parts_dir)?; + let include_sources = !opt.html_no_source; + html::render::write_not_crate_specific( + &crates, + &opt.output, + &opt, + &opt.themes, + opt.extension_css.as_deref(), + &opt.resource_suffix, + include_sources, + )?; + Ok(()) +} + fn main_args( early_dcx: &mut EarlyDiagCtxt, at_args: &[String], @@ -727,22 +780,35 @@ fn main_args( // Note that we discard any distinction between different non-zero exit // codes from `from_matches` here. - let (options, render_options) = match config::Options::from_matches(early_dcx, &matches, args) { - Some(opts) => opts, - None => return Ok(()), - }; + let (input, options, render_options) = + match config::Options::from_matches(early_dcx, &matches, args) { + Some(opts) => opts, + None => return Ok(()), + }; let dcx = core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts); let dcx = dcx.handle(); - match (options.should_test, options.markdown_input()) { - (true, Some(_)) => return wrap_return(dcx, doctest::test_markdown(options)), - (true, None) => return doctest::run(dcx, options), - (false, Some(input)) => { - let input = input.to_owned(); + let input = match input { + config::InputMode::HasFile(input) => input, + config::InputMode::NoInputMergeFinalize => { + return wrap_return( + dcx, + run_merge_finalize(render_options) + .map_err(|e| format!("could not write merged cross-crate info: {e}")), + ); + } + }; + + match (options.should_test, config::markdown_input(&input)) { + (true, Some(_)) => return wrap_return(dcx, doctest::test_markdown(&input, options)), + (true, None) => return doctest::run(dcx, input, options), + (false, Some(md_input)) => { + let md_input = md_input.to_owned(); let edition = options.edition; - let config = core::create_config(options, &render_options, using_internal_features); + let config = + core::create_config(input, options, &render_options, using_internal_features); // `markdown::render` can invoke `doctest::make_test`, which // requires session globals and a thread pool, so we use @@ -750,7 +816,7 @@ fn main_args( return wrap_return( dcx, interface::run_compiler(config, |_compiler| { - markdown::render(&input, render_options, edition) + markdown::render(&md_input, render_options, edition) }), ); } @@ -775,7 +841,7 @@ fn main_args( let scrape_examples_options = options.scrape_examples_options.clone(); let bin_crate = options.bin_crate; - let config = core::create_config(options, &render_options, using_internal_features); + let config = core::create_config(input, options, &render_options, using_internal_features); interface::run_compiler(config, |compiler| { let sess = &compiler.sess; diff --git a/tests/run-make/rustdoc-default-output/output-default.stdout b/tests/run-make/rustdoc-default-output/output-default.stdout index bc38d9e9dc6b3..125443ce61964 100644 --- a/tests/run-make/rustdoc-default-output/output-default.stdout +++ b/tests/run-make/rustdoc-default-output/output-default.stdout @@ -172,6 +172,23 @@ Options: --scrape-tests Include test code when scraping examples --with-examples path to function call information (for displaying examples in the documentation) + --merge none|shared|finalize + Controls how rustdoc handles files from previously + documented crates in the doc root + none = Do not write cross-crate information to the + --out-dir + shared = Append current crate's info to files found in + the --out-dir + finalize = Write current crate's info and + --include-parts-dir info to the --out-dir, overwriting + conflicting files + --parts-out-dir path/to/doc.parts/ + Writes trait implementations and other info for the + current crate to provided path. Only use with + --merge=none + --include-parts-dir path/to/doc.parts/ + Includes trait implementations and other crate info + from provided path. Only use with --merge=finalize --disable-minification removed --plugin-path DIR diff --git a/tests/rustdoc/merge-cross-crate-info/cargo-transitive-read-write/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/cargo-transitive-read-write/auxiliary/quebec.rs new file mode 100644 index 0000000000000..fdafb3b7ac341 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/cargo-transitive-read-write/auxiliary/quebec.rs @@ -0,0 +1,5 @@ +//@ doc-flags:--merge=shared +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/cargo-transitive-read-write/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/cargo-transitive-read-write/auxiliary/tango.rs new file mode 100644 index 0000000000000..ff12fe98d82c2 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/cargo-transitive-read-write/auxiliary/tango.rs @@ -0,0 +1,8 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ doc-flags:--merge=shared +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/cargo-transitive-read-write/sierra.rs b/tests/rustdoc/merge-cross-crate-info/cargo-transitive-read-write/sierra.rs new file mode 100644 index 0000000000000..665f9567ba2d2 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/cargo-transitive-read-write/sierra.rs @@ -0,0 +1,25 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ doc-flags:--merge=shared +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has index.html +//@ has index.html '//h1' 'List of all crates' +//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec' +//@ has index.html '//ul[@class="all-items"]//a[@href="sierra/index.html"]' 'sierra' +//@ has index.html '//ul[@class="all-items"]//a[@href="tango/index.html"]' 'tango' +//@ has quebec/struct.Quebec.html +//@ has sierra/struct.Sierra.html +//@ has tango/trait.Tango.html +//@ hasraw sierra/struct.Sierra.html 'Tango' +//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html' +//@ hasraw search-index.js 'Tango' +//@ hasraw search-index.js 'Sierra' +//@ hasraw search-index.js 'Quebec' + +// similar to cargo-workflow-transitive, but we use --merge=read-write, +// which is the default. +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/quebec.rs new file mode 100644 index 0000000000000..d10bc0316ccad --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/quebec.rs @@ -0,0 +1,7 @@ +//@ unique-doc-out-dir +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/quebec +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/romeo.rs b/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/romeo.rs new file mode 100644 index 0000000000000..6d0c8651db53f --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/romeo.rs @@ -0,0 +1,10 @@ +//@ aux-build:sierra.rs +//@ build-aux-docs +//@ unique-doc-out-dir +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/romeo +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate sierra; +pub type Romeo = sierra::Sierra; diff --git a/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/sierra.rs b/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/sierra.rs new file mode 100644 index 0000000000000..10898f3886477 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/sierra.rs @@ -0,0 +1,11 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ unique-doc-out-dir +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/sierra +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/tango.rs new file mode 100644 index 0000000000000..3c3721ee6eb7d --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/auxiliary/tango.rs @@ -0,0 +1,10 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ unique-doc-out-dir +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/tango +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/indigo.rs b/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/indigo.rs new file mode 100644 index 0000000000000..f03f6bd6026fa --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/indigo.rs @@ -0,0 +1,35 @@ +//@ aux-build:tango.rs +//@ aux-build:romeo.rs +//@ aux-build:quebec.rs +//@ aux-build:sierra.rs +//@ build-aux-docs +//@ doc-flags:--merge=finalize +//@ doc-flags:--include-parts-dir=info/doc.parts/tango +//@ doc-flags:--include-parts-dir=info/doc.parts/romeo +//@ doc-flags:--include-parts-dir=info/doc.parts/quebec +//@ doc-flags:--include-parts-dir=info/doc.parts/sierra +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has index.html '//h1' 'List of all crates' +//@ has index.html +//@ has index.html '//ul[@class="all-items"]//a[@href="indigo/index.html"]' 'indigo' +//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec' +//@ has index.html '//ul[@class="all-items"]//a[@href="romeo/index.html"]' 'romeo' +//@ has index.html '//ul[@class="all-items"]//a[@href="sierra/index.html"]' 'sierra' +//@ has index.html '//ul[@class="all-items"]//a[@href="tango/index.html"]' 'tango' +//@ !has quebec/struct.Quebec.html +//@ !has romeo/type.Romeo.html +//@ !has sierra/struct.Sierra.html +//@ !has tango/trait.Tango.html +//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html' +//@ hasraw search-index.js 'Quebec' +//@ hasraw search-index.js 'Romeo' +//@ hasraw search-index.js 'Sierra' +//@ hasraw search-index.js 'Tango' +//@ has type.impl/sierra/struct.Sierra.js +//@ hasraw type.impl/sierra/struct.Sierra.js 'Tango' +//@ hasraw type.impl/sierra/struct.Sierra.js 'Romeo' + +// document everything in the default mode, there are separate out +// directories that are linked together diff --git a/tests/rustdoc/merge-cross-crate-info/no-merge-separate/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/no-merge-separate/auxiliary/quebec.rs new file mode 100644 index 0000000000000..61210529fa6bc --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/no-merge-separate/auxiliary/quebec.rs @@ -0,0 +1,6 @@ +//@ unique-doc-out-dir +//@ doc-flags:--merge=none +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/no-merge-separate/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/no-merge-separate/auxiliary/tango.rs new file mode 100644 index 0000000000000..70d8a4b91f549 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/no-merge-separate/auxiliary/tango.rs @@ -0,0 +1,9 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ unique-doc-out-dir +//@ doc-flags:--merge=none +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/no-merge-separate/sierra.rs b/tests/rustdoc/merge-cross-crate-info/no-merge-separate/sierra.rs new file mode 100644 index 0000000000000..7eac207e51886 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/no-merge-separate/sierra.rs @@ -0,0 +1,17 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ doc-flags:--merge=none +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ !has index.html +//@ has sierra/struct.Sierra.html +//@ hasraw sierra/struct.Sierra.html 'Tango' +//@ !has trait.impl/tango/trait.Tango.js +//@ !has search-index.js + +// we don't generate any cross-crate info if --merge=none, even if we +// document crates separately +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/no-merge-write-anyway/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/no-merge-write-anyway/auxiliary/quebec.rs new file mode 100644 index 0000000000000..6ab921533b037 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/no-merge-write-anyway/auxiliary/quebec.rs @@ -0,0 +1,6 @@ +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/quebec +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/no-merge-write-anyway/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/no-merge-write-anyway/auxiliary/tango.rs new file mode 100644 index 0000000000000..9fa99d3be8af4 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/no-merge-write-anyway/auxiliary/tango.rs @@ -0,0 +1,9 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/tango +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/no-merge-write-anyway/sierra.rs b/tests/rustdoc/merge-cross-crate-info/no-merge-write-anyway/sierra.rs new file mode 100644 index 0000000000000..f3340a80c848b --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/no-merge-write-anyway/sierra.rs @@ -0,0 +1,18 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/sierra +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ !has index.html +//@ has sierra/struct.Sierra.html +//@ has tango/trait.Tango.html +//@ hasraw sierra/struct.Sierra.html 'Tango' +//@ !has trait.impl/tango/trait.Tango.js +//@ !has search-index.js + +// we --merge=none, so --parts-out-dir doesn't do anything +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/overwrite-but-include/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/overwrite-but-include/auxiliary/quebec.rs new file mode 100644 index 0000000000000..6ab921533b037 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/overwrite-but-include/auxiliary/quebec.rs @@ -0,0 +1,6 @@ +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/quebec +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/overwrite-but-include/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/overwrite-but-include/auxiliary/tango.rs new file mode 100644 index 0000000000000..9fa99d3be8af4 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/overwrite-but-include/auxiliary/tango.rs @@ -0,0 +1,9 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/tango +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/overwrite-but-include/sierra.rs b/tests/rustdoc/merge-cross-crate-info/overwrite-but-include/sierra.rs new file mode 100644 index 0000000000000..8eb0f1d049831 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/overwrite-but-include/sierra.rs @@ -0,0 +1,22 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ doc-flags:--merge=finalize +//@ doc-flags:--include-parts-dir=info/doc.parts/tango +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has quebec/struct.Quebec.html +//@ has sierra/struct.Sierra.html +//@ has tango/trait.Tango.html +//@ hasraw sierra/struct.Sierra.html 'Tango' +//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html' +//@ hasraw search-index.js 'Tango' +//@ hasraw search-index.js 'Sierra' +//@ !hasraw search-index.js 'Quebec' + +// we overwrite quebec and tango's cross-crate information, but we +// include the info from tango meaning that it should appear in the out +// dir +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/overwrite-but-separate/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/overwrite-but-separate/auxiliary/quebec.rs new file mode 100644 index 0000000000000..d10bc0316ccad --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/overwrite-but-separate/auxiliary/quebec.rs @@ -0,0 +1,7 @@ +//@ unique-doc-out-dir +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/quebec +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/overwrite-but-separate/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/overwrite-but-separate/auxiliary/tango.rs new file mode 100644 index 0000000000000..3c3721ee6eb7d --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/overwrite-but-separate/auxiliary/tango.rs @@ -0,0 +1,10 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ unique-doc-out-dir +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/tango +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/overwrite-but-separate/sierra.rs b/tests/rustdoc/merge-cross-crate-info/overwrite-but-separate/sierra.rs new file mode 100644 index 0000000000000..4ee036238b4b1 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/overwrite-but-separate/sierra.rs @@ -0,0 +1,25 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ doc-flags:--merge=finalize +//@ doc-flags:--include-parts-dir=info/doc.parts/tango +//@ doc-flags:--include-parts-dir=info/doc.parts/quebec +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has index.html +//@ has index.html '//h1' 'List of all crates' +//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec' +//@ has index.html '//ul[@class="all-items"]//a[@href="sierra/index.html"]' 'sierra' +//@ has index.html '//ul[@class="all-items"]//a[@href="tango/index.html"]' 'tango' +//@ has sierra/struct.Sierra.html +//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html' +//@ hasraw search-index.js 'Tango' +//@ hasraw search-index.js 'Sierra' +//@ hasraw search-index.js 'Quebec' + +// If these were documeted into the same directory, the info would be +// overwritten. However, since they are merged, we can still recover all +// of the cross-crate information +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/overwrite/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/overwrite/auxiliary/quebec.rs new file mode 100644 index 0000000000000..0e28d8e646647 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/overwrite/auxiliary/quebec.rs @@ -0,0 +1,5 @@ +//@ doc-flags:--merge=none +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/overwrite/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/overwrite/auxiliary/tango.rs new file mode 100644 index 0000000000000..363b2d5508e63 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/overwrite/auxiliary/tango.rs @@ -0,0 +1,8 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ doc-flags:--merge=finalize +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/overwrite/sierra.rs b/tests/rustdoc/merge-cross-crate-info/overwrite/sierra.rs new file mode 100644 index 0000000000000..11e61dd2744b6 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/overwrite/sierra.rs @@ -0,0 +1,20 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ doc-flags:--merge=shared +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has quebec/struct.Quebec.html +//@ has sierra/struct.Sierra.html +//@ has tango/trait.Tango.html +//@ hasraw sierra/struct.Sierra.html 'Tango' +//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html' +//@ hasraw search-index.js 'Tango' +//@ hasraw search-index.js 'Sierra' +//@ !hasraw search-index.js 'Quebec' + +// since tango is documented with --merge=finalize, we overwrite q's +// cross-crate information +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/single-crate-finalize/quebec.rs b/tests/rustdoc/merge-cross-crate-info/single-crate-finalize/quebec.rs new file mode 100644 index 0000000000000..09bb78c06f1c2 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/single-crate-finalize/quebec.rs @@ -0,0 +1,13 @@ +//@ doc-flags:--merge=finalize +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has index.html +//@ has index.html '//h1' 'List of all crates' +//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec' +//@ has quebec/struct.Quebec.html +//@ hasraw search-index.js 'Quebec' + +// there is nothing to read from the output directory if we use a single +// crate +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/single-crate-read-write/quebec.rs b/tests/rustdoc/merge-cross-crate-info/single-crate-read-write/quebec.rs new file mode 100644 index 0000000000000..72475426f6edd --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/single-crate-read-write/quebec.rs @@ -0,0 +1,12 @@ +//@ doc-flags:--merge=shared +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has index.html +//@ has index.html '//h1' 'List of all crates' +//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec' +//@ has quebec/struct.Quebec.html +//@ hasraw search-index.js 'Quebec' + +// read-write is the default and this does the same as `single-crate` +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/single-crate-write-anyway/quebec.rs b/tests/rustdoc/merge-cross-crate-info/single-crate-write-anyway/quebec.rs new file mode 100644 index 0000000000000..b20e173a8307e --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/single-crate-write-anyway/quebec.rs @@ -0,0 +1,13 @@ +//@ doc-flags:--parts-out-dir=info/doc.parts/quebec +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has index.html +//@ has index.html '//h1' 'List of all crates' +//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec' +//@ has quebec/struct.Quebec.html +//@ hasraw search-index.js 'Quebec' + +// we can --parts-out-dir, but that doesn't do anything other than create +// the file +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/single-merge-none-useless-write/quebec.rs b/tests/rustdoc/merge-cross-crate-info/single-merge-none-useless-write/quebec.rs new file mode 100644 index 0000000000000..e888a43c46042 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/single-merge-none-useless-write/quebec.rs @@ -0,0 +1,11 @@ +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/quebec +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ !has index.html +//@ has quebec/struct.Quebec.html +//@ !has search-index.js + +// --merge=none doesn't write anything, despite --parts-out-dir +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-finalize/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/transitive-finalize/auxiliary/quebec.rs new file mode 100644 index 0000000000000..1beca543f814f --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-finalize/auxiliary/quebec.rs @@ -0,0 +1,5 @@ +//@ doc-flags:--merge=finalize +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-finalize/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/transitive-finalize/auxiliary/tango.rs new file mode 100644 index 0000000000000..363b2d5508e63 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-finalize/auxiliary/tango.rs @@ -0,0 +1,8 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ doc-flags:--merge=finalize +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-finalize/sierra.rs b/tests/rustdoc/merge-cross-crate-info/transitive-finalize/sierra.rs new file mode 100644 index 0000000000000..68fc4b13fa8f7 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-finalize/sierra.rs @@ -0,0 +1,20 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ doc-flags:--merge=finalize +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has index.html +//@ has index.html '//h1' 'List of all crates' +//@ has index.html '//ul[@class="all-items"]//a[@href="sierra/index.html"]' 'sierra' +//@ has index.html '//ul[@class="all-items"]//a[@href="tango/index.html"]' 'tango' +//@ has sierra/struct.Sierra.html +//@ has tango/trait.Tango.html +//@ hasraw sierra/struct.Sierra.html 'Tango' +//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html' +//@ hasraw search-index.js 'Sierra' + +// write only overwrites stuff in the output directory +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-merge-none/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/transitive-merge-none/auxiliary/quebec.rs new file mode 100644 index 0000000000000..6ab921533b037 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-merge-none/auxiliary/quebec.rs @@ -0,0 +1,6 @@ +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/quebec +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-merge-none/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/transitive-merge-none/auxiliary/tango.rs new file mode 100644 index 0000000000000..9fa99d3be8af4 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-merge-none/auxiliary/tango.rs @@ -0,0 +1,9 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ doc-flags:--merge=none +//@ doc-flags:--parts-out-dir=info/doc.parts/tango +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-merge-none/sierra.rs b/tests/rustdoc/merge-cross-crate-info/transitive-merge-none/sierra.rs new file mode 100644 index 0000000000000..b407228085e88 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-merge-none/sierra.rs @@ -0,0 +1,27 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ doc-flags:--merge=finalize +//@ doc-flags:--include-parts-dir=info/doc.parts/tango +//@ doc-flags:--include-parts-dir=info/doc.parts/quebec +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has index.html +//@ has index.html '//h1' 'List of all crates' +//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec' +//@ has index.html '//ul[@class="all-items"]//a[@href="sierra/index.html"]' 'sierra' +//@ has index.html '//ul[@class="all-items"]//a[@href="tango/index.html"]' 'tango' +//@ has quebec/struct.Quebec.html +//@ has sierra/struct.Sierra.html +//@ has tango/trait.Tango.html +//@ hasraw sierra/struct.Sierra.html 'Tango' +//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html' +//@ hasraw search-index.js 'Tango' +//@ hasraw search-index.js 'Sierra' +//@ hasraw search-index.js 'Quebec' + +// We avoid writing any cross-crate information, preferring to include it +// with --include-parts-dir. +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-merge-read-write/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/transitive-merge-read-write/auxiliary/quebec.rs new file mode 100644 index 0000000000000..1beca543f814f --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-merge-read-write/auxiliary/quebec.rs @@ -0,0 +1,5 @@ +//@ doc-flags:--merge=finalize +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-merge-read-write/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/transitive-merge-read-write/auxiliary/tango.rs new file mode 100644 index 0000000000000..ff12fe98d82c2 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-merge-read-write/auxiliary/tango.rs @@ -0,0 +1,8 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ doc-flags:--merge=shared +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-merge-read-write/sierra.rs b/tests/rustdoc/merge-cross-crate-info/transitive-merge-read-write/sierra.rs new file mode 100644 index 0000000000000..15e32d5941fb2 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-merge-read-write/sierra.rs @@ -0,0 +1,25 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ doc-flags:--merge=shared +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ has index.html +//@ has index.html '//h1' 'List of all crates' +//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec' +//@ has index.html '//ul[@class="all-items"]//a[@href="sierra/index.html"]' 'sierra' +//@ has index.html '//ul[@class="all-items"]//a[@href="tango/index.html"]' 'tango' +//@ has quebec/struct.Quebec.html +//@ has sierra/struct.Sierra.html +//@ has tango/trait.Tango.html +//@ hasraw sierra/struct.Sierra.html 'Tango' +//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html' +//@ hasraw search-index.js 'Tango' +//@ hasraw search-index.js 'Sierra' +//@ hasraw search-index.js 'Quebec' + +// We can use read-write to emulate the default behavior of rustdoc, when +// --merge is left out. +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-no-info/auxiliary/quebec.rs b/tests/rustdoc/merge-cross-crate-info/transitive-no-info/auxiliary/quebec.rs new file mode 100644 index 0000000000000..0e28d8e646647 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-no-info/auxiliary/quebec.rs @@ -0,0 +1,5 @@ +//@ doc-flags:--merge=none +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +pub struct Quebec; diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-no-info/auxiliary/tango.rs b/tests/rustdoc/merge-cross-crate-info/transitive-no-info/auxiliary/tango.rs new file mode 100644 index 0000000000000..3827119f696f5 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-no-info/auxiliary/tango.rs @@ -0,0 +1,8 @@ +//@ aux-build:quebec.rs +//@ build-aux-docs +//@ doc-flags:--merge=none +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +extern crate quebec; +pub trait Tango {} diff --git a/tests/rustdoc/merge-cross-crate-info/transitive-no-info/sierra.rs b/tests/rustdoc/merge-cross-crate-info/transitive-no-info/sierra.rs new file mode 100644 index 0000000000000..3eb2cebd74363 --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/transitive-no-info/sierra.rs @@ -0,0 +1,17 @@ +//@ aux-build:tango.rs +//@ build-aux-docs +//@ doc-flags:--merge=none +//@ doc-flags:--enable-index-page +//@ doc-flags:-Zunstable-options + +//@ !has index.html +//@ has sierra/struct.Sierra.html +//@ has tango/trait.Tango.html +//@ hasraw sierra/struct.Sierra.html 'Tango' +//@ !has trait.impl/tango/trait.Tango.js +//@ !has search-index.js + +// --merge=none on all crates does not generate any cross-crate info +extern crate tango; +pub struct Sierra; +impl tango::Tango for Sierra {} diff --git a/tests/rustdoc/merge-cross-crate-info/two-separate-out-dir/auxiliary/foxtrot.rs b/tests/rustdoc/merge-cross-crate-info/two-separate-out-dir/auxiliary/foxtrot.rs new file mode 100644 index 0000000000000..e492b700dbc2b --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/two-separate-out-dir/auxiliary/foxtrot.rs @@ -0,0 +1,5 @@ +//@ unique-doc-out-dir +//@ doc-flags:--parts-out-dir=info/doc.parts/foxtrot +//@ doc-flags:-Zunstable-options + +pub trait Foxtrot {} diff --git a/tests/rustdoc/merge-cross-crate-info/two-separate-out-dir/echo.rs b/tests/rustdoc/merge-cross-crate-info/two-separate-out-dir/echo.rs new file mode 100644 index 0000000000000..ee2b646e43cda --- /dev/null +++ b/tests/rustdoc/merge-cross-crate-info/two-separate-out-dir/echo.rs @@ -0,0 +1,16 @@ +//@ aux-build:foxtrot.rs +//@ build-aux-docs +//@ doc-flags:--include-parts-dir=info/doc.parts/foxtrot +//@ doc-flags:-Zunstable-options + +//@ has echo/enum.Echo.html +//@ hasraw echo/enum.Echo.html 'Foxtrot' +//@ hasraw trait.impl/foxtrot/trait.Foxtrot.js 'enum.Echo.html' +//@ hasraw search-index.js 'Foxtrot' +//@ hasraw search-index.js 'Echo' + +// document two crates in different places, and merge their docs after +// they are generated +extern crate foxtrot; +pub enum Echo {} +impl foxtrot::Foxtrot for Echo {}