Rollup merge of #60971 - rbtcollins:docs-perf, r=rbtcollins,GuillaumeGomez
Add DocFS layer to rustdoc * Move fs::create_dir_all calls into DocFS to provide a clean extension point if async extension there is needed. * Convert callsites of create_dir_all to ensure_dir to reduce syscalls. * Convert fs::write usage to DocFS.write (which also removes a lot of try_err! usage for easier reading) * Convert File::create calls to use Vec buffers and then DocFS.write in order to both consistently reduce syscalls as well as make deferring to threads cleaner. * Convert OpenOptions usage similarly - I could find no discussion on the use of create_new for that one output file vs all the other files render creates, if link redirection attacks are a concern DocFS will provide a good central point to introduce systematic create_new usage. * DocFS::write defers to rayon for IO on Windows producing a modest speedup: before this patch on my development workstation: $ time cargo +mystg1 doc -p winapi:0.3.7 Documenting winapi v0.3.7 Finished dev [unoptimized + debuginfo] target(s) in 6m 11s real 6m11.734s user 0m0.015s sys 0m0.000s Afterwards: $ time cargo +mystg1 doc -p winapi:0.3.7 Compiling winapi v0.3.7 Documenting winapi v0.3.7 Finished dev [unoptimized + debuginfo] target(s) in 49.53s real 0m49.643s user 0m0.000s sys 0m0.015s I haven't measured how much time is in the compilation logic vs in the IO and outputting etc, but this takes it from frustating to tolerable for me, at least for now.
This commit is contained in:
commit
f2a0ce9c37
@ -3254,6 +3254,7 @@ dependencies = [
|
||||
"minifier 0.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pulldown-cmark 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-rayon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -11,5 +11,6 @@ path = "lib.rs"
|
||||
[dependencies]
|
||||
pulldown-cmark = { version = "0.5.2", default-features = false }
|
||||
minifier = "0.0.30"
|
||||
rayon = { version = "0.2.0", package = "rustc-rayon" }
|
||||
tempfile = "3"
|
||||
parking_lot = "0.7"
|
||||
|
116
src/librustdoc/docfs.rs
Normal file
116
src/librustdoc/docfs.rs
Normal file
@ -0,0 +1,116 @@
|
||||
//! Rustdoc's FileSystem abstraction module.
|
||||
//!
|
||||
//! On Windows this indirects IO into threads to work around performance issues
|
||||
//! with Defender (and other similar virus scanners that do blocking operations).
|
||||
//! On other platforms this is a thin shim to fs.
|
||||
//!
|
||||
//! Only calls needed to permit this workaround have been abstracted: thus
|
||||
//! fs::read is still done directly via the fs module; if in future rustdoc
|
||||
//! needs to read-after-write from a file, then it would be added to this
|
||||
//! abstraction.
|
||||
|
||||
use errors;
|
||||
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
|
||||
macro_rules! try_err {
|
||||
($e:expr, $file:expr) => {{
|
||||
match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Err(E::new(e, $file)),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub trait PathError {
|
||||
fn new<P: AsRef<Path>>(e: io::Error, path: P) -> Self;
|
||||
}
|
||||
|
||||
pub struct ErrorStorage {
|
||||
sender: Option<Sender<Option<String>>>,
|
||||
receiver: Receiver<Option<String>>,
|
||||
}
|
||||
|
||||
impl ErrorStorage {
|
||||
pub fn new() -> ErrorStorage {
|
||||
let (sender, receiver) = channel();
|
||||
ErrorStorage {
|
||||
sender: Some(sender),
|
||||
receiver,
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints all stored errors. Returns the number of printed errors.
|
||||
pub fn write_errors(&mut self, diag: &errors::Handler) -> usize {
|
||||
let mut printed = 0;
|
||||
// In order to drop the sender part of the channel.
|
||||
self.sender = None;
|
||||
|
||||
for msg in self.receiver.iter() {
|
||||
if let Some(ref error) = msg {
|
||||
diag.struct_err(&error).emit();
|
||||
printed += 1;
|
||||
}
|
||||
}
|
||||
printed
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DocFS {
|
||||
sync_only: bool,
|
||||
errors: Arc<ErrorStorage>,
|
||||
}
|
||||
|
||||
impl DocFS {
|
||||
pub fn new(errors: &Arc<ErrorStorage>) -> DocFS {
|
||||
DocFS {
|
||||
sync_only: false,
|
||||
errors: Arc::clone(errors),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_sync_only(&mut self, sync_only: bool) {
|
||||
self.sync_only = sync_only;
|
||||
}
|
||||
|
||||
pub fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||
// For now, dir creation isn't a huge time consideration, do it
|
||||
// synchronously, which avoids needing ordering between write() actions
|
||||
// and directory creation.
|
||||
fs::create_dir_all(path)
|
||||
}
|
||||
|
||||
pub fn write<P, C, E>(&self, path: P, contents: C) -> Result<(), E>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
C: AsRef<[u8]>,
|
||||
E: PathError,
|
||||
{
|
||||
if !self.sync_only && cfg!(windows) {
|
||||
// A possible future enhancement after more detailed profiling would
|
||||
// be to create the file sync so errors are reported eagerly.
|
||||
let contents = contents.as_ref().to_vec();
|
||||
let path = path.as_ref().to_path_buf();
|
||||
let sender = self.errors.sender.clone().unwrap();
|
||||
rayon::spawn(move || {
|
||||
match fs::write(&path, &contents) {
|
||||
Ok(_) => {
|
||||
sender.send(None)
|
||||
.expect(&format!("failed to send error on \"{}\"", path.display()));
|
||||
}
|
||||
Err(e) => {
|
||||
sender.send(Some(format!("\"{}\": {}", path.display(), e)))
|
||||
.expect(&format!("failed to send non-error on \"{}\"", path.display()));
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(try_err!(fs::write(&path, contents), path))
|
||||
}
|
||||
}
|
||||
}
|
@ -35,9 +35,9 @@ use std::default::Default;
|
||||
use std::error;
|
||||
use std::fmt::{self, Display, Formatter, Write as FmtWrite};
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufWriter, BufReader};
|
||||
use std::io::{self, BufReader};
|
||||
use std::mem;
|
||||
use std::path::{PathBuf, Path, Component};
|
||||
use std::str;
|
||||
@ -61,6 +61,7 @@ use rustc_data_structures::flock;
|
||||
|
||||
use crate::clean::{self, AttributesExt, Deprecation, GetDefId, SelfTy, Mutability};
|
||||
use crate::config::RenderOptions;
|
||||
use crate::docfs::{DocFS, ErrorStorage, PathError};
|
||||
use crate::doctree;
|
||||
use crate::fold::DocFolder;
|
||||
use crate::html::escape::Escape;
|
||||
@ -89,6 +90,58 @@ impl<'a> Display for SlashChecker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub file: PathBuf,
|
||||
pub error: io::Error,
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
self.error.description()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let file = self.file.display().to_string();
|
||||
if file.is_empty() {
|
||||
write!(f, "{}", self.error)
|
||||
} else {
|
||||
write!(f, "\"{}\": {}", self.file.display(), self.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PathError for Error {
|
||||
fn new<P: AsRef<Path>>(e: io::Error, path: P) -> Error {
|
||||
Error {
|
||||
file: path.as_ref().to_path_buf(),
|
||||
error: e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! try_none {
|
||||
($e:expr, $file:expr) => ({
|
||||
use std::io;
|
||||
match $e {
|
||||
Some(e) => e,
|
||||
None => return Err(Error::new(io::Error::new(io::ErrorKind::Other, "not found"),
|
||||
$file))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! try_err {
|
||||
($e:expr, $file:expr) => ({
|
||||
match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Err(Error::new(e, $file)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Major driving force in all rustdoc rendering. This contains information
|
||||
/// about where in the tree-like hierarchy rendering is occurring and controls
|
||||
/// how the current page is being rendered.
|
||||
@ -156,13 +209,15 @@ struct SharedContext {
|
||||
pub generate_search_filter: bool,
|
||||
/// Option disabled by default to generate files used by RLS and some other tools.
|
||||
pub generate_redirect_pages: bool,
|
||||
/// The fs handle we are working with.
|
||||
pub fs: DocFS,
|
||||
}
|
||||
|
||||
impl SharedContext {
|
||||
fn ensure_dir(&self, dst: &Path) -> io::Result<()> {
|
||||
fn ensure_dir(&self, dst: &Path) -> Result<(), Error> {
|
||||
let mut dirs = self.created_dirs.borrow_mut();
|
||||
if !dirs.contains(dst) {
|
||||
fs::create_dir_all(dst)?;
|
||||
try_err!(self.fs.create_dir_all(dst), dst);
|
||||
dirs.insert(dst.to_path_buf());
|
||||
}
|
||||
|
||||
@ -216,53 +271,6 @@ impl Impl {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub file: PathBuf,
|
||||
pub error: io::Error,
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
self.error.description()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "\"{}\": {}", self.file.display(), self.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(e: io::Error, file: &Path) -> Error {
|
||||
Error {
|
||||
file: file.to_path_buf(),
|
||||
error: e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! try_none {
|
||||
($e:expr, $file:expr) => ({
|
||||
use std::io;
|
||||
match $e {
|
||||
Some(e) => e,
|
||||
None => return Err(Error::new(io::Error::new(io::ErrorKind::Other, "not found"),
|
||||
$file))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! try_err {
|
||||
($e:expr, $file:expr) => ({
|
||||
match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Err(Error::new(e, $file)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// This cache is used to store information about the `clean::Crate` being
|
||||
/// rendered in order to provide more useful documentation. This contains
|
||||
/// information like all implementors of a trait, all traits a type implements,
|
||||
@ -544,6 +552,7 @@ pub fn run(mut krate: clean::Crate,
|
||||
},
|
||||
_ => PathBuf::new(),
|
||||
};
|
||||
let mut errors = Arc::new(ErrorStorage::new());
|
||||
let mut scx = SharedContext {
|
||||
src_root,
|
||||
passes,
|
||||
@ -564,6 +573,7 @@ pub fn run(mut krate: clean::Crate,
|
||||
static_root_path,
|
||||
generate_search_filter,
|
||||
generate_redirect_pages,
|
||||
fs: DocFS::new(&errors),
|
||||
};
|
||||
|
||||
// If user passed in `--playground-url` arg, we fill in crate name here
|
||||
@ -601,9 +611,9 @@ pub fn run(mut krate: clean::Crate,
|
||||
}
|
||||
}
|
||||
let dst = output;
|
||||
try_err!(fs::create_dir_all(&dst), &dst);
|
||||
scx.ensure_dir(&dst)?;
|
||||
krate = render_sources(&dst, &mut scx, krate)?;
|
||||
let cx = Context {
|
||||
let mut cx = Context {
|
||||
current: Vec::new(),
|
||||
dst,
|
||||
render_redirect_pages: false,
|
||||
@ -705,10 +715,21 @@ pub fn run(mut krate: clean::Crate,
|
||||
CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone());
|
||||
CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear());
|
||||
|
||||
// Write shared runs within a flock; disable thread dispatching of IO temporarily.
|
||||
Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
|
||||
write_shared(&cx, &krate, &*cache, index, &md_opts, diag)?;
|
||||
Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
|
||||
|
||||
// And finally render the whole crate's documentation
|
||||
cx.krate(krate)
|
||||
let ret = cx.krate(krate);
|
||||
let nb_errors = Arc::get_mut(&mut errors).map_or_else(|| 0, |errors| errors.write_errors(diag));
|
||||
if ret.is_err() {
|
||||
ret
|
||||
} else if nb_errors > 0 {
|
||||
Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), ""))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the search index from the collected metadata
|
||||
@ -797,13 +818,13 @@ fn write_shared(
|
||||
// Add all the static files. These may already exist, but we just
|
||||
// overwrite them anyway to make sure that they're fresh and up-to-date.
|
||||
|
||||
write_minify(cx.dst.join(&format!("rustdoc{}.css", cx.shared.resource_suffix)),
|
||||
write_minify(&cx.shared.fs, cx.dst.join(&format!("rustdoc{}.css", cx.shared.resource_suffix)),
|
||||
static_files::RUSTDOC_CSS,
|
||||
options.enable_minification)?;
|
||||
write_minify(cx.dst.join(&format!("settings{}.css", cx.shared.resource_suffix)),
|
||||
write_minify(&cx.shared.fs, cx.dst.join(&format!("settings{}.css", cx.shared.resource_suffix)),
|
||||
static_files::SETTINGS_CSS,
|
||||
options.enable_minification)?;
|
||||
write_minify(cx.dst.join(&format!("noscript{}.css", cx.shared.resource_suffix)),
|
||||
write_minify(&cx.shared.fs, cx.dst.join(&format!("noscript{}.css", cx.shared.resource_suffix)),
|
||||
static_files::NOSCRIPT_CSS,
|
||||
options.enable_minification)?;
|
||||
|
||||
@ -815,11 +836,13 @@ fn write_shared(
|
||||
let content = try_err!(fs::read(&entry), &entry);
|
||||
let theme = try_none!(try_none!(entry.file_stem(), &entry).to_str(), &entry);
|
||||
let extension = try_none!(try_none!(entry.extension(), &entry).to_str(), &entry);
|
||||
write(cx.dst.join(format!("{}{}.{}", theme, cx.shared.resource_suffix, extension)),
|
||||
content.as_slice())?;
|
||||
cx.shared.fs.write(
|
||||
cx.dst.join(format!("{}{}.{}", theme, cx.shared.resource_suffix, extension)),
|
||||
content.as_slice())?;
|
||||
themes.insert(theme.to_owned());
|
||||
}
|
||||
|
||||
let write = |p, c| { cx.shared.fs.write(p, c) };
|
||||
if (*cx.shared).layout.logo.is_empty() {
|
||||
write(cx.dst.join(&format!("rust-logo{}.png", cx.shared.resource_suffix)),
|
||||
static_files::RUST_LOGO)?;
|
||||
@ -834,11 +857,11 @@ fn write_shared(
|
||||
static_files::WHEEL_SVG)?;
|
||||
write(cx.dst.join(&format!("down-arrow{}.svg", cx.shared.resource_suffix)),
|
||||
static_files::DOWN_ARROW_SVG)?;
|
||||
write_minify(cx.dst.join(&format!("light{}.css", cx.shared.resource_suffix)),
|
||||
write_minify(&cx.shared.fs, cx.dst.join(&format!("light{}.css", cx.shared.resource_suffix)),
|
||||
static_files::themes::LIGHT,
|
||||
options.enable_minification)?;
|
||||
themes.insert("light".to_owned());
|
||||
write_minify(cx.dst.join(&format!("dark{}.css", cx.shared.resource_suffix)),
|
||||
write_minify(&cx.shared.fs, cx.dst.join(&format!("dark{}.css", cx.shared.resource_suffix)),
|
||||
static_files::themes::DARK,
|
||||
options.enable_minification)?;
|
||||
themes.insert("dark".to_owned());
|
||||
@ -847,8 +870,7 @@ fn write_shared(
|
||||
themes.sort();
|
||||
// To avoid theme switch latencies as much as possible, we put everything theme related
|
||||
// at the beginning of the html files into another js file.
|
||||
write(cx.dst.join(&format!("theme{}.js", cx.shared.resource_suffix)),
|
||||
format!(
|
||||
let theme_js = format!(
|
||||
r#"var themes = document.getElementById("theme-choices");
|
||||
var themePicker = document.getElementById("theme-picker");
|
||||
|
||||
@ -891,39 +913,45 @@ themePicker.onblur = handleThemeButtonsBlur;
|
||||
themes.iter()
|
||||
.map(|s| format!("\"{}\"", s))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")).as_bytes(),
|
||||
.join(","));
|
||||
write(cx.dst.join(&format!("theme{}.js", cx.shared.resource_suffix)),
|
||||
theme_js.as_bytes()
|
||||
)?;
|
||||
|
||||
write_minify(cx.dst.join(&format!("main{}.js", cx.shared.resource_suffix)),
|
||||
write_minify(&cx.shared.fs, cx.dst.join(&format!("main{}.js", cx.shared.resource_suffix)),
|
||||
static_files::MAIN_JS,
|
||||
options.enable_minification)?;
|
||||
write_minify(cx.dst.join(&format!("settings{}.js", cx.shared.resource_suffix)),
|
||||
write_minify(&cx.shared.fs, cx.dst.join(&format!("settings{}.js", cx.shared.resource_suffix)),
|
||||
static_files::SETTINGS_JS,
|
||||
options.enable_minification)?;
|
||||
if cx.shared.include_sources {
|
||||
write_minify(cx.dst.join(&format!("source-script{}.js", cx.shared.resource_suffix)),
|
||||
static_files::sidebar::SOURCE_SCRIPT,
|
||||
options.enable_minification)?;
|
||||
write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.dst.join(&format!("source-script{}.js", cx.shared.resource_suffix)),
|
||||
static_files::sidebar::SOURCE_SCRIPT,
|
||||
options.enable_minification)?;
|
||||
}
|
||||
|
||||
{
|
||||
write_minify(cx.dst.join(&format!("storage{}.js", cx.shared.resource_suffix)),
|
||||
&format!("var resourcesSuffix = \"{}\";{}",
|
||||
cx.shared.resource_suffix,
|
||||
static_files::STORAGE_JS),
|
||||
options.enable_minification)?;
|
||||
write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.dst.join(&format!("storage{}.js", cx.shared.resource_suffix)),
|
||||
&format!("var resourcesSuffix = \"{}\";{}",
|
||||
cx.shared.resource_suffix,
|
||||
static_files::STORAGE_JS),
|
||||
options.enable_minification)?;
|
||||
}
|
||||
|
||||
if let Some(ref css) = cx.shared.css_file_extension {
|
||||
let out = cx.dst.join(&format!("theme{}.css", cx.shared.resource_suffix));
|
||||
let buffer = try_err!(fs::read_to_string(css), css);
|
||||
if !options.enable_minification {
|
||||
try_err!(fs::copy(css, out), css);
|
||||
cx.shared.fs.write(&out, &buffer)?;
|
||||
} else {
|
||||
let buffer = try_err!(fs::read_to_string(css), css);
|
||||
write_minify(out, &buffer, options.enable_minification)?;
|
||||
write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?;
|
||||
}
|
||||
}
|
||||
write_minify(cx.dst.join(&format!("normalize{}.css", cx.shared.resource_suffix)),
|
||||
write_minify(&cx.shared.fs, cx.dst.join(&format!("normalize{}.css", cx.shared.resource_suffix)),
|
||||
static_files::NORMALIZE_CSS,
|
||||
options.enable_minification)?;
|
||||
write(cx.dst.join("FiraSans-Regular.woff"),
|
||||
@ -999,7 +1027,6 @@ themePicker.onblur = handleThemeButtonsBlur;
|
||||
let dst = cx.dst.join(&format!("aliases{}.js", cx.shared.resource_suffix));
|
||||
{
|
||||
let (mut all_aliases, _, _) = try_err!(collect(&dst, &krate.name, "ALIASES", false), &dst);
|
||||
let mut w = try_err!(File::create(&dst), &dst);
|
||||
let mut output = String::with_capacity(100);
|
||||
for (alias, items) in &cache.aliases {
|
||||
if items.is_empty() {
|
||||
@ -1014,10 +1041,12 @@ themePicker.onblur = handleThemeButtonsBlur;
|
||||
}
|
||||
all_aliases.push(format!("ALIASES[\"{}\"] = {{{}}};", krate.name, output));
|
||||
all_aliases.sort();
|
||||
try_err!(writeln!(&mut w, "var ALIASES = {{}};"), &dst);
|
||||
let mut v = Vec::new();
|
||||
try_err!(writeln!(&mut v, "var ALIASES = {{}};"), &dst);
|
||||
for aliases in &all_aliases {
|
||||
try_err!(writeln!(&mut w, "{}", aliases), &dst);
|
||||
try_err!(writeln!(&mut v, "{}", aliases), &dst);
|
||||
}
|
||||
cx.shared.fs.write(&dst, &v)?;
|
||||
}
|
||||
|
||||
use std::ffi::OsString;
|
||||
@ -1101,11 +1130,12 @@ themePicker.onblur = handleThemeButtonsBlur;
|
||||
&krate.name,
|
||||
hierarchy.to_json_string()));
|
||||
all_sources.sort();
|
||||
let mut w = try_err!(File::create(&dst), &dst);
|
||||
try_err!(writeln!(&mut w,
|
||||
let mut v = Vec::new();
|
||||
try_err!(writeln!(&mut v,
|
||||
"var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();",
|
||||
all_sources.join("\n")),
|
||||
&dst);
|
||||
cx.shared.fs.write(&dst, &v)?;
|
||||
}
|
||||
|
||||
// Update the search index
|
||||
@ -1119,14 +1149,17 @@ themePicker.onblur = handleThemeButtonsBlur;
|
||||
// Sort the indexes by crate so the file will be generated identically even
|
||||
// with rustdoc running in parallel.
|
||||
all_indexes.sort();
|
||||
let mut w = try_err!(File::create(&dst), &dst);
|
||||
try_err!(writeln!(&mut w, "var N=null,E=\"\",T=\"t\",U=\"u\",searchIndex={{}};"), &dst);
|
||||
try_err!(write_minify_replacer(&mut w,
|
||||
&format!("{}\n{}", variables.join(""), all_indexes.join("\n")),
|
||||
options.enable_minification),
|
||||
&dst);
|
||||
try_err!(write!(&mut w, "initSearch(searchIndex);addSearchOptions(searchIndex);"), &dst);
|
||||
|
||||
{
|
||||
let mut v = Vec::new();
|
||||
try_err!(writeln!(&mut v, "var N=null,E=\"\",T=\"t\",U=\"u\",searchIndex={{}};"), &dst);
|
||||
try_err!(write_minify_replacer(
|
||||
&mut v,
|
||||
&format!("{}\n{}", variables.join(""), all_indexes.join("\n")),
|
||||
options.enable_minification),
|
||||
&dst);
|
||||
try_err!(write!(&mut v, "initSearch(searchIndex);addSearchOptions(searchIndex);"), &dst);
|
||||
cx.shared.fs.write(&dst, &v)?;
|
||||
}
|
||||
if options.enable_index_page {
|
||||
if let Some(index_page) = options.index_page.clone() {
|
||||
let mut md_opts = options.clone();
|
||||
@ -1136,7 +1169,6 @@ themePicker.onblur = handleThemeButtonsBlur;
|
||||
crate::markdown::render(index_page, md_opts, diag, cx.edition);
|
||||
} else {
|
||||
let dst = cx.dst.join("index.html");
|
||||
let mut w = BufWriter::new(try_err!(File::create(&dst), &dst));
|
||||
let page = layout::Page {
|
||||
title: "Index of crates",
|
||||
css_class: "mod",
|
||||
@ -1163,12 +1195,13 @@ themePicker.onblur = handleThemeButtonsBlur;
|
||||
SlashChecker(s), s)
|
||||
})
|
||||
.collect::<String>());
|
||||
try_err!(layout::render(&mut w, &cx.shared.layout,
|
||||
let mut v = Vec::new();
|
||||
try_err!(layout::render(&mut v, &cx.shared.layout,
|
||||
&page, &(""), &content,
|
||||
cx.shared.css_file_extension.is_some(),
|
||||
&cx.shared.themes,
|
||||
cx.shared.generate_search_filter), &dst);
|
||||
try_err!(w.flush(), &dst);
|
||||
cx.shared.fs.write(&dst, &v)?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1220,7 +1253,7 @@ themePicker.onblur = handleThemeButtonsBlur;
|
||||
for part in &remote_path[..remote_path.len() - 1] {
|
||||
mydst.push(part);
|
||||
}
|
||||
try_err!(fs::create_dir_all(&mydst), &mydst);
|
||||
cx.shared.ensure_dir(&mydst)?;
|
||||
mydst.push(&format!("{}.{}.js",
|
||||
remote_item_type.css_class(),
|
||||
remote_path[remote_path.len() - 1]));
|
||||
@ -1233,19 +1266,20 @@ themePicker.onblur = handleThemeButtonsBlur;
|
||||
// identically even with rustdoc running in parallel.
|
||||
all_implementors.sort();
|
||||
|
||||
let mut f = try_err!(File::create(&mydst), &mydst);
|
||||
try_err!(writeln!(&mut f, "(function() {{var implementors = {{}};"), &mydst);
|
||||
let mut v = Vec::new();
|
||||
try_err!(writeln!(&mut v, "(function() {{var implementors = {{}};"), &mydst);
|
||||
for implementor in &all_implementors {
|
||||
try_err!(writeln!(&mut f, "{}", *implementor), &mydst);
|
||||
try_err!(writeln!(&mut v, "{}", *implementor), &mydst);
|
||||
}
|
||||
try_err!(writeln!(&mut f, "{}", r"
|
||||
try_err!(writeln!(&mut v, "{}", r"
|
||||
if (window.register_implementors) {
|
||||
window.register_implementors(implementors);
|
||||
} else {
|
||||
window.pending_implementors = implementors;
|
||||
}
|
||||
"), &mydst);
|
||||
try_err!(writeln!(&mut f, r"}})()"), &mydst);
|
||||
try_err!(writeln!(&mut v, r"}})()"), &mydst);
|
||||
cx.shared.fs.write(&mydst, &v)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -1254,7 +1288,7 @@ fn render_sources(dst: &Path, scx: &mut SharedContext,
|
||||
krate: clean::Crate) -> Result<clean::Crate, Error> {
|
||||
info!("emitting source files");
|
||||
let dst = dst.join("src").join(&krate.name);
|
||||
try_err!(fs::create_dir_all(&dst), &dst);
|
||||
scx.ensure_dir(&dst)?;
|
||||
let mut folder = SourceCollector {
|
||||
dst,
|
||||
scx,
|
||||
@ -1262,22 +1296,17 @@ fn render_sources(dst: &Path, scx: &mut SharedContext,
|
||||
Ok(folder.fold_crate(krate))
|
||||
}
|
||||
|
||||
/// Writes the entire contents of a string to a destination, not attempting to
|
||||
/// catch any errors.
|
||||
fn write(dst: PathBuf, contents: &[u8]) -> Result<(), Error> {
|
||||
Ok(try_err!(fs::write(&dst, contents), &dst))
|
||||
}
|
||||
|
||||
fn write_minify(dst: PathBuf, contents: &str, enable_minification: bool) -> Result<(), Error> {
|
||||
fn write_minify(fs:&DocFS, dst: PathBuf, contents: &str, enable_minification: bool
|
||||
) -> Result<(), Error> {
|
||||
if enable_minification {
|
||||
if dst.extension() == Some(&OsStr::new("css")) {
|
||||
let res = try_none!(minifier::css::minify(contents).ok(), &dst);
|
||||
write(dst, res.as_bytes())
|
||||
fs.write(dst, res.as_bytes())
|
||||
} else {
|
||||
write(dst, minifier::js::minify(contents).as_bytes())
|
||||
fs.write(dst, minifier::js::minify(contents).as_bytes())
|
||||
}
|
||||
} else {
|
||||
write(dst, contents.as_bytes())
|
||||
fs.write(dst, contents.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1439,7 +1468,7 @@ impl<'a> DocFolder for SourceCollector<'a> {
|
||||
|
||||
impl<'a> SourceCollector<'a> {
|
||||
/// Renders the given filename into its corresponding HTML source file.
|
||||
fn emit_source(&mut self, filename: &FileName) -> io::Result<()> {
|
||||
fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
|
||||
let p = match *filename {
|
||||
FileName::Real(ref file) => file,
|
||||
_ => return Ok(()),
|
||||
@ -1449,7 +1478,7 @@ impl<'a> SourceCollector<'a> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let contents = fs::read_to_string(&p)?;
|
||||
let contents = try_err!(fs::read_to_string(&p), &p);
|
||||
|
||||
// Remove the utf-8 BOM if any
|
||||
let contents = if contents.starts_with("\u{feff}") {
|
||||
@ -1468,7 +1497,7 @@ impl<'a> SourceCollector<'a> {
|
||||
href.push_str(&component.to_string_lossy());
|
||||
href.push('/');
|
||||
});
|
||||
fs::create_dir_all(&cur)?;
|
||||
self.scx.ensure_dir(&cur)?;
|
||||
let mut fname = p.file_name()
|
||||
.expect("source has no filename")
|
||||
.to_os_string();
|
||||
@ -1476,7 +1505,7 @@ impl<'a> SourceCollector<'a> {
|
||||
cur.push(&fname);
|
||||
href.push_str(&fname.to_string_lossy());
|
||||
|
||||
let mut w = BufWriter::new(File::create(&cur)?);
|
||||
let mut v = Vec::new();
|
||||
let title = format!("{} -- source", cur.file_name().expect("failed to get file name")
|
||||
.to_string_lossy());
|
||||
let desc = format!("Source to the Rust file `{}`.", filename);
|
||||
@ -1491,12 +1520,12 @@ impl<'a> SourceCollector<'a> {
|
||||
extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
|
||||
static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
|
||||
};
|
||||
layout::render(&mut w, &self.scx.layout,
|
||||
try_err!(layout::render(&mut v, &self.scx.layout,
|
||||
&page, &(""), &Source(contents),
|
||||
self.scx.css_file_extension.is_some(),
|
||||
&self.scx.themes,
|
||||
self.scx.generate_search_filter)?;
|
||||
w.flush()?;
|
||||
self.scx.generate_search_filter), &cur);
|
||||
self.scx.fs.write(&cur, &v)?;
|
||||
self.scx.local_sources.insert(p.clone(), href);
|
||||
Ok(())
|
||||
}
|
||||
@ -2073,7 +2102,6 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
let mut w = BufWriter::new(try_err!(File::create(&final_file), &final_file));
|
||||
let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
|
||||
if !root_path.ends_with('/') {
|
||||
root_path.push('/');
|
||||
@ -2099,12 +2127,14 @@ impl Context {
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
try_err!(layout::render(&mut w, &self.shared.layout,
|
||||
let mut v = Vec::new();
|
||||
try_err!(layout::render(&mut v, &self.shared.layout,
|
||||
&page, &sidebar, &all,
|
||||
self.shared.css_file_extension.is_some(),
|
||||
&self.shared.themes,
|
||||
self.shared.generate_search_filter),
|
||||
&final_file);
|
||||
self.shared.fs.write(&final_file, &v)?;
|
||||
|
||||
// Generating settings page.
|
||||
let settings = Settings::new(self.shared.static_root_path.deref().unwrap_or("./"),
|
||||
@ -2113,17 +2143,18 @@ impl Context {
|
||||
page.description = "Settings of Rustdoc";
|
||||
page.root_path = "./";
|
||||
|
||||
let mut w = BufWriter::new(try_err!(File::create(&settings_file), &settings_file));
|
||||
let mut themes = self.shared.themes.clone();
|
||||
let sidebar = "<p class='location'>Settings</p><div class='sidebar-elems'></div>";
|
||||
themes.push(PathBuf::from("settings.css"));
|
||||
let layout = self.shared.layout.clone();
|
||||
try_err!(layout::render(&mut w, &layout,
|
||||
let mut v = Vec::new();
|
||||
try_err!(layout::render(&mut v, &layout,
|
||||
&page, &sidebar, &settings,
|
||||
self.shared.css_file_extension.is_some(),
|
||||
&themes,
|
||||
self.shared.generate_search_filter),
|
||||
&settings_file);
|
||||
self.shared.fs.write(&settings_file, &v)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -2223,6 +2254,7 @@ impl Context {
|
||||
// recurse into the items of the module as well.
|
||||
let name = item.name.as_ref().unwrap().to_string();
|
||||
let mut item = Some(item);
|
||||
let scx = self.shared.clone();
|
||||
self.recurse(name, |this| {
|
||||
let item = item.take().unwrap();
|
||||
|
||||
@ -2230,9 +2262,9 @@ impl Context {
|
||||
this.render_item(&mut buf, &item, false).unwrap();
|
||||
// buf will be empty if the module is stripped and there is no redirect for it
|
||||
if !buf.is_empty() {
|
||||
try_err!(this.shared.ensure_dir(&this.dst), &this.dst);
|
||||
this.shared.ensure_dir(&this.dst)?;
|
||||
let joint_dst = this.dst.join("index.html");
|
||||
try_err!(fs::write(&joint_dst, buf), &joint_dst);
|
||||
scx.fs.write(&joint_dst, buf)?;
|
||||
}
|
||||
|
||||
let m = match item.inner {
|
||||
@ -2245,9 +2277,10 @@ impl Context {
|
||||
if !this.render_redirect_pages {
|
||||
let items = this.build_sidebar_items(&m);
|
||||
let js_dst = this.dst.join("sidebar-items.js");
|
||||
let mut js_out = BufWriter::new(try_err!(File::create(&js_dst), &js_dst));
|
||||
try_err!(write!(&mut js_out, "initSidebarItems({});",
|
||||
let mut v = Vec::new();
|
||||
try_err!(write!(&mut v, "initSidebarItems({});",
|
||||
as_json(&items)), &js_dst);
|
||||
scx.fs.write(&js_dst, &v)?;
|
||||
}
|
||||
|
||||
for item in m.items {
|
||||
@ -2264,9 +2297,9 @@ impl Context {
|
||||
let name = item.name.as_ref().unwrap();
|
||||
let item_type = item.type_();
|
||||
let file_name = &item_path(item_type, name);
|
||||
try_err!(self.shared.ensure_dir(&self.dst), &self.dst);
|
||||
self.shared.ensure_dir(&self.dst)?;
|
||||
let joint_dst = self.dst.join(file_name);
|
||||
try_err!(fs::write(&joint_dst, buf), &joint_dst);
|
||||
self.shared.fs.write(&joint_dst, buf)?;
|
||||
|
||||
if !self.render_redirect_pages {
|
||||
all.append(full_path(self, &item), &item_type);
|
||||
@ -2276,21 +2309,18 @@ impl Context {
|
||||
// URL for the page.
|
||||
let redir_name = format!("{}.{}.html", name, item_type.name_space());
|
||||
let redir_dst = self.dst.join(redir_name);
|
||||
if let Ok(redirect_out) = OpenOptions::new().create_new(true)
|
||||
.write(true)
|
||||
.open(&redir_dst) {
|
||||
let mut redirect_out = BufWriter::new(redirect_out);
|
||||
try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst);
|
||||
}
|
||||
let mut v = Vec::new();
|
||||
try_err!(layout::redirect(&mut v, file_name), &redir_dst);
|
||||
self.shared.fs.write(&redir_dst, &v)?;
|
||||
}
|
||||
// If the item is a macro, redirect from the old macro URL (with !)
|
||||
// to the new one (without).
|
||||
if item_type == ItemType::Macro {
|
||||
let redir_name = format!("{}.{}!.html", item_type, name);
|
||||
let redir_dst = self.dst.join(redir_name);
|
||||
let redirect_out = try_err!(File::create(&redir_dst), &redir_dst);
|
||||
let mut redirect_out = BufWriter::new(redirect_out);
|
||||
try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst);
|
||||
let mut v = Vec::new();
|
||||
try_err!(layout::redirect(&mut v, file_name), &redir_dst);
|
||||
self.shared.fs.write(&redir_dst, &v)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ mod externalfiles;
|
||||
mod clean;
|
||||
mod config;
|
||||
mod core;
|
||||
mod docfs;
|
||||
mod doctree;
|
||||
mod fold;
|
||||
pub mod html {
|
||||
|
Loading…
x
Reference in New Issue
Block a user