about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/config.rs560
-rw-r--r--src/librustdoc/core.rs77
-rw-r--r--src/librustdoc/externalfiles.rs2
-rw-r--r--src/librustdoc/html/markdown.rs2
-rw-r--r--src/librustdoc/html/render.rs89
-rw-r--r--src/librustdoc/lib.rs436
-rw-r--r--src/librustdoc/markdown.rs54
-rw-r--r--src/librustdoc/test.rs59
-rw-r--r--src/test/rustdoc-ui/failed-doctest-output.stdout4
9 files changed, 729 insertions, 554 deletions
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
new file mode 100644
index 00000000000..903aafed641
--- /dev/null
+++ b/src/librustdoc/config.rs
@@ -0,0 +1,560 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::collections::{BTreeMap, BTreeSet};
+use std::fmt;
+use std::path::PathBuf;
+
+use errors;
+use errors::emitter::ColorConfig;
+use getopts;
+use rustc::lint::Level;
+use rustc::session::early_error;
+use rustc::session::config::{CodegenOptions, DebuggingOptions, ErrorOutputType, Externs};
+use rustc::session::config::{nightly_options, build_codegen_options, build_debugging_options,
+                             get_cmd_lint_options};
+use rustc::session::search_paths::SearchPaths;
+use rustc_driver;
+use rustc_target::spec::TargetTriple;
+use syntax::edition::Edition;
+
+use core::new_handler;
+use externalfiles::ExternalHtml;
+use html;
+use html::markdown::IdMap;
+use opts;
+use passes::{self, DefaultPassOption};
+use theme;
+
+/// Configuration options for rustdoc.
+#[derive(Clone)]
+pub struct Options {
+    // Basic options / Options passed directly to rustc
+
+    /// The crate root or Markdown file to load.
+    pub input: PathBuf,
+    /// The name of the crate being documented.
+    pub crate_name: Option<String>,
+    /// How to format errors and warnings.
+    pub error_format: ErrorOutputType,
+    /// Library search paths to hand to the compiler.
+    pub libs: SearchPaths,
+    /// The list of external crates to link against.
+    pub externs: Externs,
+    /// List of `cfg` flags to hand to the compiler. Always includes `rustdoc`.
+    pub cfgs: Vec<String>,
+    /// Codegen options to hand to the compiler.
+    pub codegen_options: CodegenOptions,
+    /// Debugging (`-Z`) options to pass to the compiler.
+    pub debugging_options: DebuggingOptions,
+    /// The target used to compile the crate against.
+    pub target: Option<TargetTriple>,
+    /// Edition used when reading the crate. Defaults to "2015". Also used by default when
+    /// compiling doctests from the crate.
+    pub edition: Edition,
+    /// The path to the sysroot. Used during the compilation process.
+    pub maybe_sysroot: Option<PathBuf>,
+    /// Linker to use when building doctests.
+    pub linker: Option<PathBuf>,
+    /// Lint information passed over the command-line.
+    pub lint_opts: Vec<(String, Level)>,
+    /// Whether to ask rustc to describe the lints it knows. Practically speaking, this will not be
+    /// used, since we abort if we have no input file, but it's included for completeness.
+    pub describe_lints: bool,
+    /// What level to cap lints at.
+    pub lint_cap: Option<Level>,
+
+    // Options specific to running doctests
+
+    /// Whether we should run doctests instead of generating docs.
+    pub should_test: bool,
+    /// List of arguments to pass to the test harness, if running tests.
+    pub test_args: Vec<String>,
+
+    // Options that affect the documentation process
+
+    /// The selected default set of passes to use.
+    ///
+    /// Be aware: This option can come both from the CLI and from crate attributes!
+    pub default_passes: DefaultPassOption,
+    /// Any passes manually selected by the user.
+    ///
+    /// Be aware: This option can come both from the CLI and from crate attributes!
+    pub manual_passes: Vec<String>,
+    /// Whether to display warnings during doc generation or while gathering doctests. By default,
+    /// all non-rustdoc-specific lints are allowed when generating docs.
+    pub display_warnings: bool,
+
+    // Options that alter generated documentation pages
+
+    /// Crate version to note on the sidebar of generated docs.
+    pub crate_version: Option<String>,
+    /// Collected options specific to outputting final pages.
+    pub render_options: RenderOptions,
+}
+
+impl fmt::Debug for Options {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        struct FmtExterns<'a>(&'a Externs);
+
+        impl<'a> fmt::Debug for FmtExterns<'a> {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                f.debug_map()
+                    .entries(self.0.iter())
+                    .finish()
+            }
+        }
+
+        f.debug_struct("Options")
+            .field("input", &self.input)
+            .field("crate_name", &self.crate_name)
+            .field("error_format", &self.error_format)
+            .field("libs", &self.libs)
+            .field("externs", &FmtExterns(&self.externs))
+            .field("cfgs", &self.cfgs)
+            .field("codegen_options", &"...")
+            .field("debugging_options", &"...")
+            .field("target", &self.target)
+            .field("edition", &self.edition)
+            .field("maybe_sysroot", &self.maybe_sysroot)
+            .field("linker", &self.linker)
+            .field("lint_opts", &self.lint_opts)
+            .field("describe_lints", &self.describe_lints)
+            .field("lint_cap", &self.lint_cap)
+            .field("should_test", &self.should_test)
+            .field("test_args", &self.test_args)
+            .field("default_passes", &self.default_passes)
+            .field("manual_passes", &self.manual_passes)
+            .field("display_warnings", &self.display_warnings)
+            .field("crate_version", &self.crate_version)
+            .field("render_options", &self.render_options)
+            .finish()
+    }
+}
+
+/// Configuration options for the HTML page-creation process.
+#[derive(Clone, Debug)]
+pub struct RenderOptions {
+    /// Output directory to generate docs into. Defaults to `doc`.
+    pub output: PathBuf,
+    /// External files to insert into generated pages.
+    pub external_html: ExternalHtml,
+    /// A pre-populated `IdMap` with the default headings and any headings added by Markdown files
+    /// processed by `external_html`.
+    pub id_map: IdMap,
+    /// If present, playground URL to use in the "Run" button added to code samples.
+    ///
+    /// Be aware: This option can come both from the CLI and from crate attributes!
+    pub playground_url: Option<String>,
+    /// Whether to sort modules alphabetically on a module page instead of using declaration order.
+    /// `true` by default.
+    ///
+    /// FIXME(misdreavus): the flag name is `--sort-modules-by-appearance` but the meaning is
+    /// inverted once read
+    pub sort_modules_alphabetically: bool,
+    /// List of themes to extend the docs with. Original argument name is included to assist in
+    /// displaying errors if it fails a theme check.
+    pub themes: Vec<PathBuf>,
+    /// If present, CSS file that contains rules to add to the default CSS.
+    pub extension_css: Option<PathBuf>,
+    /// A map of crate names to the URL to use instead of querying the crate's `html_root_url`.
+    pub extern_html_root_urls: BTreeMap<String, String>,
+    /// If present, suffix added to CSS/JavaScript files when referencing them in generated pages.
+    pub resource_suffix: String,
+    /// Whether to run the static CSS/JavaScript through a minifier when outputting them. `true` by
+    /// default.
+    ///
+    /// FIXME(misdreavus): the flag name is `--disable-minification` but the meaning is inverted
+    /// once read
+    pub enable_minification: bool,
+    /// Whether to create an index page in the root of the output directory. If this is true but
+    /// `enable_index_page` is None, generate a static listing of crates instead.
+    pub enable_index_page: bool,
+    /// A file to use as the index page at the root of the output directory. Overrides
+    /// `enable_index_page` to be true if set.
+    pub index_page: Option<PathBuf>,
+
+    // Options specific to reading standalone Markdown files
+
+    /// Whether to generate a table of contents on the output file when reading a standalone
+    /// Markdown file.
+    pub markdown_no_toc: bool,
+    /// Additional CSS files to link in pages generated from standlone Markdown files.
+    pub markdown_css: Vec<String>,
+    /// If present, playground URL to use in the "Run" button added to code samples generated from
+    /// standalone Markdown files. If not present, `playground_url` is used.
+    pub markdown_playground_url: Option<String>,
+}
+
+impl Options {
+    /// Parses the given command-line for options. If an error message or other early-return has
+    /// been printed, returns `Err` with the exit code.
+    pub fn from_matches(matches: &getopts::Matches) -> Result<Options, isize> {
+        // Check for unstable options.
+        nightly_options::check_nightly_options(&matches, &opts());
+
+        if matches.opt_present("h") || matches.opt_present("help") {
+            ::usage("rustdoc");
+            return Err(0);
+        } else if matches.opt_present("version") {
+            rustc_driver::version("rustdoc", &matches);
+            return Err(0);
+        }
+
+        if matches.opt_strs("passes") == ["list"] {
+            println!("Available passes for running rustdoc:");
+            for pass in passes::PASSES {
+                println!("{:>20} - {}", pass.name(), pass.description());
+            }
+            println!("\nDefault passes for rustdoc:");
+            for &name in passes::DEFAULT_PASSES {
+                println!("{:>20}", name);
+            }
+            println!("\nPasses run with `--document-private-items`:");
+            for &name in passes::DEFAULT_PRIVATE_PASSES {
+                println!("{:>20}", name);
+            }
+            return Err(0);
+        }
+
+        let color = match matches.opt_str("color").as_ref().map(|s| &s[..]) {
+            Some("auto") => ColorConfig::Auto,
+            Some("always") => ColorConfig::Always,
+            Some("never") => ColorConfig::Never,
+            None => ColorConfig::Auto,
+            Some(arg) => {
+                early_error(ErrorOutputType::default(),
+                            &format!("argument for --color must be `auto`, `always` or `never` \
+                                      (instead was `{}`)", arg));
+            }
+        };
+        let error_format = match matches.opt_str("error-format").as_ref().map(|s| &s[..]) {
+            Some("human") => ErrorOutputType::HumanReadable(color),
+            Some("json") => ErrorOutputType::Json(false),
+            Some("pretty-json") => ErrorOutputType::Json(true),
+            Some("short") => ErrorOutputType::Short(color),
+            None => ErrorOutputType::HumanReadable(color),
+            Some(arg) => {
+                early_error(ErrorOutputType::default(),
+                            &format!("argument for --error-format must be `human`, `json` or \
+                                      `short` (instead was `{}`)", arg));
+            }
+        };
+
+        let codegen_options = build_codegen_options(matches, error_format);
+        let debugging_options = build_debugging_options(matches, error_format);
+
+        let diag = new_handler(error_format,
+                               None,
+                               debugging_options.treat_err_as_bug,
+                               debugging_options.ui_testing);
+
+        // check for deprecated options
+        check_deprecated_options(&matches, &diag);
+
+        let to_check = matches.opt_strs("theme-checker");
+        if !to_check.is_empty() {
+            let paths = theme::load_css_paths(include_bytes!("html/static/themes/light.css"));
+            let mut errors = 0;
+
+            println!("rustdoc: [theme-checker] Starting tests!");
+            for theme_file in to_check.iter() {
+                print!(" - Checking \"{}\"...", theme_file);
+                let (success, differences) = theme::test_theme_against(theme_file, &paths, &diag);
+                if !differences.is_empty() || !success {
+                    println!(" FAILED");
+                    errors += 1;
+                    if !differences.is_empty() {
+                        println!("{}", differences.join("\n"));
+                    }
+                } else {
+                    println!(" OK");
+                }
+            }
+            if errors != 0 {
+                return Err(1);
+            }
+            return Err(0);
+        }
+
+        if matches.free.is_empty() {
+            diag.struct_err("missing file operand").emit();
+            return Err(1);
+        }
+        if matches.free.len() > 1 {
+            diag.struct_err("too many file operands").emit();
+            return Err(1);
+        }
+        let input = PathBuf::from(&matches.free[0]);
+
+        let mut libs = SearchPaths::new();
+        for s in &matches.opt_strs("L") {
+            libs.add_path(s, error_format);
+        }
+        let externs = match parse_externs(&matches) {
+            Ok(ex) => ex,
+            Err(err) => {
+                diag.struct_err(&err).emit();
+                return Err(1);
+            }
+        };
+        let extern_html_root_urls = match parse_extern_html_roots(&matches) {
+            Ok(ex) => ex,
+            Err(err) => {
+                diag.struct_err(err).emit();
+                return Err(1);
+            }
+        };
+
+        let test_args = matches.opt_strs("test-args");
+        let test_args: Vec<String> = test_args.iter()
+                                              .flat_map(|s| s.split_whitespace())
+                                              .map(|s| s.to_string())
+                                              .collect();
+
+        let should_test = matches.opt_present("test");
+
+        let output = matches.opt_str("o")
+                            .map(|s| PathBuf::from(&s))
+                            .unwrap_or_else(|| PathBuf::from("doc"));
+        let mut cfgs = matches.opt_strs("cfg");
+        cfgs.push("rustdoc".to_string());
+
+        let extension_css = matches.opt_str("e").map(|s| PathBuf::from(&s));
+
+        if let Some(ref p) = extension_css {
+            if !p.is_file() {
+                diag.struct_err("option --extend-css argument must be a file").emit();
+                return Err(1);
+            }
+        }
+
+        let mut themes = Vec::new();
+        if matches.opt_present("themes") {
+            let paths = theme::load_css_paths(include_bytes!("html/static/themes/light.css"));
+
+            for (theme_file, theme_s) in matches.opt_strs("themes")
+                                                .iter()
+                                                .map(|s| (PathBuf::from(&s), s.to_owned())) {
+                if !theme_file.is_file() {
+                    diag.struct_err("option --themes arguments must all be files").emit();
+                    return Err(1);
+                }
+                let (success, ret) = theme::test_theme_against(&theme_file, &paths, &diag);
+                if !success || !ret.is_empty() {
+                    diag.struct_err(&format!("invalid theme: \"{}\"", theme_s))
+                        .help("check what's wrong with the --theme-checker option")
+                        .emit();
+                    return Err(1);
+                }
+                themes.push(theme_file);
+            }
+        }
+
+        let mut id_map = html::markdown::IdMap::new();
+        id_map.populate(html::render::initial_ids());
+        let external_html = match ExternalHtml::load(
+                &matches.opt_strs("html-in-header"),
+                &matches.opt_strs("html-before-content"),
+                &matches.opt_strs("html-after-content"),
+                &matches.opt_strs("markdown-before-content"),
+                &matches.opt_strs("markdown-after-content"), &diag, &mut id_map) {
+            Some(eh) => eh,
+            None => return Err(3),
+        };
+
+        let edition = matches.opt_str("edition").unwrap_or("2015".to_string());
+        let edition = match edition.parse() {
+            Ok(e) => e,
+            Err(_) => {
+                diag.struct_err("could not parse edition").emit();
+                return Err(1);
+            }
+        };
+
+        match matches.opt_str("r").as_ref().map(|s| &**s) {
+            Some("rust") | None => {}
+            Some(s) => {
+                diag.struct_err(&format!("unknown input format: {}", s)).emit();
+                return Err(1);
+            }
+        }
+
+        match matches.opt_str("w").as_ref().map(|s| &**s) {
+            Some("html") | None => {}
+            Some(s) => {
+                diag.struct_err(&format!("unknown output format: {}", s)).emit();
+                return Err(1);
+            }
+        }
+
+        let index_page = matches.opt_str("index-page").map(|s| PathBuf::from(&s));
+        if let Some(ref index_page) = index_page {
+            if !index_page.is_file() {
+                diag.struct_err("option `--index-page` argument must be a file").emit();
+                return Err(1);
+            }
+        }
+
+        let target = matches.opt_str("target").map(|target| {
+            if target.ends_with(".json") {
+                TargetTriple::TargetPath(PathBuf::from(target))
+            } else {
+                TargetTriple::TargetTriple(target)
+            }
+        });
+
+        let default_passes = if matches.opt_present("no-defaults") {
+            passes::DefaultPassOption::None
+        } else if matches.opt_present("document-private-items") {
+            passes::DefaultPassOption::Private
+        } else {
+            passes::DefaultPassOption::Default
+        };
+        let manual_passes = matches.opt_strs("passes");
+
+        let crate_name = matches.opt_str("crate-name");
+        let playground_url = matches.opt_str("playground-url");
+        let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from);
+        let display_warnings = matches.opt_present("display-warnings");
+        let linker = matches.opt_str("linker").map(PathBuf::from);
+        let sort_modules_alphabetically = !matches.opt_present("sort-modules-by-appearance");
+        let resource_suffix = matches.opt_str("resource-suffix").unwrap_or_default();
+        let enable_minification = !matches.opt_present("disable-minification");
+        let markdown_no_toc = matches.opt_present("markdown-no-toc");
+        let markdown_css = matches.opt_strs("markdown-css");
+        let markdown_playground_url = matches.opt_str("markdown-playground-url");
+        let crate_version = matches.opt_str("crate-version");
+        let enable_index_page = matches.opt_present("enable-index-page") || index_page.is_some();
+
+        let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
+
+        Ok(Options {
+            input,
+            crate_name,
+            error_format,
+            libs,
+            externs,
+            cfgs,
+            codegen_options,
+            debugging_options,
+            target,
+            edition,
+            maybe_sysroot,
+            linker,
+            lint_opts,
+            describe_lints,
+            lint_cap,
+            should_test,
+            test_args,
+            default_passes,
+            manual_passes,
+            display_warnings,
+            crate_version,
+            render_options: RenderOptions {
+                output,
+                external_html,
+                id_map,
+                playground_url,
+                sort_modules_alphabetically,
+                themes,
+                extension_css,
+                extern_html_root_urls,
+                resource_suffix,
+                enable_minification,
+                enable_index_page,
+                index_page,
+                markdown_no_toc,
+                markdown_css,
+                markdown_playground_url,
+            }
+        })
+    }
+
+    /// Returns whether the file given as `self.input` is a Markdown file.
+    pub fn markdown_input(&self) -> bool {
+        self.input.extension()
+            .map_or(false, |e| e == "md" || e == "markdown")
+    }
+}
+
+/// Prints deprecation warnings for deprecated options
+fn check_deprecated_options(matches: &getopts::Matches, diag: &errors::Handler) {
+    let deprecated_flags = [
+       "input-format",
+       "output-format",
+       "no-defaults",
+       "passes",
+    ];
+
+    for flag in deprecated_flags.into_iter() {
+        if matches.opt_present(flag) {
+            let mut err = diag.struct_warn(&format!("the '{}' flag is considered deprecated",
+                                                    flag));
+            err.warn("please see https://github.com/rust-lang/rust/issues/44136");
+
+            if *flag == "no-defaults" {
+                err.help("you may want to use --document-private-items");
+            }
+
+            err.emit();
+        }
+    }
+
+    let removed_flags = [
+        "plugins",
+        "plugin-path",
+    ];
+
+    for &flag in removed_flags.iter() {
+        if matches.opt_present(flag) {
+            diag.struct_warn(&format!("the '{}' flag no longer functions", flag))
+                .warn("see CVE-2018-1000622")
+                .emit();
+        }
+    }
+}
+
+/// Extracts `--extern-html-root-url` arguments from `matches` and returns a map of crate names to
+/// the given URLs. If an `--extern-html-root-url` argument was ill-formed, returns an error
+/// describing the issue.
+fn parse_extern_html_roots(
+    matches: &getopts::Matches,
+) -> Result<BTreeMap<String, String>, &'static str> {
+    let mut externs = BTreeMap::new();
+    for arg in &matches.opt_strs("extern-html-root-url") {
+        let mut parts = arg.splitn(2, '=');
+        let name = parts.next().ok_or("--extern-html-root-url must not be empty")?;
+        let url = parts.next().ok_or("--extern-html-root-url must be of the form name=url")?;
+        externs.insert(name.to_string(), url.to_string());
+    }
+
+    Ok(externs)
+}
+
+/// Extracts `--extern CRATE=PATH` arguments from `matches` and
+/// returns a map mapping crate names to their paths or else an
+/// error message.
+// FIXME(eddyb) This shouldn't be duplicated with `rustc::session`.
+fn parse_externs(matches: &getopts::Matches) -> Result<Externs, String> {
+    let mut externs: BTreeMap<_, BTreeSet<_>> = BTreeMap::new();
+    for arg in &matches.opt_strs("extern") {
+        let mut parts = arg.splitn(2, '=');
+        let name = parts.next().ok_or("--extern value must not be empty".to_string())?;
+        let location = parts.next().map(|s| s.to_string());
+        if location.is_none() && !nightly_options::is_unstable_enabled(matches) {
+            return Err("the `-Z unstable-options` flag must also be passed to \
+                        enable `--extern crate_name` without `=path`".to_string());
+        }
+        let name = name.to_string();
+        externs.entry(name).or_default().insert(location);
+    }
+    Ok(Externs::new(externs))
+}
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index d6b0127e44d..0bd6f6bf8a2 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -28,7 +28,6 @@ use rustc_target::spec::TargetTriple;
 
 use syntax::ast::{self, Ident, NodeId};
 use syntax::source_map;
-use syntax::edition::Edition;
 use syntax::feature_gate::UnstableFeatures;
 use syntax::json::JsonEmitter;
 use syntax::ptr::P;
@@ -43,9 +42,9 @@ use std::mem;
 use rustc_data_structures::sync::{self, Lrc};
 use std::rc::Rc;
 use std::sync::Arc;
-use std::path::PathBuf;
 
 use visit_ast::RustdocVisitor;
+use config::{Options as RustdocOptions, RenderOptions};
 use clean;
 use clean::{get_path_for_type, Clean, MAX_DEF_ID, AttributesExt};
 use html::render::RenderInfo;
@@ -320,32 +319,33 @@ pub fn new_handler(error_format: ErrorOutputType,
     )
 }
 
-pub fn run_core(search_paths: SearchPaths,
-                cfgs: Vec<String>,
-                externs: config::Externs,
-                input: Input,
-                triple: Option<TargetTriple>,
-                maybe_sysroot: Option<PathBuf>,
-                allow_warnings: bool,
-                crate_name: Option<String>,
-                force_unstable_if_unmarked: bool,
-                edition: Edition,
-                cg: CodegenOptions,
-                error_format: ErrorOutputType,
-                cmd_lints: Vec<(String, lint::Level)>,
-                lint_cap: Option<lint::Level>,
-                describe_lints: bool,
-                mut manual_passes: Vec<String>,
-                mut default_passes: passes::DefaultPassOption,
-                treat_err_as_bug: bool,
-                ui_testing: bool,
-) -> (clean::Crate, RenderInfo, Vec<String>) {
+pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOptions, Vec<String>) {
     // Parse, resolve, and typecheck the given crate.
 
-    let cpath = match input {
-        Input::File(ref p) => Some(p.clone()),
-        _ => None
-    };
+    let RustdocOptions {
+        input,
+        crate_name,
+        error_format,
+        libs,
+        externs,
+        cfgs,
+        codegen_options,
+        debugging_options,
+        target,
+        edition,
+        maybe_sysroot,
+        lint_opts,
+        describe_lints,
+        lint_cap,
+        mut default_passes,
+        mut manual_passes,
+        display_warnings,
+        render_options,
+        ..
+    } = options;
+
+    let cpath = Some(input.clone());
+    let input = Input::File(input);
 
     let intra_link_resolution_failure_name = lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE.name;
     let warnings_lint_name = lint::builtin::WARNINGS.name;
@@ -359,7 +359,7 @@ pub fn run_core(search_paths: SearchPaths,
                                      missing_docs.to_owned(),
                                      missing_doc_example.to_owned()];
 
-    whitelisted_lints.extend(cmd_lints.iter().map(|(lint, _)| lint).cloned());
+    whitelisted_lints.extend(lint_opts.iter().map(|(lint, _)| lint).cloned());
 
     let lints = lint::builtin::HardwiredLints.get_lints()
                     .into_iter()
@@ -372,33 +372,28 @@ pub fn run_core(search_paths: SearchPaths,
                             Some((lint.name_lower(), lint::Allow))
                         }
                     })
-                    .chain(cmd_lints.into_iter())
+                    .chain(lint_opts.into_iter())
                     .collect::<Vec<_>>();
 
     let host_triple = TargetTriple::from_triple(config::host_triple());
     // plays with error output here!
     let sessopts = config::Options {
         maybe_sysroot,
-        search_paths,
+        search_paths: libs,
         crate_types: vec![config::CrateType::Rlib],
-        lint_opts: if !allow_warnings {
+        lint_opts: if !display_warnings {
             lints
         } else {
             vec![]
         },
         lint_cap: Some(lint_cap.unwrap_or_else(|| lint::Forbid)),
-        cg,
+        cg: codegen_options,
         externs,
-        target_triple: triple.unwrap_or(host_triple),
+        target_triple: target.unwrap_or(host_triple),
         // Ensure that rustdoc works even if rustc is feature-staged
         unstable_features: UnstableFeatures::Allow,
         actually_rustdoc: true,
-        debugging_opts: config::DebuggingOptions {
-            force_unstable_if_unmarked,
-            treat_err_as_bug,
-            ui_testing,
-            ..config::basic_debugging_options()
-        },
+        debugging_opts: debugging_options.clone(),
         error_format,
         edition,
         describe_lints,
@@ -408,8 +403,8 @@ pub fn run_core(search_paths: SearchPaths,
         let source_map = Lrc::new(source_map::SourceMap::new(sessopts.file_path_mapping()));
         let diagnostic_handler = new_handler(error_format,
                                              Some(source_map.clone()),
-                                             treat_err_as_bug,
-                                             ui_testing);
+                                             debugging_options.treat_err_as_bug,
+                                             debugging_options.ui_testing);
 
         let mut sess = session::build_session_(
             sessopts, cpath, diagnostic_handler, source_map,
@@ -621,7 +616,7 @@ pub fn run_core(search_paths: SearchPaths,
 
             ctxt.sess().abort_if_errors();
 
-            (krate, ctxt.renderinfo.into_inner(), passes)
+            (krate, ctxt.renderinfo.into_inner(), render_options, passes)
         }), &sess)
     })
 }
diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs
index 9631ea059cc..c7a2dd6da3f 100644
--- a/src/librustdoc/externalfiles.rs
+++ b/src/librustdoc/externalfiles.rs
@@ -16,7 +16,7 @@ use syntax::feature_gate::UnstableFeatures;
 use html::markdown::{IdMap, ErrorCodes, Markdown};
 use std::cell::RefCell;
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct ExternalHtml {
     /// Content that will be included inline in the <head> section of a
     /// rendered Markdown file or generated documentation
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 22fa887c358..649a5c7ff33 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -905,7 +905,7 @@ pub fn markdown_links(md: &str) -> Vec<(String, Option<Range<usize>>)> {
     links
 }
 
-#[derive(Default)]
+#[derive(Clone, Default, Debug)]
 pub struct IdMap {
     map: FxHashMap<String, usize>,
 }
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index 8ba299d2298..efd71ad0763 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -52,11 +52,7 @@ use std::str;
 use std::sync::Arc;
 use std::rc::Rc;
 
-use externalfiles::ExternalHtml;
-
 use errors;
-use getopts;
-
 use serialize::json::{ToJson, Json, as_json};
 use syntax::ast;
 use syntax::ext::base::MacroKind;
@@ -70,6 +66,7 @@ use rustc::util::nodemap::{FxHashMap, FxHashSet};
 use rustc_data_structures::flock;
 
 use clean::{self, AttributesExt, GetDefId, SelfTy, Mutability};
+use config::RenderOptions;
 use doctree;
 use fold::DocFolder;
 use html::escape::Escape;
@@ -109,8 +106,6 @@ struct Context {
     /// The map used to ensure all generated 'id=' attributes are unique.
     id_map: Rc<RefCell<IdMap>>,
     pub shared: Arc<SharedContext>,
-    pub enable_index_page: bool,
-    pub index_page: Option<PathBuf>,
 }
 
 struct SharedContext {
@@ -495,23 +490,25 @@ pub fn initial_ids() -> Vec<String> {
 
 /// Generates the documentation for `crate` into the directory `dst`
 pub fn run(mut krate: clean::Crate,
-           extern_urls: BTreeMap<String, String>,
-           external_html: &ExternalHtml,
-           playground_url: Option<String>,
-           dst: PathBuf,
-           resource_suffix: String,
+           options: RenderOptions,
            passes: FxHashSet<String>,
-           css_file_extension: Option<PathBuf>,
            renderinfo: RenderInfo,
-           sort_modules_alphabetically: bool,
-           themes: Vec<PathBuf>,
-           enable_minification: bool,
-           id_map: IdMap,
-           enable_index_page: bool,
-           index_page: Option<PathBuf>,
-           matches: &getopts::Matches,
-           diag: &errors::Handler,
-) -> Result<(), Error> {
+           diag: &errors::Handler) -> Result<(), Error> {
+    // need to save a copy of the options for rendering the index page
+    let md_opts = options.clone();
+    let RenderOptions {
+        output,
+        external_html,
+        id_map,
+        playground_url,
+        sort_modules_alphabetically,
+        themes,
+        extension_css,
+        extern_html_root_urls,
+        resource_suffix,
+        ..
+    } = options;
+
     let src_root = match krate.src {
         FileName::Real(ref p) => match p.parent() {
             Some(p) => p.to_path_buf(),
@@ -528,10 +525,10 @@ pub fn run(mut krate: clean::Crate,
         layout: layout::Layout {
             logo: String::new(),
             favicon: String::new(),
-            external_html: external_html.clone(),
+            external_html,
             krate: krate.name.clone(),
         },
-        css_file_extension,
+        css_file_extension: extension_css,
         created_dirs: Default::default(),
         sort_modules_alphabetically,
         themes,
@@ -573,6 +570,7 @@ pub fn run(mut krate: clean::Crate,
             }
         }
     }
+    let dst = output;
     try_err!(fs::create_dir_all(&dst), &dst);
     krate = render_sources(&dst, &mut scx, krate)?;
     let cx = Context {
@@ -582,8 +580,6 @@ pub fn run(mut krate: clean::Crate,
         codes: ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build()),
         id_map: Rc::new(RefCell::new(id_map)),
         shared: Arc::new(scx),
-        enable_index_page,
-        index_page,
     };
 
     // Crawl the crate to build various caches used for the output
@@ -637,7 +633,7 @@ pub fn run(mut krate: clean::Crate,
             },
             _ => PathBuf::new(),
         };
-        let extern_url = extern_urls.get(&e.name).map(|u| &**u);
+        let extern_url = extern_html_root_urls.get(&e.name).map(|u| &**u);
         cache.extern_locations.insert(n, (e.name.clone(), src_root,
                                           extern_location(e, extern_url, &cx.dst)));
 
@@ -678,7 +674,7 @@ pub fn run(mut krate: clean::Crate,
     CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone());
     CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear());
 
-    write_shared(&cx, &krate, &*cache, index, enable_minification, matches, diag)?;
+    write_shared(&cx, &krate, &*cache, index, &md_opts, diag)?;
 
     // And finally render the whole crate's documentation
     cx.krate(krate)
@@ -759,8 +755,7 @@ fn write_shared(
     krate: &clean::Crate,
     cache: &Cache,
     search_index: String,
-    enable_minification: bool,
-    matches: &getopts::Matches,
+    options: &RenderOptions,
     diag: &errors::Handler,
 ) -> Result<(), Error> {
     // Write out the shared files. Note that these are shared among all rustdoc
@@ -773,10 +768,10 @@ fn write_shared(
 
     write_minify(cx.dst.join(&format!("rustdoc{}.css", cx.shared.resource_suffix)),
                  include_str!("static/rustdoc.css"),
-                 enable_minification)?;
+                 options.enable_minification)?;
     write_minify(cx.dst.join(&format!("settings{}.css", cx.shared.resource_suffix)),
                  include_str!("static/settings.css"),
-                 enable_minification)?;
+                 options.enable_minification)?;
 
     // To avoid "light.css" to be overwritten, we'll first run over the received themes and only
     // then we'll run over the "official" styles.
@@ -800,11 +795,11 @@ fn write_shared(
           include_bytes!("static/wheel.svg"))?;
     write_minify(cx.dst.join(&format!("light{}.css", cx.shared.resource_suffix)),
                  include_str!("static/themes/light.css"),
-                 enable_minification)?;
+                 options.enable_minification)?;
     themes.insert("light".to_owned());
     write_minify(cx.dst.join(&format!("dark{}.css", cx.shared.resource_suffix)),
                  include_str!("static/themes/dark.css"),
-                 enable_minification)?;
+                 options.enable_minification)?;
     themes.insert("dark".to_owned());
 
     let mut themes: Vec<&String> = themes.iter().collect();
@@ -860,10 +855,10 @@ themePicker.onblur = handleThemeButtonsBlur;
 
     write_minify(cx.dst.join(&format!("main{}.js", cx.shared.resource_suffix)),
                  include_str!("static/main.js"),
-                 enable_minification)?;
+                 options.enable_minification)?;
     write_minify(cx.dst.join(&format!("settings{}.js", cx.shared.resource_suffix)),
                  include_str!("static/settings.js"),
-                 enable_minification)?;
+                 options.enable_minification)?;
 
     {
         let mut data = format!("var resourcesSuffix = \"{}\";\n",
@@ -871,24 +866,24 @@ themePicker.onblur = handleThemeButtonsBlur;
         data.push_str(include_str!("static/storage.js"));
         write_minify(cx.dst.join(&format!("storage{}.js", cx.shared.resource_suffix)),
                      &data,
-                     enable_minification)?;
+                     options.enable_minification)?;
     }
 
     if let Some(ref css) = cx.shared.css_file_extension {
         let out = cx.dst.join(&format!("theme{}.css", cx.shared.resource_suffix));
-        if !enable_minification {
+        if !options.enable_minification {
             try_err!(fs::copy(css, out), css);
         } else {
             let mut f = try_err!(File::open(css), css);
             let mut buffer = String::with_capacity(1000);
 
             try_err!(f.read_to_string(&mut buffer), css);
-            write_minify(out, &buffer, enable_minification)?;
+            write_minify(out, &buffer, options.enable_minification)?;
         }
     }
     write_minify(cx.dst.join(&format!("normalize{}.css", cx.shared.resource_suffix)),
                  include_str!("static/normalize.css"),
-                 enable_minification)?;
+                 options.enable_minification)?;
     write(cx.dst.join("FiraSans-Regular.woff"),
           include_bytes!("static/FiraSans-Regular.woff"))?;
     write(cx.dst.join("FiraSans-Medium.woff"),
@@ -984,19 +979,19 @@ themePicker.onblur = handleThemeButtonsBlur;
     let mut w = try_err!(File::create(&dst), &dst);
     try_err!(writeln!(&mut w, "var N = null;var searchIndex = {{}};"), &dst);
     for index in &all_indexes {
-        try_err!(write_minify_replacer(&mut w, &*index, enable_minification,
+        try_err!(write_minify_replacer(&mut w, &*index, options.enable_minification,
                                        &[(minifier::js::Keyword::Null, "N")]),
                  &dst);
     }
     try_err!(writeln!(&mut w, "initSearch(searchIndex);"), &dst);
 
-    if cx.enable_index_page == true {
-        if let Some(ref index_page) = cx.index_page {
-            ::markdown::render(index_page,
-                               cx.dst.clone(),
-                               &matches, &(*cx.shared).layout.external_html,
-                               !matches.opt_present("markdown-no-toc"),
-                               diag);
+    if options.enable_index_page {
+        if let Some(index_page) = options.index_page.clone() {
+            let mut md_opts = options.clone();
+            md_opts.output = cx.dst.clone();
+            md_opts.external_html = (*cx.shared).layout.external_html.clone();
+
+            ::markdown::render(index_page, md_opts, diag);
         } else {
             let dst = cx.dst.join("index.html");
             let mut w = BufWriter::new(try_err!(File::create(&dst), &dst));
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index e1cb96edd48..f0f36f0355e 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -53,29 +53,20 @@ extern crate parking_lot;
 
 extern crate serialize as rustc_serialize; // used by deriving
 
-use errors::ColorConfig;
-
-use std::collections::{BTreeMap, BTreeSet};
 use std::default::Default;
 use std::env;
 use std::panic;
-use std::path::{Path, PathBuf};
 use std::process;
 use std::sync::mpsc::channel;
 
-use syntax::edition::Edition;
-use externalfiles::ExternalHtml;
 use rustc::session::{early_warn, early_error};
-use rustc::session::search_paths::SearchPaths;
-use rustc::session::config::{ErrorOutputType, RustcOptGroup, Externs, CodegenOptions};
-use rustc::session::config::{nightly_options, build_codegen_options};
-use rustc_target::spec::TargetTriple;
-use rustc::session::config::get_cmd_lint_options;
+use rustc::session::config::{ErrorOutputType, RustcOptGroup};
 
 #[macro_use]
 mod externalfiles;
 
 mod clean;
+mod config;
 mod core;
 mod doctree;
 mod fold;
@@ -99,6 +90,7 @@ mod theme;
 struct Output {
     krate: clean::Crate,
     renderinfo: html::render::RenderInfo,
+    renderopts: config::RenderOptions,
     passes: Vec<String>,
 }
 
@@ -367,383 +359,57 @@ fn main_args(args: &[String]) -> isize {
             early_error(ErrorOutputType::default(), &err.to_string());
         }
     };
-    // Check for unstable options.
-    nightly_options::check_nightly_options(&matches, &opts());
-
-    if matches.opt_present("h") || matches.opt_present("help") {
-        usage("rustdoc");
-        return 0;
-    } else if matches.opt_present("version") {
-        rustc_driver::version("rustdoc", &matches);
-        return 0;
-    }
-
-    if matches.opt_strs("passes") == ["list"] {
-        println!("Available passes for running rustdoc:");
-        for pass in passes::PASSES {
-            println!("{:>20} - {}", pass.name(), pass.description());
-        }
-        println!("\nDefault passes for rustdoc:");
-        for &name in passes::DEFAULT_PASSES {
-            println!("{:>20}", name);
-        }
-        println!("\nPasses run with `--document-private-items`:");
-        for &name in passes::DEFAULT_PRIVATE_PASSES {
-            println!("{:>20}", name);
-        }
-        return 0;
-    }
-
-    let color = match matches.opt_str("color").as_ref().map(|s| &s[..]) {
-        Some("auto") => ColorConfig::Auto,
-        Some("always") => ColorConfig::Always,
-        Some("never") => ColorConfig::Never,
-        None => ColorConfig::Auto,
-        Some(arg) => {
-            early_error(ErrorOutputType::default(),
-                        &format!("argument for --color must be `auto`, `always` or `never` \
-                                  (instead was `{}`)", arg));
-        }
+    let options = match config::Options::from_matches(&matches) {
+        Ok(opts) => opts,
+        Err(code) => return code,
     };
-    let error_format = match matches.opt_str("error-format").as_ref().map(|s| &s[..]) {
-        Some("human") => ErrorOutputType::HumanReadable(color),
-        Some("json") => ErrorOutputType::Json(false),
-        Some("pretty-json") => ErrorOutputType::Json(true),
-        Some("short") => ErrorOutputType::Short(color),
-        None => ErrorOutputType::HumanReadable(color),
-        Some(arg) => {
-            early_error(ErrorOutputType::default(),
-                        &format!("argument for --error-format must be `human`, `json` or \
-                                  `short` (instead was `{}`)", arg));
-        }
-    };
-    let treat_err_as_bug = matches.opt_strs("Z").iter().any(|x| {
-        *x == "treat-err-as-bug"
-    });
-    let ui_testing = matches.opt_strs("Z").iter().any(|x| {
-        *x == "ui-testing"
-    });
-
-    let diag = core::new_handler(error_format, None, treat_err_as_bug, ui_testing);
-
-    // check for deprecated options
-    check_deprecated_options(&matches, &diag);
-
-    let to_check = matches.opt_strs("theme-checker");
-    if !to_check.is_empty() {
-        let paths = theme::load_css_paths(include_bytes!("html/static/themes/light.css"));
-        let mut errors = 0;
-
-        println!("rustdoc: [theme-checker] Starting tests!");
-        for theme_file in to_check.iter() {
-            print!(" - Checking \"{}\"...", theme_file);
-            let (success, differences) = theme::test_theme_against(theme_file, &paths, &diag);
-            if !differences.is_empty() || !success {
-                println!(" FAILED");
-                errors += 1;
-                if !differences.is_empty() {
-                    println!("{}", differences.join("\n"));
-                }
-            } else {
-                println!(" OK");
-            }
-        }
-        if errors != 0 {
-            return 1;
-        }
-        return 0;
-    }
-
-    if matches.free.is_empty() {
-        diag.struct_err("missing file operand").emit();
-        return 1;
-    }
-    if matches.free.len() > 1 {
-        diag.struct_err("too many file operands").emit();
-        return 1;
-    }
-    let input = matches.free[0].clone();
-
-    let mut libs = SearchPaths::new();
-    for s in &matches.opt_strs("L") {
-        libs.add_path(s, error_format);
-    }
-    let externs = match parse_externs(&matches) {
-        Ok(ex) => ex,
-        Err(err) => {
-            diag.struct_err(&err).emit();
-            return 1;
-        }
-    };
-    let extern_urls = match parse_extern_html_roots(&matches) {
-        Ok(ex) => ex,
-        Err(err) => {
-            diag.struct_err(err).emit();
-            return 1;
-        }
-    };
-
-    let test_args = matches.opt_strs("test-args");
-    let test_args: Vec<String> = test_args.iter()
-                                          .flat_map(|s| s.split_whitespace())
-                                          .map(|s| s.to_string())
-                                          .collect();
-
-    let should_test = matches.opt_present("test");
-    let markdown_input = Path::new(&input).extension()
-        .map_or(false, |e| e == "md" || e == "markdown");
-
-    let output = matches.opt_str("o").map(|s| PathBuf::from(&s));
-    let css_file_extension = matches.opt_str("e").map(|s| PathBuf::from(&s));
-    let mut cfgs = matches.opt_strs("cfg");
-    cfgs.push("rustdoc".to_string());
-
-    if let Some(ref p) = css_file_extension {
-        if !p.is_file() {
-            diag.struct_err("option --extend-css argument must be a file").emit();
-            return 1;
-        }
-    }
-
-    let mut themes = Vec::new();
-    if matches.opt_present("themes") {
-        let paths = theme::load_css_paths(include_bytes!("html/static/themes/light.css"));
-
-        for (theme_file, theme_s) in matches.opt_strs("themes")
-                                            .iter()
-                                            .map(|s| (PathBuf::from(&s), s.to_owned())) {
-            if !theme_file.is_file() {
-                diag.struct_err("option --themes arguments must all be files").emit();
-                return 1;
-            }
-            let (success, ret) = theme::test_theme_against(&theme_file, &paths, &diag);
-            if !success || !ret.is_empty() {
-                diag.struct_err(&format!("invalid theme: \"{}\"", theme_s))
-                    .help("check what's wrong with the --theme-checker option")
-                    .emit();
-                return 1;
-            }
-            themes.push(theme_file);
-        }
-    }
-
-    let mut id_map = html::markdown::IdMap::new();
-    id_map.populate(html::render::initial_ids());
-    let external_html = match ExternalHtml::load(
-            &matches.opt_strs("html-in-header"),
-            &matches.opt_strs("html-before-content"),
-            &matches.opt_strs("html-after-content"),
-            &matches.opt_strs("markdown-before-content"),
-            &matches.opt_strs("markdown-after-content"), &diag, &mut id_map) {
-        Some(eh) => eh,
-        None => return 3,
-    };
-    let crate_name = matches.opt_str("crate-name");
-    let playground_url = matches.opt_str("playground-url");
-    let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from);
-    let display_warnings = matches.opt_present("display-warnings");
-    let linker = matches.opt_str("linker").map(PathBuf::from);
-    let sort_modules_alphabetically = !matches.opt_present("sort-modules-by-appearance");
-    let resource_suffix = matches.opt_str("resource-suffix");
-    let index_page = matches.opt_str("index-page").map(|s| PathBuf::from(&s));
-    let enable_index_page = matches.opt_present("enable-index-page") || index_page.is_some();
-    let enable_minification = !matches.opt_present("disable-minification");
-
-    let edition = matches.opt_str("edition").unwrap_or("2015".to_string());
-    let edition = match edition.parse() {
-        Ok(e) => e,
-        Err(_) => {
-            diag.struct_err("could not parse edition").emit();
-            return 1;
-        }
-    };
-    if let Some(ref index_page) = index_page {
-        if !index_page.is_file() {
-            diag.struct_err("option `--index-page` argument must be a file").emit();
-            return 1;
-        }
-    }
 
-    let cg = build_codegen_options(&matches, ErrorOutputType::default());
+    let diag = core::new_handler(options.error_format,
+                                 None,
+                                 options.debugging_options.treat_err_as_bug,
+                                 options.debugging_options.ui_testing);
 
-    match (should_test, markdown_input) {
-        (true, true) => {
-            return markdown::test(&input, cfgs, libs, externs, test_args, maybe_sysroot,
-                                  display_warnings, linker, edition, cg, &diag)
-        }
-        (true, false) => {
-            return test::run(Path::new(&input), cfgs, libs, externs, test_args, crate_name,
-                             maybe_sysroot, display_warnings, linker, edition, cg)
-        }
-        (false, true) => return markdown::render(Path::new(&input),
-                                                 output.unwrap_or(PathBuf::from("doc")),
-                                                 &matches, &external_html,
-                                                 !matches.opt_present("markdown-no-toc"), &diag),
+    match (options.should_test, options.markdown_input()) {
+        (true, true) => return markdown::test(options, &diag),
+        (true, false) => return test::run(options),
+        (false, true) => return markdown::render(options.input, options.render_options, &diag),
         (false, false) => {}
     }
 
-    let output_format = matches.opt_str("w");
-
-    let res = acquire_input(PathBuf::from(input), externs, edition, cg, matches, error_format,
-                            move |out, matches| {
-        let Output { krate, passes, renderinfo } = out;
-        let diag = core::new_handler(error_format, None, treat_err_as_bug, ui_testing);
+    // need to move these items separately because we lose them by the time the closure is called,
+    // but we can't crates the Handler ahead of time because it's not Send
+    let diag_opts = (options.error_format,
+                     options.debugging_options.treat_err_as_bug,
+                     options.debugging_options.ui_testing);
+    rust_input(options, move |out| {
+        let Output { krate, passes, renderinfo, renderopts } = out;
         info!("going to format");
-        match output_format.as_ref().map(|s| &**s) {
-            Some("html") | None => {
-                html::render::run(krate, extern_urls, &external_html, playground_url,
-                                  output.unwrap_or(PathBuf::from("doc")),
-                                  resource_suffix.unwrap_or(String::new()),
-                                  passes.into_iter().collect(),
-                                  css_file_extension,
-                                  renderinfo,
-                                  sort_modules_alphabetically,
-                                  themes,
-                                  enable_minification, id_map,
-                                  enable_index_page, index_page,
-                                  &matches,
-                                  &diag)
-                    .expect("failed to generate documentation");
-                0
-            }
-            Some(s) => {
-                diag.struct_err(&format!("unknown output format: {}", s)).emit();
-                1
-            }
-        }
-    });
-    res.unwrap_or_else(|s| {
-        diag.struct_err(&format!("input error: {}", s)).emit();
-        1
+        let (error_format, treat_err_as_bug, ui_testing) = diag_opts;
+        let diag = core::new_handler(error_format, None, treat_err_as_bug, ui_testing);
+        html::render::run(krate, renderopts, passes.into_iter().collect(), renderinfo, &diag)
+            .expect("failed to generate documentation");
+        0
     })
 }
 
-/// Looks inside the command line arguments to extract the relevant input format
-/// and files and then generates the necessary rustdoc output for formatting.
-fn acquire_input<R, F>(input: PathBuf,
-                       externs: Externs,
-                       edition: Edition,
-                       cg: CodegenOptions,
-                       matches: getopts::Matches,
-                       error_format: ErrorOutputType,
-                       f: F)
-                       -> Result<R, String>
-where R: 'static + Send, F: 'static + Send + FnOnce(Output, &getopts::Matches) -> R {
-    match matches.opt_str("r").as_ref().map(|s| &**s) {
-        Some("rust") => Ok(rust_input(input, externs, edition, cg, matches, error_format, f)),
-        Some(s) => Err(format!("unknown input format: {}", s)),
-        None => Ok(rust_input(input, externs, edition, cg, matches, error_format, f))
-    }
-}
-
-/// Extracts `--extern CRATE=PATH` arguments from `matches` and
-/// returns a map mapping crate names to their paths or else an
-/// error message.
-// FIXME(eddyb) This shouldn't be duplicated with `rustc::session`.
-fn parse_externs(matches: &getopts::Matches) -> Result<Externs, String> {
-    let mut externs: BTreeMap<_, BTreeSet<_>> = BTreeMap::new();
-    for arg in &matches.opt_strs("extern") {
-        let mut parts = arg.splitn(2, '=');
-        let name = parts.next().ok_or("--extern value must not be empty".to_string())?;
-        let location = parts.next().map(|s| s.to_string());
-        if location.is_none() && !nightly_options::is_unstable_enabled(matches) {
-            return Err("the `-Z unstable-options` flag must also be passed to \
-                        enable `--extern crate_name` without `=path`".to_string());
-        }
-        let name = name.to_string();
-        externs.entry(name).or_default().insert(location);
-    }
-    Ok(Externs::new(externs))
-}
-
-/// Extracts `--extern-html-root-url` arguments from `matches` and returns a map of crate names to
-/// the given URLs. If an `--extern-html-root-url` argument was ill-formed, returns an error
-/// describing the issue.
-fn parse_extern_html_roots(matches: &getopts::Matches)
-    -> Result<BTreeMap<String, String>, &'static str>
-{
-    let mut externs = BTreeMap::new();
-    for arg in &matches.opt_strs("extern-html-root-url") {
-        let mut parts = arg.splitn(2, '=');
-        let name = parts.next().ok_or("--extern-html-root-url must not be empty")?;
-        let url = parts.next().ok_or("--extern-html-root-url must be of the form name=url")?;
-        externs.insert(name.to_string(), url.to_string());
-    }
-
-    Ok(externs)
-}
-
 /// Interprets the input file as a rust source file, passing it through the
 /// compiler all the way through the analysis passes. The rustdoc output is then
 /// generated from the cleaned AST of the crate.
 ///
 /// This form of input will run all of the plug/cleaning passes
-fn rust_input<R, F>(cratefile: PathBuf,
-                    externs: Externs,
-                    edition: Edition,
-                    cg: CodegenOptions,
-                    matches: getopts::Matches,
-                    error_format: ErrorOutputType,
-                    f: F) -> R
+fn rust_input<R, F>(options: config::Options, f: F) -> R
 where R: 'static + Send,
-      F: 'static + Send + FnOnce(Output, &getopts::Matches) -> R
+      F: 'static + Send + FnOnce(Output) -> R
 {
-    let default_passes = if matches.opt_present("no-defaults") {
-        passes::DefaultPassOption::None
-    } else if matches.opt_present("document-private-items") {
-        passes::DefaultPassOption::Private
-    } else {
-        passes::DefaultPassOption::Default
-    };
-
-    let manual_passes = matches.opt_strs("passes");
-    let plugins = matches.opt_strs("plugins");
-
     // First, parse the crate and extract all relevant information.
-    let mut paths = SearchPaths::new();
-    for s in &matches.opt_strs("L") {
-        paths.add_path(s, ErrorOutputType::default());
-    }
-    let mut cfgs = matches.opt_strs("cfg");
-    cfgs.push("rustdoc".to_string());
-    let triple = matches.opt_str("target").map(|target| {
-        if target.ends_with(".json") {
-            TargetTriple::TargetPath(PathBuf::from(target))
-        } else {
-            TargetTriple::TargetTriple(target)
-        }
-    });
-    let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from);
-    let crate_name = matches.opt_str("crate-name");
-    let crate_version = matches.opt_str("crate-version");
-    let plugin_path = matches.opt_str("plugin-path");
-
     info!("starting to run rustc");
-    let display_warnings = matches.opt_present("display-warnings");
-
-    let force_unstable_if_unmarked = matches.opt_strs("Z").iter().any(|x| {
-        *x == "force-unstable-if-unmarked"
-    });
-    let treat_err_as_bug = matches.opt_strs("Z").iter().any(|x| {
-        *x == "treat-err-as-bug"
-    });
-    let ui_testing = matches.opt_strs("Z").iter().any(|x| {
-        *x == "ui-testing"
-    });
-
-    let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(&matches, error_format);
 
     let (tx, rx) = channel();
 
     let result = rustc_driver::monitor(move || syntax::with_globals(move || {
-        use rustc::session::config::Input;
-
-        let (mut krate, renderinfo, passes) =
-            core::run_core(paths, cfgs, externs, Input::File(cratefile), triple, maybe_sysroot,
-                           display_warnings, crate_name.clone(),
-                           force_unstable_if_unmarked, edition, cg, error_format,
-                           lint_opts, lint_cap, describe_lints, manual_passes, default_passes,
-                           treat_err_as_bug, ui_testing);
+        let crate_name = options.crate_name.clone();
+        let crate_version = options.crate_version.clone();
+        let (mut krate, renderinfo, renderopts, passes) = core::run_core(options);
 
         info!("finished with rustc");
 
@@ -753,14 +419,6 @@ where R: 'static + Send,
 
         krate.version = crate_version;
 
-        if !plugins.is_empty() {
-            eprintln!("WARNING: --plugins no longer functions; see CVE-2018-1000622");
-        }
-
-        if !plugin_path.is_none() {
-            eprintln!("WARNING: --plugin-path no longer functions; see CVE-2018-1000622");
-        }
-
         info!("Executing passes");
 
         for pass in &passes {
@@ -783,8 +441,12 @@ where R: 'static + Send,
             krate = pass(krate);
         }
 
-        tx.send(f(Output { krate: krate, renderinfo: renderinfo, passes: passes },
-                  &matches)).unwrap();
+        tx.send(f(Output {
+            krate: krate,
+            renderinfo: renderinfo,
+            renderopts,
+            passes: passes
+        })).unwrap();
     }));
 
     match result {
@@ -792,27 +454,3 @@ where R: 'static + Send,
         Err(_) => panic::resume_unwind(Box::new(errors::FatalErrorMarker)),
     }
 }
-
-/// Prints deprecation warnings for deprecated options
-fn check_deprecated_options(matches: &getopts::Matches, diag: &errors::Handler) {
-    let deprecated_flags = [
-       "input-format",
-       "output-format",
-       "no-defaults",
-       "passes",
-    ];
-
-    for flag in deprecated_flags.into_iter() {
-        if matches.opt_present(flag) {
-            let mut err = diag.struct_warn(&format!("the '{}' flag is considered deprecated",
-                                                    flag));
-            err.warn("please see https://github.com/rust-lang/rust/issues/44136");
-
-            if *flag == "no-defaults" {
-                err.help("you may want to use --document-private-items");
-            }
-
-            err.emit();
-        }
-    }
-}
diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs
index 0084c0f8592..8008f8848d4 100644
--- a/src/librustdoc/markdown.rs
+++ b/src/librustdoc/markdown.rs
@@ -11,20 +11,17 @@
 use std::default::Default;
 use std::fs::File;
 use std::io::prelude::*;
-use std::path::{PathBuf, Path};
+use std::path::PathBuf;
 use std::cell::RefCell;
 
 use errors;
-use getopts;
 use testing;
-use rustc::session::search_paths::SearchPaths;
-use rustc::session::config::{Externs, CodegenOptions};
 use syntax::source_map::DUMMY_SP;
 use syntax::feature_gate::UnstableFeatures;
-use syntax::edition::Edition;
 
-use externalfiles::{ExternalHtml, LoadStringError, load_string};
+use externalfiles::{LoadStringError, load_string};
 
+use config::{Options, RenderOptions};
 use html::escape::Escape;
 use html::markdown;
 use html::markdown::{ErrorCodes, IdMap, Markdown, MarkdownWithToc, find_testable_code};
@@ -51,24 +48,25 @@ fn extract_leading_metadata<'a>(s: &'a str) -> (Vec<&'a str>, &'a str) {
 
 /// Render `input` (e.g. "foo.md") into an HTML file in `output`
 /// (e.g. output = "bar" => "bar/foo.html").
-pub fn render(input: &Path, mut output: PathBuf, matches: &getopts::Matches,
-              external_html: &ExternalHtml, include_toc: bool, diag: &errors::Handler) -> isize {
+pub fn render(input: PathBuf, options: RenderOptions, diag: &errors::Handler) -> isize {
+    let mut output = options.output;
     output.push(input.file_stem().unwrap());
     output.set_extension("html");
 
     let mut css = String::new();
-    for name in &matches.opt_strs("markdown-css") {
+    for name in &options.markdown_css {
         let s = format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">\n", name);
         css.push_str(&s)
     }
 
-    let input_str = match load_string(input, diag) {
+    let input_str = match load_string(&input, diag) {
         Ok(s) => s,
         Err(LoadStringError::ReadFail) => return 1,
         Err(LoadStringError::BadUtf8) => return 2,
     };
-    if let Some(playground) = matches.opt_str("markdown-playground-url").or(
-                              matches.opt_str("playground-url")) {
+    let playground_url = options.markdown_playground_url
+                            .or(options.playground_url);
+    if let Some(playground) = playground_url {
         markdown::PLAYGROUND.with(|s| { *s.borrow_mut() = Some((None, playground)); });
     }
 
@@ -89,7 +87,7 @@ pub fn render(input: &Path, mut output: PathBuf, matches: &getopts::Matches,
 
     let mut ids = IdMap::new();
     let error_codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
-    let text = if include_toc {
+    let text = if !options.markdown_no_toc {
         MarkdownWithToc(text, RefCell::new(&mut ids), error_codes).to_string()
     } else {
         Markdown(text, &[], RefCell::new(&mut ids), error_codes).to_string()
@@ -124,10 +122,10 @@ pub fn render(input: &Path, mut output: PathBuf, matches: &getopts::Matches,
 </html>"#,
         title = Escape(title),
         css = css,
-        in_header = external_html.in_header,
-        before_content = external_html.before_content,
+        in_header = options.external_html.in_header,
+        before_content = options.external_html.before_content,
         text = text,
-        after_content = external_html.after_content,
+        after_content = options.external_html.after_content,
     );
 
     match err {
@@ -140,11 +138,8 @@ pub fn render(input: &Path, mut output: PathBuf, matches: &getopts::Matches,
 }
 
 /// Run any tests/code examples in the markdown file `input`.
-pub fn test(input: &str, cfgs: Vec<String>, libs: SearchPaths, externs: Externs,
-            mut test_args: Vec<String>, maybe_sysroot: Option<PathBuf>,
-            display_warnings: bool, linker: Option<PathBuf>, edition: Edition,
-            cg: CodegenOptions, diag: &errors::Handler) -> isize {
-    let input_str = match load_string(input, diag) {
+pub fn test(mut options: Options, diag: &errors::Handler) -> isize {
+    let input_str = match load_string(&options.input, diag) {
         Ok(s) => s,
         Err(LoadStringError::ReadFail) => return 1,
         Err(LoadStringError::BadUtf8) => return 2,
@@ -152,19 +147,20 @@ pub fn test(input: &str, cfgs: Vec<String>, libs: SearchPaths, externs: Externs,
 
     let mut opts = TestOptions::default();
     opts.no_crate_inject = true;
-    opts.display_warnings = display_warnings;
-    let mut collector = Collector::new(input.to_owned(), cfgs, libs, cg, externs,
-                                       true, opts, maybe_sysroot, None,
-                                       Some(PathBuf::from(input)),
-                                       linker, edition);
+    opts.display_warnings = options.display_warnings;
+    let mut collector = Collector::new(options.input.display().to_string(), options.cfgs,
+                                       options.libs, options.codegen_options, options.externs,
+                                       true, opts, options.maybe_sysroot, None,
+                                       Some(options.input),
+                                       options.linker, options.edition);
     collector.set_position(DUMMY_SP);
     let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
     let res = find_testable_code(&input_str, &mut collector, codes);
     if let Err(err) = res {
         diag.span_warn(DUMMY_SP, &err.to_string());
     }
-    test_args.insert(0, "rustdoctest".to_string());
-    testing::test_main(&test_args, collector.tests,
-                       testing::Options::new().display_output(display_warnings));
+    options.test_args.insert(0, "rustdoctest".to_string());
+    testing::test_main(&options.test_args, collector.tests,
+                       testing::Options::new().display_output(options.display_warnings));
     0
 }
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index 06cb4fbd716..d9bab91fd0c 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -12,7 +12,7 @@ use std::env;
 use std::ffi::OsString;
 use std::io::prelude::*;
 use std::io;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 use std::panic::{self, AssertUnwindSafe};
 use std::process::Command;
 use std::str;
@@ -42,6 +42,7 @@ use errors;
 use errors::emitter::ColorConfig;
 
 use clean::Attributes;
+use config::Options;
 use html::markdown::{self, ErrorCodes, LangString};
 
 #[derive(Clone, Default)]
@@ -55,34 +56,23 @@ pub struct TestOptions {
     pub attrs: Vec<String>,
 }
 
-pub fn run(input_path: &Path,
-           cfgs: Vec<String>,
-           libs: SearchPaths,
-           externs: Externs,
-           mut test_args: Vec<String>,
-           crate_name: Option<String>,
-           maybe_sysroot: Option<PathBuf>,
-           display_warnings: bool,
-           linker: Option<PathBuf>,
-           edition: Edition,
-           cg: CodegenOptions)
-           -> isize {
-    let input = config::Input::File(input_path.to_owned());
+pub fn run(mut options: Options) -> isize {
+    let input = config::Input::File(options.input.clone());
 
     let sessopts = config::Options {
-        maybe_sysroot: maybe_sysroot.clone().or_else(
+        maybe_sysroot: options.maybe_sysroot.clone().or_else(
             || Some(env::current_exe().unwrap().parent().unwrap().parent().unwrap().to_path_buf())),
-        search_paths: libs.clone(),
+        search_paths: options.libs.clone(),
         crate_types: vec![config::CrateType::Dylib],
-        cg: cg.clone(),
-        externs: externs.clone(),
+        cg: options.codegen_options.clone(),
+        externs: options.externs.clone(),
         unstable_features: UnstableFeatures::from_environment(),
         lint_cap: Some(::rustc::lint::Level::Allow),
         actually_rustdoc: true,
         debugging_opts: config::DebuggingOptions {
             ..config::basic_debugging_options()
         },
-        edition,
+        edition: options.edition,
         ..config::Options::default()
     };
     driver::spawn_thread_pool(sessopts, |sessopts| {
@@ -93,13 +83,14 @@ pub fn run(input_path: &Path,
                                             Some(source_map.clone()));
 
         let mut sess = session::build_session_(
-            sessopts, Some(input_path.to_owned()), handler, source_map.clone(),
+            sessopts, Some(options.input), handler, source_map.clone(),
         );
         let codegen_backend = rustc_driver::get_codegen_backend(&sess);
         let cstore = CStore::new(codegen_backend.metadata_loader());
         rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
 
-        let mut cfg = config::build_configuration(&sess, config::parse_cfgspecs(cfgs.clone()));
+        let mut cfg = config::build_configuration(&sess,
+                                                  config::parse_cfgspecs(options.cfgs.clone()));
         target_features::add_configuration(&mut cfg, &sess, &*codegen_backend);
         sess.parse_sess.config = cfg;
 
@@ -119,24 +110,24 @@ pub fn run(input_path: &Path,
             ).expect("phase_2_configure_and_expand aborted in rustdoc!")
         };
 
-        let crate_name = crate_name.unwrap_or_else(|| {
+        let crate_name = options.crate_name.unwrap_or_else(|| {
             ::rustc_codegen_utils::link::find_crate_name(None, &hir_forest.krate().attrs, &input)
         });
         let mut opts = scrape_test_config(hir_forest.krate());
-        opts.display_warnings |= display_warnings;
+        opts.display_warnings |= options.display_warnings;
         let mut collector = Collector::new(
             crate_name,
-            cfgs,
-            libs,
-            cg,
-            externs,
+            options.cfgs,
+            options.libs,
+            options.codegen_options,
+            options.externs,
             false,
             opts,
-            maybe_sysroot,
+            options.maybe_sysroot,
             Some(source_map),
-             None,
-            linker,
-            edition
+            None,
+            options.linker,
+            options.edition
         );
 
         {
@@ -153,11 +144,11 @@ pub fn run(input_path: &Path,
             });
         }
 
-        test_args.insert(0, "rustdoctest".to_string());
+        options.test_args.insert(0, "rustdoctest".to_string());
 
-        testing::test_main(&test_args,
+        testing::test_main(&options.test_args,
                         collector.tests.into_iter().collect(),
-                        testing::Options::new().display_output(display_warnings));
+                        testing::Options::new().display_output(options.display_warnings));
         0
     })
 }
diff --git a/src/test/rustdoc-ui/failed-doctest-output.stdout b/src/test/rustdoc-ui/failed-doctest-output.stdout
index 876f6c0a80b..527f1355a9e 100644
--- a/src/test/rustdoc-ui/failed-doctest-output.stdout
+++ b/src/test/rustdoc-ui/failed-doctest-output.stdout
@@ -12,7 +12,7 @@ error[E0425]: cannot find value `no` in this scope
 3 | no
   | ^^ not found in this scope
 
-thread '$DIR/failed-doctest-output.rs - OtherStruct (line 27)' panicked at 'couldn't compile the test', librustdoc/test.rs:332:13
+thread '$DIR/failed-doctest-output.rs - OtherStruct (line 27)' panicked at 'couldn't compile the test', librustdoc/test.rs:323:13
 note: Run with `RUST_BACKTRACE=1` for a backtrace.
 
 ---- $DIR/failed-doctest-output.rs - SomeStruct (line 21) stdout ----
@@ -21,7 +21,7 @@ thread '$DIR/failed-doctest-output.rs - SomeStruct (line 21)' panicked at 'test
 thread 'main' panicked at 'oh no', $DIR/failed-doctest-output.rs:3:1
 note: Run with `RUST_BACKTRACE=1` for a backtrace.
 
-', librustdoc/test.rs:367:17
+', librustdoc/test.rs:358:17
 
 
 failures: