]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2012-2014 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 | #![crate_name = "rustdoc"] | |
e9174d1e | 12 | #![unstable(feature = "rustdoc", issue = "27812")] |
1a4d82fc JJ |
13 | #![crate_type = "dylib"] |
14 | #![crate_type = "rlib"] | |
e9174d1e | 15 | #![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", |
9cc50fc6 SL |
16 | html_favicon_url = "https://doc.rust-lang.org/favicon.ico", |
17 | html_root_url = "https://doc.rust-lang.org/nightly/", | |
18 | html_playground_url = "https://play.rust-lang.org/")] | |
7453a54e | 19 | #![cfg_attr(not(stage0), deny(warnings))] |
85aaf69f SL |
20 | |
21 | #![feature(box_patterns)] | |
1a4d82fc | 22 | #![feature(box_syntax)] |
85aaf69f | 23 | #![feature(libc)] |
85aaf69f | 24 | #![feature(rustc_private)] |
62682a34 | 25 | #![feature(set_stdio)] |
62682a34 | 26 | #![feature(slice_patterns)] |
85aaf69f | 27 | #![feature(staged_api)] |
85aaf69f SL |
28 | #![feature(test)] |
29 | #![feature(unicode)] | |
54a0048b | 30 | #![feature(question_mark)] |
1a4d82fc JJ |
31 | |
32 | extern crate arena; | |
33 | extern crate getopts; | |
34 | extern crate libc; | |
35 | extern crate rustc; | |
54a0048b | 36 | extern crate rustc_const_eval; |
1a4d82fc JJ |
37 | extern crate rustc_trans; |
38 | extern crate rustc_driver; | |
85aaf69f | 39 | extern crate rustc_resolve; |
c34b1796 AL |
40 | extern crate rustc_lint; |
41 | extern crate rustc_back; | |
92a42be0 | 42 | extern crate rustc_metadata; |
1a4d82fc | 43 | extern crate serialize; |
54a0048b | 44 | #[macro_use] extern crate syntax; |
c34b1796 | 45 | extern crate test as testing; |
d9579d0f | 46 | extern crate rustc_unicode; |
1a4d82fc JJ |
47 | #[macro_use] extern crate log; |
48 | ||
c34b1796 | 49 | extern crate serialize as rustc_serialize; // used by deriving |
1a4d82fc JJ |
50 | |
51 | use std::cell::RefCell; | |
52 | use std::collections::HashMap; | |
9cc50fc6 | 53 | use std::default::Default; |
85aaf69f | 54 | use std::env; |
54a0048b | 55 | use std::io::Read; |
c34b1796 | 56 | use std::path::PathBuf; |
62682a34 | 57 | use std::process; |
1a4d82fc | 58 | use std::rc::Rc; |
85aaf69f | 59 | use std::sync::mpsc::channel; |
c34b1796 | 60 | |
1a4d82fc | 61 | use externalfiles::ExternalHtml; |
1a4d82fc | 62 | use rustc::session::search_paths::SearchPaths; |
54a0048b | 63 | use rustc::session::config::{ErrorOutputType, RustcOptGroup, nightly_options}; |
1a4d82fc JJ |
64 | |
65 | #[macro_use] | |
66 | pub mod externalfiles; | |
67 | ||
68 | pub mod clean; | |
69 | pub mod core; | |
70 | pub mod doctree; | |
71 | pub mod fold; | |
72 | pub mod html { | |
73 | pub mod highlight; | |
74 | pub mod escape; | |
75 | pub mod item_type; | |
76 | pub mod format; | |
77 | pub mod layout; | |
78 | pub mod markdown; | |
79 | pub mod render; | |
80 | pub mod toc; | |
81 | } | |
82 | pub mod markdown; | |
83 | pub mod passes; | |
84 | pub mod plugins; | |
1a4d82fc JJ |
85 | pub mod visit_ast; |
86 | pub mod test; | |
87 | mod flock; | |
88 | ||
54a0048b SL |
89 | use clean::Attributes; |
90 | ||
1a4d82fc JJ |
91 | type Pass = (&'static str, // name |
92 | fn(clean::Crate) -> plugins::PluginResult, // fn | |
93 | &'static str); // description | |
94 | ||
c34b1796 | 95 | const PASSES: &'static [Pass] = &[ |
1a4d82fc JJ |
96 | ("strip-hidden", passes::strip_hidden, |
97 | "strips all doc(hidden) items from the output"), | |
98 | ("unindent-comments", passes::unindent_comments, | |
99 | "removes excess indentation on comments in order for markdown to like it"), | |
100 | ("collapse-docs", passes::collapse_docs, | |
101 | "concatenates all document attributes into one document attribute"), | |
102 | ("strip-private", passes::strip_private, | |
54a0048b SL |
103 | "strips all private items from a crate which cannot be seen externally, \ |
104 | implies strip-priv-imports"), | |
105 | ("strip-priv-imports", passes::strip_priv_imports, | |
106 | "strips all private import statements (`use`, `extern crate`) from a crate"), | |
1a4d82fc JJ |
107 | ]; |
108 | ||
c34b1796 | 109 | const DEFAULT_PASSES: &'static [&'static str] = &[ |
1a4d82fc JJ |
110 | "strip-hidden", |
111 | "strip-private", | |
112 | "collapse-docs", | |
113 | "unindent-comments", | |
114 | ]; | |
115 | ||
116 | thread_local!(pub static ANALYSISKEY: Rc<RefCell<Option<core::CrateAnalysis>>> = { | |
117 | Rc::new(RefCell::new(None)) | |
118 | }); | |
119 | ||
120 | struct Output { | |
121 | krate: clean::Crate, | |
1a4d82fc JJ |
122 | passes: Vec<String>, |
123 | } | |
124 | ||
125 | pub fn main() { | |
85aaf69f | 126 | const STACK_SIZE: usize = 32000000; // 32MB |
9346a6ac | 127 | let res = std::thread::Builder::new().stack_size(STACK_SIZE).spawn(move || { |
85aaf69f SL |
128 | let s = env::args().collect::<Vec<_>>(); |
129 | main_args(&s) | |
c1a9b12d | 130 | }).unwrap().join().unwrap_or(101); |
62682a34 | 131 | process::exit(res as i32); |
1a4d82fc JJ |
132 | } |
133 | ||
54a0048b SL |
134 | fn stable(g: getopts::OptGroup) -> RustcOptGroup { RustcOptGroup::stable(g) } |
135 | fn unstable(g: getopts::OptGroup) -> RustcOptGroup { RustcOptGroup::unstable(g) } | |
136 | ||
137 | pub fn opts() -> Vec<RustcOptGroup> { | |
1a4d82fc JJ |
138 | use getopts::*; |
139 | vec!( | |
54a0048b SL |
140 | stable(optflag("h", "help", "show this help message")), |
141 | stable(optflag("V", "version", "print rustdoc's version")), | |
142 | stable(optflag("v", "verbose", "use verbose output")), | |
143 | stable(optopt("r", "input-format", "the input type of the specified file", | |
144 | "[rust]")), | |
145 | stable(optopt("w", "output-format", "the output type to write", | |
146 | "[html]")), | |
147 | stable(optopt("o", "output", "where to place the output", "PATH")), | |
148 | stable(optopt("", "crate-name", "specify the name of this crate", "NAME")), | |
149 | stable(optmulti("L", "library-path", "directory to add to crate search path", | |
150 | "DIR")), | |
151 | stable(optmulti("", "cfg", "pass a --cfg to rustc", "")), | |
152 | stable(optmulti("", "extern", "pass an --extern to rustc", "NAME=PATH")), | |
153 | stable(optmulti("", "plugin-path", "directory to load plugins from", "DIR")), | |
154 | stable(optmulti("", "passes", | |
155 | "list of passes to also run, you might want \ | |
156 | to pass it multiple times; a value of `list` \ | |
157 | will print available passes", | |
158 | "PASSES")), | |
159 | stable(optmulti("", "plugins", "space separated list of plugins to also load", | |
160 | "PLUGINS")), | |
161 | stable(optflag("", "no-defaults", "don't run the default passes")), | |
162 | stable(optflag("", "test", "run code examples as tests")), | |
163 | stable(optmulti("", "test-args", "arguments to pass to the test runner", | |
164 | "ARGS")), | |
165 | stable(optopt("", "target", "target triple to document", "TRIPLE")), | |
166 | stable(optmulti("", "markdown-css", | |
167 | "CSS files to include via <link> in a rendered Markdown file", | |
168 | "FILES")), | |
169 | stable(optmulti("", "html-in-header", | |
170 | "files to include inline in the <head> section of a rendered Markdown file \ | |
171 | or generated documentation", | |
172 | "FILES")), | |
173 | stable(optmulti("", "html-before-content", | |
174 | "files to include inline between <body> and the content of a rendered \ | |
175 | Markdown file or generated documentation", | |
176 | "FILES")), | |
177 | stable(optmulti("", "html-after-content", | |
178 | "files to include inline between the content and </body> of a rendered \ | |
179 | Markdown file or generated documentation", | |
180 | "FILES")), | |
181 | stable(optopt("", "markdown-playground-url", | |
182 | "URL to send code snippets to", "URL")), | |
183 | stable(optflag("", "markdown-no-toc", "don't include table of contents")), | |
184 | unstable(optopt("e", "extend-css", | |
185 | "to redefine some css rules with a given file to generate doc with your \ | |
186 | own theme", "PATH")), | |
187 | unstable(optmulti("Z", "", | |
188 | "internal and debugging options (only on nightly build)", "FLAG")), | |
1a4d82fc JJ |
189 | ) |
190 | } | |
191 | ||
192 | pub fn usage(argv0: &str) { | |
193 | println!("{}", | |
85aaf69f | 194 | getopts::usage(&format!("{} [options] <input>", argv0), |
54a0048b SL |
195 | &opts().into_iter() |
196 | .map(|x| x.opt_group) | |
197 | .collect::<Vec<getopts::OptGroup>>())); | |
1a4d82fc JJ |
198 | } |
199 | ||
c34b1796 | 200 | pub fn main_args(args: &[String]) -> isize { |
54a0048b SL |
201 | let all_groups: Vec<getopts::OptGroup> = opts() |
202 | .into_iter() | |
203 | .map(|x| x.opt_group) | |
204 | .collect(); | |
205 | let matches = match getopts::getopts(&args[1..], &all_groups) { | |
1a4d82fc JJ |
206 | Ok(m) => m, |
207 | Err(err) => { | |
208 | println!("{}", err); | |
209 | return 1; | |
210 | } | |
211 | }; | |
54a0048b SL |
212 | // Check for unstable options. |
213 | nightly_options::check_nightly_options(&matches, &opts()); | |
214 | ||
1a4d82fc | 215 | if matches.opt_present("h") || matches.opt_present("help") { |
85aaf69f | 216 | usage(&args[0]); |
1a4d82fc JJ |
217 | return 0; |
218 | } else if matches.opt_present("version") { | |
219 | rustc_driver::version("rustdoc", &matches); | |
220 | return 0; | |
221 | } | |
222 | ||
223 | if matches.opt_strs("passes") == ["list"] { | |
224 | println!("Available passes for running rustdoc:"); | |
85aaf69f | 225 | for &(name, _, description) in PASSES { |
1a4d82fc JJ |
226 | println!("{:>20} - {}", name, description); |
227 | } | |
b039eaaf | 228 | println!("\nDefault passes for rustdoc:"); |
85aaf69f | 229 | for &name in DEFAULT_PASSES { |
1a4d82fc JJ |
230 | println!("{:>20}", name); |
231 | } | |
232 | return 0; | |
233 | } | |
234 | ||
9346a6ac | 235 | if matches.free.is_empty() { |
1a4d82fc JJ |
236 | println!("expected an input file to act on"); |
237 | return 1; | |
238 | } if matches.free.len() > 1 { | |
239 | println!("only one input file may be specified"); | |
240 | return 1; | |
241 | } | |
85aaf69f | 242 | let input = &matches.free[0]; |
1a4d82fc JJ |
243 | |
244 | let mut libs = SearchPaths::new(); | |
85aaf69f | 245 | for s in &matches.opt_strs("L") { |
9cc50fc6 | 246 | libs.add_path(s, ErrorOutputType::default()); |
1a4d82fc JJ |
247 | } |
248 | let externs = match parse_externs(&matches) { | |
249 | Ok(ex) => ex, | |
250 | Err(err) => { | |
251 | println!("{}", err); | |
252 | return 1; | |
253 | } | |
254 | }; | |
255 | ||
256 | let test_args = matches.opt_strs("test-args"); | |
257 | let test_args: Vec<String> = test_args.iter() | |
d9579d0f | 258 | .flat_map(|s| s.split_whitespace()) |
1a4d82fc JJ |
259 | .map(|s| s.to_string()) |
260 | .collect(); | |
261 | ||
262 | let should_test = matches.opt_present("test"); | |
263 | let markdown_input = input.ends_with(".md") || input.ends_with(".markdown"); | |
264 | ||
c34b1796 | 265 | let output = matches.opt_str("o").map(|s| PathBuf::from(&s)); |
54a0048b | 266 | let css_file_extension = matches.opt_str("e").map(|s| PathBuf::from(&s)); |
1a4d82fc JJ |
267 | let cfgs = matches.opt_strs("cfg"); |
268 | ||
54a0048b SL |
269 | if let Some(ref p) = css_file_extension { |
270 | if !p.is_file() { | |
271 | println!("{}", "--extend-css option must take a css file as input"); | |
272 | return 1; | |
273 | } | |
274 | } | |
275 | ||
1a4d82fc | 276 | let external_html = match ExternalHtml::load( |
85aaf69f SL |
277 | &matches.opt_strs("html-in-header"), |
278 | &matches.opt_strs("html-before-content"), | |
279 | &matches.opt_strs("html-after-content")) { | |
1a4d82fc JJ |
280 | Some(eh) => eh, |
281 | None => return 3 | |
282 | }; | |
283 | let crate_name = matches.opt_str("crate-name"); | |
284 | ||
285 | match (should_test, markdown_input) { | |
286 | (true, true) => { | |
92a42be0 | 287 | return markdown::test(input, cfgs, libs, externs, test_args) |
1a4d82fc JJ |
288 | } |
289 | (true, false) => { | |
290 | return test::run(input, cfgs, libs, externs, test_args, crate_name) | |
291 | } | |
c34b1796 AL |
292 | (false, true) => return markdown::render(input, |
293 | output.unwrap_or(PathBuf::from("doc")), | |
1a4d82fc JJ |
294 | &matches, &external_html, |
295 | !matches.opt_present("markdown-no-toc")), | |
296 | (false, false) => {} | |
297 | } | |
1a4d82fc JJ |
298 | let out = match acquire_input(input, externs, &matches) { |
299 | Ok(out) => out, | |
300 | Err(s) => { | |
301 | println!("input error: {}", s); | |
302 | return 1; | |
303 | } | |
304 | }; | |
54a0048b | 305 | let Output { krate, passes, } = out; |
1a4d82fc | 306 | info!("going to format"); |
85aaf69f | 307 | match matches.opt_str("w").as_ref().map(|s| &**s) { |
1a4d82fc | 308 | Some("html") | None => { |
54a0048b SL |
309 | html::render::run(krate, &external_html, |
310 | output.unwrap_or(PathBuf::from("doc")), | |
311 | passes.into_iter().collect(), | |
312 | css_file_extension) | |
313 | .expect("failed to generate documentation") | |
1a4d82fc JJ |
314 | } |
315 | Some(s) => { | |
316 | println!("unknown output format: {}", s); | |
317 | return 1; | |
318 | } | |
319 | } | |
320 | ||
321 | return 0; | |
322 | } | |
323 | ||
324 | /// Looks inside the command line arguments to extract the relevant input format | |
325 | /// and files and then generates the necessary rustdoc output for formatting. | |
326 | fn acquire_input(input: &str, | |
327 | externs: core::Externs, | |
328 | matches: &getopts::Matches) -> Result<Output, String> { | |
85aaf69f | 329 | match matches.opt_str("r").as_ref().map(|s| &**s) { |
1a4d82fc | 330 | Some("rust") => Ok(rust_input(input, externs, matches)), |
1a4d82fc JJ |
331 | Some(s) => Err(format!("unknown input format: {}", s)), |
332 | None => { | |
54a0048b | 333 | Ok(rust_input(input, externs, matches)) |
1a4d82fc JJ |
334 | } |
335 | } | |
336 | } | |
337 | ||
338 | /// Extracts `--extern CRATE=PATH` arguments from `matches` and | |
339 | /// returns a `HashMap` mapping crate names to their paths or else an | |
340 | /// error message. | |
341 | fn parse_externs(matches: &getopts::Matches) -> Result<core::Externs, String> { | |
342 | let mut externs = HashMap::new(); | |
85aaf69f | 343 | for arg in &matches.opt_strs("extern") { |
c34b1796 | 344 | let mut parts = arg.splitn(2, '='); |
54a0048b SL |
345 | let name = parts.next().ok_or("--extern value must not be empty".to_string())?; |
346 | let location = parts.next() | |
347 | .ok_or("--extern value must be of the format `foo=bar`" | |
348 | .to_string())?; | |
1a4d82fc | 349 | let name = name.to_string(); |
c34b1796 | 350 | externs.entry(name).or_insert(vec![]).push(location.to_string()); |
1a4d82fc JJ |
351 | } |
352 | Ok(externs) | |
353 | } | |
354 | ||
355 | /// Interprets the input file as a rust source file, passing it through the | |
356 | /// compiler all the way through the analysis passes. The rustdoc output is then | |
357 | /// generated from the cleaned AST of the crate. | |
358 | /// | |
359 | /// This form of input will run all of the plug/cleaning passes | |
360 | fn rust_input(cratefile: &str, externs: core::Externs, matches: &getopts::Matches) -> Output { | |
361 | let mut default_passes = !matches.opt_present("no-defaults"); | |
362 | let mut passes = matches.opt_strs("passes"); | |
363 | let mut plugins = matches.opt_strs("plugins"); | |
364 | ||
365 | // First, parse the crate and extract all relevant information. | |
366 | let mut paths = SearchPaths::new(); | |
85aaf69f | 367 | for s in &matches.opt_strs("L") { |
9cc50fc6 | 368 | paths.add_path(s, ErrorOutputType::default()); |
1a4d82fc JJ |
369 | } |
370 | let cfgs = matches.opt_strs("cfg"); | |
371 | let triple = matches.opt_str("target"); | |
372 | ||
c34b1796 | 373 | let cr = PathBuf::from(cratefile); |
1a4d82fc JJ |
374 | info!("starting to run rustc"); |
375 | ||
85aaf69f | 376 | let (tx, rx) = channel(); |
c1a9b12d | 377 | rustc_driver::monitor(move || { |
85aaf69f SL |
378 | use rustc::session::config::Input; |
379 | ||
c34b1796 AL |
380 | tx.send(core::run_core(paths, cfgs, externs, Input::File(cr), |
381 | triple)).unwrap(); | |
c1a9b12d | 382 | }); |
85aaf69f | 383 | let (mut krate, analysis) = rx.recv().unwrap(); |
1a4d82fc JJ |
384 | info!("finished with rustc"); |
385 | let mut analysis = Some(analysis); | |
386 | ANALYSISKEY.with(|s| { | |
387 | *s.borrow_mut() = analysis.take(); | |
388 | }); | |
389 | ||
7453a54e SL |
390 | if let Some(name) = matches.opt_str("crate-name") { |
391 | krate.name = name | |
1a4d82fc JJ |
392 | } |
393 | ||
394 | // Process all of the crate attributes, extracting plugin metadata along | |
395 | // with the passes which we are supposed to run. | |
54a0048b SL |
396 | for attr in krate.module.as_ref().unwrap().attrs.list("doc") { |
397 | match *attr { | |
398 | clean::Word(ref w) if "no_default_passes" == *w => { | |
399 | default_passes = false; | |
400 | }, | |
401 | clean::NameValue(ref name, ref value) => { | |
402 | let sink = match &name[..] { | |
403 | "passes" => &mut passes, | |
404 | "plugins" => &mut plugins, | |
405 | _ => continue, | |
406 | }; | |
407 | for p in value.split_whitespace() { | |
408 | sink.push(p.to_string()); | |
1a4d82fc JJ |
409 | } |
410 | } | |
54a0048b | 411 | _ => (), |
1a4d82fc | 412 | } |
1a4d82fc | 413 | } |
54a0048b | 414 | |
1a4d82fc JJ |
415 | if default_passes { |
416 | for name in DEFAULT_PASSES.iter().rev() { | |
417 | passes.insert(0, name.to_string()); | |
418 | } | |
419 | } | |
420 | ||
421 | // Load all plugins/passes into a PluginManager | |
422 | let path = matches.opt_str("plugin-path") | |
423 | .unwrap_or("/tmp/rustdoc/plugins".to_string()); | |
c34b1796 | 424 | let mut pm = plugins::PluginManager::new(PathBuf::from(path)); |
85aaf69f | 425 | for pass in &passes { |
1a4d82fc JJ |
426 | let plugin = match PASSES.iter() |
427 | .position(|&(p, _, _)| { | |
428 | p == *pass | |
429 | }) { | |
430 | Some(i) => PASSES[i].1, | |
431 | None => { | |
432 | error!("unknown pass {}, skipping", *pass); | |
433 | continue | |
434 | }, | |
435 | }; | |
436 | pm.add_plugin(plugin); | |
437 | } | |
438 | info!("loading plugins..."); | |
85aaf69f | 439 | for pname in plugins { |
1a4d82fc JJ |
440 | pm.load_plugin(pname); |
441 | } | |
442 | ||
443 | // Run everything! | |
444 | info!("Executing passes/plugins"); | |
54a0048b SL |
445 | let krate = pm.run_plugins(krate); |
446 | Output { krate: krate, passes: passes } | |
1a4d82fc | 447 | } |