]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/doc.rs
Auto merge of #46592 - o01eg:fix-45345, r=alexcrichton
[rust.git] / src / bootstrap / doc.rs
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.
4 //
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.
10
11 //! Documentation generation for rustbuild.
12 //!
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.
16 //!
17 //! Everything here is basically just a shim around calling either `rustbook` or
18 //! `rustdoc`.
19
20 use std::fs::{self, File};
21 use std::io::prelude::*;
22 use std::io;
23 use std::path::{PathBuf, Path};
24
25 use Mode;
26 use build_helper::up_to_date;
27
28 use util::{cp_r, symlink_dir};
29 use builder::{Builder, Compiler, RunConfig, ShouldRun, Step};
30 use tool::Tool;
31 use compile;
32 use cache::{INTERNER, Interned};
33
34 macro_rules! book {
35     ($($name:ident, $path:expr, $book_name:expr;)+) => {
36         $(
37             #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
38         pub struct $name {
39             target: Interned<String>,
40         }
41
42         impl Step for $name {
43             type Output = ();
44             const DEFAULT: bool = true;
45
46             fn should_run(run: ShouldRun) -> ShouldRun {
47                 let builder = run.builder;
48                 run.path($path).default_condition(builder.build.config.docs)
49             }
50
51             fn make_run(run: RunConfig) {
52                 run.builder.ensure($name {
53                     target: run.target,
54                 });
55             }
56
57             fn run(self, builder: &Builder) {
58                 builder.ensure(Rustbook {
59                     target: self.target,
60                     name: INTERNER.intern_str($book_name),
61                 })
62             }
63         }
64         )+
65     }
66 }
67
68 book!(
69     Nomicon, "src/doc/nomicon", "nomicon";
70     Reference, "src/doc/reference", "reference";
71     Rustdoc, "src/doc/rustdoc", "rustdoc";
72 );
73
74 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
75 struct Rustbook {
76     target: Interned<String>,
77     name: Interned<String>,
78 }
79
80 impl Step for Rustbook {
81     type Output = ();
82
83     // rustbook is never directly called, and only serves as a shim for the nomicon and the
84     // reference.
85     fn should_run(run: ShouldRun) -> ShouldRun {
86         run.never()
87     }
88
89     /// Invoke `rustbook` for `target` for the doc book `name`.
90     ///
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 {
96             target: self.target,
97             name: self.name,
98             src: INTERNER.intern_path(src),
99         });
100     }
101 }
102
103 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
104 pub struct UnstableBook {
105     target: Interned<String>,
106 }
107
108 impl Step for UnstableBook {
109     type Output = ();
110     const DEFAULT: bool = true;
111
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)
115     }
116
117     fn make_run(run: RunConfig) {
118         run.builder.ensure(UnstableBook {
119             target: run.target,
120         });
121     }
122
123     fn run(self, builder: &Builder) {
124         builder.ensure(UnstableBookGen {
125             target: self.target,
126         });
127         builder.ensure(RustbookSrc {
128             target: self.target,
129             name: INTERNER.intern_str("unstable-book"),
130             src: builder.build.md_doc_out(self.target),
131         })
132     }
133 }
134
135 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
136 pub struct CargoBook {
137     target: Interned<String>,
138     name: Interned<String>,
139 }
140
141 impl Step for CargoBook {
142     type Output = ();
143     const DEFAULT: bool = true;
144
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)
148     }
149
150     fn make_run(run: RunConfig) {
151         run.builder.ensure(CargoBook {
152             target: run.target,
153             name: INTERNER.intern_str("cargo"),
154         });
155     }
156
157     fn run(self, builder: &Builder) {
158         let build = builder.build;
159
160         let target = self.target;
161         let name = self.name;
162         let src = build.src.join("src/tools/cargo/src/doc/book");
163
164         let out = build.doc_out(target);
165         t!(fs::create_dir_all(&out));
166
167         let out = out.join(name);
168
169         println!("Cargo Book ({}) - {}", target, name);
170
171         let _ = fs::remove_dir_all(&out);
172
173         build.run(builder.tool_cmd(Tool::Rustbook)
174                        .arg("build")
175                        .arg(&src)
176                        .arg("-d")
177                        .arg(out));
178     }
179 }
180
181 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
182 struct RustbookSrc {
183     target: Interned<String>,
184     name: Interned<String>,
185     src: Interned<PathBuf>,
186 }
187
188 impl Step for RustbookSrc {
189     type Output = ();
190
191     fn should_run(run: ShouldRun) -> ShouldRun {
192         run.never()
193     }
194
195     /// Invoke `rustbook` for `target` for the doc book `name` from the `src` path.
196     ///
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;
203         let src = self.src;
204         let out = build.doc_out(target);
205         t!(fs::create_dir_all(&out));
206
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) {
212             return
213         }
214         println!("Rustbook ({}) - {}", target, name);
215         let _ = fs::remove_dir_all(&out);
216         build.run(builder.tool_cmd(Tool::Rustbook)
217                        .arg("build")
218                        .arg(&src)
219                        .arg("-d")
220                        .arg(out));
221     }
222 }
223
224 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
225 pub struct TheBook {
226     compiler: Compiler,
227     target: Interned<String>,
228     name: &'static str,
229 }
230
231 impl Step for TheBook {
232     type Output = ();
233     const DEFAULT: bool = true;
234
235     fn should_run(run: ShouldRun) -> ShouldRun {
236         let builder = run.builder;
237         run.path("src/doc/book").default_condition(builder.build.config.docs)
238     }
239
240     fn make_run(run: RunConfig) {
241         run.builder.ensure(TheBook {
242             compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
243             target: run.target,
244             name: "book",
245         });
246     }
247
248     /// Build the book and associated stuff.
249     ///
250     /// We need to build:
251     ///
252     /// * Book (first edition)
253     /// * Book (second edition)
254     /// * Version info and CSS
255     /// * Index page
256     /// * Redirect pages
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 {
264             target,
265             name: INTERNER.intern_string(format!("{}/first-edition", name)),
266         });
267
268         // build book second edition
269         builder.ensure(Rustbook {
270             target,
271             name: INTERNER.intern_string(format!("{}/second-edition", name)),
272         });
273
274         // build the version info page and CSS
275         builder.ensure(Standalone {
276             compiler,
277             target,
278         });
279
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);
284
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"))) {
288             let file = t!(file);
289             let path = file.path();
290             let path = path.to_str().unwrap();
291
292             invoke_rustdoc(builder, compiler, target, path);
293         }
294     }
295 }
296
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);
300
301     let path = build.src.join("src/doc").join(markdown);
302
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");
306
307     let mut cmd = builder.rustdoc_cmd(compiler.host);
308
309     let out = out.join("book");
310
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/")
316         .arg("-o").arg(&out)
317         .arg(&path)
318         .arg("--markdown-css")
319         .arg("../rust.css");
320
321     build.run(&mut cmd);
322 }
323
324 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
325 pub struct Standalone {
326     compiler: Compiler,
327     target: Interned<String>,
328 }
329
330 impl Step for Standalone {
331     type Output = ();
332     const DEFAULT: bool = true;
333
334     fn should_run(run: ShouldRun) -> ShouldRun {
335         let builder = run.builder;
336         run.path("src/doc").default_condition(builder.build.config.docs)
337     }
338
339     fn make_run(run: RunConfig) {
340         run.builder.ensure(Standalone {
341             compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
342             target: run.target,
343         });
344     }
345
346     /// Generates all standalone documentation as compiled by the rustdoc in `stage`
347     /// for the `target` into `out`.
348     ///
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.
352     ///
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));
361
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")));
366
367         let version_input = build.src.join("src/doc/version_info.html.template");
368         let version_info = out.join("version_info.html");
369
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()));
377         }
378
379         for file in t!(fs::read_dir(build.src.join("src/doc"))) {
380             let file = t!(file);
381             let path = file.path();
382             let filename = path.file_name().unwrap().to_str().unwrap();
383             if !filename.ends_with(".md") || filename == "README.md" {
384                 continue
385             }
386
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) {
395                 continue
396             }
397
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/")
404                .arg("-o").arg(&out)
405                .arg(&path);
406
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");
411             } else {
412                 cmd.arg("--markdown-css").arg("rust.css");
413             }
414             build.run(&mut cmd);
415         }
416     }
417 }
418
419 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
420 pub struct Std {
421     stage: u32,
422     target: Interned<String>,
423 }
424
425 impl Step for Std {
426     type Output = ();
427     const DEFAULT: bool = true;
428
429     fn should_run(run: ShouldRun) -> ShouldRun {
430         let builder = run.builder;
431         run.krate("std").default_condition(builder.build.config.docs)
432     }
433
434     fn make_run(run: RunConfig) {
435         run.builder.ensure(Std {
436             stage: run.builder.top_stage,
437             target: run.target
438         });
439     }
440
441     /// Compile all standard library documentation.
442     ///
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)
456         } else {
457             compiler
458         };
459
460         builder.ensure(compile::Std { compiler, target });
461         let out_dir = build.stage_out(compiler, Mode::Libstd)
462                            .join(target).join("doc");
463
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.
469         //
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
473         // all of them.
474         //
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));
480
481         let mut cargo = builder.cargo(compiler, Mode::Libstd, target, "doc");
482         compile::std_cargo(build, &compiler, target, &mut cargo);
483
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
492                 // relative links.
493                 // FIXME: Cargo should probably do this itself.
494                 t!(fs::create_dir_all(out_dir.join(krate)));
495             }
496         }
497
498
499         build.run(&mut cargo);
500         cp_r(&my_out, &out);
501     }
502 }
503
504 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
505 pub struct Test {
506     stage: u32,
507     target: Interned<String>,
508 }
509
510 impl Step for Test {
511     type Output = ();
512     const DEFAULT: bool = true;
513
514     fn should_run(run: ShouldRun) -> ShouldRun {
515         let builder = run.builder;
516         run.krate("test").default_condition(builder.config.compiler_docs)
517     }
518
519     fn make_run(run: RunConfig) {
520         run.builder.ensure(Test {
521             stage: run.builder.top_stage,
522             target: run.target,
523         });
524     }
525
526     /// Compile all libtest documentation.
527     ///
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)
541         } else {
542             compiler
543         };
544
545         // Build libstd docs so that we generate relative links
546         builder.ensure(Std { stage, target });
547
548         builder.ensure(compile::Test { compiler, target });
549         let out_dir = build.stage_out(compiler, Mode::Libtest)
550                            .join(target).join("doc");
551
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));
556
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);
560         cp_r(&my_out, &out);
561     }
562 }
563
564 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
565 pub struct Rustc {
566     stage: u32,
567     target: Interned<String>,
568 }
569
570 impl Step for Rustc {
571     type Output = ();
572     const DEFAULT: bool = true;
573     const ONLY_HOSTS: bool = true;
574
575     fn should_run(run: ShouldRun) -> ShouldRun {
576         let builder = run.builder;
577         run.krate("rustc-main").default_condition(builder.build.config.docs)
578     }
579
580     fn make_run(run: RunConfig) {
581         run.builder.ensure(Rustc {
582             stage: run.builder.top_stage,
583             target: run.target,
584         });
585     }
586
587     /// Generate all compiler documentation.
588     ///
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)
602         } else {
603             compiler
604         };
605
606         // Build libstd docs so that we generate relative links
607         builder.ensure(Std { stage, target });
608
609         builder.ensure(compile::Rustc { compiler, target });
610         let out_dir = build.stage_out(compiler, Mode::Librustc)
611                            .join(target).join("doc");
612
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));
617
618         let mut cargo = builder.cargo(compiler, Mode::Librustc, target, "doc");
619         compile::rustc_cargo(build, target, &mut cargo);
620
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");
625         } else {
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);
631             }
632         }
633
634         build.run(&mut cargo);
635         cp_r(&my_out, &out);
636     }
637 }
638
639 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
640 pub struct ErrorIndex {
641     target: Interned<String>,
642 }
643
644 impl Step for ErrorIndex {
645     type Output = ();
646     const DEFAULT: bool = true;
647     const ONLY_HOSTS: bool = true;
648
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)
652     }
653
654     fn make_run(run: RunConfig) {
655         run.builder.ensure(ErrorIndex {
656             target: run.target,
657         });
658     }
659
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;
665
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);
670         index.arg("html");
671         index.arg(out.join("error-index.html"));
672
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());
676
677         build.run(&mut index);
678     }
679 }
680
681 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
682 pub struct UnstableBookGen {
683     target: Interned<String>,
684 }
685
686 impl Step for UnstableBookGen {
687     type Output = ();
688     const DEFAULT: bool = true;
689     const ONLY_HOSTS: bool = true;
690
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)
694     }
695
696     fn make_run(run: RunConfig) {
697         run.builder.ensure(UnstableBookGen {
698             target: run.target,
699         });
700     }
701
702     fn run(self, builder: &Builder) {
703         let build = builder.build;
704         let target = self.target;
705
706         builder.ensure(compile::Std {
707             compiler: builder.compiler(builder.top_stage, build.build),
708             target,
709         });
710
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"));
717         cmd.arg(out);
718
719         build.run(&mut cmd);
720     }
721 }
722
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));
727         } else {
728             // handle directory junctions on windows by falling back to
729             // `remove_dir`.
730             try!(fs::remove_file(dst).or_else(|_| {
731                 fs::remove_dir(dst)
732             }));
733         }
734     }
735
736     symlink_dir(src, dst)
737 }