3 use std
::fs
::{self, File}
;
4 use std
::io
::prelude
::*;
5 use std
::io
::{self, BufReader}
;
6 use std
::lazy
::SyncLazy
as Lazy
;
7 use std
::path
::{Component, Path, PathBuf}
;
9 use itertools
::Itertools
;
10 use rustc_data_structures
::flock
;
11 use rustc_data_structures
::fx
::{FxHashMap, FxHashSet}
;
14 use super::{collect_paths_for_type, ensure_trailing_slash, Context, BASIC_KEYWORDS}
;
15 use crate::clean
::Crate
;
16 use crate::config
::{EmitType, RenderOptions}
;
17 use crate::docfs
::PathError
;
18 use crate::error
::Error
;
19 use crate::html
::{layout, static_files}
;
21 static FILES_UNVERSIONED
: Lazy
<FxHashMap
<&str, &[u8]>> = Lazy
::new(|| {
23 "FiraSans-Regular.woff2" => static_files
::fira_sans
::REGULAR2
,
24 "FiraSans-Medium.woff2" => static_files
::fira_sans
::MEDIUM2
,
25 "FiraSans-Regular.woff" => static_files
::fira_sans
::REGULAR
,
26 "FiraSans-Medium.woff" => static_files
::fira_sans
::MEDIUM
,
27 "FiraSans-LICENSE.txt" => static_files
::fira_sans
::LICENSE
,
28 "SourceSerif4-Regular.ttf.woff" => static_files
::source_serif_4
::REGULAR
,
29 "SourceSerif4-Bold.ttf.woff" => static_files
::source_serif_4
::BOLD
,
30 "SourceSerif4-It.ttf.woff" => static_files
::source_serif_4
::ITALIC
,
31 "SourceSerif4-LICENSE.md" => static_files
::source_serif_4
::LICENSE
,
32 "SourceCodePro-Regular.ttf.woff" => static_files
::source_code_pro
::REGULAR
,
33 "SourceCodePro-Semibold.ttf.woff" => static_files
::source_code_pro
::SEMIBOLD
,
34 "SourceCodePro-It.ttf.woff" => static_files
::source_code_pro
::ITALIC
,
35 "SourceCodePro-LICENSE.txt" => static_files
::source_code_pro
::LICENSE
,
36 "noto-sans-kr-v13-korean-regular.woff" => static_files
::noto_sans_kr
::REGULAR
,
37 "noto-sans-kr-v13-korean-regular-LICENSE.txt" => static_files
::noto_sans_kr
::LICENSE
,
38 "LICENSE-MIT.txt" => static_files
::LICENSE_MIT
,
39 "LICENSE-APACHE.txt" => static_files
::LICENSE_APACHE
,
40 "COPYRIGHT.txt" => static_files
::COPYRIGHT
,
44 enum SharedResource
<'a
> {
45 /// This file will never change, no matter what toolchain is used to build it.
47 /// It does not have a resource suffix.
48 Unversioned { name: &'static str }
,
49 /// This file may change depending on the toolchain.
51 /// It has a resource suffix.
52 ToolchainSpecific { basename: &'static str }
,
53 /// This file may change for any crate within a build, or based on the CLI arguments.
55 /// This differs from normal invocation-specific files because it has a resource suffix.
56 InvocationSpecific { basename: &'a str }
,
59 impl SharedResource
<'_
> {
60 fn extension(&self) -> Option
<&OsStr
> {
61 use SharedResource
::*;
64 | ToolchainSpecific { basename: name }
65 | InvocationSpecific { basename: name }
=> Path
::new(name
).extension(),
69 fn path(&self, cx
: &Context
<'_
>) -> PathBuf
{
71 SharedResource
::Unversioned { name }
=> cx
.dst
.join(name
),
72 SharedResource
::ToolchainSpecific { basename }
=> cx
.suffix_path(basename
),
73 SharedResource
::InvocationSpecific { basename }
=> cx
.suffix_path(basename
),
77 fn should_emit(&self, emit
: &[EmitType
]) -> bool
{
81 let kind
= match self {
82 SharedResource
::Unversioned { .. }
=> EmitType
::Unversioned
,
83 SharedResource
::ToolchainSpecific { .. }
=> EmitType
::Toolchain
,
84 SharedResource
::InvocationSpecific { .. }
=> EmitType
::InvocationSpecific
,
91 fn suffix_path(&self, filename
: &str) -> PathBuf
{
92 // We use splitn vs Path::extension here because we might get a filename
93 // like `style.min.css` and we want to process that into
94 // `style-suffix.min.css`. Path::extension would just return `css`
95 // which would result in `style.min-suffix.css` which isn't what we
97 let (base
, ext
) = filename
.split_once('
.'
).unwrap();
98 let filename
= format
!("{}{}.{}", base
, self.shared
.resource_suffix
, ext
);
99 self.dst
.join(&filename
)
102 fn write_shared
<C
: AsRef
<[u8]>>(
104 resource
: SharedResource
<'_
>,
107 ) -> Result
<(), Error
> {
108 if resource
.should_emit(emit
) {
109 self.shared
.fs
.write(resource
.path(self), contents
)
117 resource
: SharedResource
<'_
>,
121 ) -> Result
<(), Error
> {
123 let contents
= if minify
{
124 tmp
= if resource
.extension() == Some(&OsStr
::new("css")) {
125 minifier
::css
::minify(contents
).map_err(|e
| {
126 Error
::new(format
!("failed to minify CSS file: {}", e
), resource
.path(self))
129 minifier
::js
::minify(contents
)
136 self.write_shared(resource
, contents
, emit
)
140 pub(super) fn write_shared(
143 search_index
: String
,
144 options
: &RenderOptions
,
145 ) -> Result
<(), Error
> {
146 // Write out the shared files. Note that these are shared among all rustdoc
147 // docs placed in the output directory, so this needs to be a synchronized
148 // operation with respect to all other rustdocs running around.
149 let lock_file
= cx
.dst
.join(".lock");
150 let _lock
= try_err
!(flock
::Lock
::new(&lock_file
, true, true, true), &lock_file
);
152 // The weird `: &_` is to work around a borrowck bug: https://github.com/rust-lang/rust/issues/41078#issuecomment-293646723
153 let write_minify
= |p
, c
: &_
| {
155 SharedResource
::ToolchainSpecific { basename: p }
,
157 options
.enable_minification
,
161 // Toolchain resources should never be dynamic.
162 let write_toolchain
= |p
: &'
static _
, c
: &'
static _
| {
163 cx
.write_shared(SharedResource
::ToolchainSpecific { basename: p }
, c
, &options
.emit
)
166 // Crate resources should always be dynamic.
167 let write_crate
= |p
: &_
, make_content
: &dyn Fn() -> Result
<Vec
<u8>, Error
>| {
168 let content
= make_content()?
;
169 cx
.write_shared(SharedResource
::InvocationSpecific { basename: p }
, content
, &options
.emit
)
172 // Add all the static files. These may already exist, but we just
173 // overwrite them anyway to make sure that they're fresh and up-to-date.
174 write_minify("rustdoc.css", static_files
::RUSTDOC_CSS
)?
;
175 write_minify("settings.css", static_files
::SETTINGS_CSS
)?
;
176 write_minify("noscript.css", static_files
::NOSCRIPT_CSS
)?
;
178 // To avoid "light.css" to be overwritten, we'll first run over the received themes and only
179 // then we'll run over the "official" styles.
180 let mut themes
: FxHashSet
<String
> = FxHashSet
::default();
182 for entry
in &cx
.shared
.style_files
{
183 let theme
= try_none
!(try_none
!(entry
.path
.file_stem(), &entry
.path
).to_str(), &entry
.path
);
185 try_none
!(try_none
!(entry
.path
.extension(), &entry
.path
).to_str(), &entry
.path
);
187 // Handle the official themes
189 "light" => write_minify("light.css", static_files
::themes
::LIGHT
)?
,
190 "dark" => write_minify("dark.css", static_files
::themes
::DARK
)?
,
191 "ayu" => write_minify("ayu.css", static_files
::themes
::AYU
)?
,
193 // Handle added third-party themes
194 let filename
= format
!("{}.{}", theme
, extension
);
195 write_crate(&filename
, &|| Ok(try_err
!(fs
::read(&entry
.path
), &entry
.path
)))?
;
199 themes
.insert(theme
.to_owned());
202 if (*cx
.shared
).layout
.logo
.is_empty() {
203 write_toolchain("rust-logo.png", static_files
::RUST_LOGO
)?
;
205 if (*cx
.shared
).layout
.favicon
.is_empty() {
206 write_toolchain("favicon.svg", static_files
::RUST_FAVICON_SVG
)?
;
207 write_toolchain("favicon-16x16.png", static_files
::RUST_FAVICON_PNG_16
)?
;
208 write_toolchain("favicon-32x32.png", static_files
::RUST_FAVICON_PNG_32
)?
;
210 write_toolchain("brush.svg", static_files
::BRUSH_SVG
)?
;
211 write_toolchain("wheel.svg", static_files
::WHEEL_SVG
)?
;
212 write_toolchain("clipboard.svg", static_files
::CLIPBOARD_SVG
)?
;
213 write_toolchain("down-arrow.svg", static_files
::DOWN_ARROW_SVG
)?
;
215 let mut themes
: Vec
<&String
> = themes
.iter().collect();
218 // FIXME: this should probably not be a toolchain file since it depends on `--theme`.
219 // But it seems a shame to copy it over and over when it's almost always the same.
220 // Maybe we can change the representation to move this out of main.js?
223 &static_files
::MAIN_JS
.replace(
224 "/* INSERT THEMES HERE */",
225 &format
!(" = {}", serde_json
::to_string(&themes
).unwrap()),
228 write_minify("search.js", static_files
::SEARCH_JS
)?
;
229 write_minify("settings.js", static_files
::SETTINGS_JS
)?
;
231 if cx
.shared
.include_sources
{
232 write_minify("source-script.js", static_files
::sidebar
::SOURCE_SCRIPT
)?
;
239 "var resourcesSuffix = \"{}\";{}",
240 cx
.shared
.resource_suffix
,
241 static_files
::STORAGE_JS
246 if let Some(ref css
) = cx
.shared
.layout
.css_file_extension
{
247 let buffer
= try_err
!(fs
::read_to_string(css
), css
);
248 // This varies based on the invocation, so it can't go through the write_minify wrapper.
250 SharedResource
::InvocationSpecific { basename: "theme.css" }
,
252 options
.enable_minification
,
256 write_minify("normalize.css", static_files
::NORMALIZE_CSS
)?
;
257 for (name
, contents
) in &*FILES_UNVERSIONED
{
258 cx
.write_shared(SharedResource
::Unversioned { name }
, contents
, &options
.emit
)?
;
261 fn collect(path
: &Path
, krate
: &str, key
: &str) -> io
::Result
<(Vec
<String
>, Vec
<String
>)> {
262 let mut ret
= Vec
::new();
263 let mut krates
= Vec
::new();
266 let prefix
= format
!(r
#"{}["{}"]"#, key, krate);
267 for line
in BufReader
::new(File
::open(path
)?
).lines() {
269 if !line
.starts_with(key
) {
272 if line
.starts_with(&prefix
) {
275 ret
.push(line
.to_string());
277 line
[key
.len() + 2..]
280 .map(|s| s.to_owned())
281 .unwrap_or_else(String::new),
288 fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec<String>, Vec<String>)> {
289 let mut ret = Vec::new();
290 let mut krates = Vec::new();
293 let prefix = format!("\"{}
\"", krate);
294 for line in BufReader::new(File::open(path)?).lines() {
296 if !line.starts_with('"'
) {
299 if line
.starts_with(&prefix
) {
302 if line
.ends_with(",\\") {
303 ret
.push(line
[..line
.len() - 2].to_string());
305 // Ends with "\\" (it's the case for the last added crate line)
306 ret
.push(line
[..line
.len() - 1].to_string());
310 .find(|s| !s.is_empty())
311 .map(|s| s.to_owned())
312 .unwrap_or_else(String::new),
319 use std::ffi::OsString;
324 children: FxHashMap<OsString, Hierarchy>,
325 elems: FxHashSet<OsString>,
329 fn new(elem: OsString) -> Hierarchy {
330 Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() }
333 fn to_json_string(&self) -> String {
334 let mut subs: Vec<&Hierarchy> = self.children.values().collect();
335 subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
339 .map(|s| format!("\"{}
\"", s.to_str().expect("invalid osstring conversion
")))
340 .collect::<Vec<_>>();
341 files.sort_unstable();
342 let subs = subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().join(",");
344 if subs.is_empty() { String::new() } else { format!(",\"dirs\":[{}]", subs
) };
345 let files
= files
.join(",");
347 if files
.is_empty() { String::new() }
else { format!(",\"files\":[{}
]", files) };
349 "{{\"name\":\"{name}
\"{dirs}{files}
}}",
350 name = self.elem.to_str().expect("invalid osstring conversion
"),
357 if cx.shared.include_sources {
358 let mut hierarchy = Hierarchy::new(OsString::new());
363 .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
365 let mut h = &mut hierarchy;
366 let mut elems = source
368 .filter_map(|s| match s {
369 Component::Normal(s) => Some(s.to_owned()),
374 let cur_elem = elems.next().expect("empty file path
");
375 if elems.peek().is_none() {
376 h.elems.insert(cur_elem);
379 let e = cur_elem.clone();
380 h = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
385 let dst = cx.dst.join(&format!("source
-files{}
.js
", cx.shared.resource_suffix));
386 let make_sources = || {
387 let (mut all_sources, _krates) =
388 try_err!(collect(&dst, &krate.name.as_str(), "sourcesIndex
"), &dst);
389 all_sources.push(format!(
390 "sourcesIndex
[\"{}
\"] = {}
;",
392 hierarchy.to_json_string()
396 "var N
= null
;var sourcesIndex
= {{}
};\n{}
\ncreateSourceSidebar
();\n",
397 all_sources.join("\n")
401 write_crate("source
-files
.js
", &make_sources)?;
404 // Update the search index and crate list.
405 let dst = cx.dst.join(&format!("search
-index{}
.js
", cx.shared.resource_suffix));
406 let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst);
407 all_indexes.push(search_index);
408 krates.push(krate.name.to_string());
411 // Sort the indexes by crate so the file will be generated identically even
412 // with rustdoc running in parallel.
414 write_crate("search
-index
.js
", &|| {
415 let mut v = String::from("var searchIndex
= JSON
.parse('
{\\\n");
416 v.push_str(&all_indexes.join(",\\\n"));
417 v.push_str("\\\n}'
);\nif
(window
.initSearch
) {window.initSearch(searchIndex)}
;");
421 write_crate("crates
.js
", &|| {
422 let krates = krates.iter().map(|k| format!("\"{}
\"", k)).join(",");
423 Ok(format!("window
.ALL_CRATES
= [{}
];", krates).into_bytes())
426 if options.enable_index_page {
427 if let Some(index_page) = options.index_page.clone() {
428 let mut md_opts = options.clone();
429 md_opts.output = cx.dst.clone();
430 md_opts.external_html = (*cx.shared).layout.external_html.clone();
432 crate::markdown::render(&index_page, md_opts, cx.shared.edition())
433 .map_err(|e| Error::new(e, &index_page))?;
435 let dst = cx.dst.join("index
.html
");
436 let page = layout::Page {
437 title: "Index of crates
",
440 static_root_path: cx.shared.static_root_path.as_deref(),
441 description: "List of crates
",
442 keywords: BASIC_KEYWORDS,
443 resource_suffix: &cx.shared.resource_suffix,
445 static_extra_scripts: &[],
448 let content = format!(
450 <span class
=\"in-band
\">List of all crates
</span
>\
451 </h1
><ul class
=\"crate mod\">{}
</ul
>",
456 "<li
><a class
=\"crate mod\" href
=\"{}index
.html
\">{}
</a
></li
>",
457 ensure_trailing_slash(s),
463 let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files);
464 cx.shared.fs.write(&dst, v.as_bytes())?;
468 // Update the list of all implementors for traits
469 let dst = cx.dst.join("implementors
");
470 for (&did, imps) in &cx.cache.implementors {
471 // Private modules can leak through to this phase of rustdoc, which
472 // could contain implementations for otherwise private types. In some
473 // rare cases we could find an implementation for an item which wasn't
474 // indexed, so we just skip this step in that case.
476 // FIXME: this is a vague explanation for why this can't be a `get`, in
477 // theory it should be...
478 let &(ref remote_path, remote_item_type) = match cx.cache.paths.get(&did) {
480 None => match cx.cache.external_paths.get(&did) {
493 let implementors = imps
496 // If the trait and implementation are in the same crate, then
497 // there's no need to emit information about it (there's inlining
498 // going on). If they're in different crates then the crate defining
499 // the trait will be interested in our implementation.
501 // If the implementation is from another crate then that crate
503 if imp.impl_item.def_id.krate() == did.krate || !imp.impl_item.def_id.is_local() {
507 text: imp.inner_impl().print(false, cx).to_string(),
508 synthetic: imp.inner_impl().synthetic,
509 types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()),
513 .collect::<Vec<_>>();
515 // Only create a js file if we have impls to add to it. If the trait is
516 // documented locally though we always create the file to avoid dead
518 if implementors.is_empty() && !cx.cache.paths.contains_key(&did) {
522 let implementors = format!(
523 r#"implementors
["{}"] = {}
;"#,
525 serde_json::to_string(&implementors).unwrap()
528 let mut mydst = dst.clone();
529 for part in &remote_path[..remote_path.len() - 1] {
532 cx.shared.ensure_dir(&mydst)?;
533 mydst.push(&format!("{}
.{}
.js
", remote_item_type, remote_path[remote_path.len() - 1]));
535 let (mut all_implementors, _) =
536 try_err!(collect(&mydst, &krate.name.as_str(), "implementors
"), &mydst);
537 all_implementors.push(implementors);
538 // Sort the implementors by crate so the file will be generated
539 // identically even with rustdoc running in parallel.
540 all_implementors.sort();
542 let mut v = String::from("(function() {var implementors = {}
;\n");
543 for implementor in &all_implementors {
544 writeln!(v, "{}
", *implementor).unwrap();
547 "if (window
.register_implementors
) {\
548 window
.register_implementors(implementors
);\
550 window
.pending_implementors
= implementors
;\
554 cx.shared.fs.write(&mydst, &v)?;