Skip to main content

cargo/ops/
cargo_doc.rs

1use crate::core::compiler::RustcTargetData;
2use crate::core::resolver::{HasDevUnits, ResolveOpts};
3use crate::core::{Shell, Workspace};
4use crate::ops;
5use crate::util::CargoResult;
6use std::collections::HashMap;
7use std::path::Path;
8use std::process::Command;
9
10/// Strongly typed options for the `cargo doc` command.
11#[derive(Debug)]
12pub struct DocOptions {
13    /// Whether to attempt to open the browser after compiling the docs
14    pub open_result: bool,
15    /// Options to pass through to the compiler
16    pub compile_opts: ops::CompileOptions,
17}
18
19/// Main method for `cargo doc`.
20pub fn doc(ws: &Workspace<'_>, options: &DocOptions) -> CargoResult<()> {
21    let specs = options.compile_opts.spec.to_package_id_specs(ws)?;
22    let opts = ResolveOpts::new(
23        /*dev_deps*/ true,
24        &options.compile_opts.features,
25        options.compile_opts.all_features,
26        !options.compile_opts.no_default_features,
27    );
28    let requested_kind = options.compile_opts.build_config.requested_kind;
29    let target_data = RustcTargetData::new(ws, requested_kind)?;
30    let ws_resolve = ops::resolve_ws_with_opts(
31        ws,
32        &target_data,
33        requested_kind,
34        &opts,
35        &specs,
36        HasDevUnits::No,
37    )?;
38
39    let ids = specs
40        .iter()
41        .map(|s| s.query(ws_resolve.targeted_resolve.iter()))
42        .collect::<CargoResult<Vec<_>>>()?;
43    let pkgs = ws_resolve.pkg_set.get_many(ids)?;
44
45    let mut lib_names = HashMap::new();
46    let mut bin_names = HashMap::new();
47    let mut names = Vec::new();
48    for package in &pkgs {
49        for target in package.targets().iter().filter(|t| t.documented()) {
50            if target.is_lib() {
51                if let Some(prev) = lib_names.insert(target.crate_name(), package) {
52                    anyhow::bail!(
53                        "The library `{}` is specified by packages `{}` and \
54                         `{}` but can only be documented once. Consider renaming \
55                         or marking one of the targets as `doc = false`.",
56                        target.crate_name(),
57                        prev,
58                        package
59                    );
60                }
61            } else if let Some(prev) = bin_names.insert(target.crate_name(), package) {
62                anyhow::bail!(
63                    "The binary `{}` is specified by packages `{}` and \
64                     `{}` but can be documented only once. Consider renaming \
65                     or marking one of the targets as `doc = false`.",
66                    target.crate_name(),
67                    prev,
68                    package
69                );
70            }
71            names.push(target.crate_name());
72        }
73    }
74
75    let compilation = ops::compile(ws, &options.compile_opts)?;
76
77    if options.open_result {
78        let name = match names.first() {
79            Some(s) => s.to_string(),
80            None => return Ok(()),
81        };
82        let path = compilation
83            .root_output
84            .with_file_name("doc")
85            .join(&name)
86            .join("index.html");
87        if path.exists() {
88            let mut shell = ws.config().shell();
89            shell.status("Opening", path.display())?;
90            open_docs(&path, &mut shell)?;
91        }
92    }
93
94    Ok(())
95}
96
97fn open_docs(path: &Path, shell: &mut Shell) -> CargoResult<()> {
98    match std::env::var_os("BROWSER") {
99        Some(browser) => {
100            if let Err(e) = Command::new(&browser).arg(path).status() {
101                shell.warn(format!(
102                    "Couldn't open docs with {}: {}",
103                    browser.to_string_lossy(),
104                    e
105                ))?;
106            }
107        }
108        None => {
109            if let Err(e) = opener::open(&path) {
110                shell.warn(format!("Couldn't open docs: {}", e))?;
111                for cause in anyhow::Error::new(e).chain().skip(1) {
112                    shell.warn(format!("Caused by:\n {}", cause))?;
113                }
114            }
115        }
116    };
117
118    Ok(())
119}