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#[derive(Debug)]
12pub struct DocOptions {
13 pub open_result: bool,
15 pub compile_opts: ops::CompileOptions,
17}
18
19pub 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 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}