1 // Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Documentation generation for rustbuild.
13 //! This module implements generation for all bits and pieces of documentation
14 //! for the Rust project. This notably includes suites like the rust book, the
15 //! nomicon, standalone documentation, etc.
17 //! Everything here is basically just a shim around calling either `rustbook` or
20 use std::fs::{self, File};
21 use std::io::prelude::*;
23 use std::path::{PathBuf, Path};
26 use build_helper::up_to_date;
28 use util::{cp_r, symlink_dir};
29 use builder::{Builder, Compiler, RunConfig, ShouldRun, Step};
32 use cache::{INTERNER, Interned};
35 ($($name:ident, $path:expr, $book_name:expr;)+) => {
37 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
39 target: Interned<String>,
44 const DEFAULT: bool = true;
46 fn should_run(run: ShouldRun) -> ShouldRun {
47 let builder = run.builder;
48 run.path($path).default_condition(builder.build.config.docs)
51 fn make_run(run: RunConfig) {
52 run.builder.ensure($name {
57 fn run(self, builder: &Builder) {
58 builder.ensure(Rustbook {
60 name: INTERNER.intern_str($book_name),
69 Nomicon, "src/doc/nomicon", "nomicon";
70 Reference, "src/doc/reference", "reference";
71 Rustdoc, "src/doc/rustdoc", "rustdoc";
74 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
76 target: Interned<String>,
77 name: Interned<String>,
80 impl Step for Rustbook {
83 // rustbook is never directly called, and only serves as a shim for the nomicon and the
85 fn should_run(run: ShouldRun) -> ShouldRun {
89 /// Invoke `rustbook` for `target` for the doc book `name`.
91 /// This will not actually generate any documentation if the documentation has
92 /// already been generated.
93 fn run(self, builder: &Builder) {
94 let src = builder.build.src.join("src/doc");
95 builder.ensure(RustbookSrc {
98 src: INTERNER.intern_path(src),
103 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
104 pub struct UnstableBook {
105 target: Interned<String>,
108 impl Step for UnstableBook {
110 const DEFAULT: bool = true;
112 fn should_run(run: ShouldRun) -> ShouldRun {
113 let builder = run.builder;
114 run.path("src/doc/unstable-book").default_condition(builder.build.config.docs)
117 fn make_run(run: RunConfig) {
118 run.builder.ensure(UnstableBook {
123 fn run(self, builder: &Builder) {
124 builder.ensure(UnstableBookGen {
127 builder.ensure(RustbookSrc {
129 name: INTERNER.intern_str("unstable-book"),
130 src: builder.build.md_doc_out(self.target),
135 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
136 pub struct CargoBook {
137 target: Interned<String>,
138 name: Interned<String>,
141 impl Step for CargoBook {
143 const DEFAULT: bool = true;
145 fn should_run(run: ShouldRun) -> ShouldRun {
146 let builder = run.builder;
147 run.path("src/tools/cargo/src/doc/book").default_condition(builder.build.config.docs)
150 fn make_run(run: RunConfig) {
151 run.builder.ensure(CargoBook {
153 name: INTERNER.intern_str("cargo"),
157 fn run(self, builder: &Builder) {
158 let build = builder.build;
160 let target = self.target;
161 let name = self.name;
162 let src = build.src.join("src/tools/cargo/src/doc/book");
164 let out = build.doc_out(target);
165 t!(fs::create_dir_all(&out));
167 let out = out.join(name);
169 println!("Cargo Book ({}) - {}", target, name);
171 let _ = fs::remove_dir_all(&out);
173 build.run(builder.tool_cmd(Tool::Rustbook)
181 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
183 target: Interned<String>,
184 name: Interned<String>,
185 src: Interned<PathBuf>,
188 impl Step for RustbookSrc {
191 fn should_run(run: ShouldRun) -> ShouldRun {
195 /// Invoke `rustbook` for `target` for the doc book `name` from the `src` path.
197 /// This will not actually generate any documentation if the documentation has
198 /// already been generated.
199 fn run(self, builder: &Builder) {
200 let build = builder.build;
201 let target = self.target;
202 let name = self.name;
204 let out = build.doc_out(target);
205 t!(fs::create_dir_all(&out));
207 let out = out.join(name);
208 let src = src.join(name);
209 let index = out.join("index.html");
210 let rustbook = builder.tool_exe(Tool::Rustbook);
211 if up_to_date(&src, &index) && up_to_date(&rustbook, &index) {
214 println!("Rustbook ({}) - {}", target, name);
215 let _ = fs::remove_dir_all(&out);
216 build.run(builder.tool_cmd(Tool::Rustbook)
224 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
227 target: Interned<String>,
231 impl Step for TheBook {
233 const DEFAULT: bool = true;
235 fn should_run(run: ShouldRun) -> ShouldRun {
236 let builder = run.builder;
237 run.path("src/doc/book").default_condition(builder.build.config.docs)
240 fn make_run(run: RunConfig) {
241 run.builder.ensure(TheBook {
242 compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
248 /// Build the book and associated stuff.
250 /// We need to build:
252 /// * Book (first edition)
253 /// * Book (second edition)
254 /// * Version info and CSS
257 fn run(self, builder: &Builder) {
258 let build = builder.build;
259 let compiler = self.compiler;
260 let target = self.target;
261 let name = self.name;
262 // build book first edition
263 builder.ensure(Rustbook {
265 name: INTERNER.intern_string(format!("{}/first-edition", name)),
268 // build book second edition
269 builder.ensure(Rustbook {
271 name: INTERNER.intern_string(format!("{}/second-edition", name)),
274 // build the version info page and CSS
275 builder.ensure(Standalone {
280 // build the index page
281 let index = format!("{}/index.md", name);
282 println!("Documenting book index ({})", target);
283 invoke_rustdoc(builder, compiler, target, &index);
285 // build the redirect pages
286 println!("Documenting book redirect pages ({})", target);
287 for file in t!(fs::read_dir(build.src.join("src/doc/book/redirects"))) {
289 let path = file.path();
290 let path = path.to_str().unwrap();
292 invoke_rustdoc(builder, compiler, target, path);
297 fn invoke_rustdoc(builder: &Builder, compiler: Compiler, target: Interned<String>, markdown: &str) {
298 let build = builder.build;
299 let out = build.doc_out(target);
301 let path = build.src.join("src/doc").join(markdown);
303 let favicon = build.src.join("src/doc/favicon.inc");
304 let footer = build.src.join("src/doc/footer.inc");
305 let version_info = out.join("version_info.html");
307 let mut cmd = builder.rustdoc_cmd(compiler.host);
309 let out = out.join("book");
311 cmd.arg("--html-after-content").arg(&footer)
312 .arg("--html-before-content").arg(&version_info)
313 .arg("--html-in-header").arg(&favicon)
314 .arg("--markdown-playground-url")
315 .arg("https://play.rust-lang.org/")
318 .arg("--markdown-css")
324 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
325 pub struct Standalone {
327 target: Interned<String>,
330 impl Step for Standalone {
332 const DEFAULT: bool = true;
334 fn should_run(run: ShouldRun) -> ShouldRun {
335 let builder = run.builder;
336 run.path("src/doc").default_condition(builder.build.config.docs)
339 fn make_run(run: RunConfig) {
340 run.builder.ensure(Standalone {
341 compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
346 /// Generates all standalone documentation as compiled by the rustdoc in `stage`
347 /// for the `target` into `out`.
349 /// This will list all of `src/doc` looking for markdown files and appropriately
350 /// perform transformations like substituting `VERSION`, `SHORT_HASH`, and
351 /// `STAMP` along with providing the various header/footer HTML we've customized.
353 /// In the end, this is just a glorified wrapper around rustdoc!
354 fn run(self, builder: &Builder) {
355 let build = builder.build;
356 let target = self.target;
357 let compiler = self.compiler;
358 println!("Documenting standalone ({})", target);
359 let out = build.doc_out(target);
360 t!(fs::create_dir_all(&out));
362 let favicon = build.src.join("src/doc/favicon.inc");
363 let footer = build.src.join("src/doc/footer.inc");
364 let full_toc = build.src.join("src/doc/full-toc.inc");
365 t!(fs::copy(build.src.join("src/doc/rust.css"), out.join("rust.css")));
367 let version_input = build.src.join("src/doc/version_info.html.template");
368 let version_info = out.join("version_info.html");
370 if !up_to_date(&version_input, &version_info) {
371 let mut info = String::new();
372 t!(t!(File::open(&version_input)).read_to_string(&mut info));
373 let info = info.replace("VERSION", &build.rust_release())
374 .replace("SHORT_HASH", build.rust_info.sha_short().unwrap_or(""))
375 .replace("STAMP", build.rust_info.sha().unwrap_or(""));
376 t!(t!(File::create(&version_info)).write_all(info.as_bytes()));
379 for file in t!(fs::read_dir(build.src.join("src/doc"))) {
381 let path = file.path();
382 let filename = path.file_name().unwrap().to_str().unwrap();
383 if !filename.ends_with(".md") || filename == "README.md" {
387 let html = out.join(filename).with_extension("html");
388 let rustdoc = builder.rustdoc(compiler.host);
389 if up_to_date(&path, &html) &&
390 up_to_date(&footer, &html) &&
391 up_to_date(&favicon, &html) &&
392 up_to_date(&full_toc, &html) &&
393 up_to_date(&version_info, &html) &&
394 up_to_date(&rustdoc, &html) {
398 let mut cmd = builder.rustdoc_cmd(compiler.host);
399 cmd.arg("--html-after-content").arg(&footer)
400 .arg("--html-before-content").arg(&version_info)
401 .arg("--html-in-header").arg(&favicon)
402 .arg("--markdown-playground-url")
403 .arg("https://play.rust-lang.org/")
407 if filename == "not_found.md" {
408 cmd.arg("--markdown-no-toc")
409 .arg("--markdown-css")
410 .arg("https://doc.rust-lang.org/rust.css");
412 cmd.arg("--markdown-css").arg("rust.css");
419 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
422 target: Interned<String>,
427 const DEFAULT: bool = true;
429 fn should_run(run: ShouldRun) -> ShouldRun {
430 let builder = run.builder;
431 run.krate("std").default_condition(builder.build.config.docs)
434 fn make_run(run: RunConfig) {
435 run.builder.ensure(Std {
436 stage: run.builder.top_stage,
441 /// Compile all standard library documentation.
443 /// This will generate all documentation for the standard library and its
444 /// dependencies. This is largely just a wrapper around `cargo doc`.
445 fn run(self, builder: &Builder) {
446 let build = builder.build;
447 let stage = self.stage;
448 let target = self.target;
449 println!("Documenting stage{} std ({})", stage, target);
450 let out = build.doc_out(target);
451 t!(fs::create_dir_all(&out));
452 let compiler = builder.compiler(stage, build.build);
453 let rustdoc = builder.rustdoc(compiler.host);
454 let compiler = if build.force_use_stage1(compiler, target) {
455 builder.compiler(1, compiler.host)
460 builder.ensure(compile::Std { compiler, target });
461 let out_dir = build.stage_out(compiler, Mode::Libstd)
462 .join(target).join("doc");
464 // Here what we're doing is creating a *symlink* (directory junction on
465 // Windows) to the final output location. This is not done as an
466 // optimization but rather for correctness. We've got three trees of
467 // documentation, one for std, one for test, and one for rustc. It's then
468 // our job to merge them all together.
470 // Unfortunately rustbuild doesn't know nearly as well how to merge doc
471 // trees as rustdoc does itself, so instead of actually having three
472 // separate trees we just have rustdoc output to the same location across
475 // This way rustdoc generates output directly into the output, and rustdoc
476 // will also directly handle merging.
477 let my_out = build.crate_doc_out(target);
478 build.clear_if_dirty(&my_out, &rustdoc);
479 t!(symlink_dir_force(&my_out, &out_dir));
481 let mut cargo = builder.cargo(compiler, Mode::Libstd, target, "doc");
482 compile::std_cargo(build, &compiler, target, &mut cargo);
484 // We don't want to build docs for internal std dependencies unless
485 // in compiler-docs mode. When not in that mode, we whitelist the crates
486 // for which docs must be built.
487 if !build.config.compiler_docs {
488 cargo.arg("--no-deps");
489 for krate in &["alloc", "core", "std", "std_unicode"] {
490 cargo.arg("-p").arg(krate);
491 // Create all crate output directories first to make sure rustdoc uses
493 // FIXME: Cargo should probably do this itself.
494 t!(fs::create_dir_all(out_dir.join(krate)));
499 build.run(&mut cargo);
504 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
507 target: Interned<String>,
512 const DEFAULT: bool = true;
514 fn should_run(run: ShouldRun) -> ShouldRun {
515 let builder = run.builder;
516 run.krate("test").default_condition(builder.config.compiler_docs)
519 fn make_run(run: RunConfig) {
520 run.builder.ensure(Test {
521 stage: run.builder.top_stage,
526 /// Compile all libtest documentation.
528 /// This will generate all documentation for libtest and its dependencies. This
529 /// is largely just a wrapper around `cargo doc`.
530 fn run(self, builder: &Builder) {
531 let build = builder.build;
532 let stage = self.stage;
533 let target = self.target;
534 println!("Documenting stage{} test ({})", stage, target);
535 let out = build.doc_out(target);
536 t!(fs::create_dir_all(&out));
537 let compiler = builder.compiler(stage, build.build);
538 let rustdoc = builder.rustdoc(compiler.host);
539 let compiler = if build.force_use_stage1(compiler, target) {
540 builder.compiler(1, compiler.host)
545 // Build libstd docs so that we generate relative links
546 builder.ensure(Std { stage, target });
548 builder.ensure(compile::Test { compiler, target });
549 let out_dir = build.stage_out(compiler, Mode::Libtest)
550 .join(target).join("doc");
552 // See docs in std above for why we symlink
553 let my_out = build.crate_doc_out(target);
554 build.clear_if_dirty(&my_out, &rustdoc);
555 t!(symlink_dir_force(&my_out, &out_dir));
557 let mut cargo = builder.cargo(compiler, Mode::Libtest, target, "doc");
558 compile::test_cargo(build, &compiler, target, &mut cargo);
559 build.run(&mut cargo);
564 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
567 target: Interned<String>,
570 impl Step for Rustc {
572 const DEFAULT: bool = true;
573 const ONLY_HOSTS: bool = true;
575 fn should_run(run: ShouldRun) -> ShouldRun {
576 let builder = run.builder;
577 run.krate("rustc-main").default_condition(builder.build.config.docs)
580 fn make_run(run: RunConfig) {
581 run.builder.ensure(Rustc {
582 stage: run.builder.top_stage,
587 /// Generate all compiler documentation.
589 /// This will generate all documentation for the compiler libraries and their
590 /// dependencies. This is largely just a wrapper around `cargo doc`.
591 fn run(self, builder: &Builder) {
592 let build = builder.build;
593 let stage = self.stage;
594 let target = self.target;
595 println!("Documenting stage{} compiler ({})", stage, target);
596 let out = build.doc_out(target);
597 t!(fs::create_dir_all(&out));
598 let compiler = builder.compiler(stage, build.build);
599 let rustdoc = builder.rustdoc(compiler.host);
600 let compiler = if build.force_use_stage1(compiler, target) {
601 builder.compiler(1, compiler.host)
606 // Build libstd docs so that we generate relative links
607 builder.ensure(Std { stage, target });
609 builder.ensure(compile::Rustc { compiler, target });
610 let out_dir = build.stage_out(compiler, Mode::Librustc)
611 .join(target).join("doc");
613 // See docs in std above for why we symlink
614 let my_out = build.crate_doc_out(target);
615 build.clear_if_dirty(&my_out, &rustdoc);
616 t!(symlink_dir_force(&my_out, &out_dir));
618 let mut cargo = builder.cargo(compiler, Mode::Librustc, target, "doc");
619 compile::rustc_cargo(build, target, &mut cargo);
621 if build.config.compiler_docs {
622 // src/rustc/Cargo.toml contains a bin crate called rustc which
623 // would otherwise overwrite the docs for the real rustc lib crate.
624 cargo.arg("-p").arg("rustc_driver");
626 // Like with libstd above if compiler docs aren't enabled then we're not
627 // documenting internal dependencies, so we have a whitelist.
628 cargo.arg("--no-deps");
629 for krate in &["proc_macro"] {
630 cargo.arg("-p").arg(krate);
634 build.run(&mut cargo);
639 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
640 pub struct ErrorIndex {
641 target: Interned<String>,
644 impl Step for ErrorIndex {
646 const DEFAULT: bool = true;
647 const ONLY_HOSTS: bool = true;
649 fn should_run(run: ShouldRun) -> ShouldRun {
650 let builder = run.builder;
651 run.path("src/tools/error_index_generator").default_condition(builder.build.config.docs)
654 fn make_run(run: RunConfig) {
655 run.builder.ensure(ErrorIndex {
660 /// Generates the HTML rendered error-index by running the
661 /// `error_index_generator` tool.
662 fn run(self, builder: &Builder) {
663 let build = builder.build;
664 let target = self.target;
666 println!("Documenting error index ({})", target);
667 let out = build.doc_out(target);
668 t!(fs::create_dir_all(&out));
669 let mut index = builder.tool_cmd(Tool::ErrorIndex);
671 index.arg(out.join("error-index.html"));
673 // FIXME: shouldn't have to pass this env var
674 index.env("CFG_BUILD", &build.build)
675 .env("RUSTC_ERROR_METADATA_DST", build.extended_error_dir());
677 build.run(&mut index);
681 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
682 pub struct UnstableBookGen {
683 target: Interned<String>,
686 impl Step for UnstableBookGen {
688 const DEFAULT: bool = true;
689 const ONLY_HOSTS: bool = true;
691 fn should_run(run: ShouldRun) -> ShouldRun {
692 let builder = run.builder;
693 run.path("src/tools/unstable-book-gen").default_condition(builder.build.config.docs)
696 fn make_run(run: RunConfig) {
697 run.builder.ensure(UnstableBookGen {
702 fn run(self, builder: &Builder) {
703 let build = builder.build;
704 let target = self.target;
706 builder.ensure(compile::Std {
707 compiler: builder.compiler(builder.top_stage, build.build),
711 println!("Generating unstable book md files ({})", target);
712 let out = build.md_doc_out(target).join("unstable-book");
713 t!(fs::create_dir_all(&out));
714 t!(fs::remove_dir_all(&out));
715 let mut cmd = builder.tool_cmd(Tool::UnstableBookGen);
716 cmd.arg(build.src.join("src"));
723 fn symlink_dir_force(src: &Path, dst: &Path) -> io::Result<()> {
724 if let Ok(m) = fs::symlink_metadata(dst) {
725 if m.file_type().is_dir() {
726 try!(fs::remove_dir_all(dst));
728 // handle directory junctions on windows by falling back to
730 try!(fs::remove_file(dst).or_else(|_| {
736 symlink_dir(src, dst)