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/book", "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)]
137 target: Interned<String>,
138 name: Interned<String>,
139 src: Interned<PathBuf>,
142 impl Step for RustbookSrc {
145 fn should_run(run: ShouldRun) -> ShouldRun {
149 /// Invoke `rustbook` for `target` for the doc book `name` from the `src` path.
151 /// This will not actually generate any documentation if the documentation has
152 /// already been generated.
153 fn run(self, builder: &Builder) {
154 let build = builder.build;
155 let target = self.target;
156 let name = self.name;
158 let out = build.doc_out(target);
159 t!(fs::create_dir_all(&out));
161 let out = out.join(name);
162 let src = src.join(name);
163 let index = out.join("index.html");
164 let rustbook = builder.tool_exe(Tool::Rustbook);
165 if up_to_date(&src, &index) && up_to_date(&rustbook, &index) {
168 println!("Rustbook ({}) - {}", target, name);
169 let _ = fs::remove_dir_all(&out);
170 build.run(builder.tool_cmd(Tool::Rustbook)
178 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
181 target: Interned<String>,
185 impl Step for TheBook {
187 const DEFAULT: bool = true;
189 fn should_run(run: ShouldRun) -> ShouldRun {
190 let builder = run.builder;
191 run.path("src/doc/book").default_condition(builder.build.config.docs)
194 fn make_run(run: RunConfig) {
195 run.builder.ensure(TheBook {
196 compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
202 /// Build the book and associated stuff.
204 /// We need to build:
206 /// * Book (first edition)
207 /// * Book (second edition)
210 fn run(self, builder: &Builder) {
211 let build = builder.build;
212 let target = self.target;
213 let name = self.name;
214 // build book first edition
215 builder.ensure(Rustbook {
217 name: INTERNER.intern_string(format!("{}/first-edition", name)),
220 // build book second edition
221 builder.ensure(Rustbook {
223 name: INTERNER.intern_string(format!("{}/second-edition", name)),
226 // build the index page
227 let index = format!("{}/index.md", name);
228 println!("Documenting book index ({})", target);
229 invoke_rustdoc(builder, self.compiler, target, &index);
231 // build the redirect pages
232 println!("Documenting book redirect pages ({})", target);
233 for file in t!(fs::read_dir(build.src.join("src/doc/book/redirects"))) {
235 let path = file.path();
236 let path = path.to_str().unwrap();
238 invoke_rustdoc(builder, self.compiler, target, path);
243 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
244 pub struct CargoBook {
245 target: Interned<String>,
248 impl Step for CargoBook {
250 const DEFAULT: bool = true;
252 fn should_run(run: ShouldRun) -> ShouldRun {
253 let builder = run.builder;
254 run.path("src/doc/cargo").default_condition(builder.build.config.docs)
257 fn make_run(run: RunConfig) {
258 run.builder.ensure(CargoBook {
263 /// Create a placeholder for the cargo documentation so that doc.rust-lang.org/cargo will
264 /// redirect to doc.crates.io. We want to publish doc.rust-lang.org/cargo in the paper
265 /// version of the book, but we don't want to rush the process of switching cargo's docs
266 /// over to mdbook and deploying them. When the cargo book is ready, this implementation
267 /// should build the mdbook instead of this redirect page.
268 fn run(self, builder: &Builder) {
269 let build = builder.build;
270 let out = build.doc_out(self.target);
272 let cargo_dir = out.join("cargo");
273 t!(fs::create_dir_all(&cargo_dir));
275 let index = cargo_dir.join("index.html");
276 let redirect_html = r#"
279 <meta http-equiv="refresh" content="0; URL='http://doc.crates.io'" />
283 println!("Creating cargo book redirect page");
284 t!(t!(File::create(&index)).write_all(redirect_html.as_bytes()));
288 fn invoke_rustdoc(builder: &Builder, compiler: Compiler, target: Interned<String>, markdown: &str) {
289 let build = builder.build;
290 let out = build.doc_out(target);
292 let path = build.src.join("src/doc").join(markdown);
294 let favicon = build.src.join("src/doc/favicon.inc");
295 let footer = build.src.join("src/doc/footer.inc");
297 let version_input = build.src.join("src/doc/version_info.html.template");
298 let version_info = out.join("version_info.html");
300 if !up_to_date(&version_input, &version_info) {
301 let mut info = String::new();
302 t!(t!(File::open(&version_input)).read_to_string(&mut info));
303 let info = info.replace("VERSION", &build.rust_release())
304 .replace("SHORT_HASH", build.rust_info.sha_short().unwrap_or(""))
305 .replace("STAMP", build.rust_info.sha().unwrap_or(""));
306 t!(t!(File::create(&version_info)).write_all(info.as_bytes()));
309 let mut cmd = builder.rustdoc_cmd(compiler.host);
311 let out = out.join("book");
313 t!(fs::copy(build.src.join("src/doc/rust.css"), out.join("rust.css")));
315 cmd.arg("--html-after-content").arg(&footer)
316 .arg("--html-before-content").arg(&version_info)
317 .arg("--html-in-header").arg(&favicon)
318 .arg("--markdown-playground-url")
319 .arg("https://play.rust-lang.org/")
322 .arg("--markdown-css")
328 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
329 pub struct Standalone {
331 target: Interned<String>,
334 impl Step for Standalone {
336 const DEFAULT: bool = true;
338 fn should_run(run: ShouldRun) -> ShouldRun {
339 let builder = run.builder;
340 run.path("src/doc").default_condition(builder.build.config.docs)
343 fn make_run(run: RunConfig) {
344 run.builder.ensure(Standalone {
345 compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
350 /// Generates all standalone documentation as compiled by the rustdoc in `stage`
351 /// for the `target` into `out`.
353 /// This will list all of `src/doc` looking for markdown files and appropriately
354 /// perform transformations like substituting `VERSION`, `SHORT_HASH`, and
355 /// `STAMP` along with providing the various header/footer HTML we've customized.
357 /// In the end, this is just a glorified wrapper around rustdoc!
358 fn run(self, builder: &Builder) {
359 let build = builder.build;
360 let target = self.target;
361 let compiler = self.compiler;
362 println!("Documenting standalone ({})", target);
363 let out = build.doc_out(target);
364 t!(fs::create_dir_all(&out));
366 let favicon = build.src.join("src/doc/favicon.inc");
367 let footer = build.src.join("src/doc/footer.inc");
368 let full_toc = build.src.join("src/doc/full-toc.inc");
369 t!(fs::copy(build.src.join("src/doc/rust.css"), out.join("rust.css")));
371 let version_input = build.src.join("src/doc/version_info.html.template");
372 let version_info = out.join("version_info.html");
374 if !up_to_date(&version_input, &version_info) {
375 let mut info = String::new();
376 t!(t!(File::open(&version_input)).read_to_string(&mut info));
377 let info = info.replace("VERSION", &build.rust_release())
378 .replace("SHORT_HASH", build.rust_info.sha_short().unwrap_or(""))
379 .replace("STAMP", build.rust_info.sha().unwrap_or(""));
380 t!(t!(File::create(&version_info)).write_all(info.as_bytes()));
383 for file in t!(fs::read_dir(build.src.join("src/doc"))) {
385 let path = file.path();
386 let filename = path.file_name().unwrap().to_str().unwrap();
387 if !filename.ends_with(".md") || filename == "README.md" {
391 let html = out.join(filename).with_extension("html");
392 let rustdoc = builder.rustdoc(compiler.host);
393 if up_to_date(&path, &html) &&
394 up_to_date(&footer, &html) &&
395 up_to_date(&favicon, &html) &&
396 up_to_date(&full_toc, &html) &&
397 up_to_date(&version_info, &html) &&
398 up_to_date(&rustdoc, &html) {
402 let mut cmd = builder.rustdoc_cmd(compiler.host);
403 cmd.arg("--html-after-content").arg(&footer)
404 .arg("--html-before-content").arg(&version_info)
405 .arg("--html-in-header").arg(&favicon)
406 .arg("--markdown-playground-url")
407 .arg("https://play.rust-lang.org/")
411 if filename == "not_found.md" {
412 cmd.arg("--markdown-no-toc")
413 .arg("--markdown-css")
414 .arg("https://doc.rust-lang.org/rust.css");
416 cmd.arg("--markdown-css").arg("rust.css");
423 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
426 target: Interned<String>,
431 const DEFAULT: bool = true;
433 fn should_run(run: ShouldRun) -> ShouldRun {
434 let builder = run.builder;
435 run.krate("std").default_condition(builder.build.config.docs)
438 fn make_run(run: RunConfig) {
439 run.builder.ensure(Std {
440 stage: run.builder.top_stage,
445 /// Compile all standard library documentation.
447 /// This will generate all documentation for the standard library and its
448 /// dependencies. This is largely just a wrapper around `cargo doc`.
449 fn run(self, builder: &Builder) {
450 let build = builder.build;
451 let stage = self.stage;
452 let target = self.target;
453 println!("Documenting stage{} std ({})", stage, target);
454 let out = build.doc_out(target);
455 t!(fs::create_dir_all(&out));
456 let compiler = builder.compiler(stage, build.build);
457 let rustdoc = builder.rustdoc(compiler.host);
458 let compiler = if build.force_use_stage1(compiler, target) {
459 builder.compiler(1, compiler.host)
464 builder.ensure(compile::Std { compiler, target });
465 let out_dir = build.stage_out(compiler, Mode::Libstd)
466 .join(target).join("doc");
468 // Here what we're doing is creating a *symlink* (directory junction on
469 // Windows) to the final output location. This is not done as an
470 // optimization but rather for correctness. We've got three trees of
471 // documentation, one for std, one for test, and one for rustc. It's then
472 // our job to merge them all together.
474 // Unfortunately rustbuild doesn't know nearly as well how to merge doc
475 // trees as rustdoc does itself, so instead of actually having three
476 // separate trees we just have rustdoc output to the same location across
479 // This way rustdoc generates output directly into the output, and rustdoc
480 // will also directly handle merging.
481 let my_out = build.crate_doc_out(target);
482 build.clear_if_dirty(&my_out, &rustdoc);
483 t!(symlink_dir_force(&my_out, &out_dir));
485 let mut cargo = builder.cargo(compiler, Mode::Libstd, target, "doc");
486 compile::std_cargo(build, &compiler, target, &mut cargo);
488 // We don't want to build docs for internal std dependencies unless
489 // in compiler-docs mode. When not in that mode, we whitelist the crates
490 // for which docs must be built.
491 if !build.config.compiler_docs {
492 cargo.arg("--no-deps");
493 for krate in &["alloc", "collections", "core", "std", "std_unicode"] {
494 cargo.arg("-p").arg(krate);
495 // Create all crate output directories first to make sure rustdoc uses
497 // FIXME: Cargo should probably do this itself.
498 t!(fs::create_dir_all(out_dir.join(krate)));
503 build.run(&mut cargo);
508 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
511 target: Interned<String>,
516 const DEFAULT: bool = true;
518 fn should_run(run: ShouldRun) -> ShouldRun {
519 let builder = run.builder;
520 run.krate("test").default_condition(builder.config.compiler_docs)
523 fn make_run(run: RunConfig) {
524 run.builder.ensure(Test {
525 stage: run.builder.top_stage,
530 /// Compile all libtest documentation.
532 /// This will generate all documentation for libtest and its dependencies. This
533 /// is largely just a wrapper around `cargo doc`.
534 fn run(self, builder: &Builder) {
535 let build = builder.build;
536 let stage = self.stage;
537 let target = self.target;
538 println!("Documenting stage{} test ({})", stage, target);
539 let out = build.doc_out(target);
540 t!(fs::create_dir_all(&out));
541 let compiler = builder.compiler(stage, build.build);
542 let rustdoc = builder.rustdoc(compiler.host);
543 let compiler = if build.force_use_stage1(compiler, target) {
544 builder.compiler(1, compiler.host)
549 // Build libstd docs so that we generate relative links
550 builder.ensure(Std { stage, target });
552 builder.ensure(compile::Test { compiler, target });
553 let out_dir = build.stage_out(compiler, Mode::Libtest)
554 .join(target).join("doc");
556 // See docs in std above for why we symlink
557 let my_out = build.crate_doc_out(target);
558 build.clear_if_dirty(&my_out, &rustdoc);
559 t!(symlink_dir_force(&my_out, &out_dir));
561 let mut cargo = builder.cargo(compiler, Mode::Libtest, target, "doc");
562 compile::test_cargo(build, &compiler, target, &mut cargo);
563 build.run(&mut cargo);
568 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
571 target: Interned<String>,
574 impl Step for Rustc {
576 const DEFAULT: bool = true;
577 const ONLY_HOSTS: bool = true;
579 fn should_run(run: ShouldRun) -> ShouldRun {
580 let builder = run.builder;
581 run.krate("rustc-main").default_condition(builder.build.config.docs)
584 fn make_run(run: RunConfig) {
585 run.builder.ensure(Rustc {
586 stage: run.builder.top_stage,
591 /// Generate all compiler documentation.
593 /// This will generate all documentation for the compiler libraries and their
594 /// dependencies. This is largely just a wrapper around `cargo doc`.
595 fn run(self, builder: &Builder) {
596 let build = builder.build;
597 let stage = self.stage;
598 let target = self.target;
599 println!("Documenting stage{} compiler ({})", stage, target);
600 let out = build.doc_out(target);
601 t!(fs::create_dir_all(&out));
602 let compiler = builder.compiler(stage, build.build);
603 let rustdoc = builder.rustdoc(compiler.host);
604 let compiler = if build.force_use_stage1(compiler, target) {
605 builder.compiler(1, compiler.host)
610 // Build libstd docs so that we generate relative links
611 builder.ensure(Std { stage, target });
613 builder.ensure(compile::Rustc { compiler, target });
614 let out_dir = build.stage_out(compiler, Mode::Librustc)
615 .join(target).join("doc");
617 // See docs in std above for why we symlink
618 let my_out = build.crate_doc_out(target);
619 build.clear_if_dirty(&my_out, &rustdoc);
620 t!(symlink_dir_force(&my_out, &out_dir));
622 let mut cargo = builder.cargo(compiler, Mode::Librustc, target, "doc");
623 compile::rustc_cargo(build, &compiler, target, &mut cargo);
625 if build.config.compiler_docs {
626 // src/rustc/Cargo.toml contains bin crates called rustc and rustdoc
627 // which would otherwise overwrite the docs for the real rustc and
628 // rustdoc lib crates.
629 cargo.arg("-p").arg("rustc_driver")
630 .arg("-p").arg("rustdoc");
632 // Like with libstd above if compiler docs aren't enabled then we're not
633 // documenting internal dependencies, so we have a whitelist.
634 cargo.arg("--no-deps");
635 for krate in &["proc_macro"] {
636 cargo.arg("-p").arg(krate);
640 build.run(&mut cargo);
645 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
646 pub struct ErrorIndex {
647 target: Interned<String>,
650 impl Step for ErrorIndex {
652 const DEFAULT: bool = true;
653 const ONLY_HOSTS: bool = true;
655 fn should_run(run: ShouldRun) -> ShouldRun {
656 let builder = run.builder;
657 run.path("src/tools/error_index_generator").default_condition(builder.build.config.docs)
660 fn make_run(run: RunConfig) {
661 run.builder.ensure(ErrorIndex {
666 /// Generates the HTML rendered error-index by running the
667 /// `error_index_generator` tool.
668 fn run(self, builder: &Builder) {
669 let build = builder.build;
670 let target = self.target;
672 println!("Documenting error index ({})", target);
673 let out = build.doc_out(target);
674 t!(fs::create_dir_all(&out));
675 let mut index = builder.tool_cmd(Tool::ErrorIndex);
677 index.arg(out.join("error-index.html"));
679 // FIXME: shouldn't have to pass this env var
680 index.env("CFG_BUILD", &build.build);
682 build.run(&mut index);
686 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
687 pub struct UnstableBookGen {
688 target: Interned<String>,
691 impl Step for UnstableBookGen {
693 const DEFAULT: bool = true;
694 const ONLY_HOSTS: bool = true;
696 fn should_run(run: ShouldRun) -> ShouldRun {
697 let builder = run.builder;
698 run.path("src/tools/unstable-book-gen").default_condition(builder.build.config.docs)
701 fn make_run(run: RunConfig) {
702 run.builder.ensure(UnstableBookGen {
707 fn run(self, builder: &Builder) {
708 let build = builder.build;
709 let target = self.target;
711 builder.ensure(compile::Std {
712 compiler: builder.compiler(builder.top_stage, build.build),
716 println!("Generating unstable book md files ({})", target);
717 let out = build.md_doc_out(target).join("unstable-book");
718 t!(fs::create_dir_all(&out));
719 t!(fs::remove_dir_all(&out));
720 let mut cmd = builder.tool_cmd(Tool::UnstableBookGen);
721 cmd.arg(build.src.join("src"));
728 fn symlink_dir_force(src: &Path, dst: &Path) -> io::Result<()> {
729 if let Ok(m) = fs::symlink_metadata(dst) {
730 if m.file_type().is_dir() {
731 try!(fs::remove_dir_all(dst));
733 // handle directory junctions on windows by falling back to
735 try!(fs::remove_file(dst).or_else(|_| {
741 symlink_dir(src, dst)