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, rust by example, 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";
72 RustByExample, "src/doc/rust-by-example", "rust-by-example";
75 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
77 target: Interned<String>,
78 name: Interned<String>,
81 impl Step for Rustbook {
84 // rustbook is never directly called, and only serves as a shim for the nomicon and the
86 fn should_run(run: ShouldRun) -> ShouldRun {
90 /// Invoke `rustbook` for `target` for the doc book `name`.
92 /// This will not actually generate any documentation if the documentation has
93 /// already been generated.
94 fn run(self, builder: &Builder) {
95 let src = builder.build.src.join("src/doc");
96 builder.ensure(RustbookSrc {
99 src: INTERNER.intern_path(src),
104 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
105 pub struct UnstableBook {
106 target: Interned<String>,
109 impl Step for UnstableBook {
111 const DEFAULT: bool = true;
113 fn should_run(run: ShouldRun) -> ShouldRun {
114 let builder = run.builder;
115 run.path("src/doc/unstable-book").default_condition(builder.build.config.docs)
118 fn make_run(run: RunConfig) {
119 run.builder.ensure(UnstableBook {
124 fn run(self, builder: &Builder) {
125 builder.ensure(UnstableBookGen {
128 builder.ensure(RustbookSrc {
130 name: INTERNER.intern_str("unstable-book"),
131 src: builder.build.md_doc_out(self.target),
136 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
137 pub struct CargoBook {
138 target: Interned<String>,
139 name: Interned<String>,
142 impl Step for CargoBook {
144 const DEFAULT: bool = true;
146 fn should_run(run: ShouldRun) -> ShouldRun {
147 let builder = run.builder;
148 run.path("src/tools/cargo/src/doc/book").default_condition(builder.build.config.docs)
151 fn make_run(run: RunConfig) {
152 run.builder.ensure(CargoBook {
154 name: INTERNER.intern_str("cargo"),
158 fn run(self, builder: &Builder) {
159 let build = builder.build;
161 let target = self.target;
162 let name = self.name;
163 let src = build.src.join("src/tools/cargo/src/doc");
165 let out = build.doc_out(target);
166 t!(fs::create_dir_all(&out));
168 let out = out.join(name);
170 println!("Cargo Book ({}) - {}", target, name);
172 let _ = fs::remove_dir_all(&out);
174 build.run(builder.tool_cmd(Tool::Rustbook)
182 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
184 target: Interned<String>,
185 name: Interned<String>,
186 src: Interned<PathBuf>,
189 impl Step for RustbookSrc {
192 fn should_run(run: ShouldRun) -> ShouldRun {
196 /// Invoke `rustbook` for `target` for the doc book `name` from the `src` path.
198 /// This will not actually generate any documentation if the documentation has
199 /// already been generated.
200 fn run(self, builder: &Builder) {
201 let build = builder.build;
202 let target = self.target;
203 let name = self.name;
205 let out = build.doc_out(target);
206 t!(fs::create_dir_all(&out));
208 let out = out.join(name);
209 let src = src.join(name);
210 let index = out.join("index.html");
211 let rustbook = builder.tool_exe(Tool::Rustbook);
212 if up_to_date(&src, &index) && up_to_date(&rustbook, &index) {
215 println!("Rustbook ({}) - {}", target, name);
216 let _ = fs::remove_dir_all(&out);
217 build.run(builder.tool_cmd(Tool::Rustbook)
225 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
228 target: Interned<String>,
232 impl Step for TheBook {
234 const DEFAULT: bool = true;
236 fn should_run(run: ShouldRun) -> ShouldRun {
237 let builder = run.builder;
238 run.path("src/doc/book").default_condition(builder.build.config.docs)
241 fn make_run(run: RunConfig) {
242 run.builder.ensure(TheBook {
243 compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
249 /// Build the book and associated stuff.
251 /// We need to build:
253 /// * Book (first edition)
254 /// * Book (second edition)
255 /// * Version info and CSS
258 fn run(self, builder: &Builder) {
259 let build = builder.build;
260 let compiler = self.compiler;
261 let target = self.target;
262 let name = self.name;
263 // build book first edition
264 builder.ensure(Rustbook {
266 name: INTERNER.intern_string(format!("{}/first-edition", name)),
269 // build book second edition
270 builder.ensure(Rustbook {
272 name: INTERNER.intern_string(format!("{}/second-edition", name)),
275 // build the version info page and CSS
276 builder.ensure(Standalone {
281 // build the index page
282 let index = format!("{}/index.md", name);
283 println!("Documenting book index ({})", target);
284 invoke_rustdoc(builder, compiler, target, &index);
286 // build the redirect pages
287 println!("Documenting book redirect pages ({})", target);
288 for file in t!(fs::read_dir(build.src.join("src/doc/book/redirects"))) {
290 let path = file.path();
291 let path = path.to_str().unwrap();
293 invoke_rustdoc(builder, compiler, target, path);
298 fn invoke_rustdoc(builder: &Builder, compiler: Compiler, target: Interned<String>, markdown: &str) {
299 let build = builder.build;
300 let out = build.doc_out(target);
302 let path = build.src.join("src/doc").join(markdown);
304 let favicon = build.src.join("src/doc/favicon.inc");
305 let footer = build.src.join("src/doc/footer.inc");
306 let version_info = out.join("version_info.html");
308 let mut cmd = builder.rustdoc_cmd(compiler.host);
310 let out = out.join("book");
312 cmd.arg("--html-after-content").arg(&footer)
313 .arg("--html-before-content").arg(&version_info)
314 .arg("--html-in-header").arg(&favicon)
315 .arg("--markdown-playground-url")
316 .arg("https://play.rust-lang.org/")
319 .arg("--markdown-css")
325 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
326 pub struct Standalone {
328 target: Interned<String>,
331 impl Step for Standalone {
333 const DEFAULT: bool = true;
335 fn should_run(run: ShouldRun) -> ShouldRun {
336 let builder = run.builder;
337 run.path("src/doc").default_condition(builder.build.config.docs)
340 fn make_run(run: RunConfig) {
341 run.builder.ensure(Standalone {
342 compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
347 /// Generates all standalone documentation as compiled by the rustdoc in `stage`
348 /// for the `target` into `out`.
350 /// This will list all of `src/doc` looking for markdown files and appropriately
351 /// perform transformations like substituting `VERSION`, `SHORT_HASH`, and
352 /// `STAMP` along with providing the various header/footer HTML we've customized.
354 /// In the end, this is just a glorified wrapper around rustdoc!
355 fn run(self, builder: &Builder) {
356 let build = builder.build;
357 let target = self.target;
358 let compiler = self.compiler;
359 println!("Documenting standalone ({})", target);
360 let out = build.doc_out(target);
361 t!(fs::create_dir_all(&out));
363 let favicon = build.src.join("src/doc/favicon.inc");
364 let footer = build.src.join("src/doc/footer.inc");
365 let full_toc = build.src.join("src/doc/full-toc.inc");
366 t!(fs::copy(build.src.join("src/doc/rust.css"), out.join("rust.css")));
368 let version_input = build.src.join("src/doc/version_info.html.template");
369 let version_info = out.join("version_info.html");
371 if !up_to_date(&version_input, &version_info) {
372 let mut info = String::new();
373 t!(t!(File::open(&version_input)).read_to_string(&mut info));
374 let info = info.replace("VERSION", &build.rust_release())
375 .replace("SHORT_HASH", build.rust_info.sha_short().unwrap_or(""))
376 .replace("STAMP", build.rust_info.sha().unwrap_or(""));
377 t!(t!(File::create(&version_info)).write_all(info.as_bytes()));
380 for file in t!(fs::read_dir(build.src.join("src/doc"))) {
382 let path = file.path();
383 let filename = path.file_name().unwrap().to_str().unwrap();
384 if !filename.ends_with(".md") || filename == "README.md" {
388 let html = out.join(filename).with_extension("html");
389 let rustdoc = builder.rustdoc(compiler.host);
390 if up_to_date(&path, &html) &&
391 up_to_date(&footer, &html) &&
392 up_to_date(&favicon, &html) &&
393 up_to_date(&full_toc, &html) &&
394 up_to_date(&version_info, &html) &&
395 up_to_date(&rustdoc, &html) {
399 let mut cmd = builder.rustdoc_cmd(compiler.host);
400 cmd.arg("--html-after-content").arg(&footer)
401 .arg("--html-before-content").arg(&version_info)
402 .arg("--html-in-header").arg(&favicon)
403 .arg("--markdown-playground-url")
404 .arg("https://play.rust-lang.org/")
408 if filename == "not_found.md" {
409 cmd.arg("--markdown-no-toc")
410 .arg("--markdown-css")
411 .arg("https://doc.rust-lang.org/rust.css");
413 cmd.arg("--markdown-css").arg("rust.css");
420 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
423 pub target: Interned<String>,
428 const DEFAULT: bool = true;
430 fn should_run(run: ShouldRun) -> ShouldRun {
431 let builder = run.builder;
432 run.krate("std").default_condition(builder.build.config.docs)
435 fn make_run(run: RunConfig) {
436 run.builder.ensure(Std {
437 stage: run.builder.top_stage,
442 /// Compile all standard library documentation.
444 /// This will generate all documentation for the standard library and its
445 /// dependencies. This is largely just a wrapper around `cargo doc`.
446 fn run(self, builder: &Builder) {
447 let build = builder.build;
448 let stage = self.stage;
449 let target = self.target;
450 println!("Documenting stage{} std ({})", stage, target);
451 let out = build.doc_out(target);
452 t!(fs::create_dir_all(&out));
453 let compiler = builder.compiler(stage, build.build);
454 let rustdoc = builder.rustdoc(compiler.host);
455 let compiler = if build.force_use_stage1(compiler, target) {
456 builder.compiler(1, compiler.host)
461 builder.ensure(compile::Std { compiler, target });
462 let out_dir = build.stage_out(compiler, Mode::Libstd)
463 .join(target).join("doc");
465 // Here what we're doing is creating a *symlink* (directory junction on
466 // Windows) to the final output location. This is not done as an
467 // optimization but rather for correctness. We've got three trees of
468 // documentation, one for std, one for test, and one for rustc. It's then
469 // our job to merge them all together.
471 // Unfortunately rustbuild doesn't know nearly as well how to merge doc
472 // trees as rustdoc does itself, so instead of actually having three
473 // separate trees we just have rustdoc output to the same location across
476 // This way rustdoc generates output directly into the output, and rustdoc
477 // will also directly handle merging.
478 let my_out = build.crate_doc_out(target);
479 build.clear_if_dirty(&my_out, &rustdoc);
480 t!(symlink_dir_force(&my_out, &out_dir));
482 let mut cargo = builder.cargo(compiler, Mode::Libstd, target, "doc");
483 compile::std_cargo(build, &compiler, target, &mut cargo);
485 // We don't want to build docs for internal std dependencies unless
486 // in compiler-docs mode. When not in that mode, we whitelist the crates
487 // for which docs must be built.
488 if !build.config.compiler_docs {
489 cargo.arg("--no-deps");
490 for krate in &["alloc", "core", "std", "std_unicode"] {
491 cargo.arg("-p").arg(krate);
492 // Create all crate output directories first to make sure rustdoc uses
494 // FIXME: Cargo should probably do this itself.
495 t!(fs::create_dir_all(out_dir.join(krate)));
500 build.run(&mut cargo);
505 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
508 target: Interned<String>,
513 const DEFAULT: bool = true;
515 fn should_run(run: ShouldRun) -> ShouldRun {
516 let builder = run.builder;
517 run.krate("test").default_condition(builder.config.compiler_docs)
520 fn make_run(run: RunConfig) {
521 run.builder.ensure(Test {
522 stage: run.builder.top_stage,
527 /// Compile all libtest documentation.
529 /// This will generate all documentation for libtest and its dependencies. This
530 /// is largely just a wrapper around `cargo doc`.
531 fn run(self, builder: &Builder) {
532 let build = builder.build;
533 let stage = self.stage;
534 let target = self.target;
535 println!("Documenting stage{} test ({})", stage, target);
536 let out = build.doc_out(target);
537 t!(fs::create_dir_all(&out));
538 let compiler = builder.compiler(stage, build.build);
539 let rustdoc = builder.rustdoc(compiler.host);
540 let compiler = if build.force_use_stage1(compiler, target) {
541 builder.compiler(1, compiler.host)
546 // Build libstd docs so that we generate relative links
547 builder.ensure(Std { stage, target });
549 builder.ensure(compile::Test { compiler, target });
550 let out_dir = build.stage_out(compiler, Mode::Libtest)
551 .join(target).join("doc");
553 // See docs in std above for why we symlink
554 let my_out = build.crate_doc_out(target);
555 build.clear_if_dirty(&my_out, &rustdoc);
556 t!(symlink_dir_force(&my_out, &out_dir));
558 let mut cargo = builder.cargo(compiler, Mode::Libtest, target, "doc");
559 compile::test_cargo(build, &compiler, target, &mut cargo);
560 build.run(&mut cargo);
565 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
568 target: Interned<String>,
571 impl Step for Rustc {
573 const DEFAULT: bool = true;
574 const ONLY_HOSTS: bool = true;
576 fn should_run(run: ShouldRun) -> ShouldRun {
577 let builder = run.builder;
578 run.krate("rustc-main").default_condition(builder.build.config.docs)
581 fn make_run(run: RunConfig) {
582 run.builder.ensure(Rustc {
583 stage: run.builder.top_stage,
588 /// Generate all compiler documentation.
590 /// This will generate all documentation for the compiler libraries and their
591 /// dependencies. This is largely just a wrapper around `cargo doc`.
592 fn run(self, builder: &Builder) {
593 let build = builder.build;
594 let stage = self.stage;
595 let target = self.target;
596 println!("Documenting stage{} compiler ({})", stage, target);
597 let out = build.doc_out(target);
598 t!(fs::create_dir_all(&out));
599 let compiler = builder.compiler(stage, build.build);
600 let rustdoc = builder.rustdoc(compiler.host);
601 let compiler = if build.force_use_stage1(compiler, target) {
602 builder.compiler(1, compiler.host)
607 // Build libstd docs so that we generate relative links
608 builder.ensure(Std { stage, target });
610 builder.ensure(compile::Rustc { compiler, target });
611 let out_dir = build.stage_out(compiler, Mode::Librustc)
612 .join(target).join("doc");
614 // See docs in std above for why we symlink
615 let my_out = build.crate_doc_out(target);
616 build.clear_if_dirty(&my_out, &rustdoc);
617 t!(symlink_dir_force(&my_out, &out_dir));
619 let mut cargo = builder.cargo(compiler, Mode::Librustc, target, "doc");
620 compile::rustc_cargo(build, &mut cargo);
622 if build.config.compiler_docs {
623 // src/rustc/Cargo.toml contains a bin crate called rustc which
624 // would otherwise overwrite the docs for the real rustc lib crate.
625 cargo.arg("-p").arg("rustc_driver");
627 // Like with libstd above if compiler docs aren't enabled then we're not
628 // documenting internal dependencies, so we have a whitelist.
629 cargo.arg("--no-deps");
630 for krate in &["proc_macro"] {
631 cargo.arg("-p").arg(krate);
635 build.run(&mut cargo);
640 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
641 pub struct ErrorIndex {
642 target: Interned<String>,
645 impl Step for ErrorIndex {
647 const DEFAULT: bool = true;
648 const ONLY_HOSTS: bool = true;
650 fn should_run(run: ShouldRun) -> ShouldRun {
651 let builder = run.builder;
652 run.path("src/tools/error_index_generator").default_condition(builder.build.config.docs)
655 fn make_run(run: RunConfig) {
656 run.builder.ensure(ErrorIndex {
661 /// Generates the HTML rendered error-index by running the
662 /// `error_index_generator` tool.
663 fn run(self, builder: &Builder) {
664 let build = builder.build;
665 let target = self.target;
667 println!("Documenting error index ({})", target);
668 let out = build.doc_out(target);
669 t!(fs::create_dir_all(&out));
670 let mut index = builder.tool_cmd(Tool::ErrorIndex);
672 index.arg(out.join("error-index.html"));
674 // FIXME: shouldn't have to pass this env var
675 index.env("CFG_BUILD", &build.build)
676 .env("RUSTC_ERROR_METADATA_DST", build.extended_error_dir());
678 build.run(&mut index);
682 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
683 pub struct UnstableBookGen {
684 target: Interned<String>,
687 impl Step for UnstableBookGen {
689 const DEFAULT: bool = true;
690 const ONLY_HOSTS: bool = true;
692 fn should_run(run: ShouldRun) -> ShouldRun {
693 let builder = run.builder;
694 run.path("src/tools/unstable-book-gen").default_condition(builder.build.config.docs)
697 fn make_run(run: RunConfig) {
698 run.builder.ensure(UnstableBookGen {
703 fn run(self, builder: &Builder) {
704 let build = builder.build;
705 let target = self.target;
707 builder.ensure(compile::Std {
708 compiler: builder.compiler(builder.top_stage, build.build),
712 println!("Generating unstable book md files ({})", target);
713 let out = build.md_doc_out(target).join("unstable-book");
714 t!(fs::create_dir_all(&out));
715 t!(fs::remove_dir_all(&out));
716 let mut cmd = builder.tool_cmd(Tool::UnstableBookGen);
717 cmd.arg(build.src.join("src"));
724 fn symlink_dir_force(src: &Path, dst: &Path) -> io::Result<()> {
725 if let Ok(m) = fs::symlink_metadata(dst) {
726 if m.file_type().is_dir() {
727 try!(fs::remove_dir_all(dst));
729 // handle directory junctions on windows by falling back to
731 try!(fs::remove_file(dst).or_else(|_| {
737 symlink_dir(src, dst)